언리얼 팀프로젝트

멀티플레이어 (데디서버) AI 랜덤 좌표 이동

Turtle_Jun 2025. 9. 10. 17:44

이번 팀프로젝트에서 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 펭귄을 배치하여 잘 움직이고 애니가 잘 나오는지 확인하면 끝이난다.