게임 개발에서 적 AI는 플레이어에게 생동감 있는 경험을 제공하는 중요한 요소 중 하나입니다. 특히 AI 캐릭터가 다양한 상태를 유지하며 상호작용할 때, 상태에 따른 적절한 행동이 요구됩니다. 이때 효율적인 AI 구현을 위해 **상태 패턴(State Pattern)**을 활용하는 것은 매우 유용한 접근법입니다. 상태 패턴은 AI의 행동과 조건이 복잡해질수록 코드 관리가 어렵고 복잡해지는 문제를 효과적으로 해결해 줍니다. 이번 글에서는 Unity에서 상태 패턴을 활용하여 적 AI를 구현하는 방법을 실전 예제 코드와 함께 소개하겠습니다.


상태 패턴의 필요성과 중요성

게임 AI는 플레이어와의 상호작용에 따라 다양한 상태로 전환해야 합니다. 예를 들어 적 AI는 대기 상태에서 경계 상태, 공격 상태 등으로 전환될 수 있으며, 상황에 따라 적절한 반응을 보여야 합니다. **상태 패턴(State Pattern)**을 사용하면 이런 복잡한 상태 전환을 코드에서 깔끔하게 관리할 수 있습니다.

상태 패턴을 통해 AI 행동 코드를 모듈화하면, 코드의 유지보수성과 확장성이 높아지며, 새로운 상태나 행동을 추가할 때 다른 코드에 영향을 주지 않고 독립적으로 작업할 수 있습니다. 이로 인해 개발 속도가 빨라지고, 코드 오류의 발생 가능성도 줄어들게 됩니다.


상태 패턴이란 무엇인가?

**상태 패턴(State Pattern)**은 객체의 상태에 따라 해당 객체의 행동을 캡슐화하여 상태 전환을 쉽게 관리하는 설계 패턴입니다. 게임 AI에서 상태 패턴은 캐릭터의 행동을 여러 상태로 나누고, 각 상태마다 다른 행동을 수행하게 만들어 다양한 상황에 유연하게 대응하도록 합니다.

상태 패턴을 사용한 적 AI 구현에서는 상태를 개별 클래스로 분리하여, AI 캐릭터가 상태에 따라 적절한 행동을 취하게 합니다. 이로 인해 코드가 구조화되고, 상태 전환에 필요한 조건을 쉽게 설정할 수 있어 보다 직관적인 코드를 작성할 수 있습니다.


상태 패턴의 활용 예: 적 AI 행동 구현

적 AI가 플레이어와 마주칠 때, 거리에 따라 다른 상태로 행동하게 만드는 예제를 살펴보겠습니다. 이 예제에서는 Idle, Patrol, Chase, Attack 네 가지 상태를 구현하여, 적이 다양한 상태에서 자연스러운 행동을 취하도록 합니다.

1. 상태 인터페이스 설계

모든 상태가 공통으로 가져야 할 메서드를 정의하는 인터페이스를 생성합니다. Enter, Execute, Exit 메서드는 상태 진입, 상태 유지, 상태 종료 시 각각 호출됩니다.

public interface IEnemyState
{
    void Enter(Enemy enemy); // 상태 진입 시
    void Execute(Enemy enemy); // 상태 유지 시
    void Exit(Enemy enemy); // 상태 종료 시
}

2. 구체적인 상태 클래스 구현

이제 각 상태에 따라 서로 다른 행동을 구현해보겠습니다.

  • Idle 상태: 적이 대기하며 아무 행동도 하지 않는 상태
  • Patrol 상태: 적이 지정된 경로를 따라 순찰하는 상태
  • Chase 상태: 플레이어를 발견했을 때 추적하는 상태
  • Attack 상태: 플레이어와 근접했을 때 공격하는 상태
public class IdleState : IEnemyState
{
    public void Enter(Enemy enemy) { /* 초기화 작업 */ }

    public void Execute(Enemy enemy)
    {
        // 플레이어가 범위 내에 있을 경우 추적 상태로 전환
        if (enemy.IsPlayerInRange())
        {
            enemy.ChangeState(new ChaseState());
        }
    }

