이번 팀프로젝트에서 NPC 구현 및 서버 보조 역할을 담당받았다.
NPC 기능 중 하나인 NavMesh 내부에서 NPC가 랜덤하게 이동하는 기능을 이번 글에서 다뤄보도록 한다.
1. 클래스 생성
1-1) NPC (캐릭터 클래스)
- DCNPC.h

- DCNPC.cpp

NPC 캐릭터클래스는 Character클래스를 기반으로 생성 후 Getter 함수로 BehaivorTree 함수를 선언해준다.
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; 를 통해 월드에 스폰되거나 배치되어있을때 NPC에 자동 빙의로직을 실행해둔다.
또한 bReplicates = true; 로 설정하여 해당 NPC 가 서버 -> 클라로 복제되어 보여지도록 설정한다.
- DCNPC.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BehaviorTree.h"
#include "GameFramework/Character.h"
#include "DCNPC.generated.h"
UCLASS()
class DC_API ADCNPC : public ACharacter
{
GENERATED_BODY()
public:
ADCNPC();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UBehaviorTree* GetBehaviorTree() const;
protected:
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
UBehaviorTree* Tree; // 비헤이비어 트리 설정
};
- DCNPC.cpp
// 정기준
#include "NPC/DCNPC.h"
// Sets default values
ADCNPC::ADCNPC()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void ADCNPC::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADCNPC::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ADCNPC::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
UBehaviorTree* ADCNPC::GetBehaviorTree() const
{
return Tree;
}
1-2) NPC 컨트롤러 클래스 생성(AIController)
- DCNPCController.h

- DCNPCController.cpp

AIController를 기반으로 생성한 DCNPCController 클래스에선 OnPossess 함수를 오버라이드 하여 해당 NPC 캐릭터에 AI 컨트롤러가 빙의 되었을때 NPC가 가지고있는 비헤이비어 트리를 얻어온 후 해당 비헤이비어 트리에 있는 블랙보드를 가져와 컨트롤러의 블랙보드에도 설정해준후 비헤이비어 트리를 실행하는 로직을 구현
- DCNPCController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "DCNPCController.generated.h"
UCLASS()
class DC_API ADCNPCController : public AAIController
{
GENERATED_BODY()
public:
// explicit 는 형변환 방지를 위한 키워드로 실수방지차원에서 선언
explicit ADCNPCController(FObjectInitializer const& ObjectInitializer);
protected:
virtual void OnPossess(APawn *InPawn) override;
};
- DCNPCController.cpp
#include "NPC/DCNPCController.h"
#include "DCNPC.h"
ADCNPCController::ADCNPCController(FObjectInitializer const& ObjectInitializer)
{
bReplicates = true; // 리플리케이트 실행
}
void ADCNPCController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (ADCNPC* const npc = Cast<ADCNPC>(InPawn))
{
if (UBehaviorTree* const tree = npc->GetBehaviorTree())
{
// 빙의된 NPC 의 비헤이비어 트리의 블랙보드를 기반으로 Bb컴포넌트를 초기화
UBlackboardComponent* Bb;
UseBlackboard(tree->BlackboardAsset, Bb); // BT에서 지정한 블랙보드 에셋을 기반으로 Bb를 초기화
Blackboard = Bb; // 컨트롤러가 가지고있는 블랙보드를 Bb로 할당
// BehaviorTree 시작
RunBehaviorTree(tree);
}
}
}
1-3) BTTask_BlackboardBase 클래스 생성
- DCBTTask_FindRandomLocation.h

- DCBTTask_FindRandomLocation.cpp

생성자에선 위처럼 비헤이비어 트리에서 사용될 Task 노드 이름을 "Find Random Location In NaveMesh" 로 설정하여준다.

다음으론 RandomLocation 을 얻어오는 로직으로 순서는 아래와 같다.
1-3-1) NPC 현재 위치 얻어오기

1-3-2) 현재 월드에 존재하는 NaviMesh 얻어온 후 랜덤 위치 반환

GetRandomPointInNavigableRadius() : NaveMesh 내부에서 Random 한 좌표를 반환해주는 함수
매개 변수
OriginLocation : 현재 위치 (NPC의 현재 위치를 담고있음)
SearchRadius : Random 할 위치를 선정할 현재 위치로부터의 반경
NavLoc : Random 위치의 값을 저장할 변수
현재 위치를 중심으로 반경 이내에 랜덤한 위치를 NavLoc에 저장하는 로직이다.
저장 후 NavLoc.Location을 통해 해당 태스크가 가지고있는 OwnerComp의 블랙보드 컴포넌트에 있는 Vector 값을 NavLoc.Location으로 설정해준다.
1-3-3) 태스크가 정상적으로 실행하지 않을시 Failed 반환

