Menu
CoddyTech

Состояния как классы

Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 35 из 64.

В разделе 3 переключение по перечислению объединяет работу всех состояний в одном Update. Такой подход перестаёт масштабироваться, когда у состояний появляются собственные данные: таймер, используемый только одним состоянием, становится полем MonoBehaviour рядом с полями всех остальных состояний, и в каждом case нужно не забыть сбросить используемые данные. Шаблон проектирования «Состояние» в C# даёт каждому состоянию собственный класс, и каждый класс реализует один интерфейс. Этот дорожный маяк мигает или горит постоянно, а клавиша Space переключает режимы:

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

Состояния — это обычные классы C#, а не MonoBehaviours. Их создают с помощью new и передают им всё необходимое через конструктор — в данном случае Renderer лампы, поскольку это не компоненты, и они не могут вызывать GetComponent или отображать поля в Inspector. Unity также не вызывает их методы: MonoBehaviour здесь — Beacon, поэтому его Update вызывает current.Tick() каждый кадр.

Переменная timer принадлежит только BlinkState, а его Enter каждый раз устанавливает её в 0, когда маяк начинает мигать. Exit приводит состояние в порядок при его завершении: Space может быть нажата, пока лампа выключена, и Exit снова включает её, поэтому SteadyState никогда не начинается с тёмной лампой. SetState вызывает Exit старого состояния, а затем Enter нового. Третье состояние, например медленное пульсирование, — это ещё один класс, и Update в Beacon не меняется.

challenge icon

Задание

Средне

Напишите состояние сканирования турели.

  1. В IState.cs объявите public interface IState с методами void Enter();, void Tick(); и void Exit();.
  2. В TurretStates.cs напишите ScanState : IState, передав ему головку. При входе: угол 0, цвет головки Color.green. При обновлении: поворачивайте головку вокруг локальной оси y между -45 и 45 со скоростью 90 градусов в секунду.
  3. Turret: создайте экземпляр в Start и обновляйте его в Update.

Нажмите «Играть»: зелёная головка будет поворачиваться из стороны в сторону. Затем нажмите «Проверить».

Попробуйте сами

using UnityEngine;

// ScanState

Все уроки раздела Архитектура игр в Unity

Потренируйтесь самостоятельно: Песочница Unity