멈추지 않고 끈질기게

[Unreal] 최종 프로젝트 4일차 - 채팅 방식 추가 / BT 틀 만들기 본문

Unreal

[Unreal] 최종 프로젝트 4일차 - 채팅 방식 추가 / BT 틀 만들기

sam0308 2024. 5. 16. 19:16

※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.

※ 해당 포스팅은 청년취업사관학교 교육 과정의 최종 프로젝트에 관한 내용을 포함하고 있습니다.

 

※ 해당 포스팅은 Unreal 5.4.1 버전을 기준으로 작성되었습니다.

 

0. 서론

 오늘은 사실 통신 방식을 변경하여 딜레이를 좀 더 줄이는 작업을 하려고 했는데 잘 진행되지 않았습니다. 강사님에게도 여쭤봤는데 클라이언트 지망으로서 통신을 개선하는데 시간을 쏟는 것 보다는, 대기 시간이 티가 덜 나게 하는 방법을 고민해보는 것이 좋을 것 같다는 의견이셔서 일단 보류하게 되었습니다. 오전에 거의 해당 작업에 매진했어서 결과적으로 작업량이 좀 적게 되어버렸습니다. 

 

 

1. 채팅 입력 방식 추가

 현재 마이크 입력만으로 NPC와 대화할 수 있게 되어있는데, 채팅 입력 방식을 추가하기로 했습니다. 강사님도 어차피 테스트에 있어서도 채팅 대화 기능이 있는 것이 편리할 것이라는 의견이셔서 추가해보았습니다. 플레이어 작업하는 분은 따로 계셔서, 관련 내용은 컴포넌트로 작성하였습니다.

#include "PKH/Test/TextInputComponent.h"

#include "Blueprint/UserWidget.h"
#include "PKH/Component/STTComponent.h"
#include "PKH/UI/ChatUIWidget.h"

UTextInputComponent::UTextInputComponent()
{
	PrimaryComponentTick.bCanEverTick = false;

	static ConstructorHelpers::FClassFinder<UChatUIWidget> ChatUIClassRef(TEXT("/Game/PKH/UI/WBP_ChatUI.WBP_ChatUI_C"));
	if(ChatUIClassRef.Class)
	{
		ChatUIClass = ChatUIClassRef.Class;
	}
}

void UTextInputComponent::BeginPlay()
{
	Super::BeginPlay();

	ChatUI = CreateWidget<UChatUIWidget>(GetWorld(), ChatUIClass);
	ensure(ChatUI);
	ChatUI->AddToViewport();
	ChatUI->SetVisibility(ESlateVisibility::Hidden);
}


#pragma region 채팅 입력
void UTextInputComponent::Chat()
{
	if(false == IsChatting)
	{
		ChatUI->SetVisibility(ESlateVisibility::Visible);
		ChatUI->Focus();
		IsChatting = true;
	}
	else
	{
		ChatUI->SetVisibility(ESlateVisibility::Hidden);
		IsChatting = false;

		FString InputText = ChatUI->GetChatText();
		if(InputText.IsEmpty())
		{
			return;
		}

		USTTComponent* STTComp = Cast<USTTComponent>(GetOwner()->GetComponentByClass(USTTComponent::StaticClass()));
		if(STTComp)
		{
			STTComp->CheckNearbyObjects(InputText);
		}
	}
}
#pragma endregion

 

 관련 UI도 따로 제작하여 컴포넌트가 들게 하였고, 플레이어 쪽에서는 해당 컴포넌트를 추가한 후에 특정 키보드 입력 시 Chat() 함수만 호출하면 되도록 구현하였습니다. 다른 팀원이 사용할 기능이라 플레이어 쪽에서는 최대한 추가할 코드가 적게 만들고자 했습니다.

 

 이 밖에도 텍스트 형식으로 웹서버에 전달하는 함수도 별도로 작성했지만, 기존의 코드에서 입력만 살짝 바꾼 수준에 불과하여 별도로 첨부하진 않겠습니다. 

 

 

2. Behavior Tree 기본 틀 제작

 NPC가 대화 외에도 어느정도 행동패턴을 가져야 하므로, Behavior Tree를 통해 행동 패턴을 구현할 예정입니다. 우선 오늘은 기본 틀만 만들어 두었습니다. Blackboard와 Behavior Tree, 그리고 이를 사용할 AIController 등을 우선 생성해두었습니다.

