← Назад к вопросам

Реализовать конечный автомат (FSM) для ИИ врага

1.7 Middle🔥 211 комментариев
#C# и ООП#Unity Core#Опыт и софт-скиллы#Паттерны проектирования

Условие

Реализуйте систему конечного автомата (Finite State Machine) для управления поведением ИИ врага в игре.

Требования

  1. Создайте базовый класс State с методами:
    • OnEnter() - вызывается при входе в состояние
    • OnUpdate() - вызывается каждый кадр
    • OnExit() - вызывается при выходе из состояния
  2. Создайте класс StateMachine для управления переходами
  3. Реализуйте состояния для врага:
    • IdleState - враг стоит на месте
    • PatrolState - враг патрулирует по точкам
    • ChaseState - враг преследует игрока
    • AttackState - враг атакует игрока
  4. Переходы между состояниями должны зависеть от расстояния до игрока

Пример структуры

public abstract class State
{
    protected StateMachine stateMachine;
    public virtual void OnEnter() { }
    public virtual void OnUpdate() { }
    public virtual void OnExit() { }
}

public class StateMachine : MonoBehaviour
{
    private State currentState;
    public void ChangeState(State newState) { /* ... */ }
}

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Что такое FSM (Finite State Machine)?

Конечный автомат — фундаментальный паттерн для управления поведением ИИ в играх. FSM позволяет разбить сложное поведение на дискретные состояния и чётко определённые переходы между ними. Это делает код предсказуемым, легко тестируемым и расширяемым. Враг может быть в одном состоянии одновременно (Idle, Patrol, Chase или Attack), и логика переходов определяется условиями окружения.

Базовая реализация FSM

using UnityEngine;
using System;

/// <summary>Базовый класс для состояния в FSM</summary>
public abstract class State
{
    protected StateMachine stateMachine;
    protected GameObject gameObject;
    protected Transform transform;
    
    public State(StateMachine machine)
    {
        stateMachine = machine;
        gameObject = machine.gameObject;
        transform = machine.transform;
    }
    
    /// <summary>Вызывается при входе в состояние</summary>
    public virtual void OnEnter()
    {
    }
    
    /// <summary>Вызывается каждый кадр</summary>
    public virtual void OnUpdate()
    {
    }
    
    /// <summary>Вызывается в FixedUpdate для физики</summary>
    public virtual void OnFixedUpdate()
    {
    }
    
    /// <summary>Вызывается при выходе из состояния</summary>
    public virtual void OnExit()
    {
    }
}

/// <summary>Управляет переходами между состояниями</summary>
public class StateMachine : MonoBehaviour
{
    private State currentState;
    private State previousState;
    
    protected virtual void Update()
    {
        if (currentState != null)
        {
            currentState.OnUpdate();
        }
    }
    
    protected virtual void FixedUpdate()
    {
        if (currentState != null)
        {
            currentState.OnFixedUpdate();
        }
    }
    
    /// <summary>Переключить состояние</summary>
    public void ChangeState(State newState)
    {
        if (newState == currentState)
            return; // Не переключаемся на то же состояние
        
        // Вызываем OnExit для текущего состояния
        currentState?.OnExit();
        
        // Сохраняем предыдущее состояние
        previousState = currentState;
        currentState = newState;
        
        // Вызываем OnEnter для нового состояния
        currentState?.OnEnter();
        
        Debug.Log($"FSM: Переход {previousState?.GetType().Name} -> {currentState.GetType().Name}");
    }
    
    /// <summary>Получить текущее состояние</summary>
    public State GetCurrentState() => currentState;
    
    /// <summary>Проверить является ли текущее состояние типом T</summary>
    public bool IsInState<T>() where T : State => currentState is T;
}

Реализация ИИ врага

public class Enemy : StateMachine
{
    [Header("Movement")]
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float patrolDistance = 10f;
    
    [Header("Detection")]
    [SerializeField] private Transform player;
    [SerializeField] private float detectionRange = 15f;
    [SerializeField] private float attackRange = 2f;
    
