본문 바로가기
Unreal Engine5/KDT 2024

[UE5 KDT2024] 36. 맵 네비게이션, 팀셋팅 1편

by 유잉유잉유잉 2025. 3. 20.
728x90

1. AIController에서 액터 감지 후

블랙보드에 데이터를 저장시키자.

void AMonsterAIControllerKDT::OnTargetFound( AActor* Actor, FAIStimulus Stimulus )
{
	// 감지성공
	if ( Stimulus.WasSuccessfullySensed() )
	{
		GEngine->AddOnScreenDebugMessage( 1, 10.f, FColor::Red, L"true" );

		AController* con = Cast<AController>( Actor );
		if ( IsValid( con ) )
			Actor = con->GetPawn<AActor>();

		// 감지된 액터 메모리 저장. 블랙보드에 데이터를 저장. 
		// Blackboard : AIController가 들고있는 블랙보드
		Blackboard->SetValueAsObject( L"Target", Actor );

	}
	// 감지 실패 
	else
	{
		GEngine->AddOnScreenDebugMessage( 1, 10.f, FColor::Blue, L"false" );

		// 감지 되었던거 제거 
		Blackboard->SetValueAsObject( L"Target", Actor );

	}
}

 

=> 서버로 돌릴 경우, 

시야안에 1플레이어가 있는데 2플레이어가 시야안에 들어가면 감지 타겟을 바꾼다.



 


 

 

2. 선등록된 타겟이 시야에 있을경우 다른 타겟이 시야에 들어와도 바꾸지 않기

 

선등록된 타겟이 시야를 나갈때만 타겟을 없얘보자.

 

1) PlayerBase에 태그 추가

APlayerBase::APlayerBase()
{
    ..
    Tags.Add( L"Player" );
}



2) 기존 타겟 있는지 체크, 기존 타겟과 새 타겟이 플레이어면 리턴

void AMonsterAIControllerKDT::OnTargetFound(AActor* Actor, FAIStimulus Stimulus)
{
	if (true == Stimulus.WasSuccessfullySensed())
	{
		//감지 성공
		GEngine->AddOnScreenDebugMessage(1, 10.f, FColor::Red, TEXT("true == Stimulus.WasSuccessfullySensed()"));

		//기존에 타겟을 잡고 있었는지 가져오고
		//해당 타겟과 새로 감지한 타겟이 플레이어였는지
		//비교하여 둘 다 플레이어라면 타겟을 교체하지 않는다.
		AActor* foundActor = Cast<AActor>(Blackboard->GetValueAsObject(TEXT("Target")));
		if (true == IsValid(foundActor))
		{
			if(foundActor->ActorHasTag(TEXT("Player")) &&
				Actor->ActorHasTag(TEXT("Player")))
			{
				return;
			}
		}

		//여기서 그러면 감지 된 액터 메모리 저장
		//블랙보드에 데이터를 저장
		Blackboard->SetValueAsObject(TEXT("Target"), Actor);

	}
	else
	{
		//감지 실패
		GEngine->AddOnScreenDebugMessage(1, 10.f, FColor::Red, TEXT("false == Stimulus.WasSuccessfullySensed()"));

		AActor* foundActor = Cast<AActor>(Blackboard->GetValueAsObject(TEXT("Target")));
		if (true == IsValid(foundActor))
		{
			if (foundActor->GetUniqueID() == Actor->GetUniqueID())
				Blackboard->SetValueAsObject(TEXT("Target"), nullptr);

			return;
		}

		//여기서는 감지 못한거니까 감지 된게 있으면 그거 날리고
		Blackboard->SetValueAsObject(TEXT("Target"), nullptr);
	}
}

=> 실행시

플레이어1이 감지당한 후, 같은 태그를 가진 플레이어2가 감지되어도 블랙보드에 등록되지 않는다.

플레이어1이 감지당한 후, 플레이어2가 감지 시야 들어갔다가 시야에서 벗어나도 플레이어1이 블랙보드의 감지타겟으로 등록되어 있다.



 


 

 

3. 블랙보드의 출력 태스크 정상화

 

1) 비헤이비어트리에서 감지에 성공해도 오른쪽 노드만 계속 타는 이유

->Finish Excute를 추가 + Success값을 체크해서 결과가 성공했음을 설정 해줘야 한다. 

 

Event Receive Tick 추가 후 PrintString에 추가하면 매 틱마다 출력하게됨 

 

=> 실행하면 타겟이 생길때마다 로그가 다르게 출력된다



 


 

 

4. 블랙보드 안에서 다른 비헤이비어트리를 실행해보자 

 

1) BT_MonsterTree에서 시퀀스 아래에 print제거하고, Run Behavior 추가 

2) 테스트용 BT_TestTree 비헤이비어 트리 추가 ( ai폴더 - 인공지능 - 비헤이비어 트리 ) 

