Состояния как классы
Часть раздела Архитектура игр в 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 не меняется.
Задание
СреднеНапишите состояние сканирования турели.
- В
IState.csобъявитеpublic interface IStateс методамиvoid Enter();,void Tick();иvoid Exit();. - В
TurretStates.csнапишитеScanState : IState, передав ему головку. При входе: угол 0, цвет головкиColor.green. При обновлении: поворачивайте головку вокруг локальной оси y между-45и45со скоростью90градусов в секунду. Turret: создайте экземпляр в Start и обновляйте его в Update.
Нажмите «Играть»: зелёная головка будет поворачиваться из стороны в сторону. Затем нажмите «Проверить».
Попробуйте сами
using UnityEngine;
// ScanState
Все уроки раздела Архитектура игр в Unity
1Компоненты, а не иерархии
Небольшие компонентыБазовый скрипт врагаАбстрактные компонентыКомпоненты работают вместеПовторение: модульная турельПовторение: спаренная пушка4Сериализованные данные
Сериализуемые классыСписки в инспектореПеречисления в инспектореПовторение: таблица добычиПовторение: определяем добычу7Машины состояний
Состояния как классыСостояние тревогиМашина состоянийПереходыЛогика враговРыцарь атакуетПовторение: состояния двери2Интерфейсы в Unity
IDamageableИнтерфейсы и TryGetComponentGetComponentsПовторение: клавиша взаимодействияПовторение: ближайший в пределах досягаемости5ScriptableObjects
Ресурсы данныхХарактеристики оружияОбщие настройкиСохраняем ветерВолны как данныеПовторение: база предметовПовторение: покупка в магазинеПотренируйтесь самостоятельно: Песочница Unity