프로토타입 디자인 패턴
원형이 되는 인스턴스를 사용하여 생성할 객체의 종류를 명시하고, 새로운 객체를 생성한다.
[GoF의 디자인 패턴]
게임들 중 몬스터들과 같이 꾸준하게 스폰되는 Actor들이 존재한다.
이를 구현하기 위해 각 몬스터들의 클래스를 만들어주고, 마구잡이식으로 각 몬스터 클래스마다의 Spawner 클래스를 만들어준다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Monster.generated.h"
UCLASS()
class DESIGNPATTERNSTUDY_API AMonster : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMonster();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
UCLASS()
class DESIGNPATTERNSTUDY_API AGhost : public AMonster
{
GENERATED_BODY()
};
UCLASS()
class DESIGNPATTERNSTUDY_API ADemon : public AMonster
{
GENERATED_BODY()
};
몬스터 부모 클래스를 만든 후, Ghost와 Demon 클래스를 자식 클래스로 만들어주었다.
각 클래스를 상속받는 블루프린트를 만들어 몬스터가 스폰되는 것을 구현하고자 하였다.
mesh는 귀찮으니까 그냥 큐브만 추가하였다.


그 후, Spawner를 만들어주었는데, 몬스터 클래스의 상속구조와 동일하게 구현하였다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Monster.h"
#include "Spawner.generated.h"
UCLASS()
class DESIGNPATTERNSTUDY_API ASpawner : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ASpawner();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual AMonster* SpawnMonster();
};
UCLASS()
class AGhostSpawner : public ASpawner
{
GENERATED_BODY()
virtual void BeginPlay() override;
public:
virtual AMonster* SpawnMonster() override
{
UBlueprintGeneratedClass* LoadedBP =
LoadObject<UBlueprintGeneratedClass>(nullptr, TEXT("Blueprint'/Game/4_PrototypePattern/MyGhost.MyGhost_C'"), nullptr, LOAD_None, nullptr);
FVector const& loc = FVector(100, 100, 100);
AGhost* Ghost = GetWorld()->SpawnActor<AGhost>(LoadedBP, loc, FRotator::ZeroRotator);
return Ghost;
}
};
UCLASS()
class ADemonSpawner : public ASpawner
{
GENERATED_BODY()
virtual void BeginPlay() override;
public:
virtual AMonster* SpawnMonster() override
{
UBlueprintGeneratedClass* LoadedBP =
LoadObject<UBlueprintGeneratedClass>(nullptr, TEXT("Blueprint'/Game/4_PrototypePattern/MyDemon.MyDemon_C'"), nullptr, LOAD_None, nullptr);
FVector const& loc = FVector(-100, -100, 100);
ADemon* Demon = GetWorld()->SpawnActor<ADemon>(LoadedBP, loc, FRotator::ZeroRotator);
return Demon;
}
};
Spawner의 상세 코드는 다음과 같고, 각 클래스의 BeginPlay() 마다 SpawnMonster()가 실행되어 게임을 시작함과 동시에 두 몬스터들이 스폰되도록 구현하였다.

Ghost Spawner와 Demon Spawner를 level에 추가해준 후 실행시킨다.

