유니티에서 커맨드 패턴, 언제 그리고 왜 사용해야 할까?

2025. 2. 7. 01:43유니티 unity/디자인패턴

커맨드 패턴은 행동(명령)을 객체로 캡슐화하여 호출자와 실행자 간의 결합도를 낮추고, 실행 취소 및 재실행, 매크로 명령, 행동 기록 및 재생 등 다양한 기능을 구현할 수 있는 디자인 패턴입니다. 본 글에서는 커맨드 패턴의 주요 적용 상황과 그 이유에 대해 자세하게 설명드리겠습니다.

 

 

실행 취소(Undo) 및 재실행(Redo) 기능 구현

적용 상황

  • 사용자 인터랙션이 많은 응용 프로그램: 텍스트 에디터, 그래픽 디자인 툴, 레벨 에디터 등에서는 사용자가 수행한 작업을 쉽게 취소하거나 다시 실행할 필요가 있습니다.
  • 게임 내 행동 취소: 퍼즐 게임이나 전략 게임 등에서 플레이어의 행동을 취소하거나 재실행해야 할 경우에 유용합니다.

커맨드 패턴의 역할

  • 각 행동(예: 이동, 공격, 회전 등)을 커맨드 객체로 캡슐화하여 실행 전 상태를 저장합니다.
  • 실행한 커맨드를 스택이나 리스트에 저장함으로써, 필요 시 Undo() 메서드를 호출하여 해당 행동을 역순으로 취소할 수 있습니다.
  • 예를 들어, 플레이어의 이동 행동을 기록해두면 잘못된 방향으로 이동하였을 때 Undo 명령을 통해 이전 위치로 복귀할 수 있습니다.

 

먼저, 모든 커맨드가 구현해야 할 인터페이스를 정의합니다.

 

public interface ICommand
{
    void Execute();
    void Undo();
}

 

예를 들어, 플레이어의 이동 행동을 구현하는 커맨드 클래스는 다음과 같이 작성할 수 있습니다.

 

using UnityEngine;

public class MoveCommand : ICommand
{
    private Transform _transform;
    private Vector3 _direction;
    private float _distance;
    private Vector3 _previousPosition;

    public MoveCommand(Transform transform, Vector3 direction, float distance)
    {
        _transform = transform;
        _direction = direction.normalized;
        _distance = distance;
    }

    public void Execute()
    {
        // 이동 전의 위치를 저장합니다.
        _previousPosition = _transform.position;
        _transform.position += _direction * _distance;
    }

    public void Undo()
    {
        // 이전 위치로 복귀합니다.
        _transform.position = _previousPosition;
    }
}

 

이제 CommandManager를 사용하여 커맨드를 관리할 수 있습니다.

using System.Collections.Generic;
using UnityEngine;

public class CommandManager : MonoBehaviour
{
    // 실행된 커맨드를 LIFO(후입선출) 방식으로 관리합니다.
    private Stack<ICommand> _commandHistory = new Stack<ICommand>();

    // 커맨드를 실행하고 기록합니다.
    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _commandHistory.Push(command);
    }

    // 가장 최근에 실행된 커맨드를 취소합니다.
    public void UndoCommand()
    {
        if (_commandHistory.Count > 0)
        {
            ICommand command = _commandHistory.Pop();
            command.Undo();
        }
    }
}

 

플레이어 입력에 따라 커맨드를 실행하는 예제는 다음과 같습니다.

 

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float moveDistance = 1f;
    private CommandManager _commandManager;

    private void Start()
    {
        // 씬 내에서 CommandManager를 찾습니다.
        _commandManager = FindObjectOfType<CommandManager>();
    }

    private void Update()
    {
        // 방향키 입력에 따라 해당 방향으로 이동하는 커맨드를 생성 및 실행합니다.
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            ICommand moveUp = new MoveCommand(transform, Vector3.up, moveDistance);
            _commandManager.ExecuteCommand(moveUp);
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            ICommand moveDown = new MoveCommand(transform, Vector3.down, moveDistance);
            _commandManager.ExecuteCommand(moveDown);
        }
        else if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            ICommand moveLeft = new MoveCommand(transform, Vector3.left, moveDistance);
            _commandManager.ExecuteCommand(moveLeft);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            ICommand moveRight = new MoveCommand(transform, Vector3.right, moveDistance);
            _commandManager.ExecuteCommand(moveRight);
        }

        // 예를 들어 'Z' 키 입력 시 마지막 명령을 취소합니다.
        if (Input.GetKeyDown(KeyCode.Z))
        {
            _commandManager.UndoCommand();
        }
    }
}

 

 

 

 

 

복합 행동 및 매크로 명령 구현

적용 상황

  • 복합 행동 처리: 단일 버튼 클릭이나 키 입력으로 여러 행동이 동시에 실행되어야 할 때 사용됩니다.
  • 매크로 기록: 게임에서 특정 콤보 기술이나 복합 행동을 하나의 매크로로 묶어 실행하고자 할 때 활용할 수 있습니다.

커맨드 패턴의 역할

  • 여러 개의 개별 커맨드를 하나의 매크로 커맨드로 묶어 단일 호출로 연속된 행동을 실행하거나 취소할 수 있습니다.
  • 각 커맨드가 독립적으로 실행되고 Undo 기능을 지원하므로, 매크로 커맨드의 Undo 역시 쉽게 구현할 수 있습니다.
  • 예를 들어, 플레이어가 공격, 점프, 회피 등의 여러 행동을 연속적으로 수행할 경우, 전체 행동을 하나의 매크로로 기록하고 필요 시 전체 행동을 한 번에 취소할 수 있습니다.

