Представление игровых объектов в виде конечных автоматов

Гарантия сохранения последовательности действий.

Пользователь Reddit под ником distinctdan опубликовал небольшой пример использования конечных автоматов для создания логики поведения игровых объектов. Мы выбрали из его рассказал главное.

Если вы не знакомы с конечными автоматами, но хотите получить начальные знания по этой теме, советуем прочитать этот текст.

Изображение главного примера этого текста: шар стреляет лазером в игрока, когда тот попадает в поле видимости. У шара есть конечное количество состояний в игре — бездействие, прицеливание и атака
Изображение главного примера этого текста: шар стреляет лазером в игрока, когда тот попадает в поле видимости. У шара есть конечное количество состояний в игре — бездействие, прицеливание и атака

По словам пользователя, недавно он закончил свою мобильную игру Orbus и решил поделиться техниками, которые применял в разработке. Использование конечных автоматов в игре даёт несколько преимуществ.

  • Если организовать весь код в виде чётко прописанных состояний, то это может предотвратить множество ошибок.
  • Это помогает спланировать все состояния, в которых может находиться игровой объект.
  • Прописанные переходы между состояниями гарантируют, что объекты не попадут в странные или незапланированные состояния.
  • Также это позволяет легко сериализовать игровые объекты.

Поведение шара можно определить как последовательность из трёх состояний: бездействие, прицеливание, атака. Чтобы следить за тем, как долго продолжается то или иное состояние, используются таймеры: cooldownTimer и stateTimer. Вот список правил:

  • шар стреляет, если видит игрока, и если он перезарядился;
  • во время прицеливания шар испускает слабый луч, который показывает, что скоро будет атака и игроку пора уворачиваться;
  • атака наносит урон игроку.
tick(delta) { switch(this.state) { case State.Normal: if (this.cooldownTimer > 0) this.cooldownTimer -= delta; if (this.canSeePlayer() && this.cooldownTimer <= 0) { this.transitionToState(State.Targeting); } break; case State.Targeting: this.stateTimer -= delta; if (this.stateTimer <= 0) { this.transitionToState(State.Firing); } break; case State.Firing: this.stateTimer -= delta; if (this.intersectLaserPlayer(this.targetLocation, this.player)) { this.doDamageToPlayer(); } if (this.stateTimer <= 0) { this.transitionToState(State.Normal); } break; } }

Теперь нужно прописать переходы между состояниями. Когда шар входит в новое состояние, он определяет все свойства, необходимые для этого состояния. Можно использовать таймер, чтобы объект оставался в определённом состоянии в течение какого-то времени.

transitionToState(newState) { switch(newState) { case State.Targeting: this.stateTimer = this.TIME_TARGETING; // Set our firing target to the player's current location. this.targetLocation = this.player.location; break; case State.Firing: // Do validation if (this.state !== State.Targeting) { this.log("Invalid state!!! Can't fire without targeting first"); return; } this.stateTimer = this.TIME_FIRING; break; case State.Normal: // Only do a cooldown if we're currently firing. if (this.state === States.Firing) { this.cooldownTimer = this.TIME_COOLDOWN; } else { this.cooldownTimer = 0; } break; } this.state = newState; }

Все таймеры хранятся в виде чисел — это упрощает сохранение и восстановление текущего состояния лазера.

serialize() { return { cooldownTimer: this.cooldownTimer, state: this.state, stateTimer: this.stateTimer, targetLocation: this.targetLocation, } }

Если представить состояние в виде конечного автомата, то это гарантирует, что состояния всегда будут адекватно и последовательно переходить от одного к другому: бездействие — прицеливание — атака. Поэтому шар никогда не выстрелит, пока не прицелится.

3232
23 комментария

Разраб VVVVVV одобряет.

7
Ответить

Тут написано через switch для наглядности. Можно было бы конечно написать через какой-нибудь словарь делегатов, типа

IDictionary<State, Func<State, State>>

Но так было бы не иллюстративно.

Ответить

кошмар! сразу видно, сильный и независимый инди, работает один

Ответить

Для ++ есть классная либа, для быстрого описания конечных автоматов.

4
Ответить

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

3
Ответить

Комментарий недоступен

2
Ответить

Так это оно и есть, только упрощённое максимально

1
Ответить