Skip to content

Event System

DidiSkywalker edited this page Oct 25, 2022 · 4 revisions

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 ScriptableObjects 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

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);
            }
        }
    }
}

Listener

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:
Screenshot des SceneLoader Inspektors zeigt seine Eventlistener

Events

Events sind Geschehnisse im Spiel, die durch EventChannel propagiert werden. Einige Beispiele:

Aus dem Hauptmenu ein Level starten.

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.
Diagramm wie ein Buch im Hauptmenu mit dem SceneLoader kommuniziert.

Clone this wiki locally