URL: http://gameprogrammingpatterns.com/command.html



[게임 프로그래밍 패턴] 1.1 디자인패턴을 다시보자 - Command 패턴


Command 패턴은 필자가 가장 좋아하는 패턴중 하나이다. 대부분의 큰 프로그램에서는 (게임이든 아니든) 결국은 어딘가에 이 패턴을 쓰게 된다. 이 패턴에 대해서 Gang of Four는 다음과 같은 난해한 문장을 통해 설명하였다. 


Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.


아마도 모두가 이 문장은 정말 이상하다고 생각할 것이다. 아마, 비유를 통해 뭔가를 설명하려 한 것 같지만 그저 난해한 문장이 되었을 뿐이다. software라는 조금 이상한 영역을 벗어난 세계에서는 client는 당신과 사업을 하는 사람을 지칭한다. 또한, 사람(Human being)은 parametrized 될 수 없다. 


내가 Command pattern에 대해 간략하게 설명해보자면: A command is a reified method call. (Command 는 method call을 구체화 시킨 것이다.)


Reify라는 단어 자체는 '현실화 시키다 - make real' 이라는 뜻을 가지고 있다. 프로그래밍 관점에서 말해보자면, 어떤 data를 object로 만들어서 variable에 대응시키거나, 함수에 넘겨주거와 같은 일들을 할 수 있게 만든다는 뜻이다. 이는 어떤 method call을 object로 만든다고 할 수도 있겠다. 


이는 마치 'call back' , 'first-class function', ' function pointer' , 'closure', or 'partially applied function' 과 비슷하게 들릴 지도 모른다. 이들은 어떤 언어에 종속적이라는 한계가 있긴 하지만, Command pattern과 크게 다를게 없다. 이에 대해서 Ganf of Four는 나중에 다음과 같이 덧붙였다.


Commands are an object-oriented replacement for callbacks. 


 하지만 지금까지의 설명은 다소 추상적이었다고 생각한다. 그러므로 지금부터는 command가 아주 잘 맞는 몇가지 예시를 통해 설명할 것이다. 



설정 수정( Configuring Input )


어떤 게임이든 코드의 어떤부분은 raw user input ( button presses, keyboard events, mouse clicks, whatever) 을 읽기 위한 코드를 가지고 있을 것이다. 이러한 코드들은 단순한 input을 game의 어떤 의미있는 행동으로 바꾸는 역할을 한다. 


...

Press X button - Attack()

Press Y button - Jump()

Press Z button - Lurch()

....


코드로 간단하게 보면 아래와 같다. 

void InputHandler::handleInput() { if (isPressed(BUTTON_X)) attack(); else if (isPressed(BUTTON_Y)) jump(); else if (isPressed(BUTTON_Z)) lurchIneffectively(); }

위의 함수는 game loop에 의해서 매 프레임마다 계속해서 반복적으로 불린다. 위의 코드에서는 X,Y,Z 버튼과 각 action이 대응되어 있다. 하지만 많은 게임에서는 유저가 직접 이들을 수정할 수 있다. X버튼이 attack() 인 대신, Y버튼이 attack() 일 수 있도록 말이다. 이를 지원하기 위해서 우리는 jump() 와 attack()과 같은 direct call을 교체(swap out) 할 수 있는 무엇인가로 바꿔야 한다. 그러므로, game action을 표현할 수 있는 object가 필요하게 된다. Command pattern을 사용할 때가 된 것이다.


먼저 바꿀 수 있는(triggerable) game command class를 정의 한다.

class Command
{
public:
  virtual ~Command() {}
  virtual void execute() = 0;
};


그리고 각 각 서로다른 game action을 가지는 subclass를 만든다.

class JumpCommand : public Command
{
public:
  virtual void execute() { jump(); }
};

class AttackCommand: public Command
{
public:
  virtual void execute() { attack(); }
};


Input Handler에서 우리는 각 버튼 command 의 pointer를 저장한다. 

