Состояния как классы
Часть раздела Архитектура игр в Unity путешествия по Unity на Coddy. Урок 28 из 50.
В разделе 3 оператор switch по перечислению объединяет работу всех состояний в одном 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 также не вызывает их методы: Beacon — это MonoBehaviour, поэтому его Update вызывает current.Tick() каждый кадр.
timer принадлежит только BlinkState, а его Enter каждый раз устанавливает его в 0, когда маяк начинает мигать. Exit приводит состояние в порядок при его завершении: нажатие Space может произойти, пока лампа выключена, и Exit включает её обратно, поэтому SteadyState никогда не начинается с погасшей лампой. SetState вызывает Exit старого состояния, затем Enter нового. Третье состояние, например медленная пульсация, — это ещё один класс, а Update в Beacon не меняется.
Задание
СреднеТурель-часовой охраняет двор. Стрелки управляют персонажем Player (готово). Задай турели два состояния в виде классов:
- В
IState.csобъявиpublic interface IStateс методамиvoid Enter(),void Tick()иvoid Exit(). - В
TurretStates.csнапиши два обычных класса, реализующих этот интерфейс.ScanStateполучает голову турели в конструкторе. Она поворачивает голову из стороны в сторону: локальный угол головы по оси y меняется со скоростью 90 градусов в секунду в диапазоне от-45до45, начиная с 0 и каждый раз при начале сканирования поворачиваясь вверх. В методе Enter она также окрашивает голову вColor.green. AlertStateполучает голову и игрока. В методе Enter он окрашивает голову вColor.redи выводит в журналIntruder!; в методе Tick поворачивает голову к игроку только по оси y, без наклона; в методе Exit выводит в журналAll clear.- В
Turret.csсоздай оба состояния в методе Start и начни сканирование. Каждый кадр переключайся в состояние тревоги, если игрок находится ближе, чемrange(4), к турели, и обратно в состояние сканирования, если это не так, а затем запускай метод Tick текущего состояния. При смене состояния сначала запускается метод Exit старого состояния, а затем метод Enter нового.
Нажми Play, подойди к турели и снова отойди, затем нажми Check.
Попробуйте сами
using UnityEngine;
// ScanState и AlertState
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Архитектура игр в Unity
1Компоненты, а не иерархии
Небольшие компонентыБазовый скрипт врагаАбстрактные компонентыСовместная работа компонентовПовторение: модульная турель4Сериализованные данные
Сериализуемые классыСписки в InspectorПеречисления в InspectorПовторение: таблица лута7Конечные автоматы
Состояния как классыКонечный автоматПереходыЛогика враговПовторение: состояния двери2Интерфейсы в Unity
IDamageableИнтерфейсы и TryGetComponentGetComponentsПовторение: клавиша взаимодействия5ScriptableObjects
Ресурсы данныхХарактеристики оружияОбщие настройкиВолны как данныеПовторение: база предметов