Menu
CoddyTech

Zustände als Klassen

Teil des Abschnitts Spielarchitektur in Unity der Unity-Journey von Coddy. Lektion 35 von 64.

Der Schalter über eine Enumeration in Abschnitt 3 bündelt die Arbeit aller Zustände in einem einzigen Update. Das lässt sich nicht mehr gut erweitern, sobald Zustände eigene Daten haben: Ein Timer, den nur ein Zustand verwendet, wird zu einem Feld des MonoBehaviour, neben den Feldern aller anderen Zustände, und jeder Fall muss daran denken, das jeweils Verwendete zurückzusetzen. Das Zustandsmuster aus C# gibt jedem Zustand eine eigene Klasse, und jede Klasse implementiert dieselbe Schnittstelle. Dieses Straßenleuchtfeuer blinkt oder bleibt eingeschaltet, und die Leertaste wechselt zwischen den beiden Zuständen:

public interface IState
{
    void Enter();
    void Tick();
    void Exit();
}

public class BlinkState : IState
{
    private Renderer lamp;
    private float timer;

    public BlinkState(Renderer lamp)
    {
        this.lamp = lamp;
    }

    public void Enter() { timer = 0f; }

    public void Tick()
    {
        timer += Time.deltaTime;
        if (timer >= 0.5f)
        {
            timer = 0f;
            lamp.enabled = !lamp.enabled;
        }
    }

    public void Exit() { lamp.enabled = true; }
}

public class SteadyState : IState
{
    public void Enter() { }
    public void Tick() { }
    public void Exit() { }
}

public class Beacon : MonoBehaviour
{
    public Renderer lamp;
    private IState blink;
    private IState steady;
    private IState current;

    void Start()
    {
        blink = new BlinkState(lamp);
        steady = new SteadyState();
        SetState(steady);
    }

    void Update()
    {
        if (Keyboard.current.spaceKey.wasPressedThisFrame) SetState(current == steady ? blink : steady);
        current.Tick();
    }

    void SetState(IState next)
    {
        if (current != null) current.Exit();
        current = next;
        current.Enter();
    }
}

Die Zustände sind einfache C#-Klassen, keine MonoBehaviours. Du erstellst sie mit new und übergibst ihnen über den Konstruktor, was sie benötigen – hier den Renderer der Lampe –, denn sie sind keine Komponenten und können weder GetComponent aufrufen noch Felder im Inspector anzeigen. Unity ruft ihre Methoden ebenfalls nie auf: Beacon ist das MonoBehaviour, daher ruft sein Update in jedem Frame current.Tick() auf.

Der timer gehört allein zu BlinkState, und sein Enter setzt ihn jedes Mal auf 0, wenn das Leuchtfeuer zu blinken beginnt. Exit räumt auf, wenn der Zustand endet: Die Leertaste kann gedrückt werden, während die Lampe ausgeschaltet ist, und Exit schaltet sie wieder ein, sodass SteadyState nie mit einer dunklen Lampe beginnt. SetState führt Exit des alten Zustands aus und danach Enter des neuen Zustands. Ein dritter Zustand, etwa ein langsames Pulsieren, ist eine weitere Klasse, und Beacons Update ändert sich nicht.

challenge icon

Aufgabe

Mittel

Schreibe den Scan-Zustand des Geschützturms.

  1. Definiere in IState.cs public interface IState mit void Enter();, void Tick(); und void Exit();.
  2. Schreibe in TurretStates.cs ScanState : IState, der den Kopf übergeben wird. Enter: Winkel 0, Kopf Color.green. Tick: Schwenke den lokalen y-Winkel des Kopfes zwischen -45 und 45 mit 90 Grad pro Sekunde.
  3. Turret: Starte einen Zustand in Start und führe Tick in Update aus.

Drücke „Spielen“: Der grüne Kopf schwenkt. Drücke dann „Prüfen“.

Probier es selbst

using UnityEngine;

// ScanState

Alle Lektionen in Spielarchitektur in Unity

Übe selbstständig: Unity-Playground