Поведение врага: конечный автомат (FSM)
Враг, который всегда делает одно и то же, скучен. Чтобы он патрулировал, замечал героя и нападал, используют конечный автомат — простую и мощную идею.
Суть: поведение врага удобно описать как конечный автомат (FSM, Finite State Machine): набор состояний (Покой, Патруль, Погоня, Атака) и переходов между ними по условиям. В каждый момент враг находится ровно в одном состоянии и действует согласно ему.
Если писать ИИ врага кучей вложенных if, код быстро превращается в кашу. Лучше мыслить состояниями. Враг может быть в одном из: Покой (стоит), Патруль (ходит туда-сюда), Погоня (бежит за героем), Атака (бьёт). Это и есть конечный автомат: в любой момент он в одном состоянии, и есть правила перехода между ними.
Как работает под капотом
Конечный автомат врага (FSM):
[Покой] --герой близко--> [Погоня] --дистанция атаки--> [Атака]
^ | |
| | герой убежал | герой далеко
| v v
таймер вышел -> [Патруль] <---+----------------------------+
В каждом состоянии своё поведение; переход — по УСЛОВИЮ.Главное достоинство FSM — ясность. Каждое состояние делает одну вещь, переходы явные. Добавить новое поведение — значит добавить состояние и переходы к нему, не ломая остальные.
public enum EnemyState { Idle, Patrol, Chase, Attack }
public class EnemyAI : MonoBehaviour
{
[SerializeField] private float chaseRange = 5f;
[SerializeField] private float attackRange = 1.5f;
private EnemyState state = EnemyState.Patrol;
void Update()
{
float dist = DistanceToPlayer();
switch (state)
{
case EnemyState.Patrol:
if (dist < chaseRange) state = EnemyState.Chase;
break;
case EnemyState.Chase:
if (dist < attackRange) state = EnemyState.Attack;
else if (dist > chaseRange) state = EnemyState.Patrol;
break;
case EnemyState.Attack:
if (dist > attackRange) state = EnemyState.Chase;
break;
}
}
float DistanceToPlayer() { return 3f; } // упрощённо
}FSM языко-независим. Реализуем тот же автомат врага на чистом Python и прогоним по разным дистанциям до героя:
# Состояния врага и переходы по дистанции до героя
def next_state(state, dist, chase=5.0, attack=1.5):
if state == "Patrol":
if dist < chase:
return "Chase"
elif state == "Chase":
if dist < attack:
return "Attack"
elif dist > chase:
return "Patrol"
elif state == "Attack":
if dist > attack:
return "Chase"
return state
state = "Patrol"
# Герой постепенно подходит, потом убегает
for dist in [8, 4, 1, 1, 3, 9]:
state = next_state(state, dist)
print(f"дистанция {dist} -> состояние {state}")Та же логика на Python ▶ — видно, как враг переходит Patrol → Chase → Attack по мере приближения героя и обратно, когда тот убегает. Это и есть «мозг» врага: одно состояние в момент времени плюс правила перехода.
Частые ошибки
- Гигантский if-else вместо состояний. Без FSM логика ИИ становится нечитаемой уже на трёх вариантах поведения.
- Забыть обратные переходы. Враг входит в Погоню, но не выходит из неё — гонится вечно. Каждому переходу нужен обратный.
- Считать дистанцию каждый кадр дорого. Сравнивай квадрат расстояния с квадратом радиуса — это избегает медленного корня.
- Дёргать переход на самой границе. Если порог один и тот же, враг «мерцает» между состояниями. Делай зону погони чуть шире зоны выхода (гистерезис).
Best practices
- Описывай ИИ через явные состояния и переходы — это читаемо и расширяемо.
- Используй
enumдля состояний, чтобы не путаться в строках. - Сравнивай квадраты расстояний (
sqrMagnitude) вместо вычисления корня — это быстрее.
Итоги: поведение врага описывают конечным автоматом: состояния (Покой, Патруль, Погоня, Атака) и переходы между ними по условиям. В любой момент враг в одном состоянии. FSM делает ИИ читаемым и расширяемым; не забывай обратные переходы и используй квадрат расстояния для скорости.
Куда растёт FSM дальше
Конечный автомат отлично работает, пока состояний немного. Но когда враг умеет десятки вещей, переходы между всеми состояниями превращаются в запутанную паутину «каждый со всеми». Индустрия придумала на это ответы, и полезно знать, что они есть. Иерархические автоматы группируют состояния: например, общее состояние «Бой» внутри себя содержит «Погоня» и «Атака», и переходы упрощаются. Behavior Trees (деревья поведения) описывают ИИ как дерево задач с приоритетами — это стандарт в больших играх. Но не спеши к ним: для платформера или аркады простого FSM из четырёх состояний хватает с запасом, и преждевременное усложнение только вредит. Сначала доведи игру до играбельности на простом автомате, а более сложные модели подключай, только когда поведение врага реально упрётся в потолок FSM.