본문 바로가기

Unity/디자인 패턴

[Unity] 디자인 패턴 - 커맨드(Command) 패턴 이해하기

반응형

Unity에서 커맨드 패턴 (Command Pattern) 이해하기

커맨드 패턴은 객체 지향 프로그래밍의 디자인 패턴 중 하나로, 명령을 하나의 객체로 캡슐화하여 호출자와 수신자의 결합도를 낮추고 코드의 유연성과 확장성을 높입니다. 특히 게임 개발에서 자주 사용되며, 다양한 입력 처리를 효율적으로 관리할 수 있습니다.

커맨드 패턴의 개념

커맨드 패턴은 명령을 객체 형태로 캡슐화하여 호출자(Invoker)와 수신자(Receiver) 간의 결합도를 낮춥니다. 이를 통해 명령을 큐(queue)나 스택(stack)에 저장하여 나중에 실행하거나 취소(undo), 재실행(redo)할 수 있습니다. 예를 들어, 전략 게임에서 유닛의 이동 명령을 큐에 저장하고, 자원에 따라 순차적으로 실행할 수 있습니다. 이를 통해 각 명령을 독립적으로 관리하고, 명령을 쉽게 추가하거나 수정할 수 있습니다.

주요 구성 요소

  1. 커맨드 인터페이스 (Command Interface):
    • 모든 커맨드 객체가 구현해야 하는 메서드를 정의합니다. 주로 Execute()와 Undo() 메서드를 포함합니다.
  2. 구체적인 커맨드 클래스 (Concrete Command Class):
    • Command 인터페이스를 구현하며, 실제 수행할 명령을 정의합니다.
  3. 리시버 (Receiver):
    • 실제 명령을 수행하는 객체입니다. 예를 들어, Light 클래스는 불을 켜고 끄는 기능을 가집니다.
  4. 인보커 (Invoker):
    • 클라이언트가 명령을 요청하는 역할을 합니다. 인보커는 커맨드 객체를 받아서 Execute() 메서드를 호출합니다.
  5. 클라이언트 (Client):
    • 인보커에게 어떤 명령을 수행할지 설정합니다.

예제: Unity에서 커맨드 패턴 구현하기

다음은 Unity에서 커맨드 패턴을 사용하여 캐릭터를 이동시키고, undo/redo 기능을 구현하는 예제입니다.

  1. 커맨드 인터페이스 정의
public interface ICommand
{
    void Execute();
    void Undo();
}
  1. 구체적인 커맨드 클래스 정의
public class MoveCommand : ICommand
{
    private Transform _characterTransform;
    private Vector3 _direction;

    public MoveCommand(Transform characterTransform, Vector3 direction)
    {
        _characterTransform = characterTransform;
        _direction = direction;
    }

    public void Execute()
    {
        _characterTransform.position += _direction;
    }

    public void Undo()
    {
        _characterTransform.position -= _direction;
    }
}
  1. 리시버 클래스 정의
public class CharacterMover : MonoBehaviour
{
    public Transform characterTransform;

    public void Move(Vector3 direction)
    {
        characterTransform.position += direction;
    }

    public void UndoMove(Vector3 direction)
    {
        characterTransform.position -= direction;
    }
}
  1. 인보커 클래스 정의
public class InputHandler : MonoBehaviour
{
    public Transform characterTransform;
    private Stack<ICommand> _commandHistory = new Stack<ICommand>();

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            ExecuteCommand(new MoveCommand(characterTransform, Vector3.forward));
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            ExecuteCommand(new MoveCommand(characterTransform, Vector3.back));
        }
        if (Input.GetKeyDown(KeyCode.A))
        {
            ExecuteCommand(new MoveCommand(characterTransform, Vector3.left));
        }
        if (Input.GetKeyDown(KeyCode.D))
        {
            ExecuteCommand(new MoveCommand(characterTransform, Vector3.right));
        }
        if (Input.GetKeyDown(KeyCode.Z) && _commandHistory.Count > 0)
        {
            UndoCommand();
        }
    }

    void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _commandHistory.Push(command);
    }

    void UndoCommand()
    {
        ICommand command = _commandHistory.Pop();
        command.Undo();
    }
}

이 예제에서는 InputHandler 클래스가 인보커 역할을 하며, MoveCommand 객체를 생성하고 실행합니다. 실행된 명령은 스택에 저장되어 나중에 취소할 수 있습니다.

커맨드 패턴의 장점과 단점

장점:

  1. 단일 책임 원칙 준수: 각 커맨드 객체는 하나의 명령만 처리하여 단일 책임 원칙(Single Responsibility Principle)을 지킵니다.
  2. 유연성과 확장성: 새로운 명령을 추가할 때 기존 코드를 수정할 필요 없이 확장할 수 있습니다.
  3. 명령 저장 및 관리: 명령을 큐나 스택에 저장하여 나중에 실행하거나 취소할 수 있습니다.
  4. 저결합성: 호출자와 수신자 간의 결합도를 낮춰 코드의 유연성과 재사용성을 높입니다.

단점:

  1. 클래스 수 증가: 각 명령마다 클래스를 생성해야 하므로 클래스 수가 증가하여 복잡성이 높아질 수 있습니다.
  2. 오버헤드: 명령을 관리하는 추가적인 오버헤드가 발생할 수 있습니다.

결론

커맨드 패턴은 게임 개발에서 다양한 입력 처리를 효율적으로 관리하고 복잡한 기능을 간단하게 구현할 수 있는 강력한 도구입니다. 이 패턴을 활용하면 코드의 가독성을 높이고, 유지보수와 확장이 쉬운 유연한 구조를 만들 수 있습니다. 특히 전략 게임이나 퍼즐 게임처럼 복잡한 입력과 명령 관리가 필요한 경우에 매우 유용합니다. 커맨드 패턴을 통해 명령을 독립적으로 관리하고 쉽게 추가하거나 수정할 수 있어, 개발 과정에서의 생산성을 크게 향상시킬 수 있습니다. 이러한 장점들은 코드의 품질을 높이고, 프로젝트의 성공 가능성을 증가시키는 중요한 요소가 됩니다.

반응형