class InputHandler { public: void handleInput(); // Methods to bind commands... private: Command* buttonX_; Command* buttonY_; Command* buttonZ_; };

그리고 나서 아래와 같이 하면 완성. 

void InputHandler::handleInput() { if (isPressed(BUTTON_X)) buttonX_->execute(); else if (isPressed(BUTTON_Y)) buttonY_->execute(); else if (isPressed(BUTTON_Z)) buttonA_->execute(); }


이제 각 user input은 function을 호출하고, 수행 함수와 실제 수행되는 함수 사이의 관계는 간접적이므로, 수정이 용이하다. 


 


타겟 오브젝트의 설정 (Directions for Actors)


바로 위에서 정의했던 command class는 위에서 가정한 상황에서는 아무런 문제 없이 작동할 것이다. 하지만 위의 디자인에서는 뚜렷한 한계가 존재한다. 위에서 정의한 jump()와 attack()등의 함수는 어떤 캐릭터가 이 행동을 취할지를 당연히 알고 있듯이 작동한다는 것이다. 위 처럼 디자인 한다면, 위의 함수는 어떤 특정 캐릭터 하나의 움직임만을 제어하게 될 것이다. 이를 해결해보자. 함수에 어떤 행동을 취하길 원하는 avatar를 변수로 넣어보자. 

class Command
{
public:
  virtual ~Command() {}
  virtual void execute(GameActor& actor) = 0;
};

여기에서 GameActor는 우리의 게임 세상에서 캐릭터를 대표하는  'game object' class이다. 우리는 이를 execute() 함수에 전달하고, 전달된 캐릭터에 해당된 command를 실행하게 할 것이다. 

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

위와 같이 말이다. 이를 이용하면 우리는 어떤 캐릭터든지간에 게임 내에서 뛰도록 할 수 있다. 이제 input handler와 command 사이를 연결해서 입력된 명령을 올바른 object에서 실행하도록 코드를 바꿔야 한다. 먼저 handleInput() 을 command를 return하도록 바꾼다. 

Command* InputHandler::handleInput()
{
  if (isPressed(BUTTON_X)) return buttonX_;
  if (isPressed(BUTTON_Y)) return buttonY_;
  if (isPressed(BUTTON_A)) return buttonA_;
  if (isPressed(BUTTON_B)) return buttonB_;

  // Nothing pressed, so do nothing.
  return NULL;
}

위의 코드는 어떤 actor에게 명령어가 전달될 지 모르므로, 곧바로 실행되지 못한다. (여기에서 어떤 인풋에 대해 delay가 필요하다면 쉽게 구현할 수 있다는 부가적인 이점이 발생한다) 그러므로 명령어를 받아서 actor에게 전달, 행동을 실행해주는 코드를 만든다. 

Command* command = inputHandler.handleInput();
if (command)
{
  command->execute(actor);
}

위에서 만든 코드는 아까전에 봤던 예제와 동일하게 작동한다. 다만, command와 actor사이에 간접적인 layer를 추가함으로써 'command를 전달받을 actor를 excute()에 넘겨줌으로써 다양한 actor들이 행동하도록 함' 이라는 기능을 달성하였다. 


실제로는 위의 예제가 별로 빈번하게 발생하는 문제는 아니지만, 비슷한 문제들은 많이 보인다. 지금까지는 우리는 우리가 직접 조종하는 player라는 object에 대해서만 생각했지만, 게임 세계에는 이러한 object말고도 움직여야 하는 다양한 object들이 존재한다. 바로 Game AI에 의해 움직이는 캐릭터들이다. 우리는 AI engine과 actor사이에 위와 같은 시스템을 구축해두면 손쉽게 이들을 움직일 수 있다. 


Command를 선택하는 AI engine과 이를 행동으로 받을 actor를 분리는 것은 상당한 유연성(flexibility)를 제공한다. 우리는 다른 AI module을 서로다른 actor에게 적용시킬 수 있다. 이는 조금더 똑똑하고 공격적인 AI를 할당시키거나, 온순한 Ai를 할당시키는등의 일도 손쉽게 할 수 있다. 심지어는 player에게 AI를 적용시켜서 demo program등에서 프로그램이 제대로 동작하는지를 보는데 사용할 수 도 있다. 




실행 취소와 실행취소 되돌리기 (Undo and Redo) 


마지막 예제는 이 패턴의 가장 잘 알려진 사용 예이다. 만약 command object가 무언가를 했따면, 그들을 undo (실행취소) 하는 것은 매우 쉽다. Undo는 게임에서 뭔가 마음에 들지 않는 행동을 했을 때, 그 행동을 취소하는 기능이다. 이것은 게임을 만드는 사람들을 위한 '도구(tool)' 에서 반드시 필요한(de rigueur) 기능이다. 아마 실수로 잘못된 일을 했을 때 (fat fingerred mistake) 취소하는 기능을 제공하지 않으면 게임 디자이너들로부터 확실하게 미움받을 수 있을 것이다. 


Command pattern을 이용하지 않으면 undo기능을 제공하는것은 생각보다 어렵다. 다시 말하면, 이를 사용하면 정말 쉽다는 얘기다. Single-player, Turn-based 게임을 만든다고 가정하고, 유저로 하여금 undo기능을 사용하도록 하여 추측을 이용한 전략보다 눈으로 직접 보면서 전략을 짤 수 있도록 한다고 해보자.  


플레이어를 움직이게하는 코드들은 모두 그 플레이어 class안에 모두 위치해 있다고 해보자.  아래는 플레이어를 이동하도록 하는 코드 예시이다. 

class MoveUnitCommand : public Command
{
public:
  MoveUnitCommand(Unit* unit, int x, int y)
  : unit_(unit),
    x_(x),
    y_(y)
  {}