그러나, 이 코드는 클래스도 많고, 반복 코드도 많고, 중복도 많고... 굳이 이렇게 구현할 필요가 없다.
이 문제를 프로토타입 패턴으로 해결할 수 있다.
스포너를 각 몬스터 종류별로 여러개 만드는 대신에, 한 개의 스포너로 각기 다른 종류의 몬스터를 스폰해줄것이다.
이 때 필요한 것은 원형(prototypal) 객체인데, 이 객체를 사용하여 다른 객체들을 만들 수 있게 되는 것이다.
이를 구현하기 위해서 가장 상위 클래스인 Monster에 추상 메서드 clone()을 추가해주고, 각 몬스터의 하위 클래스에서 clone을 각 몬스터에 맞게 구현해준다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Monster.generated.h"
UCLASS()
class DESIGNPATTERNSTUDY_API AMonster : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMonster();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual AMonster* clone() { return nullptr; }
};
UCLASS()
class DESIGNPATTERNSTUDY_API AGhost : public AMonster
{
GENERATED_BODY()
public:
AMonster* clone() override
{
UBlueprintGeneratedClass* LoadedBP =
LoadObject<UBlueprintGeneratedClass>(nullptr, TEXT("Blueprint'/Game/4_PrototypePattern/MyGhost.MyGhost_C'"), nullptr, LOAD_None, nullptr);
FVector const& loc = FVector(100, 100, 100);
AGhost* Ghost = GetWorld()->SpawnActor<AGhost>(LoadedBP, loc, FRotator::ZeroRotator);
return Ghost;
}
private:
UPROPERTY(EditAnywhere)
int health_;
UPROPERTY(EditAnywhere)
int speed_;
};
UCLASS()
class DESIGNPATTERNSTUDY_API ADemon : public AMonster
{
GENERATED_BODY()
};
후에, Spawner 클래스에서는 모든 Monster 하위 클래스의 객체를 스폰할 수 있도록 SpawnMonster()에 clone을 호출해준다.
구현 코드는 다음과 같다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Monster.h"
#include "Spawner.generated.h"
UCLASS()
class DESIGNPATTERNSTUDY_API ASpawner : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ASpawner(){}
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
virtual AMonster* SpawnMonster()
{
return prototype_->clone();
}
private:
UPROPERTY(EditAnywhere)
AMonster* prototype_;
};
AMonster* prototype_을 통해 원형 객체를 지정해주고, 해당 원형객체와 같은 객체를 clone을 통해 스폰시킬 수 있는 것이다.
즉, prototype_만 지정해주면 어느 Monster 객체이던지 상관없이 같은 스폰을 수행할 수 있고, 이 방식의 장점은 해당 객체 뿐만 아니라 그 객체의 상태까지도 클론할 수 있다는 점이다. 위에 Ghost 코드에 임의로 health와 speed를 변수로 선언해두었는데, 프로토타입을 이용해 클론할 때 굳이 다른 설정을 해주지 않더라도 원형 객체의 상태들을 고대로 가지고 클론된다는 것이다.

위 영상에서 보다시피 왼쪽에 미리 설정한 원형 객체와 실행 시 스폰되는 객체를 볼 수 있다.
유지보수
이제 몬스터마다 따로 스포너 클래스를 만들지 않아도 프로토타입 패턴을 이용하여 쉽게 구현할 수 있다.
하지만, 요즘 나오는 웬만한 게임 엔진에서는 몬스터마다 클래스를 따로 만들지 않는다.
클래스 상속 구조가 복잡하면 복잡할 수록, 유지보수는 점점 더 힘들어지게 된다.
그래서, 요즘은 컴포넌트나 타입객체로 모델링하는 것을 선호한다고 한다.
스폰 함수
클래스로 구현하는 것 대신, 함수 포인터를 전달하여 구현하는 방법도 존재한다.
스포너 클래스에는 prototype 객체 대신 함수 포인터를 가지고, 해당 함수 포인터를 실행시켜주는 방식으로 구현하면 된다.
템플릿
특정 몬스터 클래스를 하드 코딩하는 대신, 몬스터 클래스를 템플릿 타입 매개변수로 전달하여 구현하는 방법도 있다.
스폰함수와 템플릿에 관해서는 이 게시글에서는 자세히 다루지 않고, 그냥 그런 방법도 있구나하며 넘어갈 예정이다.
※ UE5 Project
https://github.com/haram1117/GameDesignPatterns_Study
GitHub - haram1117/GameDesignPatterns_Study: GameDesignPatterns_Study
GameDesignPatterns_Study. Contribute to haram1117/GameDesignPatterns_Study development by creating an account on GitHub.
github.com
※ Prototype Pattern 1 Commit
Prototype Pattern 1 · haram1117/GameDesignPatterns_Study@f0303fc
Show file tree Showing 31 changed files with 194 additions and 15 deletions.
github.com
'게임 개발 > 디자인 패턴' 카테고리의 다른 글
| Game Programming Design Patterns - 관찰자 패턴 (2) (0) | 2022.08.05 |
|---|---|
| Game Programming Design Patterns - 관찰자 패턴 (1) (0) | 2022.08.04 |
| Game Programming Design Patterns - 경량 패턴 (0) | 2022.08.02 |
| Game Programming Design Patterns - 명령 패턴 (2) (0) | 2022.08.02 |
| Game Programming Design Patterns - 명령 패턴 (1) (0) | 2022.07.30 |