Erklärung Event-System

Erklärung zu Event-Systemen.

[ Boundfox YouTube Tutorial ]

Wenn Du noch nicht weißt, was ein Event-System ist, dann ist weiter unten eine kleine Erklärung.

Implementierung

Im Code findest Du das Event-System hier. Es besteht aus mehreren Event Channels in Form von ScriptableObjects. Ein Event Channel ist schlicht ein Kanal über den Informationen laufen.

Jeder kann Informationen in ein Event Channel geben und jeder kann diese Informationen mitbekommen. Dadurch das wir hier auf ScriptableObjects setzen, können Sender und Empfänger in unterschiedlichen Szenen sein, was z.B. für das Multi-Scene-Management sehr praktisch ist.

flowchart LR Action["Aktion/Trigger (MonoBehaviour)"] -->|"Raise()"| Event["Event (ScriptableObject)"] EventListener["Event Listener (MonoBehaviour)"] -->|"Horcht auf"| Event EventListener --> Response["Antwort auf Event"]

Event Channel Parameter

Event Channel mit einem primitiven Datentyp wie Boolean als Parameter sollten vermieden werden, solange aus der Bezeichnung des Event Channel nicht eindeutig hervorgeht wofür der Parameter steht.

So wäre beispielsweise bei ToggleLoadingScreen als Event Channel mit einer bool’schen Variablen offensichtlich wofür diese benutzt wird. Bei WaveSpawned mit bool als Parameter hingegen müsste man aus dem Code heraus rückfolgern wie die Variable verwendet wird, oder es wurde eine Beschreibung im Inspektor angelegt, welche jedoch auch “umständlich” zu suchen wäre.

Eine schönere Lösung ist es einen eigenen Typ für die Parameter des Event Channel anzulegen, der die Absicht der Variablen genau beschreibt, womit die Verwendung im Code eindeutiger ist. Als Beispiel:

//Definition
public class LevelFinishedEventChannelSO : EventChannelSO<LevelFinishedEventChannelSO.EventArgs>
{
	public struct EventArgs
	{
		public bool PlayerHasWon;
	}
}

//Triggern des Events
private void LevelFinished()
{
	LevelFinishedEventChannel.Raise(new()
    {
        PlayerHasWon = false
    });
}

//Auf Event reagieren
private void InitDisplay(LevelFinishedEventChannelSO.EventArgs args)
{
    SetLevelFinishedText(args.PlayerHasWon);
    SetupButtons();
    ActivateCanvases();
}

Was ist ein Event-System?

Der Sinn eines Event-Systems ist es, Systeme zu entkoppeln.

Nehmen wir als Beispiel mal das Leben des Spielers. Wenn der Spieler Leben verliert oder bekommt, möchte man das in der Regel im UI anzeigen. Jetzt könnte der Spieler eine Referenz auf das UI haben (oder auch umgekehrt) und sobald der Spieler Leben verliert oder gewinnt, diese Änderung direkt an das UI übertragen. Weiter möchten wir, dass die Gegner sich anders verhalten, je nach dem, wie viel Leben der Spieler hat. Auch hier könnte jetzt der Spieler alle Gegner kennen und ihnen mitteilen, wenn sich das Leben verändert. Weiter geht’s mit anderen Systemen: Post Processing zum Effekte je nach Leben anzeigen, Audio System, dass entsprechend den Sound ändert etc.

flowchart LR Spieler --> UI Spieler --> Gegner1 Spieler --> ... Spieler --> GegnerX Spieler --> PostProcessing Spieler --> AudioSystem

Wenn der Spieler all diese Systeme direkt kennen würde, nennt man dies auch eine hohe Kopplung. In der Softwareentwicklung versucht man, genau diese hohe Kopplung zu vermeiden. Auch wenn dieses Beispiel hier sehr einfach gehalten ist, führt diese Kopplung langsam aber sicher zu schlecht wartbarem Code. Denn, wann immer man sich die Spieler-Klasse ansieht, sehen wir viele Abhängigkeiten im Code. Jetzt muss jeder Entwickler schauen, was genau diese Abhängigkeiten machen, um zu verstehen, wie die Spieler-Klasse funktioniert. Außerdem hat die Spieler-Klasse viel mehr Verantwortung, als sie eigentlich haben müsste, da sie viele Bereiche der Gesamtanwendung kennt und bearbeitet.

Das ist in der Softwareentwicklung kein erstrebenswerter Zustand.

Viel mehr wollen wir eine Entkopplung erreichen und hier tritt unser Event-System ein:

flowchart LR Spieler -->|Leben: 50| EventSystem EventSystem -->|Leben: 50| UI EventSystem -->|Leben: 50| GegnerX EventSystem -->|Leben: 50| PostProcessing EventSystem -->|Leben: 50| AudioSystem

Leider sieht es jetzt durch die Grafik so aus, als hätten wir das Problem zum Event-System verschoben, dass jetzt alle anderen kennt. Dem ist aber nicht so. Anstelle dass der Spieler jedes Einzelsystem bearbeitet, nutzt es einfach nur das Event-System und teilt sein aktuelles Leben mit. Was jetzt damit passiert, das ist dem Spieler vollkommen egal. In diesem Moment haben wir alles voneinander entkoppelt. Die Spieler-Klasse hat viel weniger Verantwortlichkeiten, auch für den Entwickler ist es jetzt sehr viel einfacher zu verstehen, da statt viele Einzelsysteme nur noch das Event-System angesprochen wird. Damit haben wir jetzt eine lose Kopplung erreicht.

Einfach ausgedrückt kann man sich das Event-System als eine Art Walkie-Talkie vorstellen. Jemand teilt seinen Zustand über das Walkie-Talkie mit, ohne zu wissen, wer diese Nachricht hört, ob sich jemand dafür interessiert und was mit der Information passiert.