Game Development, 게임개발/디자인패턴

Prototype Pattern, 프로토타입 패턴 [디자인패턴]

게임이 더 좋아 2021. 10. 2. 17:23
반응형
728x170

 

 

프로토타입이란

가장 초기 버전을 말하는 듯한 느낌을 준다.

실제로도 프로토 타입이라는 말은 개발 중에 필요한 기능만 넣어서 대충 구동해보는 것을 말한다.

그렇다면

디자인 패턴에서의 프로토타입은 무엇을 말하는 것인지 알아보자

 

 


 

 

플레이어와 몬스터가 싸우는 게임이 있다고 하자.

몬스터는 플레이어를 공격하기 위해 떼를 지어다니고

그 몬스터들은 Spawner를 통해 게임 스테이지에 등장하고

몬스터의 종류마다  Spawner가 존재한다. 

 

예제 코드로 각종 몬스터 클래스를 만들어보자

class Monster{
	//...
};

class Ghost : public Monster{};
class Demon : public Monster{};
class Sorcerer : public Monster{};

한 종류 당 하나의 클래스, 하나의 스포터는 하나의 클래스 인스턴스만 만든다. 

게임에 나오는 모든 몬스터를 지원하기 위해

마구잡이로 몬스터 클래스마다 스포터 클래스를 만든다고 생각하면

아래와 같은 그림이 될 것이다.

 

 다시 말하면 몬스터에 대해서 따로, Spawner에 대해서 따로 존재할 것이다.

 

코드로 구현하자면

 

class Spawner {
public:
    virtual ~Spawner() {}
    virtual Monster* spawnMonster() = 0;
};

class GhostSpawner : public Spawner {
public :
    virtual Monster* spawnMonster() {
        return new Ghost();
    }
};

 

Spawner에서 상속받아 추상메서드를 구현해서 Monster 객체에 대항 포인터를 반환한다.

이렇게 몬스터 종류가 늘수록

Spawner를 상속받는 클래스도 늘어날 것이다.

 

그런데 이러한 것을 프로토타입을 쓰면 줄일 수 있다.

프로토타입의 핵심은 어떤 객체가 자기와 비슷한 객체를 spawn할 수 있다는 것이다.

다시 말해서 ghost 객체 하나로 다른 객체를 생성하는 것이다.

그렇다면 demon 객체로도 다른 demon 객체를 생성할 수 있다.

즉, 기존에 있는 객체가 원형, prototype이 되어 자신과 비슷하거나 같은 객체를 생성하는 것이다.

 

이를 구현하기 위해서는 Monster 클래스에 추상 메서드를 구현해야한다.

class Monster{
public:
    virtual ~Monster() {}
    virtual Monster* clone() = 0;
    
    //그 외...
    
};

 

Monster 클래스를 상속받는 하위 클래스에서는

자신과 자료형이 같은 새로운 객체를 반환하도록 clone()을 구현하게 한다.

class Ghost : public Monster{
public:
    Ghost(int health, int speed)
    : health_(health),
      speed_(speed){
    }
    virtual Monster* clone(){
        return new Ghost(health_, speed_);
    }
private:
    int health_;
    int speed_;
};

 

Ghost 클래스에서 생성자를 구현하고

clone 에서 해당 생성자를 사용하여 Ghost 객체를 반환한다.

 

Monster 클래스를 상속받는 모든 클래스에 clone 메서드가 있다면

스포너 클래스를 굳이 여러개 만들 필요 없이 하나만 만들면 된다.

class Spawner{
public:
    Spawner(Monster* prototype) : prototype_(prototype) {}
    Monster* spawnMonster(){
        return prototype_->clone();
    }
private:
    Monster* prototype_;
};

다시 말해서 스포너 클래스는 몬스터의 원형, prototype을 받아서 생성자를 구현하고

해당 prototype이 객체에서 clone()을 실행함으로써 spawn을 구현한다.

 

다시 말하자면

 

굳이 Spawner에서 Spawn을 하지 않고

Monster 객체들을 하나씩 만들었다면

해당 몬스터 객체에서의 clone()함수를 실행함으로써 

몬스터 종류마다 Spawner 클래스를 만들 수고를 줄이는 것이다.

 

실제로 사용하자면

Moster* ghostPrototype = new Ghost(15, 3);
Spawner* ghostSpawner = new Spawner(ghostPrototype);

//즉, 객체 A를 복제하기 위해 원형 A를 받아서 구현하는 것이다.
//Spawner에서는 받은 원형에서 Clone()만 불러오는 식으로 구현한다.

 

여기서 좋은 점은

프로토타입의 패턴의 좋은 점은 프로토 타입의 클래스뿐만 아니라 상태도 같이 복제한다는 점이다.