    [Header("Combat")]
    [SerializeField] private float attackCooldown = 1.5f;
    [SerializeField] private int attackDamage = 10;
    
    private float lastAttackTime = -Mathf.Infinity;
    private Rigidbody rb;
    private Animator animator;
    
    // Состояния
    private IdleState idleState;
    private PatrolState patrolState;
    private ChaseState chaseState;
    private AttackState attackState;
    
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        animator = GetComponent<Animator>();
        
        // Инициализируем состояния
        idleState = new IdleState(this, 3f);
        patrolState = new PatrolState(this, transform.position, patrolDistance);
        chaseState = new ChaseState(this, player, moveSpeed);
        attackState = new AttackState(this, player, attackDamage, attackCooldown);
        
        // Запускаем с состояния Idle
        ChangeState(idleState);
    }
    
    void Update()
    {
        base.Update();
        UpdateStateTransitions();
    }
    
    void FixedUpdate()
    {
        base.FixedUpdate();
    }
    
    /// <summary>Логика переходов между состояниями</summary>
    private void UpdateStateTransitions()
    {
        if (player == null)
            return;
        
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);
        
        // Враг может атаковать
        if (distanceToPlayer <= attackRange)
        {
            if (!IsInState<AttackState>())
            {
                ChangeState(attackState);
            }
        }
        // Враг видит игрока и преследует его
        else if (distanceToPlayer <= detectionRange)
        {
            if (!IsInState<ChaseState>())
            {
                ChangeState(chaseState);
            }
        }
        // Игрок вне видимости - патруль
        else if (!IsInState<PatrolState>())
        {
            ChangeState(patrolState);
        }
    }
    
    public void TakeDamage(int damage)
    {
        Debug.Log($"Enemy took {damage} damage");
        // Реализуй логику получения урона
    }
    
    public float GetMoveSpeed() => moveSpeed;
    public float GetDetectionRange() => detectionRange;
    public float GetAttackRange() => attackRange;
}

/// <summary>Состояние: враг стоит на месте</summary>
public class IdleState : State
{
    private float idleDuration;
    private float idleTimer;
    
    public IdleState(StateMachine machine, float duration) : base(machine)
    {
        idleDuration = duration;
    }
    
    public override void OnEnter()
    {
        idleTimer = 0f;
        if (gameObject.TryGetComponent<Animator>(out var animator))
        {
            animator.SetBool("IsMoving", false);
        }
    }
    
    public override void OnUpdate()
    {
        idleTimer += Time.deltaTime;
        
        // После истечения времени переходим в патруль
        if (idleTimer >= idleDuration)
        {
            // Переход будет обработан в UpdateStateTransitions
        }
    }
}

/// <summary>Состояние: враг патрулирует</summary>
public class PatrolState : State
{
    private Vector3 patrolCenter;
    private float patrolRadius;
    private Vector3 targetPatrolPoint;
    private float moveSpeed;
    
    public PatrolState(StateMachine machine, Vector3 center, float radius) : base(machine)
    {
        patrolCenter = center;
        patrolRadius = radius;
        GenerateNewPatrolPoint();
    }
    
    public override void OnEnter()
    {
        if (gameObject.TryGetComponent<Animator>(out var animator))
        {
            animator.SetBool("IsMoving", true);
        }
        
        moveSpeed = (stateMachine as Enemy).GetMoveSpeed();
        GenerateNewPatrolPoint();
    }
    
    public override void OnUpdate()
    {
        // Движение к целевой точке патруля
        Vector3 direction = (targetPatrolPoint - transform.position).normalized;
        
        // Вычисляем расстояние
        float distanceToTarget = Vector3.Distance(transform.position, targetPatrolPoint);
        
        if (distanceToTarget < 1f)
        {
            GenerateNewPatrolPoint();
        }
        else
        {
            // Простое движение
            transform.position += direction * moveSpeed * Time.deltaTime;
        }
    }
    
