템플릿 패턴과 서브클래스 샌드박스 패턴,
하나의 부모 클래스를 기반으로 코드중복을 줄이고 재활용이 가능하도록 하는 패턴이다.
서브클래스 샌드박스 패턴의 경우
자식 클래스가 자유롭게 기능을 구현하고 조합 할 수 있도록 부모클래스는 최소한의 구현을 제공한다.
수정에 있어서 자유도가 높다.
다만 그만큼 자식클래스마다 기능과 흐름이 달라질 수 있고,안정성이 낮아질 수 있다.
템플릿 메서드 패턴의 경우
부모클래스는 정해진 실행 순서를 제공하고, 자식 클래스는 정해진 로직 내에서 일부 함수의 기능만을 구현할 수 있다.
수정에 있어 자유도는 낮지만, 튼튼한 구조를 가진 부모로부터 안정적인 구현이 가능하다.
표로 정리하면 다음과 같다.
| 패턴 | 서브클래스 샌드박스 패턴 | 템플릿 메서드 패턴 |
| 핵심 의도 | 자식이 자유롭게 기능을 조합해서 구현 | 부모가 실행 순서를 고정하고, 일부 단계만 자식이 구현 |
| 블루프린트 역할 | 거의 전체 기능 흐름을 직접 구성 | 정해진 단계 안의 일부 동작만 구현 |
| 장점 | 자유도 높음 | 구조 안정성 높음 |
| 단점 | 자식마다 흐름이 달라질 수 있음 | 자유도는 상대적으로 낮음 |
가장 토대가 될 부모클래스는 기본적으로 가져야 할 정보들을 선언하고, 최소한의 구현을 진행한다.
다음의 정보들이 필요할것이다.
//소모되는 탄약수
int32 AmmoPerFire;
//남은 탄약수
int32 CurrentAmmo;
//탄약 보유량
int32 MaxAmmo;
//연사속도
float RateOfFire;
//유효사거리
float Range;
//데미지양
float DamagePerHit;
//사격 가능 여부
bool CanFire;
//연사속도 제어를 위한 핸들
FTimerHandle TimerFireDelay;
총기라면 공통으로 가져야 할 데이터들을 가지록 한다.
그리고 사격을 하는 함수와 연사를 제어하는 함수 정도를 기본으로 추가한다.
public:
virtual void Fire();
protected:
void HandleFireDelay();
void AWeaponBase::Fire()
{
CanFire = false;
GetWorldTimerManager().SetTimer(
TimerFireDelay,
this,
&AWeaponBase::HandleFireDelay,
1.f / RateOfFire,
false);
}
void AWeaponBase::HandleFireDelay()
{
GetWorldTimerManager().ClearTimer(TimerFireDelay);
CanFire = true;
}
구현부는 위와 같다.
실제 총기의 기능은 하지않는다.
다만 발사를 호출하면 CanFire를 False로 지정해 연속호출을 제한하고,
연사속도를 담당하는 변수RateOfFire값을 따라 타이머를 생성하여 일정시간후에 Fire가 작동 가능하도록 하였다.
무기가 공통으로 가져야할 상태와 쿨타임(연사)처리만을 담당한다.
이 부모클래스를 상속받고 각각의 패턴을 적용시킨 부모클래스를 다시 작성한다.
우선 템플릿 메서드 패턴을 적용한 경우의 구현체
void AWeaponTemplateBase::Fire()
{
if (!CanFire)
return;
if (CheckAmmo()) {
PlayEffects();
ProcessFiring();
UpdateAmmo();
Super::Fire();
return;
}
}
해당 방식으로 작성된 뼈대 클래스는 Fire함수의 실행 순서를 다음의 순서로 제한한다.
CanFire가 True라면,
탄환을 확인하고(CheckAmmo())
이펙트와 소리 등을 출력하고(PlayEffects())
실제 사격 기능을 실행한 후(ProcessFiring())
현재 잔탄을 갱신한다.(UpdateAmmo())
마지막으로 부모클래스의 함수인 Super::Fire()를 호출하여 연사 관련 기능을 실행한다.
이 디자인 패턴에서 이렇게 작성된 부모클래스를 기반으로 작성된 자식클래스는
CheckAmmo, PlayEffects, ProcessFiring, UpdateAmmo 총 4개의 기능을 적절히 구현하는것으로 서로 다른 총기를 구현하도록 할 수 있다.
동일한 로직 아래에서
CheckAmmo또는 UpdateAmmo의 구현을 변경하여 무제한 사격이 되게 한다던지,
효과와 사격기능을 변경하여 일반적인 총기가 아닌 대포를 만든다던지.
ProcessFiring()을 수정해 한번에 여러발이 나가게 한다던지 등 다양한 구현이 가능할것이다.
하지만 전체 흐름은 반드시 Fire의 순서를 따르게 된다.
이렇게 만들어진 자식들은 동일한 구조 내에서 안정적인 구현이 가능할것이다.
정리하면 부모는 안정적인 실행구조 및 기본 기능을 제공하고,
자식은 기능을 정의 및 재정의하여 자식별로 다른 결과물을 만들어 낼 수 있다.
이번에는 서브클래스 샌드박스 패턴
public:
virtual void Fire() override;
UFUNCTION(BlueprintImplementableEvent)
void SandboxFire();
UFUNCTION(BlueprintCallable)
void Reload();
protected:
UFUNCTION(BlueprintCallable)
bool CheckAmmo();
UFUNCTION(BlueprintCallable)
void LinetraceOneShot(FVector Direction);
UFUNCTION(BlueprintCallable)
void PlaySound(USoundBase* Sound);
UFUNCTION(BlueprintCallable)
void UpdateAmmo();
그리고 구현부(의 일부분)
void ASandboxWeaponBase::Fire()
{
SandboxFire();
}
기존의 Fire에서 부모클래스의 Fire도 호출하지 않는다.
즉 자식클래스는 원래의 "정보"만을 상속받고, 모든 실행순서와 구현등은 자식클래스에서 만들어야한다.
즉, 부모는 자식에게 필요한 "도구" 만을 제공해주었고, 자식은 그걸 가지고 마음대로 구현하면 되는것이다.
위의 템플릿에서 진행한 방식대로 그대로 구현하는것도 가능하고,
원하는 기능을 마음대로 추가 및 구현이 가능한것이다.
블루프린트로 넘어가자면,
사격 함수의 진입점인 Fire또는 SandboxFire()함수에 UPROPERTY(BlueprintImplementableEvent)매크로를 지정하여
C++클래스에서 일부 기능만을 구현하여 안전성을 도모할 수도 있다.
위의 코드의 경우
bool CheckAmmo();
void LinetraceOneShot(FVector Direction);
void PlaySound(USoundBase* Sound);
void UpdateAmmo();
4개의 함수를 개발자가 미리 제공하는것으로 너무 막무가내로 만들어지지않고 도구함수를 제공하도록 할 수있다.
말이 길어졌는데,
줄이면 "부모 클래스는 도구(샌드박스 환경)를 제공하고, 자식 클래스는 그 안에서 자유롭게 기능을 조합하여 구현한다."
정도로 정리가 되겟다.
그렇다면 어떨 때 어떤 패턴을 사용할것이 좋은가?
"정형화된 패턴을 가진 객체"라면 템플릿 메서드 패턴이 유리할것이다.
위 예시처럼 "총기" 처럼 정해진 기능이 있는경우, 이 패턴이 유리할것이다.
반대로 같지만 다양한 기능을 제공하는 객체라면, 서브클래스 샌드박스 패턴이 유리할것이라 생각된다.
예를들면 "마법"을 구현한다고 할때를 상상해보면
부모클래스에서는 수많은 마법의 기능의 뼈대를 구현해두고(사용방식-공격, 버프, 디버프 / 속성 - 불,얼음,번개 등등...)
자식클래스는 부모로부터 원하는 기능을 조합하여 새로운 마법 스킬을 만들어 낼 수 있을것이다.
'개발 > 언리얼' 카테고리의 다른 글
| 언리얼 캐릭터 무기 위치 조정 및 부착(Skeleton, Socket) (0) | 2026.05.04 |
|---|---|
| 언리얼 C++ 애니메이션 몽타주 사용방법 (0) | 2026.05.04 |
| 언리얼 C++ 빌드 Unreachable Code 오류 (0) | 2026.04.30 |
| SpawnActor로 생성된 캐릭터가 움직이지 않는경우 (0) | 2026.04.29 |
| Collision Response, Linetrace (0) | 2026.04.24 |