2024. 12. 9. 20:55ㆍSTUDY/디자인 패턴
이 글은 'level up your code with game programming patterns' 서적을 기반으로 각종 블로그에서 추가적인 정보를 얻어 작성하였습니다.
[커맨드 패턴]
만약 실행 취소/다시 실행 기능이 사용되거나 입력 내역이 목록으로 유지되는 게임을 플레이해 본 적이 있다면 아마 커맨드패턴을 본적이 있을 것이다. 사용자가 실제로 여러 턴을 실행하기 전에 계획할 수 있는 전략 게임을 생각해보면 그것이 바로 커멘드 패턴이다.
커맨드는 어떠한 요청(캐릭터를 앞으로 이동시킨다)를 요청에 대한 모든 정보를 포함한 독립실행형 객체로 변환하는 행동 디자인 패턴이다. 즉 요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 나중에 이용할 수 있도록 메서드 이름, 매개변수 등 요청에 필요한 정보를 저장 또는 로깅, 취소할 수있게 하는 패턴이다.
메서드를 직접 호출하는 대신 커맨드 패턴을 사용하면 '커멘드 오브젝트'라는 하나 이상의 메서드 호출을 캡슐화할 수 있다. 커맨드 오브젝트를 스택같은 컬렉션에 두면 오브젝트의 실행 타이밍을 제어할 수 있다. 이는 작은 버퍼 기능을 한다. 그런 다음 일련의 행동을 나중에 재생하거나 실행을 취소할 수 있다.
커멘드 오브젝트 및 커맨드 호출자
다양한 방법으로 구현할 수 있지만, 인터페이스를 사용하는 버전은 다음과 같다.
public interface ICommand
{
public void Execute();
public void Undo();
}
이 예시에서는 모든 게임플레이 행동이 ICommand 인터페이스를 적용한다.
각 커맨드 오브젝트는 자체 Execute 및 Undo 메서드를 처리한다. 따라서 게임에 더 많은 커맨드를 추가해도 기존의 커맨드에는 아무런 영향을 주지 않는다.
커맨드를 실행 및 취소하려면 이를 위한 별도의 클래스(Invoker)가 필요하다.
public class CommandInvoker
{
private static Stack<ICommand> undoStack = new Stack<ICommand>();
public static void ExecuteCommand(ICommand command)
{
command.Execute();
undoStack.Push(command);
}
public static void UndoCommand()
{
if (undoStack.Count > 0)
{
ICommand command = undoStack.Pop();
command.Undo();
}
}
}
해당 클래스는 커맨드를 실행할 ExecuteCommand 메서드와 UndoCommand 메서드가 있다. 이를 통해 플레이어를 움직이는 예시를 만들어 보겠다.
플레이어에 해당하는 코드는 다음과 같다.
public class PlayerMover
{
public void Move(Vector3 movement)
{
//move 동작 구현
}
}
플레이어를 동서남북 방향에 따라 움직이기 위해 Vector3를 Move 메서드에 전달한다.
커맨드 패턴을 따르기 위해 Move 메서드를 직접 호출하는 대신, ICommand 인터페이스를 구현하는 MoveCommand 클래스를 만들어 PlayerMover의 Move 메서드를 오브젝트로 캡쳐한다.
public class MoveCommand : ICommand
{
private PlayerMover playerMover;
private Vector3 movement;
public MoveCommand(PlayerMover playerMover, Vector3 movement)
{
this.playerMover = playerMover;
this.movement = movement;
}
public void Execute()
{
playerMover.Move(movement);
}
public void Undo()
{
playerMover.Move(-movement);
}
}
MoveCommand는 실행해야할 모든 파라미터를 저장한다. 커맨드 오브젝트를 만들고 필요한 파라미터를 저장하면 CommandInvoker의 정적 ExecuteCommand 및 UndoCommand 메서드가 MoveCommand에 전달된다. 그러면 MoveCommand의 Execute 또는 Undo가 실행되며 실행 취소 스택에서 커맨드 오브젝트를 추적한다.
이를 통해 실행 및 실행 취소가 가능해졌다.
InputManager는 PlayerMover의 Move 메서드를 직접 호출하지 않는다. 대신 RunMoveCommand 메서드를 추가하여 새로운 MoveCommand를 CommandInvoker로 전송한다.
public class InputManager
{
private void RunPlayerCommand(PlayerMover playerMover, Vector3 movement)
{
if (playerMover == null)
return;
ICommand command = new MoveCommand(playerMover, movement);
CommandInvoker.ExecuteCommand(command);
}
}
다시 실행 또는 실행 취소 기능을 여러 커맨드 오브젝트를 생성하듯이 간단하게 구현할 수 있다. 커맨드 버퍼를 사용하면 특정한 컨트롤로 행동 스퀀스를 재생할 수도 있다. 커맨드 패턴 역시 다른 디자인 패턴들과 마찬가지로 더 많은 구조를 요구한다. 추가 클래스와 인터페이스가 커맨드 오브젝트를 만드는데 충분히 도움이 되는지 파악하고 결정해야 한다.
using System;
using System.Collections.Generic;
using System.Numerics;
public interface ICommand
{
public void Execute();
public void Undo();
}
public class CommandInvoker
{
private static Stack<ICommand> undoStack = new Stack<ICommand>();
public static void ExecuteCommand(ICommand command)
{
command.Execute();
undoStack.Push(command);
}
public static void UndoCommand()
{
if (undoStack.Count > 0)
{
ICommand command = undoStack.Pop();
command.Undo();
}
}
}
public class PlayerMover
{
public void Move(Vector3 movement)
{
//move 동작 구현
}
}
public class MoveCommand : ICommand
{
private PlayerMover playerMover;
private Vector3 movement;
public MoveCommand(PlayerMover playerMover, Vector3 movement)
{
this.playerMover = playerMover;
this.movement = movement;
}
public void Execute()
{
playerMover.Move(movement);
}
public void Undo()
{
playerMover.Move(-movement);
}
}
public class InputManager
{
private void RunPlayerCommand(PlayerMover playerMover, Vector3 movement)
{
if (playerMover == null)
return;
ICommand command = new MoveCommand(playerMover, movement);
CommandInvoker.ExecuteCommand(command);
}
}
'STUDY > 디자인 패턴' 카테고리의 다른 글
[Design Pattern] MVC 패턴과 MVP 패턴 (1) | 2024.12.09 |
---|---|
[Design Pattern] 팩토리 패턴(Factory Pattern) (0) | 2024.12.09 |
SOLID 원칙 (0) | 2024.12.08 |
Static, 싱글톤 (0) | 2022.07.01 |