-
Notifications
You must be signed in to change notification settings - Fork 0
Event System
Um die Anforderungen für Minigames und unsere Architektur zu erfüllen und direkte Kopplung zwischen Szenen bestmöglich zu vermeiden, haben wir ein Event-System basierend auf ScriptableObject
s verwendet. Angelehnt am Unity Open Project 1 (Video, Repo).
Dafür werden Eventchannel in Assets/Scripts/Events/Channels
implementiert, und können dann für spezifische Events als Asset instantiiert werden.
Eventchannel bieten eine Möglichkeit Events für bestimmte Datentypen zu propagieren. Ein generisch implementierter Channel für Events einer bestimmten Art (z.B. String-Events, Void-Events, etc.) kann, als ScriptableObject, instantiiert werden und somit den Eventchannel für ein bestimmtes Event bilden (z.B. der MinigameSuccessEvent_Channel
als konkrete Instanz des MinigameEventChannelSO
).
Diese ScriptableObject-Instanzen liegen in Assets/ScriptableObjects/Events
können dann an GameObjects gegeben werden, um Events auf diesem Channel zu feuern.
Für die andere Richtung wird für jeden Channel ein Listener implementiert, der direkt an das hörende GameObject angehängt wird, und eine bestimmt Methode aufruft, sobald ein Event gefeuert wird.
In der Umsetzung sehen alle Channel und Listener dann sehr ähnlich aus, mit dem Unterschied in den Parametern.
Zum Beispiel VoidEventChannelSO
. Dieser Channel ist für Events ohne Parameter.
namespace Events.Channels
{
/// <summary>
/// This class implements a channel for events without parameters. For example MinigameUIExitEvent
/// </summary>
[CreateAssetMenu(menuName = "Events/Void Event Channel")]
public class VoidEventChannelSO : ScriptableObject
{
public UnityAction OnEventRaised;
/// <summary>
/// Raise an event in this channel.
/// Any VoidEventListeners listening to this channel will be notified.
/// </summary>
public void RaiseEvent()
{
OnEventRaised?.Invoke();
}
}
}
Der MinigameEventChannelSO
hingegen, nimmt ein Minigame Object als Parameter.
namespace Events.Channels
{
/// <summary>
/// This class implements a channel for events with a parameter of type MinigameSO
/// </summary>
[CreateAssetMenu(menuName = "Events/Minigame Event Channel")]
public class MinigameEventChannelSO : ScriptableObject
{
public UnityAction<MinigameSO> OnEventRaised;
/// <summary>
///
/// Raise an event in this channel.
/// Any MinigameEventListeners listening to this channel will be notified.
/// </summary>
public void RaiseEvent(MinigameSO minigame)
{
OnEventRaised?.Invoke(minigame);
}
public void RaiseEvent(MinigameSO minigame, MinigameParams minigameParams)
{
if (OnEventRaised != null)
{
State.Instance.MinigameParams = minigameParams;
OnEventRaised.Invoke(minigame);
}
}
}
}
Der Listener für den MinigameEventChannelSO
ist dann z.B. so implementiert:
namespace Events
{
/// <summary>
/// This component adds a listener to ScriptableObject Assets of type MinigameEventChannelSO
/// to a GameObject. This allows the GameObject to react to events in this channel.
/// </summary>
public class MinigameEventListener : MonoBehaviour
{
[SerializeField] private MinigameEventChannelSO channel;
public UnityEvent<MinigameSO> onEventRaised;
private void OnEnable()
{
if (channel != null)
{
channel.OnEventRaised += Respond;
}
}
private void OnDisable()
{
if (channel != null)
{
channel.OnEventRaised -= Respond;
}
}
private void Respond(MinigameSO minigame)
{
onEventRaised?.Invoke(minigame);
}
}
}
Als Komponent auf einem GameObject registriert und entregistriert er einen Listener auf den MinigameEventChannelSO
. Das SceneLoader GameObject, zum Beispiel, hat dann mehrere MinigameEventListener
um auf verschiedene spezifische Channel zu reagieren:
Events sind Geschehnisse im Spiel, die durch EventChannel propagiert werden. Einige Beispiele:
Die Bücher im Bücherregal unseres Hauptmenus beitzen ein Skript Book
, das, wenn es geklickt wird, ein Event im LoadLevelEventChannel
mit der Szene des gewünschten Levels feurt. Die Levelszene und der Eventchannel werden dem GameObject als public
Variablen über den Inspektor zugeweisen.
public class Book : MonoBehaviour
{
public SceneReference levelScene;
public SceneEventChannelSO loadLevelEventChannel;
...
private void OnMouseOver()
{
if (Input.GetMouseButtonDown(0) && !levelScene.IsEmpty)
{
loadLevelEventChannel.RaiseEvent(levelScene);
}
}
...
}
Der SceneLoader in der BaseScene hört auf diesen Channel, und weiß dann, dass er das Hauptmenu entladen und das im Event übergebene Level laden muss.
Der Vorteil der losen Kopplung zeigt sich hier darin, dass das Buch keine Szenen laden muss, sondern nur sagen muss, es hätte jetzt gerne diese Szene. Dadurch kann die Logik für Szenen-Management in einem dedizierten Skript stattfinden.