매크로 커맨드를 구현하는 클래스는 아래와 같이 작성할 수 있습니다.

 

using System.Collections.Generic;
using UnityEngine;

public class MacroCommand : ICommand
{
    private List<ICommand> _commands = new List<ICommand>();

    // 개별 커맨드를 추가합니다.
    public void AddCommand(ICommand command)
    {
        _commands.Add(command);
    }

    // 리스트에 있는 모든 커맨드를 순차적으로 실행합니다.
    public void Execute()
    {
        foreach (ICommand command in _commands)
        {
            command.Execute();
        }
    }

    // 역순으로 모든 커맨드를 실행 취소합니다.
    public void Undo()
    {
        for (int i = _commands.Count - 1; i >= 0; i--)
        {
            _commands[i].Undo();
        }
    }
}

 

 

예를 들어, 플레이어가 공격, 점프, 회피 등의 여러 행동을 하나의 매크로로 기록하여 실행할 수 있습니다.

 

 

 

행동의 기록 및 재생

적용 상황

  • 리플레이 시스템 구현: 플레이어의 행동을 기록하여 동일한 순서로 재생하는 기능이 필요할 때 사용됩니다.
  • 네트워크 동기화: 멀티플레이어 게임에서 각 플레이어의 행동을 기록하여 동기화할 때 효과적입니다.

커맨드 패턴의 역할

  • 각 행동을 커맨드 객체로 캡슐화하면 실행된 명령의 순서와 상태를 체계적으로 기록할 수 있습니다.
  • 저장된 커맨드를 순차적으로 실행함으로써 게임 리플레이나 네트워크 동기화 기능을 자연스럽게 구현할 수 있습니다.
  • 예를 들어, 실시간 전략(RTS) 게임에서 유닛의 이동 및 공격 명령을 기록하고, 게임 종료 후 리플레이 기능을 제공하는 데 유용합니다.

 

사용자 인터페이스(UI) 및 입력 관리

적용 상황

  • 버튼 클릭 이벤트 처리: UI 버튼이나 메뉴 항목에 특정 커맨드를 할당하여, 클릭 시 해당 행동이 실행되도록 할 때 사용됩니다.
  • 입력 처리의 모듈화: 입력 처리를 커맨드 객체로 분리하면 게임 로직과 입력 처리 로직이 독립적으로 관리되어 유지보수가 용이해집니다.

커맨드 패턴의 역할

  • 각 UI 요소나 입력 이벤트에 대응하는 커맨드 객체를 미리 정의하면 사용자 액션에 따른 실행 로직을 중앙에서 통제할 수 있습니다.
  • 다양한 행동을 하나의 인터페이스(ICommand)로 통합하여 입력 관리 시스템(CommandManager 등)과의 결합도를 낮춥니다.
  • 예를 들어, 인벤토리 아이템 사용, 스킬 발동, 메뉴 탐색 등 다양한 UI 인터랙션을 커맨드 객체로 구현하여 필요 시 쉽게 변경하거나 확장할 수 있습니다.

게임 AI 및 스크립팅 시스템

적용 상황

  • AI 행동 계획: AI 캐릭터가 복잡한 행동 패턴을 미리 계획하고 순차적으로 실행할 필요가 있을 때 사용됩니다.
  • 상황별 행동 전환: AI가 특정 조건에서 행동을 취소하거나 변경해야 하는 경우에 유용합니다.

커맨드 패턴의 역할

  • AI의 각 행동을 커맨드 객체로 캡슐화하면 특정 조건(예: 플레이어의 개입, 환경 변화)에 따라 행동을 취소하거나 재구성할 수 있습니다.
  • 행동 순서를 기록하고 필요 시 Undo 또는 다른 커맨드로 전환할 수 있으므로, AI 로직의 유연성이 크게 향상됩니다.
  • 예를 들어, 적 AI가 공격, 후퇴, 대기 등의 행동을 커맨드 객체로 관리하며, 플레이어의 방어에 따라 행동을 즉시 취소하거나 변경할 수 있습니다.

추가 고려 사항

  • 상태 저장의 효율성: Undo 기능 구현 시 커맨드 객체 내에 실행 전 상태를 저장해야 하므로, 상태 저장 방법(깊은 복사, 참조 등)에 따라 메모리 사용량과 성능에 영향을 줄 수 있습니다.
  • 비동기 작업 처리: 애니메이션, 네트워크 요청 등 비동기 작업과 결합할 경우, 커맨드 실행 및 취소가 동기화 문제 없이 올바르게 처리될 수 있도록 설계해야 합니다.
  • 디버깅 및 로그 관리: 각 커맨드 객체에 실행 시간, 실행 주체 등의 메타 정보를 추가하면 복잡한 행동 기록을 디버깅하거나 로그로 남기기 용이합니다.
  • 확장성: 새로운 행동이 추가되더라도 기존의 ICommand 인터페이스를 구현하는 방식으로 확장할 수 있으므로 시스템 전체의 구조를 깔끔하게 유지할 수 있습니다.

 

커맨드 패턴은 다양한 상황에서 행동을 캡슐화하고 유연하게 제어할 수 있는 강력한 도구입니다.
특히, 실행 취소/재실행, 매크로 명령 구현, 행동 기록 및 재생, 사용자 인터페이스 및 입력 관리, 게임 AI 시스템 등 여러 분야에서 효과적으로 활용할 수 있습니다.
프로젝트의 요구 사항에 맞추어 커맨드 패턴을 적용하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있으며, 복잡한 시스템을 보다 체계적으로 관리할 수 있습니다.