
Паттерн Состояние в разработке игр
Все мы делаем игры, и во всех наших играх всегда есть состояния. Главное меню, уровень, миниигра, пауза, переход между экранами и многое другое — все это подходит под определение состояния.
Если вы не хотите превратить ваш код в кашу из разбросанных и сильно связанных состояний, паттерн «Состояние» — то, что вам нужно!
Под катом объяснение что же это такое и пример применения в коде игры.

Суть паттерна — создать единый интерфейс для всех состояний игры и использовать одни и те же методы, наделенные разным поведением в зависимости от текущего состояния.
Диаграмма классов
Простейшая реализация паттерна для игры будет выглядит так

Интерфейс IGameState определяет методы, реализуемые всеми состояниями игры. MainMenuState и GameplayState — конкретные классы реализации игровых состояний. Они реализуют интерфейс IGameState. Класс StateManager отвечает за смену состояний.
Сначала создаем интерфейс, который будут реализовывать все наши состояния. Для примера будем считать, что каждое состояние нашей игры будет будет обновляться каждый кадр функцией update() и уничтожаться функцией destroy().
Теперь создадим конкретные состояния. Ниже приведен код состояния MainMenuState. Точно также выглядит код для GameplayState
Теперь нам нужен класс, который будет управлять состояниями, в том числе совершать переходы между ними. Назовем его StateManager.
В этом классе объявляются константы, соответствующие нашим состояниям. Функция changeState() реализует выбор и смену состояния. Как мы видим, нам не нужно держать переменные для каждого сотояния, достаточно просто использовать currentState. Все остальное за нас сделает полиморфизм :)
Таким образом остался последний штрих — написать простой тест.
Компилируем и получаем в выводе:
Все работает!
В чем же прелесть такого подхода в реализации состояний? Самое главное — это изоляция состояний. То есть каждое состояние отвечает только за свой участок работы приложения. Это приводит к более понятному, логичному и отказоустойчивому коду, а также простоте расширения. Мы можем добавить сколько угодно состояний, не внося правки в кучу классов и при этом сохраняя архитектуру игры неизменной.
Исходники (делалось в FlashDevelop)
Для интересующихся могу порекоммендовать книги:
ActionScript 3.0. Шаблоны проектирования
Паттерны проектирования
Если вы не хотите превратить ваш код в кашу из разбросанных и сильно связанных состояний, паттерн «Состояние» — то, что вам нужно!
Под катом объяснение что же это такое и пример применения в коде игры.

Суть паттерна — создать единый интерфейс для всех состояний игры и использовать одни и те же методы, наделенные разным поведением в зависимости от текущего состояния.
Диаграмма классов
Простейшая реализация паттерна для игры будет выглядит так

Интерфейс IGameState определяет методы, реализуемые всеми состояниями игры. MainMenuState и GameplayState — конкретные классы реализации игровых состояний. Они реализуют интерфейс IGameState. Класс StateManager отвечает за смену состояний.
Рассмотрим код
Сначала создаем интерфейс, который будут реализовывать все наши состояния. Для примера будем считать, что каждое состояние нашей игры будет будет обновляться каждый кадр функцией update() и уничтожаться функцией destroy().
package
{
public interface IGameState
{
function update():void;
function destroy():void;
}
}
Теперь создадим конкретные состояния. Ниже приведен код состояния MainMenuState. Точно также выглядит код для GameplayState
package
{
public class MainMenuState implements IGameState
{
private var stateManager:StateManager;
public function MainMenuState(_stateManager:StateManager)
{
stateManager = _stateManager;
trace("Creating main menu");
}
public function update():void
{
stateManager.changeState(StateManager.GAMEPLAY_STATE); //Где-то в основном цикле происходит переход в состояние игрового процесса
}
public function destroy():void
{
trace("Destroying main menu");
}
}
}
Теперь нам нужен класс, который будет управлять состояниями, в том числе совершать переходы между ними. Назовем его StateManager.
package
{
public class StateManager
{
public static const MAIN_MENU_STATE:int = 1;
public static const GAMEPLAY_STATE:int = 2;
private var currentState:IGameState;
public function StateManager()
{
}
public function changeState(newState:int):void
{
if (currentState)
{
currentState.destroy();
}
switch (newState)
{
case MAIN_MENU_STATE:
{
currentState = new MainMenuState(this);
break;
}
case GAMEPLAY_STATE:
{
currentState = new GameplayState(this);
break;
}
}
}
public function update():void
{
currentState.update();
}
}
}
В этом классе объявляются константы, соответствующие нашим состояниям. Функция changeState() реализует выбор и смену состояния. Как мы видим, нам не нужно держать переменные для каждого сотояния, достаточно просто использовать currentState. Все остальное за нас сделает полиморфизм :)
Тест
Таким образом остался последний штрих — написать простой тест.
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite
{
private var stateManager:StateManager;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stateManager = new StateManager();
stateManager.changeState(StateManager.MAIN_MENU_STATE);
}
private function onEnterFrame(event:Event):void
{
stateManager.update();
}
}
}
Компилируем и получаем в выводе:
[Starting debug session with FDB]
Creating main menu
Destroying main menu
Creating gameplay
Все работает!
В чем же прелесть такого подхода в реализации состояний? Самое главное — это изоляция состояний. То есть каждое состояние отвечает только за свой участок работы приложения. Это приводит к более понятному, логичному и отказоустойчивому коду, а также простоте расширения. Мы можем добавить сколько угодно состояний, не внося правки в кучу классов и при этом сохраняя архитектуру игры неизменной.
Исходники (делалось в FlashDevelop)
Для интересующихся могу порекоммендовать книги:
ActionScript 3.0. Шаблоны проектирования
Паттерны проектирования
- +9
- Rocanten
Комментарии (22)
Я всегда так делаю, в каждом классе где нужны состояния:
private var state:uint;
private const STATE_1:uint=1;
private const STATE_BLABLA:uint=2;
…
а в апдейте — switch(state) {}
Здесь же куча классов оказывается не такой уж и большой, а выгода огромна (высокий уровень инкапсуляции поведения, низкая связность, расширяемость).
Данный паттерн помогает именно что разделить состояния и не смешивать код.
Состояния редко использую, наверное поэтому пока-что норм по простому.
Но смысл не в этом. Пользоваться паттернами стоит только тогда, когда это комфортно. Паттерны ради паттернов применять нельзя. Так что правильно и так и так.
Но вот как только сталкиваешься с менюхой более сложной, чем выбор уровня — сразу начинаются проблемы со свитчами и прочими лапшами. Но это только мой опыт. Каждый решает сам :-)
Зовется PatternCraft:
www.youtube.com/user/johnlindquist/search?query=patterncraft
В IDEA работаешь? Может что посоветуешь :-)
IDEA мне ближе, потому что это тот же производитель, что и ReSharper для Visual Studio, без которого разработка на оной уже не мыслится. Сравнивать с FD бессмысленно, поскольку IDEA обладает наилучшей в своей отрасли функциональностью по рефакторингу, в то время как FD — никакой. Сам просидел на FD года 2-3, очень её уважаю, но факты есть факты :)
2) Если у состояния несколько разных развязок, то например в MainMenuState можно было бы объявить еще несколько callback-методов на каждую из развязок.