Command 패턴 (1)
New Document

2. 커맨드 패턴 (Command Pattern)

2-1. Introduce

요청을 객체로 캡슐화한다. 그렇게함으로서 유저들은 다른 요청, 큐(Queue), 혹은 로그 요청으로 클라이언트를 매개변수화하고, 실행할 수 없는 명령을 서포터한다." -Game Programming Pattern

커맨드 패턴의 핵심은 추상화이다. 이 패턴은 객체의 함수 호출을 리얼 타임에서 구체화해서 실행시킨다. GOF에서는 "커맨드는 콜백의 객체기반 대체품이다"라고 소개한다.

2-2. Motivation

게임에는 여러가지의 입력에 반응하는 코드가 어딘가에는 존재한다. 이 코드들은 유저의 입력을 게임 월드에서 유의미한 액션으로 바꾼다.

예를들어, 유저는 A, B, X와 Y버튼을 가지고, 각각 무기변경(SwapWeapon), 달리기(Dash), 점프(Jump), 공격(FireGun)의 액션(Action)을 의미한다.

이것들은 간단하게 구현할 수 있다.

if(isPressed(Button_X)) Jump();
else if(isPressed(Button_Y)) Fire()_Gun();
else if(isPressed(Button_A)) SwapWeapon();
else if(isPressed(Button_B)) Dash();

2-3. Basic Command Pattern

그러나, 우리가 만드는 대부분의 게임은 유저가 쉽게 조작키를 변경할 수 있기에 Jump()와 Fire_Gun()을 쉽게 스왑(Swap)할 수 있는 방법이 필요하다. 이것의 해결법(Solution)이 바로 커맨드(Command) 패턴이다.
우리는 액션을 대표할 객체가 필요하기에 트리거가능한 기본 클래스를 정의할 것이다.

class Command
{   
public:
    virtaul ~Command();
    virtual void exucte();
}

그리고 우리는 각각의 다른 액션을 대표할 서브 클래스를 정의한다.

class JumpCommand
    :public Command
{
    virtual void excute() {jump();}
    //another code
}

이로서 우리의 InputHandler는 다음과 구성된다.

class InputHandler
{
public:
    void InputHandle();

private:
    Command* button_X;
    Command* button_Y;
    Command* button_A;
    Command* button_B;
}

void InputHandler::InputHandle()
{
    if(isPressed(Button_X)) button_X->excute();
    else if(isPressed(Button_Y)) button_Y->excute();
    else if(isPressed(Button_A)) button_A->excute();
    else if(isPressed(Button_B)) button_B->excute();
}

다음과 같이 구조를 그릴 수 있다.



2-4. Actors

위에서, 우리는 각각의 액션을 Command 클래스로 정의했다. 그러나, 이것은 두 문제가 있다. 이것들은 암묵적으로 Actor(플레이어 캐릭터)의 액션을 알려준다. 이것은 커맨드와 플레이어 캐릭터 객체가 크게 결합되어있기 때문이다. JumpCommand안에 있는 Action(Jump 메서드)를 분리시켜야한다.
우리는 원하는 액션이 있는 객체(GameAcotr, PlayerCharactor등등)를 매개변수로 패스할 수 있다.

class Command
{
public:
    virtual ~Command();
    virtual void excute(GameActor& actor);
};

여기서, GameActor는 우리의 Player 캐릭터를 의미한다. 또한, 우리는 상속받은 Command 클래스들이 Actor안에 있는 메서드를 선택하여 호출하게 만든다.

class JumpCommand
    :public Command
{
    virtual void excute(GameActor& actor) { actor.Jump(); }
};

이로서 우리는 성공적으로 Command와 Actor를 분리시켰고, Command들은 Actor의 액션을 알지 못하게 되었다.
이렇게 함으로서, InputHandler는 acotr안으로 던져버렸기때문에, command가 어떤 함수를 실행할지 알 수 없게되었다. 그래도 우리는 커맨드가 구체화된 호출이라는 사실을 알고있다. 약간의 코드를 추가해서 이 문제를 해결할 수 있다.

class InputHandler
{
public:
    Command* InputHandle();

private:
    Command* button_X;
    Command* button_Y;
    Command* button_A;
    Command* button_B;
};

Command* InputHandler::InputHandle()
{
    if (isPressed(Button_X)) return button_X;
    else if (isPressed(Button_Y)) return button_Y;
    else if (isPressed(Button_A)) return button_A;
    else if (isPressed(Button_B)) return button_B;

    return NULL;
}

void Update(InputHandler* handler, GameActor* actor)
{
    Command* command = handler->InputHandle();

    if (command)
        command->excute(*actor);
}


----------------------------------------------------


내가 지금 뭐하는건지...그냥 영어공부한다고 원서책에 다이빙하듯이 돌진했던걸, 왜 다시 한글로 정리한건지 모르겠네.

어짜피 디자인패턴일뿐인데...;;

다음부터 따로 정리안할 예정이고, 내가 더 실력을 키우고 해당 디자인패턴을 실제로 여러번 쓰기전까지는 더 쓰지 않을 예정

'프로그래밍 > 프로그래밍 관련' 카테고리의 다른 글

VS Code로 Markdown 편집하기  (0) 2018.01.23
VS를 깃허브에 연동  (0) 2018.01.22
Singleton 패턴  (0) 2016.10.22
ProtoType 패턴  (0) 2016.10.15
Abstract Factory 패턴  (0) 2016.10.15
더보기

댓글,

Lowpoly

게임 서버 프로그래머 지망생