(1) Selector추가 그아래 Print 추가 - Print선택 후 메세지에 SubTree 작성

 

3) BT_MonsterTree의 RunBehavior 선택 - 디테일 - 비헤이비어에셋 - BT_TestTree선택

=> 실행시 타겟 감지되면 다른 트리가 잘 실행된다.



 


 

5. 몬스터에게 플레이어가 감지되면 이동하도록 수정해보자 

 

1) 시퀀스에서 MoveTo추가

2) MoveTo 선택 후 블랙보드키를 Target으로 설정

 

=> 실행하고 탐지되어도 바로는 안쫓아온다. 네비게이션 매쉬가 안깔려있기 때문이다. 



 


 

 

6. 네비게이션 매쉬 추가

1) 네모플러스아이콘 -> 볼륨-> 네비메시 바운드 볼륨

2) 네미메쉬 선택 후 디테일창 - 브러시세팅으로 크기를 조절해주자.

너무 크게잡으면 베이크 시간이 오래 걸릴 수 있다. 

 

설정해주고, p를 누르면 네비매쉬 크기(연두색)를 볼 수 있음 

 

3) MonsterBase에 MovementComponent 추가해주기

헤더 

UPROPERTY( EditAnywhere, BlueprintReadWrite )
class UFloatingPawnMovement* mMovement;

 

#include "GameFramework/FloatingPawnMovement.h"
AMonsterBase::AMonsterBase()
{
    ...
    mMovement = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("Movement"));
    mMovement->SetUpdatedComponent(RootComponent);
}

생성자에 무브먼트 컴포넌트 추가 

 

=> 실행하면 몬스터가 플레이어 탐지시 따라온다. 하지만 서버-클라 구조에서 클라엔 이동 동기화가 되지 않는다.

 

4) 이동 동기화

orc 이동 동기화가 안되어서

orc 블루프린트 들어가서 모든 컴포넌트 리플리케이트를 활성화함 

해당내용 공식링크   



▶️ 몬스터가 타겟에게 붙는 거리제한두기

MonsterTree 비헤이비어 트리에서 MoveTo 선택 후, 허용 가능 반경 설정해주기




✅ 벽 뒤에 있으면 (시야가 막히면) 감지하지 못한다. 




 

▶️ 엔진에서 제공하는 MovementComponent  링크

 

1. UCharacterMovementComponent

 : 주로 캐릭터 이동 제어. 다양한 이동모드 (걷기, 뛰기, 수영 등) 를 지원하며, 충돌 처리와 네트워크 동기화 기능을 포함.

 - Walking : 캐릭터가 지면을 따라 이동

 - Falling 캐릭터가 중력에 의해 떨어짐

 - Swimming : 캐릭터가 물 속에서 이동

 - Flying 캐릭터가 공중에서 자유롭게 이동

 

2. UPawnMovementComponent

 : 모든 폰(Pawn)의 이동을 관리하는 기본 컴포넌트. 주로 사용자 정의 이동 로직을 구현할 때 사용.

 - AddInputVector() : 폰에 입력 벡터를 추가하여 이동을 제어.

 - ConsumeInputVector() : 입력 벡터를 소비하여 현재 프레임의 이동을 계산

 

3. UFloatingPawnMovement

 : 공중에서 부유하는 폰의 이동을 관리하는 컴포넌트. 주로 간단한 이동 로직이 필요한 폰에 사용

 - Acceleration : 이동 가속도 설정

 - Deceleration : 이동 감속도 설정

 - MaxSpeed : 최대 이동 속도 설정

 

4. UProjectileMovementComponent

 : 발사체(프로젝트 타일)의 이동을 제어하는 컴포넌트. 물리 기반의 이동을 처리. 주로 총알, 로켓 등의 발사체에 사용

 - Velecity : 초기속도

 - bShouldBounce : 발사체가 충돌 시 튕길지 여부 설정

 - Bounciness : 튕김 정도 설정.

 

5. UNavMovementComponent 

 : 네비게이션 시스템을 사용하여 이동을 관리하는 컴포넌트. 주로 AI 컨트롤러가 폰을 네비게이션 메시(nav mesh)를 따라 이동시키는데 사용.

 - MoveToLocation : 지정된 위치로 이동

 - MoveToActor : 지정된 액터를 따라 이동

 

6. UInterpMovementComponent 

: 객체의 위치를 선형보간(Linear Interpolation, Lerp) 하여 이동시키는 컴포넌트. 주로 객체가 경로를 따라 부드럽게 이동해야 하는 상황에 사용

 - ControlPoints : 객체가 이동할 경로를 정의하는 점들의 배열.

 - Duration : 경로를 따라 이동하는데 걸리는 시간

 - bPauseOnImpact : 충돌 시 이동을 멈출지 여부를 설정 

 

