C#-Ereignisse in Unity
Teil des Abschnitts Spielarchitektur in Unity der Unity-Journey von Coddy. Lektion 12 von 64.
Wenn sich der Punktestand ändert, sind mehrere Dinge davon betroffen: der Punktestandtext, ein Sound, ein Erfolg. Wenn der ScoreKeeper jedes davon aufruft, braucht er für jedes ein Feld, und jede neue Funktion bedeutet, dass er bearbeitet werden muss. Ein Ereignis kehrt das um: Der ScoreKeeper kündigt die Änderung an, und wer davon betroffen ist, abonniert sie.
Zwei C#-Wörter machen das möglich. Action<int> ist der Typ einer Methode, die einen int entgegennimmt und nichts zurückgibt. Ein event dieses Typs ist eine Liste solcher Methoden: Anderer Code fügt seine Methode mit += hinzu, und der Besitzer ruft sie alle mit Invoke auf. In einfachem C#:
using System;
class Alarm
{
public event Action<int> Rang;
public void Ring(int hour)
{
Rang?.Invoke(hour);
}
}
class Sleeper
{
public void WakeUp(int hour)
{
Console.WriteLine("Up at " + hour);
}
}
// an anderer Stelle
Alarm alarm = new Alarm();
Sleeper sleeper = new Sleeper();
alarm.Rang += sleeper.WakeUp;
alarm.Ring(7); // Aufgestanden um 7In Unity sind der Herausgeber und seine Listener Komponenten, üblicherweise auf unterschiedlichen GameObjects:
using System;
using UnityEngine;
public class ScoreKeeper : MonoBehaviour
{
public event Action<int> OnScoreChanged;
private int score;
public void AddPoints(int points)
{
score += points;
OnScoreChanged?.Invoke(score);
}
}// ScoreLabel.cs, am Text des HUDs
public ScoreKeeper keeper;
public TMP_Text label;
void Start()
{
keeper.OnScoreChanged += ShowScore;
}
void ShowScore(int score)
{
label.text = "Score: " + score;
}Action<int> bedeutet, dass jeder Listener eine Methode ist, die einen int entgegennimmt, hier den neuen Punktestand. Invoke ruft jede abonnierte Methode in der Reihenfolge auf, in der sie abonniert wurde. Gibt es keinen Listener, ist das Ereignis null. Ein einfaches OnScoreChanged(score) würde daher eine NullReferenceException auslösen, während ?. den Aufruf überspringt. Das Schlüsselwort event beschränkt andere Skripte auf += und -=: In ScoreLabel wird keeper.OnScoreChanged(5) oder keeper.OnScoreChanged = null nicht kompiliert.
Der ScoreKeeper hat kein Feld für das Label und weiß nicht, dass es existiert. Ein Combo-Zähler oder ein Sound-Player abonniert das Ereignis auf dieselbe Weise, und der ScoreKeeper bleibt unverändert. Eine Testszene ohne HUD läuft ohne Fehler. Ein Listener findet den Herausgeber über eine Inspector-Referenz, wie hier, oder auf jede andere dir bekannte Weise, etwa mit GetComponent oder einem Singleton. Einen Listener mit -= zu entfernen, ist wichtig, sobald Objekte zerstört werden; darum geht es in der nächsten Lektion.
Aufgabe
EinfachLass den Kämpfer seine Kombo anzeigen.
- Füge in
Fighter.csusing System;undpublic event Action<int> OnComboChanged;hinzu. - Ersetze die Zeilen, in denen
comboTextundflameverwendet werden, durchOnComboChanged?.Invoke(combo);, und lösche beide Felder. - Schreibe in der
Start()-Methode vonComboTextundComboFlamefighter.OnComboChanged += Show;beziehungsweise+= Grow;.
Drücke „Spielen“ und drücke ein paar Mal die Leertaste: Die Kombo wächst und wird dann zurückgesetzt. Klicke anschließend auf „Prüfen“.
Probier es selbst
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
public class Fighter : MonoBehaviour
{
public float comboWindow = 1f;
public TMP_Text comboText;
public Transform flame;
private int combo;
private float sinceHit;
void Update()
{
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
combo++;
sinceHit = 0f;
comboText.text = "Combo x" + combo;
flame.localScale = Vector3.one * (0.5f + 0.25f * combo);
}
else if (combo > 0)
{
sinceHit += Time.deltaTime;
if (sinceHit > comboWindow)
{
combo = 0;
comboText.text = "";
flame.localScale = Vector3.one * 0.5f;
}
}
}
}
Alle Lektionen in Spielarchitektur in Unity
1Komponenten statt Hierarchien
Kleine KomponentenEin Basis-GegnerskriptAbstrakte KomponentenZusammenwirkende KomponentenRückblick: Modulares GeschützRückblick: Zwillingskanone4Serialisierte Daten
Serialisierbare KlassenListen im InspectorEnums im InspectorRückblick: Loot-TabelleRückblick: Beute auswürfeln2Schnittstellen in Unity
IDamageableSchnittstellen und TryGetComponentGetComponentsZusammenfassung: InteraktionstasteZusammenfassung: Nächstes in Reichweite5ScriptableObjects
Daten-AssetsWaffenwerteGemeinsame EinstellungenDen Wind speichernWellen als DatenRückblick: Item-DatenbankRückblick: Im Shop einkaufen8Wiederverwendbarer Code
ErweiterungsmethodenEin generisches SingletonGenerische HilfsmethodenRückblick: FindNearestÜbe selbstständig: Unity-Playground