∇ RPG 강좌

 ▼ PC와 NPC의 구분

N/A

소스無


PC(Player Character)와 NPC(Non-Player Character)를 구분하는 가장 큰 기준은 사용자에 의해 조작이 가능한 캐릭터인가 하는 것이다. PC는 대부분 사용자의 입력을 받아 그에 맞는 행동을 하게 되며 NPC는 자체적인 판단으로 행동한다. 하지만 그 이외의 행동 방식은 모두 같아야 한다. “오른쪽으로 움직여라”라는 명령을 받으면 PC나 NPC나 모두 오른쪽으로 움직여야 하며 “전방에 공격을 하라”라는 명령에 똑같이 현재 장비된 무기로 공격해야 할 것이다. (어떤 무기를 어떻게 사용하라고 하는 세부 명령이 필요 없다.) 즉, 명령은 하나이고 이런 명령을 받는 객체는 각각 다를 수가 있다는 것이다.

그렇다면 프로그래밍의 구현에서는 어떻게 해야 하는가? 방법은 여러 가지가 있겠지만 그 중에 가장 간단한 방법이 NPC클래스를 상속 받아서 PC를 구성하고, PC의 메인 행동 함수를 재정의하는 것이다. 일반적으로는 PC가 NPC의 속성을 모두 포함하며 거기에다가 추가 속성이 붙도록 구성되는 것이 대부분이기 때문이다.

1. PC와 NPC의 구분

    만약 메인 행동 함수가 DoAction()이라고 한다면 다음과 같이 메인 루프를 구성할 수 있다.


    01:  // 클래스 정의
    02:  class CNonPlayerChar
    03:  {
    04:  public:
    05:     virtual int DoAction();
    06:  };
    07:  
    08:  int CNonPlayerChar::DoAction()
    09:  {
    10:     // AI에 의해 행동 패턴 결정
    11:  }
    12:  
    13:  class CPlayerChar : public CNonPlayerChar
    14:  {
    15:  public:
    16:     int DoAction();
    17:  };
    18:  
    19:  int CPlayerChar::DoAction()
    20:  {
    21:     // 사용자의 입력에 의해 행동 패턴 결정
    22:  }
    23:  
    24:  // 게임 시스템에 정의된 캐릭터 리스트
    25:  CNonPlayerChar* Characters[100];
    26:  
    27:  // PC의 생성 방법
    28:  Characters[0] = new CPlayerChar();
    29:  
    30:  // NPC의 생성 방법
    31:  Characters[1] = new CNonPlayerChar ();
    32:  
    33:  // 메인 루프 안에서 실행되는 전 캐릭터 행동 함수
    34:  for (int i = 99; i >=0; i--)
    35:  {
    36:     if (Characters[i])
    37:     {
    38:        Characters[i]->DoAction();
    39:        Characters[i]->Show();
    40:     }
    41:  }

    이렇게 하면 PC와 NPC를 구분하지 않고도 하나의 루프에서 전 캐릭터의 행동 함수를 모두 부를 수 있다. 참고로 루프를 99부터 0까지 거꾸로 돌리는 것은 0번 캐릭터인 주인공이 가장 나중에 출력되어 캐릭터들 중에서는 가장 위쪽에 찍히도록 하기 위해서다.

    코드 마지막의 캐릭터 메인 루프는 비단 롤플레잉 게임뿐만 아니라 슈팅 게임에서도 사용할 수 있다. 주인공 캐릭터, 적 캐릭터, 그리고 탄환 같은 기타 오브젝트들까지도 모두 하나의 객체로부터 상속을 받게 되면 위의 구조를 그대로 가져가서 쓸 수 있다. 물론 슈팅에서는 충돌에 의한 소멸이 있으므로 DoAction() 멤버 함수에 자신이 소멸되는지 아닌지를 리턴 값으로 줘야 할 것이다.

    즉 다음과 같은 구조를 가지는 것이 가장 바람직하다.


    01:  // 메인 루프 안에서 실행되는 전 캐릭터 행동 함수
    02:  for (int i = MAX_CHARACTER; i >=0; i--)
    03:  {
    04:     if (Characters[i])
    05:     {
    06:        if (Characters[i]->DoAction())
    07:        {
    08:           Characters[i]->Show();
    09:        }
    10:        else
    11:        {
    12:           delete Characters[i];
    13:           Characters[i] = NULL;
    14:        }
    15:     }
    16:  }


    멤버 함수인 DoAction()에서 그 캐릭터의 고유 행동을 하고 총돌 체크 등을 한 후 객체 소멸에 대한 정보를 리턴 값으로 주게 되는데, 여기서는 TRUE일 때 객체가 계속 유효함을 나타내고 FALSE일 때 객체가 소멸해야 함을 나타낸다. 따라서 객체가 계속 유지되고 있을 때는 그 캐릭터를 화면에 출력해 주고 그렇지 않을 때는 소멸의 과정을 거친다. (부가적으로 객체의 소멸자에서 소멸용 오브젝트를 추가로 생성할 수 있다. 예를 들어 파편과 같은…)

2. 행동 함수 오버라이딩

    행동 함수를 오버라이딩(overriding)하는 방법은 언어에 따라서 조금은 다르다. 만약 MS Windows용 게임만을 만든다면 C++의 문법 중에서 가상(virtual) 함수를 이용해서 이것을 구현하면 되지만, 미니 게임기나 기타 C++의 지원이 미비한 임베디드 시스템(Embedded system)에서 게임을 만들 때는 C의 문법인 함수형 변수를 통해 구현해야 하는 경우도 있다.

    그 다음 부분은 다음 주에나 쓸련다. 워드 문서의 특정 포맷으로 되어 있는 것을 html의 이 포맷을 옮기는 작업이 너무 노가다다.