이번 기능에서는 NPC가 단순히 한 줄 대사만 출력하는 수준을 넘어서, 트리 형태로 분기되는 대화 시스템을 직접 설계하고 구현해 보았습니다. 언리얼 엔진의 UObject / UActorComponent / AActor 구조를 활용해, 블루프린트에서도 직관적으로 편집 가능한 형태를 목표로 했습니다. 특히, "대화를 따라가다 특정 노드에서 액션(상점 열기 등)을 실행"하는 흐름까지 테스트 코드로 구축해 본 것이 핵심입니다.
핵심 타입
ECMNpcComponentType : NPC 컴포넌트의 종류를 표현하는 enumUCMDialoagueNode : 기본 대화 노드(텍스트, 부모/자식 참조 포함)UCMActionNode : 기본 노드를 상속받아, 추가로 액션 타입을 가지는 노드ACMNpcBase : NPC 액터, 대화 트리 전체를 소유/관리UCMNpcComponentBase : NPC용 공통 컴포넌트 베이스UCMNpcShopComponent : 예시용 상점 컴포넌트(PerformAction 시 화면 메시지 출력)설계 포인트
ParentNode + Children 배열로 표현ACMNpcBase)가RootDialogueNode (루트 노드)AllDialogueNodes (생성된 모든 노드)UCMActionNode)으로 구분ActionType: ECMNpcComponentTypeHandleActionByType 호출UENUM(BlueprintType)
enum class ECMNpcComponentType: uint8
{
Default UMETA(DisplayName = "Default"),
DialogueComponent UMETA(DisplayName = "Dialogue Component"),
QuestComponent UMETA(DisplayName = "Quest Component"),
ShopComponent UMETA(DisplayName = "Shop Component"),
};
ACMNpcBase의 RegisteredComponentMap 키로 사용UCMActionNode의 ActionType에도 사용 → 트리 상에서 어느 컴포넌트를 실행할지 지정UENUM(BlueprintType) 사용int (언더라이잉 타입 명시 생략)으로 유지UCLASS(BlueprintType) class UCMDialoagueNode : public UObjectUCLASS(BlueprintType)
class UCMDialoagueNode : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Dialogue")
TObjectPtr<UCMDialoagueNode> ParentNode = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dialogue")
TArray<TObjectPtr<UCMDialoagueNode>> Children;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dialogue")
FText DialogueText;
};
주요 필드
ParentNode : UCMDialoagueNode*Children : TArray<UCMDialoagueNode*>DialogueText : FText특징
UCLASS(BlueprintType) class UCMActionNode : public UCMDialoagueNodeUCLASS(BlueprintType)
class UCMActionNode : public UCMDialoagueNode
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dialogue")
ECMNpcComponentType ActionType;
};
추가 필드
ActionType : ECMNpcComponentType동작
UCMActionNode로 캐스팅에 성공하면ActionType 값을 읽어ACMNpcBase::HandleActionByType(ActionType) 호출ShopComponent → 상점 열기 컴포넌트의 PerformAction 호출타입
class ACMNpcBase : public AActor, public ICMNpcHandler주요 프로퍼티
RootDialogueNode : UCMDialoagueNode*AllDialogueNodes : TArray<UCMDialoagueNode*>CurrentNode : UCMDialoagueNode*NpcComponents : TArray<TSubclassOf<UCMNpcComponentBase>>ActiveNpcComponents : TArray<UCMNpcComponentBase*>RegisteredComponentMap : TMap<ECMNpcComponentType, UCMNpcComponentBase*>BeginPlay 흐름
RegisteredComponentMap.Empty() : 초기화NpcComponents를 순회하며NewObject<UCMNpcComponentBase>(this, ComponentClass) 로 생성RegisterComponent() 호출 → 언리얼 라이프사이클에 등록ActiveNpcComponents에 추가BeginPlay에서ACMNpcBase)에 RegisterComponent를 호출하여 스스로 등록FTimerHandle을 사용해 BeginPlay + 1초 후 LogAllDialogueNodeTexts() 실행시그니처
UCMDialoagueNode* CreateDialogueNode(TSubclassOf<UCMDialoagueNode> NodeClass, const FText& InDialogueText);역할
NodeClass (기본은 UCMDialoagueNode)로 새 노드를 생성하고 텍스트 설정AllDialogueNodes 배열에 추가RootDialogueNode로 설정동작 요약
if (!*NodeClass) NodeClass = UCMDialoagueNode::StaticClass();NewObject<UCMDialoagueNode>(this, NodeClass)NewNode->DialogueText = InDialogueText;AllDialogueNodes.Add(NewNode);RootDialogueNode가 비어 있으면 첫 노드를 루트로 등록시그니처
UCMDialoagueNode* CreateDialogueNodeWithSettings(TSubclassOf<UCMDialoagueNode> NodeClass, UCMDialoagueNode* Parent, const FText& InDialogueText);역할
CreateDialogueNode를 호출해 노드를 생성하고연결 로직
NewNode->ParentNode = Parent;Parent->Children.Add(NewNode);시그니처
UCMDialoagueNode* CreateActionNodeWithSettings(TSubclassOf<UCMActionNode> NodeClass, UCMDialoagueNode* Parent, const FText& InDialogueText, ECMNpcComponentType InActionType);역할
UCMActionNode 타입으로 노드를 생성DialogueText + ActionType + 부모/자식 관계를 한 번에 설정동작 요약
if (!*NodeClass) NodeClass = UCMActionNode::StaticClass();NewObject<UCMActionNode>(this, NodeClass) 로 액션 노드 생성NewNode->DialogueText = InDialogueText;NewNode->ActionType = InActionType;Parent가 있으면NewNode->ParentNode = Parent;Parent->Children.Add(NewNode);블루프린트 사용성
InActionType가 ECMNpcComponentType 이라 BP 노드에서 드롭다운으로 선택 가능
기본 대화 노드 생성
CreateDialogueNode(UCMDialoagueNode, "대사 텍스트")CreateDialogueNodeWithSettings(UCMDialoagueNode, ParentNode, "텍스트")액션 노드 생성
CreateActionNodeWithSettings(UCMActionNode, ParentNode, "텍스트", ECMNpcComponentType::ShopComponent)LogAllDialogueNodeTexts() 자동 호출
이번 NPC Dialogue 시스템 구현에서는 단순한 대사 나열을 넘어서, 트리 구조와 액션 노드를 결합한 대화 흐름을 구축해 보았습니다. 대화 노드를 UObject로 분리하고, NPC 액터가 트리 전체를 관리하도록 설계함으로써 재사용성과 확장성을 높이고자 하였습니다. 또한, 블루프린트에서 노드를 생성하고 텍스트/액션 타입을 직관적으로 지정할 수 있도록 API를 정리한 덕분에, 디자이너 입장에서의 사용성도 어느 정도 확보할 수 있었습니다.
앞으로는 이 구조를 기반으로 실제 게임 플레이에 필요한 UI 연동, 선택지 표시, 조건부 분기(퀘스트 진행도, 인벤토리 상태 등)에 따른 동적 트리 탐색 등을 추가해 볼 예정입니다. 이번 작업을 통해 언리얼 엔진의 UObject/Actor/Component 구조와 블루프린트 연동 방식에 대한 이해를 한층 더 깊게 할 수 있었던 의미 있는 경험이었습니다.