즉, 원형으로 사용할 유령 객체를 설정하면

해당 attribute만 커스터마이징 하면

유령의 객체 원형을 여러 종류를 만들어서 준다면

여러 종류의 Spawner가 생기는 것이다.

 

 


 

앞에서 설명한대로만 작동하면 너무 좋은 패턴같다.

하지만 여기서도 기술적, 현실적인 문제가 있는가? 를 생각해보자

프로토타입을 써도 코드의 양이 줄긴 하지만.. 그렇게 획기적으로 많이 줄지는 않는데다가

예제부터가 현실적이지 않다고 생각하는게 대부분이다.

 

실제로 몬스터마다 클래스를 만드는 것이 비효율적이기 때문에 앞서 말한 예제가 실제와 다르다.

또한 상속구조가 복잡하면 유지 보수가 힘들기 때문에

개체 종류별로 클래스를 만들기보다는 컴포넌트나 타입 객체로 모델링 하는 것을 선호한다.

 

하지만 이렇게 쓸모없는 것은 아니다.

프로토타입에서 우리가 배워야 할 점은 "위임"이라는 것이다.

게임은 시간이 지날수록 코드보다 데이터가 차지하는 용량이 커진다.

게임에서의 코드는 게임을 실행하기 위한 엔진일 뿐 게임 콘텐츠는 모두 데이터에서 불러온다.

 

하지만 많은 콘텐츠를 데이터로 옮기면 대규모 프로젝트의 구조 문제가 어려워진다.

프로그래밍 언어를 사용하는 이유는 복잡성을 제어할 수 있어서 그렇다.

같은 코드를 여기저기 붙여넣는 것보다 하나의 함수로 만들어 호출하는 것이 복잡도를 줄인다.

 

게임 데이터도 일정 규모 이상이 되면 코드와 비슷한 기능이 필요해진다.

즉, 여기서 프로토타입과 위임을 활용해 데이터를 재사용하는 기법같은 것이 필요하다.

앞서 말했던 몬스터에 대한 attribute들을 파일의 어딘가에 저장한다.

이럴 때 많이 사용하는 포맷이 JSON이나 XML이다. 

예를 들면 이렇게 저장되어있다.

개체에 중복이 많다.

중복이 많은 것은 좋지 않다.

아무튼 저렇게 저장이 된다면 고블린 자체를 강하게 만들 때

고블린에서 파생된 모든 아이들의 속성을 건드려야하는 불상사가 생길 수 있다.

하지만 JSON에서는 그런 기능이 없기에

다르게 구현해보자

 

객체에는 프로토타입 필드가 있어서 여기에서 위임하는 다른 객체의 이름을 찾을 수 있다고 해보자

첫 번째 객체에서 원하는 속성이 없다면 프로토타입 필드가 가리키는 객체에서 대신 찾는 것이다.

그렇다면 아래와 같이 바꿔버릴 수 있다

 

archer 와 wizard의 프로토 타입을 goblin grunt라고 해놓았기 떄문에

체력과 같은 속성에 대해서 반복 입력을 하지 않아도 좋다.

데이터 모델에서 단순한 delegation으로 중복을 제거할 수 있다.

 

실제로 저 실제 고블린을 위임할 기본 고블린 같은 추상 프로토타입과 같은 것들을

따로 만들지 않았다는 점에서 좋다고 말할 수 있다.

다시 말하면 고블린 자료형 하나로 다른 객체를 위임하게 한 것이다.

 

프로토타입 기반 시스템에서는 

새로 정의되는 객체를 만들 때 어떤 객체든 복제로 사용할 수 있는 것이 당연하다.

보스와 유니크 아이템같은 것들도 일반 아이템을 약간 다듬어서 만들 때가 많으므로 

프로토타입 방식의 delegation을 활용한다면 좋다.

 

예를 들어 일반과 레어의 차이는 뭐 주문력의 +10 이상의 차이라던가 그런 것이다.

일반을 기반으로 상위 아이템을.. 아니면 유니크는 레어에서 공격력이 +15이상 차이난다던가..그런 식이다.

 

데이터 모델링 시스템에는 아직도 프로토타입을 개념, 위임을 이용하면 중복을 피해 효율이 높아질 수 있다.

 

 


 

요약하자면

프로토타입은 우리가 모르게 이미 쓰고있다.

원형을 받아 복제하는 것은 우리가 이미 많이 쓰고 있었다.

우리가 배워야 하는 점은

프로토타입의 핵심 또한 중복을 제거하는 것이고 

어떤 식으로 제거를 하느냐가 중요한 것인데 그 중 하나가 위임, delegation을 활용하니까

줄어들더라 이말이다.

 

 

반응형
그리드형