7. URotatingMovementComponent

 : 객체를 일정한 속도로 회전시키는 컴포넌트. 주로 꾸준히 회전해야하는 객체 (ex: 풍차날개, 회전하는 플랫폼 등)에 사용

 - RotationRate : 초당 회전 속도 설정 (단위 : 도/초)

 - PivotTranslation : 회전 축의 위치를 설

 


 

 

▶️AIController에서 팀관련 설정 지원 

팀id, 팀id 설정/가져오는 함수 지원

IGenericTeamAgentInterface에서도 team설정 함수 지원 함수 있어서 다중상속 받는 구조로 변경 

 - 사용하기 위해선 프로젝트명.build.cs에 AIModule 추가 필요 

// AIModule 추가 필요 
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "AIModule" });

 

- GetTeamAttitudeTowards()는 하려면 4개 모두 재정의해줘야하니까 패스 

 

1.PlayerControllerMain

-> PlayerController는 TeamId가 없기 때문에 IGenericTeamAgentInterface 상속 및 재정의

-> teamID는 서버에서 감지하도록 설정 

#include "GenericTeamAgentInterface.h"
...
class RESTART0501_API APlayerControllerMain : public APlayerController, public IGenericTeamAgentInterface
{
...
protected:
	UPROPERTY( Replicated )
	uint8 mTeamID;

public:
	virtual void BeginPlay() override ;
	virtual void SetGenericTeamId( const FGenericTeamId& TeamID ) override;
	virtual FGenericTeamId GetGenericTeamId() const override;

	virtual void GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const override;
};
#include "Net/UnrealNetwork.h"

APlayerControllerMain::APlayerControllerMain()
{
	SetGenericTeamId( FGenericTeamId( 0 ) );
}

void APlayerControllerMain::SetGenericTeamId( const FGenericTeamId& TeamID )
{
	mTeamID = TeamID;
}

FGenericTeamId APlayerControllerMain::GetGenericTeamId() const
{
	return mTeamID;
}

void APlayerControllerMain::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const
{
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );

	DOREPLIFETIME( APlayerControllerMain, mTeamID );
}
 

 

AIPawnBase 

class RESTART0501_API AAIPawnBase : public APawn, public IGenericTeamAgentInterface
{
	..
	UPROPERTY( Replicated )
	uint8 mTeamID;

public:	
	...
	virtual void SetGenericTeamId( const FGenericTeamId& TeamID ) override;
	virtual FGenericTeamId GetGenericTeamId() const override;

	virtual void GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const override;

	virtual void PossessedBy( AController* NewController ) override;
};
 
#include "Net/UnrealNetwork.h"
..
void AAIPawnBase::SetGenericTeamId( const FGenericTeamId& TeamID )
{
	mTeamID = TeamID;
}

FGenericTeamId AAIPawnBase::GetGenericTeamId() const
{
	return mTeamID;
}

void AAIPawnBase::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const
{
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );

	DOREPLIFETIME( AAIPawnBase, mTeamID );
}

void AAIPawnBase::PossessedBy( AController * NewController )
{
	Super::PossessedBy( NewController );
}

 

APlayerBase

#include "GenericTeamAgentInterface.h"
...
class RESTART0501_API APlayerBase : public ACharacter, public IGenericTeamAgentInterface
{
...
protected:
...
	UPROPERTY( Replicated )
	uint8 mTeamID;
...
protected:
	...
	virtual void SetGenericTeamId( const FGenericTeamId& TeamID ) override;
	virtual FGenericTeamId GetGenericTeamId() const override;
	virtual void GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const override;

	virtual void PossessedBy( AController* NewController ) override;
};
 
#include "Net/UnrealNetwork.h"
#include "../Controller/PlayerControllerMain.h"
..
void APlayerBase::SetGenericTeamId( const FGenericTeamId & TeamID )
{
	mTeamID = TeamID;
}

FGenericTeamId APlayerBase::GetGenericTeamId() const
{
	return mTeamID;
}

void APlayerBase::GetLifetimeReplicatedProps( TArray<FLifetimeProperty>& OutLifetimeProps ) const
{
	Super::GetLifetimeReplicatedProps( OutLifetimeProps );

	DOREPLIFETIME( APlayerBase, mTeamID );
}

void APlayerBase::PossessedBy( AController * NewController )
{
	Super::PossessedBy( NewController );

	if ( HasAuthority() )
	{
		APlayerControllerMain* controller = Cast<APlayerControllerMain>( NewController );
		if ( false == IsValid( controller ) )
			return;

		SetGenericTeamId( controller->GetGenericTeamId() );
	}
}

 

MonsterBase

..
virtual void PossessedBy( AController* NewController ) override;

 

▶️PossessedBy()

 캐릭터가 새로운 컨트롤러에 의해 소유될 때 필요한 초기화 작업을 처리하는데 사용. 

BeginPlay될 때 호출된다.



728x90

댓글