Hero background image
Использование командного паттерна для создания гибких и расширяемых игровых систем

Реализация общих паттернов проектирования игрового программирования в вашем проекте Unity поможет вам эффективно создавать и поддерживать чистую, организованную и читаемую кодовую базу. Паттерны проектирования сокращают время рефакторинга и тестирования, ускоряя процессы разработки и способствуя созданию прочного фундамента для развития вашей игры, команды и бизнеса.

Воспринимайте паттерны проектирования не как готовые решения, которые вы можете скопировать и вставить в свой код, а как дополнительные инструменты, которые помогут вам создавать более крупные и масштабируемые приложения.

На этой странице рассказывается о шаблоне проектирования команд.

Содержание этой статьи основано на бесплатной электронной книге, Повысьте уровень своего кода с помощью паттернов программирования игр.

Ознакомьтесь с другими статьями из серии "Шаблоны проектирования игрового программирования Unity" на хабе " Лучшие практики Unity " или по этим ссылкам:

Понимание шаблона команды

Шаблон командного программирования - один из оригинальных шаблонов "Банды четырех", он полезен, когда нужно отследить определенную серию действий. Вы наверняка видели, как работает шаблон команд, если играли в игры, использующие функции отмены/повтора или сохраняющие историю ввода в виде списка. Представьте себе стратегическую игру, в которой пользователь может спланировать несколько ходов, прежде чем приступить к их выполнению. Это командная схема.

Шаблон команд позволяет представлять действия в виде объектов. Инкапсуляция действий в виде объектов позволяет создать гибкую и расширяемую систему управления поведением объектов GameObject в ответ на ввод пользователя. Это работает за счет инкапсуляции одного или нескольких вызовов методов в "командный объект", а не прямого вызова метода. Затем вы можете хранить эти командные объекты в коллекции, например в очереди или в стеке, который работает как небольшой буфер.

Такое хранение объектов команд позволяет контролировать время их выполнения, потенциально откладывая серию действий для последующего воспроизведения. Кроме того, вы можете повторить или отменить их выполнение, что дает дополнительную гибкость в управлении выполнением каждого объекта команды.

Вот несколько распространенных вариантов применения этого шаблона в разных жанрах игр:

  • В стратегической игре в реальном времени шаблон команды можно использовать для выстраивания очереди действий юнитов и зданий. Затем игра будет выполнять каждую команду по мере поступления ресурсов.
  • В пошаговой стратегической игре игрок может выбрать юнита, а затем хранить его ходы или действия в очереди или другой коллекции. В конце хода игра выполнит все команды в очереди игрока.
  • В игре-головоломке командный шаблон может позволить игроку отменять и повторять действия.
  • В файтинге считывание нажатий кнопок или движений геймпада в определенном списке команд может привести к появлению комбо и специальных приемов.
Шаблон команды в примере проекта
Шаблон команды в примере проекта

Попробуйте пример проекта на GitHub, который демонстрирует различные паттерны программирования в контексте разработки игр, включая паттерн команд.

В этом примере игрок может перемещаться по лабиринту, нажимая на кнопки с левой стороны. Когда ваш игрок перемещается, вы можете увидеть след от движения. Но самое главное - вы можете отменить и повторить свои предыдущие действия.

Чтобы найти соответствующую сцену в проекте, перейдите в папку с названием "9 Command".

Объект команды и инвокер команды

Чтобы реализовать паттерн команды, вам понадобится общий объект, который будет содержать ваше действие. В этом командном объекте будет храниться информация о том, какую логику нужно выполнить и как ее отменить.

Существует множество способов реализовать это, но вот простая версия, использующая интерфейс ICommand:

общедоступный интерфейс ICommand
{
void Execute();
void Undo();
}

В этом случае каждое игровое действие будет применять интерфейс ICommand (вы также можете реализовать это с помощью абстрактного класса).

Каждый объект команды будет отвечать за свои собственные методы Execute и Undo. Поэтому добавление новых команд в игру не повлияет на существующие.

Класс CommandInvoker отвечает за выполнение и отмену команд. Помимо методов ExecuteCommand и UndoCommand, в нем есть стек отмены для хранения последовательности объектов команд.

Пример: Неуправляемое движение

В примере проекта вы можете перемещать игрока по небольшому лабиринту. Простой вариант перемещения позиции игрока - создать PlayerMover.

Для этого в метод Move нужно передать Vector3, чтобы направить игрока по четырем направлениям компаса. Вы также можете использовать лучевой поток для обнаружения стен в соответствующей LayerMask. Конечно, реализация того, что вы хотите применить к командному шаблону, отделена от самого шаблона.