// Fill out your copyright notice in the Description page of Project Settings.


#include "PKH/NPC/NPCController.h"

#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "PKH/BT/BTNPCKey.h"

ANPCController::ANPCController()
{
	static ConstructorHelpers::FObjectFinder<UBlackboardData> BBDataRef(TEXT("/Script/AIModule.BlackboardData'/Game/PKH/BT/BB_NPC.BB_NPC'"));
	if (BBDataRef.Object)
	{
		BBData = BBDataRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTDataRef(TEXT("/Script/AIModule.BehaviorTree'/Game/PKH/BT/BT_NPC.BT_NPC'"));
	if (BTDataRef.Object)
	{
		BTData = BTDataRef.Object;
	}
}

void ANPCController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);

	RunAI();
}

void ANPCController::RunAI()
{
	BBComp = Blackboard;
	if(UseBlackboard(BBData, BBComp))
	{
		bool result = RunBehaviorTree(BTData);

		// 초기화

	}
}

void ANPCController::StopAI()
{
	UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(BrainComponent);
	if(BTComp)
	{
		BTComp->StopTree();
	}
}

void ANPCController::StartConversation()
{
	BBComp->SetValueAsBool(KEY_IN_CONV, true);
}

void ANPCController::EndConversation()
{
	BBComp->SetValueAsBool(KEY_IN_CONV, false);
}

void ANPCController::SetHomeLoc(const FVector& HomeLoc)
{
	BBComp->SetValueAsVector(KEY_HOME_LOC, HomeLoc);
}

void ANPCController::MoveToTargetLoc(const FVector& TargetLoc)
{
	BBComp->SetValueAsBool(KEY_IS_MOVING, true);
	BBComp->SetValueAsVector(KEY_TARGET_LOC, TargetLoc);
}

void ANPCController::MoveToHome()
{
	BBComp->SetValueAsBool(KEY_IS_MOVING, true);
	const FVector& HomeLoc = BBComp->GetValueAsVector(KEY_HOME_LOC);
	BBComp->SetValueAsVector(KEY_TARGET_LOC, HomeLoc);
}

 

 구체적인 행동 패턴을 정해놓진 않았지만, 우선 대화중에는 이동하면 안되므로 bool 타입 키를 생성해두고 이를 키고 끄는 함수를 작성했습니다. 그리고 특정 위치로 이동하는 함수와 원래 위치(집)로 돌아가는 함수까지 우선 생성해두었습니다. 그리고 Blackboard에서 사용하는 Key 값들은 그냥 텍스트로 작성하면 오타의 위험성이 높아, 헤더에 따로 매크로로 작성해두었습니다(2차 프로젝트 때 오타때문에 고생해서 얻은 교훈입니다).

// Blackboard key 저장용 헤더

#pragma once

// Vector
#define KEY_HOME_LOC TEXT("HomeLoc")
#define KEY_TARGET_LOC TEXT("TargetLoc")
// Bool
#define KEY_IN_CONV TEXT("InConversation")
#define KEY_IS_MOVING TEXT("IsMoving")
// Float
#define KEY_PATROL_DIST TEXT("PatrolDistance")

 

사진 1. Behavior Tree

 

 우선 기본적인 행동 패턴은 다음과 같이 구성하였습니다.

  1. 대화중이라면 계속 대기
  2. 대화중이 아니고 이동중이라면(패트롤 X), 목표 지점까지 이동
    이동 완료 후 이동 상태 해제
  3. 대화중 X, 이동중 X 일 경우 근처 위치를 패트롤

 각 상태를 체크하기 위한 Decorator와 추가로 필요한 커스텀 노드들을 C++로 생성해서 우선 사진처럼 배치까지만 해보았습니다. 

 

 

3. 프로젝트 머지

 여담으로 각 브랜치로 나누어서 작업하던 내용물을 최초로 머지해 보았습니다. 일단 폴더를 잘 나누어놔서 그런지 아직 큰 이슈는 발생하지 않았습니다. 컬리전 프로파일 관련 이슈가 하나 있었지만 급한 문제는 아니라, 다음 번에 머지할 때 수정해서 올려주시기로 했습니다. 앞으로는 팀원 분들 작업물하고 상호작용도 체크하면서 작업할 수 있겠습니다.