    private void GenerateNewPatrolPoint()
    {
        // Генерируем случайную точку в пределах патрульной зоны
        Vector2 randomPoint = Random.insideUnitCircle * patrolRadius;
        targetPatrolPoint = patrolCenter + new Vector3(randomPoint.x, 0, randomPoint.y);
    }
}

/// <summary>Состояние: враг преследует игрока</summary>
public class ChaseState : State
{
    private Transform player;
    private float moveSpeed;
    
    public ChaseState(StateMachine machine, Transform playerTransform, float speed) : base(machine)
    {
        player = playerTransform;
        moveSpeed = speed;
    }
    
    public override void OnEnter()
    {
        if (gameObject.TryGetComponent<Animator>(out var animator))
        {
            animator.SetBool("IsMoving", true);
            animator.SetBool("IsChasing", true);
        }
    }
    
    public override void OnUpdate()
    {
        if (player == null)
            return;
        
        // Направление к игроку
        Vector3 directionToPlayer = (player.position - transform.position).normalized;
        
        // Движение к игроку
        transform.position += directionToPlayer * moveSpeed * Time.deltaTime;
        
        // Поворот в сторону игрока
        if (directionToPlayer != Vector3.zero)
        {
            transform.rotation = Quaternion.LookRotation(directionToPlayer);
        }
    }
    
    public override void OnExit()
    {
        if (gameObject.TryGetComponent<Animator>(out var animator))
        {
            animator.SetBool("IsChasing", false);
        }
    }
}

/// <summary>Состояние: враг атакует игрока</summary>
public class AttackState : State
{
    private Transform player;
    private int attackDamage;
    private float attackCooldown;
    private float lastAttackTime;
    
    public AttackState(StateMachine machine, Transform playerTransform, int damage, float cooldown) : base(machine)
    {
        player = playerTransform;
        attackDamage = damage;
        attackCooldown = cooldown;
        lastAttackTime = -Mathf.Infinity;
    }
    
    public override void OnEnter()
    {
        if (gameObject.TryGetComponent<Animator>(out var animator))
        {
            animator.SetBool("IsMoving", false);
            animator.SetTrigger("Attack");
        }
    }
    
    public override void OnUpdate()
    {
        if (player == null)
            return;
        
        // Поворот в сторону игрока
        Vector3 directionToPlayer = (player.position - transform.position).normalized;
        transform.rotation = Quaternion.LookRotation(directionToPlayer);
        
        // Атака с промежутком времени
        if (Time.time - lastAttackTime >= attackCooldown)
        {
            DealDamage();
            lastAttackTime = Time.time;
        }
    }
    
    private void DealDamage()
    {
        // Проверяем есть ли у игрока компонент для получения урона
        if (player.TryGetComponent<IHealth>(out var health))
        {
            health.TakeDamage(attackDamage);
            Debug.Log($"Enemy dealt {attackDamage} damage to player");
        }
    }
}

/// <summary>Интерфейс для объектов которые могут получать урон</summary>
public interface IHealth
{
    void TakeDamage(int damage);
}

Ключевые особенности

1. Иерархия состояний — все состояния наследуют от базового класса State, что обеспечивает единообразный интерфейс.

2. Чёткие переходы — логика переходов централизована в UpdateStateTransitions(), что упрощает отладку.

3. Разделение ответственности — каждое состояние отвечает только за свою логику (Idle, Patrol, Chase, Attack).

4. Гибкость — легко добавить новые состояния, просто создав новый класс, наследующий State.

5. Отладка — логирование переходов помогает отследить поведение ИИ.

Расширение системы

Можешь добавить дополнительные состояния:

  • DamagedState — враг отступает после получения урона
  • DeadState — враг повалился
  • FleeState — враг убегает при низком здоровье

Эта реализация обеспечивает гибкость, расширяемость и лёгкую отладку ИИ поведения.

Реализовать конечный автомат (FSM) для ИИ врага | PrepBro