  virtual void execute()
  {
    unit_->moveTo(x_, y_);
  }

private:
  Unit* unit_;
  int x_, y_;
};

우리가 잎에서 만들었던 command하고는 조금 다르다. 마지막 예제에서는, 우리는 command 를 abstract하였지만, 이 코드에서는 input handler 부분을 abstract 하고 있다. 이러한 경우에는 어떤 유닛이 움직일지 정확하게 지정하고 있기 때문에, '무언가를 움직여라' 라는 명령이 아닌 게임의 어떤 턴 한 장면에서의 구체적인 움직임을 명시하게 된다. 


위의 코드는 Command pattern을 이용할 경우에 가져오는 다양성을 부각시켜주는 예제이다. 어떤 경우에는 (우리의 맨 처음 예제처럼) command는 할 수 있는 어떤 일에 대한 표현이고, 이는 계속해서 재사용될 수 있다. 우리의 초기의 input handler는 단일 명령(single command) object를 호출하였고, 그것의 execute() 함수를 어떤 버튼이 눌렸을때 아무시간에나 호출 할 수 있었다.


여기에서 명령(command)는 조금더 명확하다. 이들은 어떤 특정시간에 할 수 있는 일을 표현하고 있다. 이로 인해 input handling code는 command의 instance를 호출마다 매번 새로 만들어줘야 할 것이다. 아래의 예제를 보자. 


Command* handleInput()
{
  Unit* unit = getSelectedUnit();

  if (isPressed(BUTTON_UP)) {
    // Move the unit up one.
    int destY = unit->y() - 1;
    return new MoveUnitCommand(unit, unit->x(), destY);
  }

  if (isPressed(BUTTON_DOWN)) {
    // Move the unit down one.
    int destY = unit->y() + 1;
    return new MoveUnitCommand(unit, unit->x(), destY);
  }

  // Other moves...

  return NULL;
}

위의 코드에서의 command는 한번만 쓰일 명령어이고, 이는 우리가 하려는일에 큰 도움이 된다. Undo를 구현하기 위해서 아래처럼 undo를 command 클래스에 추가한다. 


class Command
{
public:
  virtual ~Command() {}
  virtual void execute() = 0;
  virtual void undo() = 0;
};

Undo() method는 game state를 execute() method의 정반대로 실행하는 것이다. 아래의 코드가 Undo()를 지원하는 command code이다. 

class MoveUnitCommand : public Command
{
public:
  MoveUnitCommand(Unit* unit, int x, int y)
  : unit_(unit),
    xBefore_(0),
    yBefore_(0),
    x_(x),
    y_(y)
  {}

  virtual void execute()
  {
    // Remember the unit's position before the move
    // so we can restore it.
    xBefore_ = unit_->x();
    yBefore_ = unit_->y();

    unit_->moveTo(x_, y_);
  }

  virtual void undo()
  {
    unit_->moveTo(xBefore_, yBefore_);
  }

private:
  Unit* unit_;
  int xBefore_, yBefore_;
  int x_, y_;
};

여러 단계의 Undo를 구현하는 것은 크게 어려운 것은 아니다. 그저 command list를 하나 만들고, 어떤 포인터로 command list의 한 command를 가리키게 한다. 이녀석은 current command가 될 것이다. redo일 경우 앞쪽의 command로 pointer를 옮겨주고, undo일 경우 뒤쪽의 command로 pointer를 옮겨주는 식으로 구현하면 된다. 

Posted by Cat.IanKang
,

URL: http://gameprogrammingpatterns.com/design-patterns-revisited.html



[게임 프로그래밍 패턴] 1. 디자인 패턴을 다시보자: 서론


내게는 Elements of Reusable Object-Oriented Software는 거의 20년 이상  된 물건이다.  하지만 여전히 Design Patterns는 중요하다고 생각하므로, 이번 섹션에서 이에 대해서  몇몇 original pattern을  살펴볼 것이다. 먼저 조금은 과하게 사용되고 있다고 생각되는(singleton), 그리고 너무 저평가 되어 있다고 생각되는  (Command) 그리고 게임에 중요하다고 생각되는 몇개의 패턴 (Flyweight와 Observer), 마지막으로 대규모 프로젝트에서 들여다보면 재밌다고 생각되는 (prototype과 state)에 대해서 살펴볼 것이다.

Posted by Cat.IanKang
,

1. Using virtual addresses


It makes relocation of program is simple since program can be loaded anywhere in memory. In other words, any number of compiled applications can be loaded simultaneously. 


2. TLB (translation-lookaside buffer) 


In virtual memory system, performance is degraded due to page accesses. To reduce the number of page accesses, we are going to add what is called TLB. A TLB is part of the chip's memory-management unit (MMU), and is simply a hardware cache of popular virtual-to-physical address translations; thus, a better name would be an address-translation cache. Upon each virtual memory reference, the hardware first checks the TLB to see if the desired translation is held therein; if so, the translation is performed without having to consult the page table. 

Posted by Cat.IanKang
,