MoveCommand
COMMANDINVOKER, ICOMMAND И MOVECOMMAND
MoveCommand

Чтобы следовать шаблону команд, захватите метод перемещения PlayerMover как объект. Вместо того чтобы вызывать Move напрямую, создайте новый класс MoveCommand, реализующий интерфейс ICommand.

public class MoveCommand : ICommand
{
PlayerMover playerMover;
Вектор3 движения;
public MoveCommand(PlayerMover player, Vector3 moveVector)
{
this.playerMover = player;
this.movement = moveVector;
}
public void Execute()
{
playerMover.Move(movement);
}
public void Undo()
{
playerMover.Move(-movement);
}
}

Все, что вы хотите сделать, находится здесь, поэтому вызовите Move с вектором движения.

ICommand также нуждается в методе Undo для возврата сцены в предыдущее состояние. В этом случае логика Undo вычитает вектор движения, по сути, толкая игрока в противоположном направлении.

Команда MoveCommand сохраняет все параметры, необходимые для ее выполнения. Создайте их с помощью конструктора. В этом случае вы сохраняете соответствующий компонент PlayerMover и вектор движения.

Создав объект команды и сохранив необходимые параметры, используйте статические методы CommandInvoker ExecuteCommand и UndoCommand для передачи команды MoveCommand. Это запускает команду MoveCommand " Выполнить" или "Отменить" и отслеживает объект команды в стеке отмены.

InputManager не вызывает напрямую метод Move проигрывателя PlayerMover. Вместо этого добавьте дополнительный метод RunMoveCommand, чтобы создать новую команду MoveCommand и отправить ее в CommandInvoker.

Затем настройте различные события onClick кнопок пользовательского интерфейса на вызов команды RunPlayerCommand с четырьмя векторами движения.

Подробности реализации InputManager смотрите в примере проекта. Вы также можете настроить собственный ввод с помощью клавиатуры или геймпада. Теперь ваш игрок может перемещаться по лабиринту. Нажмите кнопку Undo, чтобы вернуться к начальному квадрату.

Плюсы и минусы

Реализовать воспроизводимость или отменяемость так же просто, как создать коллекцию объектов команд. Вы также можете использовать буфер команд для последовательного воспроизведения действий с определенными элементами управления.

Например, подумайте о файтинге, где серия нажатий на определенные кнопки вызывает комбо-движение или атаку. Хранение действий игрока в шаблоне команд значительно упрощает создание таких комбинаций.

С другой стороны, паттерн "Команда", как и другие паттерны проектирования, вносит больше структуры. Вам придется решить, где эти дополнительные классы и интерфейсы дают достаточно преимуществ для развертывания командных объектов в вашем приложении.

Освоив основы, вы сможете влиять на время выполнения команд и воспроизводить их в последовательном или обратном порядке, в зависимости от контекста.

При использовании командного шаблона учитывайте следующее:

  • Создайте больше команд: В примере проекта представлен только один тип командного объекта - MoveCommand. Вы можете создать любое количество объектов команд, реализующих ICommand, и отслеживать их с помощью CommandInvoker.
  • Добавление функции повторного выполнения - это просто добавление еще одного стека: Когда вы отменяете объект команды, переместите его в отдельный стек, который отслеживает операции повтора. Таким образом, вы сможете быстро просмотреть историю отмены или повторить эти действия. Очистите стек повторов, когда пользователь вызывает совершенно новое движение (вы можете найти реализацию в проекте примера).
  • Используйте другую коллекцию для буфера командных объектов: Очередь может быть удобнее, если вам нужно поведение "первый пришел - первый ушел" (FIFO). Если вы используете список, отслеживайте активный в данный момент индекс; команды перед активным индексом можно отменить. Команды после индекса являются повторно выполняемыми.
  • Ограничьте размер стопок: Операции отмены и повтора могут быстро выйти из-под контроля. Ограничьте стеки наименьшим количеством команд.
  • Передайте в конструктор все необходимые параметры: Это помогает инкапсулировать логику, как показано в примере MoveCommand.
  • CommandInvoker, как и другие внешние объекты, не видит внутреннюю работу объекта команды, а только вызывает Execute или Undo. При вызове конструктора передайте объекту command все необходимые для работы данные.
синяя электронная книга
Дополнительные ресурсы

Дополнительные советы по использованию паттернов проектирования в приложениях Unity, а также принципы SOLID вы найдете в бесплатной электронной книге Level up your code with game programming patterns.

Все передовые технические электронные книги и статьи по Unity можно найти в хабе лучших практик. Электронные книги также доступны на странице " Передовые методы" в документации.

Понравился ли вам этот контент?