이렇게하면 코드 설정은 끝으로 다음은 에디터에서 설정을 해주면된다.
- DCBTTask_FindRandomLocation.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Tasks/BTTask_BlackboardBase.h"
#include "DCBTTask_FindRandomLocation.generated.h"
/**
*
*/
UCLASS()
class DC_API UDCBTTask_FindRandomLocation : public UBTTask_BlackboardBase
{
GENERATED_BODY()
UDCBTTask_FindRandomLocation(FObjectInitializer const& ObjectInitializer);
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& Comp, uint8* NodeMemory) override;
private:
UPROPERTY(EditAnywhere, Category = "AI")
float SearchRadius = 1500.f;
};
- DCBTTask_FindRandomLocation.cpp
#include "NPC/DCBTTask_FindRandomLocation.h"
#include "DCNPCController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
UDCBTTask_FindRandomLocation::UDCBTTask_FindRandomLocation(FObjectInitializer const& ObjectInitializer)
{
NodeName = "Find Random Location In NaveMesh"; // Task 노드 이름 설정
}
EBTNodeResult::Type UDCBTTask_FindRandomLocation::ExecuteTask(UBehaviorTreeComponent& Comp, uint8* NodeMemory)
{
// NPC 컨트롤러 얻어오기
if (ADCNPCController* const NPCController = Cast<ADCNPCController>(Comp.GetAIOwner()))
{
if (auto* const NPC = NPCController->GetPawn())
{
// NPC 위치 얻어오기
auto const OriginLocation = NPC->GetActorLocation();
// NaviMesh 얻어온 후 랜덤 위치 반환
if (auto* const NavSys = UNavigationSystemV1::GetCurrent(GetWorld()))
{
FNavLocation NavLoc;
// NavLoc에 랜덤 위치 저장
// GetRandomPointInNavigableRadius(NPC의 현재 위치, 랜덤 위치 찍을 반경, 랜덤위치 반환)
if (NavSys->GetRandomPointInNavigableRadius(OriginLocation, SearchRadius, NavLoc)) // 랜덤 위치 얻어올 수 있는 경우만 실행
{
// 블랙보드 키(에디터에서 블랙보드의 TargetLocation)값에 랜덤 위치 설정
Comp.GetBlackboardComponent()->SetValueAsVector(GetSelectedBlackboardKey(), NavLoc.Location);
}
// Task 종료
FinishLatentTask(Comp, EBTNodeResult::Succeeded);
return EBTNodeResult::Succeeded; //Task 성공했음을 반환
}
}
}
return EBTNodeResult::Failed; // Task 실패
}
2 . 비헤이비어 트리 설정
2-1) BT_DCNPC 로 비헤이비어 트리 생성



위 처럼 블랙보드 키값을 지정해주기 위해선 NPC 블랙보드도 다음처럼 생성해준다.

블랙보드 생성 후 해당 블랙보드를 비헤이비어 트리에 적용해주면 된다. 위 비헤이비어 트리 구조 구현하기 전에 블랙보드를 우선적으로 적용할것!
2-2) NPC 캐릭터 블루프린트 설정

비헤이비어 트리 설정

AI 컨트롤러 클래스 적용
3 . 캐릭터 애니메이션 설정

위 처럼 Try Get Pawn Owner 노드를 통해 로직이 구현되어있으면 AI 컨트롤러나 플레이어 컨트롤러 모두 읽어 들일 수 있게 설정되어 있으면 된다.
이후 맵에 NPC 펭귄을 배치하여 잘 움직이고 애니가 잘 나오는지 확인하면 끝이난다.
'언리얼 팀프로젝트' 카테고리의 다른 글
| 멀티 플레이어 (데디서버) NPC 랜덤 애니재생하기 (0) | 2025.09.11 |
|---|---|
| 멀티 플레이어 (데디서버) NPC 랜덤 위치 스폰 액터 구현 -1 (0) | 2025.09.10 |
| 멀티 플레이어 (데디케이트 서버) NPC 이동 구현 기획 (0) | 2025.09.08 |
| 멀티게임 팀프로젝트 Git 사용 (0) | 2025.09.05 |
| CH3 팀프로젝트 KPT 회고 (0) | 2025.08.20 |