    public void Exit(Enemy enemy) { /* 상태 종료 시 처리 */ }
}

public class PatrolState : IEnemyState
{
    public void Enter(Enemy enemy) { /* 초기화 작업 */ }

    public void Execute(Enemy enemy)
    {
        // 순찰 이동
        enemy.Patrol();

        // 플레이어가 범위 내에 있으면 추적 상태로 전환
        if (enemy.IsPlayerInRange())
        {
            enemy.ChangeState(new ChaseState());
        }
    }

    public void Exit(Enemy enemy) { /* 상태 종료 시 처리 */ }
}

public class ChaseState : IEnemyState
{
    public void Enter(Enemy enemy) { /* 초기화 작업 */ }

    public void Execute(Enemy enemy)
    {
        // 플레이어를 추적
        enemy.ChasePlayer();

        // 플레이어가 공격 범위 내에 있으면 공격 상태로 전환
        if (enemy.IsPlayerInAttackRange())
        {
            enemy.ChangeState(new AttackState());
        }
    }

    public void Exit(Enemy enemy) { /* 상태 종료 시 처리 */ }
}

public class AttackState : IEnemyState
{
    public void Enter(Enemy enemy) { /* 초기화 작업 */ }

    public void Execute(Enemy enemy)
    {
        // 플레이어를 공격
        enemy.AttackPlayer();

        // 플레이어가 범위 밖으로 나가면 추적 상태로 전환
        if (!enemy.IsPlayerInAttackRange())
        {
            enemy.ChangeState(new ChaseState());
        }
    }

    public void Exit(Enemy enemy) { /* 상태 종료 시 처리 */ }
}

3. 적 AI 클래스에 상태 패턴 적용

적 AI 클래스는 현재 상태를 보유하며, 상태를 변경할 수 있는 ChangeState 메서드를 포함합니다.

public class Enemy : MonoBehaviour
{
    private IEnemyState currentState;

    private void Start()
    {
        // 초기 상태 설정
        ChangeState(new IdleState());
    }

    private void Update()
    {
        // 현재 상태의 행동을 실행
        currentState.Execute(this);
    }

    public void ChangeState(IEnemyState newState)
    {
        // 현재 상태가 존재하면 상태 종료
        currentState?.Exit(this);

        // 새로운 상태로 변경하고 초기화
        currentState = newState;
        currentState.Enter(this);
    }

    public bool IsPlayerInRange() { /* 플레이어 거리 체크 */ }
    public bool IsPlayerInAttackRange() { /* 공격 거리 체크 */ }
    public void Patrol() { /* 순찰 행동 */ }
    public void ChasePlayer() { /* 추적 행동 */ }
    public void AttackPlayer() { /* 공격 행동 */ }
}

상태 패턴의 장점과 단점

장점

  • 코드 관리의 용이성: 상태를 별도의 클래스로 분리하여, 각 상태의 로직을 쉽게 관리할 수 있습니다.
  • 유연한 상태 추가: 새로운 상태 추가가 용이하며, 기존 코드에 영향을 주지 않습니다.
  • 코드 확장성: 다양한 상태를 추가하거나 수정할 때 수정 범위가 제한적이라 오류 가능성이 낮습니다.

단점

  • 클래스 수 증가: 상태가 많아질수록 클래스를 추가해야 하므로 코드베이스가 커질 수 있습니다.
  • 상태 전환 조건 관리 복잡성: 복잡한 상태 전환 로직이 필요할 때, 이를 관리하는 코드가 늘어날 수 있습니다.

마무리

상태 패턴을 활용하여 적 AI를 구현하면, 다양한 상황에서 유연하게 대응하는 AI 시스템을 구축할 수 있습니다. 상태 패턴을 잘 활용하면 AI의 행동이 복잡해질수록 코드의 유지보수성과 확장성을 높일 수 있으며, 게임 로직을 깔끔하게 관리할 수 있습니다.

이 글을 통해 상태 패턴의 이해가 게임 개발에 어떻게 도움이 되는지 깨닫고, 직접 Unity에서 활용해보시기 바랍니다. AI의 행동을 체계적으로 관리하고 싶다면, 상태 패턴은 유용한 선택이 될 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다