
언리얼 엔진 C++ 튜토리얼 시스템 리팩토링: GameMode 로직 개선기
초기 튜토리얼 시스템은 각 단계를 처리하는 기본적인 구조를 갖추고 있었지만, 기능이 확장되면서 여러 문제가 발생했습니다.
이러한 문제들을 해결하고, 더 안정적이고 확장성 있는 구조를 만들기 위해 AADTutorialGameMode 클래스를 중심으로 대대적인 리팩토링을 진행했습니다.
// ...
protected:
// ...
// Helper 함수들을 private으로 이동시켜 역할과 접근 범위를 명확히 함
private:
void TrackPhaseActor(AActor* Actor) { if (IsValid(Actor)) { ActorsToShowThisPhase.Add(Actor); } }
void BindIndicatorToOwner(AActor* OwnerActor, AActor* IndicatorActor);
// ...
해결책:
인디케이터를 주인 액터에 '종속'시키는 새로운 시스템을 도입했습니다. 이는 주인이 파괴되면 인디케이터도 자동으로 파괴되는, 훨씬 안정적인 방식입니다.
// ...
// 주인이 파괴되었을 때 호출될 함수
UFUNCTION()
void OnTrackedOwnerDestroyed(AActor* DestroyedActor);
private:
// 주인(Key)과 인디케이터(Value)를 짝지어 저장하는 TMap
UPROPERTY()
TMap<TWeakObjectPtr<AActor>, TWeakObjectPtr<AActor>> OwnerToIndicator;
// ...
// 인디케이터(IndicatorActor)를 주인(OwnerActor)에게 묶는 함수
void AADTutorialGameMode::BindIndicatorToOwner(AActor* OwnerActor, AActor* IndicatorActor)
{
if (!IsValid(OwnerActor) || !IsValid(IndicatorActor)) return;
// 1. TMap에 주인과 인디케이터를 기록
OwnerToIndicator.Add(OwnerActor, IndicatorActor);
// 2. 주인의 OnDestroyed 이벤트에 우리의 함수(OnTrackedOwnerDestroyed)를 바인딩(연결)
OwnerActor->OnDestroyed.AddDynamic(this, &AADTutorialGameMode::OnTrackedOwnerDestroyed);
}
// 주인이 파괴되었을 때 자동으로 호출되는 함수
void AADTutorialGameMode::OnTrackedOwnerDestroyed(AActor* DestroyedActor)
{
// TMap에서 파괴된 주인을 찾아 연결된 인디케이터가 있는지 확인
if (TWeakObjectPtr<AActor>* FoundIndicator = OwnerToIndicator.Find(DestroyedActor))
{
if (FoundIndicator->IsValid())
{
// 연결된 인디케이터를 파괴
FoundIndicator->Get()->Destroy();
}
// TMap에서 기록 삭제
OwnerToIndicator.Remove(DestroyedActor);
}
}
이제 HandlePhase_Drone처럼 주인이 명확한 경우에는 BindIndicatorToOwner를 호출하여 인디케이터를 자동으로 관리하므로, ActorsToShowThisPhase 배열에 수동으로 추가할 필요가 없어졌습니다.
// 예시: HandlePhase_Drone 함수
void AADTutorialGameMode::HandlePhase_Drone()
{
// ...
AActor* Drone = FoundActors[0];
Drone->SetActorHiddenInGame(false);
Drone->Tags.Add(FName("Radar"));
TrackPhaseActor(Drone); // 주인 액터인 드론은 '일괄 정리' 대상
if (IndicatingTargetClass)
{
AIndicatingTarget* Indicator = GetWorld()->SpawnActor<AIndicatingTarget>(...);
if (Indicator)
{
// ...
// 인디케이터는 주인에게 묶어 '자동 정리' 대상으로 설정
BindIndicatorToOwner(Drone, Indicator);
}
}
}
void AADTutorialGameMode::HidePhaseActors()
{
for (AActor* ActorToDestroy : ActorsToShowThisPhase)
{
if (ActorToDestroy)
{
// 우리가 직접 파괴할 것이므로, OnDestroyed 델리게이트가 또 호출되지 않도록 연결을 끊어줍니다.
ActorToDestroy->OnDestroyed.RemoveAll(this);
ActorToDestroy->Destroy();
}
}
ActorsToShowThisPhase.Empty();
}