본문 바로가기
Unreal Engine5/KDT 2024

[UE5 KDT2024] 37. 팀세팅 2편

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

▶️ 팀설정 함수 확인하기

AAIPawnBase 에서 상속받은 IGenericTeamAgentInterface 타고 FGenericTeamId 타고 들어가면 

팀설정 함수 확인 가능.

  • TFunction : 언리얼에서 사용하는 함수 포인터 같은 아이 
#include "GenericTeamAgentInterface.h"
..
class RESTART0501_API AAIPawnBase : public APawn, public IGenericTeamAgentInterface

 

 


 

1. 팀 나누기 

 

1) 팀간 관계 설정

AGameModeMain 에서

#include "GenericTeamAgentInterface.h"
...
private:
// 각 팀별로 서로 적인지, 아군인지 구분
// 첫번째 키, 두번째 키 넣으면 둘의 관계를 값으로 가짐
TMap<uint8, TMap<uint8, ETeamAttitude::Type>> TeamAttitudes;

 

▶️FindOrAdd() 

: 제공한 Key에 연관된 값으로 레퍼런스를 반환한다.

Key가 TMap 컨테이너에 존재하지 않는 경우, 새로 생성된 엘리먼트에 Key와 기본 생성된 값을 반환하고, TMap 컨테이너에 추가한다. 

TeamAttitudes.FindOrAdd( 0 ).Add( 1, ETeamAttitude::Type::Friendly );

=> 0번팀을 찾고 없으면 만들어서 반환하는데, 0번팀의 1번팀과의 관계는 친구 



AGameModeKDT::InitGame() 에서 팀 설정을 해준다

  • 굳이 양방향일 필요는 없다 ( 몹 - 펫 )
  • Neutral 중립  

// 0번팀은 1번팀을 적으로 간주

void AGameModeMain::InitGame( const FString& MapName, const FString& Options, FString& ErrorMessage )
{
	Super::InitGame( MapName, Options, ErrorMessage );

	TeamAttitudes.FindOrAdd( 0 ).Add( 1, ETeamAttitude::Hostile );
	TeamAttitudes.FindOrAdd( 0 ).Add( 2, ETeamAttitude::Friendly );

	TeamAttitudes.FindOrAdd( 1 ).Add( 0, ETeamAttitude::Hostile );
	TeamAttitudes.FindOrAdd( 1 ).Add( 2, ETeamAttitude::Neutral );

	TeamAttitudes.FindOrAdd( 2 ).Add( 0, ETeamAttitude::Friendly );
	TeamAttitudes.FindOrAdd( 2 ).Add( 1, ETeamAttitude::Hostile );

	// 팀설정
	FGenericTeamId::SetAttitudeSolver(
		[this] ( FGenericTeamId TeamA, FGenericTeamId TeamB )->ETeamAttitude::Type
		{
			uint8 teamA_ID = TeamA.GetId();
			uint8 teamB_ID = TeamB.GetId();

			if ( TeamAttitudes.Find( teamA_ID ) == nullptr )
				return ETeamAttitude::Neutral;

			if( TeamAttitudes[teamA_ID].Find( teamB_ID ) == nullptr )
				return ETeamAttitude::Neutral;

			return ( ETeamAttitude::Type)(* TeamAttitudes[teamA_ID].Find( teamB_ID ) );
		} );
}

 

▶️ 람다식

익명함수, 함수객체 라고도 불림.

3가지로 이루어짐 : 캡쳐절, 매개변수 목록, 반환값 

캡쳐절 : [this], 

매개변수 목록 : ( FGenericTeamId TeamA, FGenericTeamId TeamB )

반환값 : ->ETeamAttitude::Type

캡쳐절 

[& ] : 람다식 위에있는것(ex: 지역변수 등)을 참조로 가져오겠다

[= ] : 값을 복사하겠다.

반환값이 없을땐 -> 이후 비워두면 됨. 



위에 처럼 안하고 함수 호출해도됨 

ETeamAttitude::Type AGameModeKDT::TeamAttitude( FGenericTeamId TeamA, FGenericTeamId TeamB )
{
    uint8 teamA_ID = TeamA.GetId();
    uint8 teamB_ID = TeamB.GetId();

    if ( TeamAttitudes.Find( teamA_ID ) == nullptr )
    return ETeamAttitude::Neutral;

    if ( TeamAttitudes[teamA_ID].Find( teamB_ID ) == nullptr )
    return ETeamAttitude::Neutral;

    return ( ETeamAttitude::Type )( *TeamAttitudes[teamA_ID].Find( teamB_ID ) );
}

 

2) 팀 설정 

MonsterAIController. 생성자에서 teamId 1로 변경

SetGenericTeamId(FGenericTeamId(1));

PlayerControllerMain 생성자에서 teamId 0으로 변경 

SetGenericTeamId(FGenericTeamId(0));



3) 확인용

void AMonsterAIController::OnTargetFound( AActor* Actor, FAIStimulus Stimulus )
{
    ETeamAttitude::Type attitude = GetTeamAttitudeTowards( *Actor ); // 브레이크포인트 추가 
    …
}

=> 움직이는 캐릭터들(PlayerBase, MonsterBase) 클래스에

IGenericTeamAgentInterface 다중상속,

mTeamId 프로퍼티 추가 

SetGenericTeamId(), GetLifetimeReplicatedProps(),  PossessedBy() 함수들 설정 해주고

빠진거 추가하고 

실행하면 1팀(몬)이 0팀(나)을 바라보는 관계가 정상적으로 나오는걸 알 수 있음 



 


 

 

2. 팀설정 TPair방식으로 맵은  TPair사용해서 사용 해보기

 

TMap<TPair<uint8, uint8>, ETeamAttitude::Type> TeamAttitudes2; // 이런식으로도 사용 가능

 

AGameModeKDT::InitGame() 에서  팀 설정 

TeamAttitudes2.Add( TPair<uint8, uint8>( 0, 1 ), ETeamAttitude::Hostile );
TeamAttitudes2.Add( TPair<uint8, uint8>( 0, 2 ), ETeamAttitude::Friendly );
TeamAttitudes2.Add( TPair<uint8, uint8>( 1, 0 ), ETeamAttitude::Hostile );
TeamAttitudes2.Add( TPair<uint8, uint8>( 1, 2 ), ETeamAttitude::Friendly );
TeamAttitudes2.Add( TPair<uint8, uint8>( 2, 0 ), ETeamAttitude::Friendly );
TeamAttitudes2.Add( TPair<uint8, uint8>( 2, 1 ), ETeamAttitude::Hostile );

 

FGenericTeamId::SetAttitudeSolver(
    [this] ( FGenericTeamId TeamA, FGenericTeamId TeamB )->ETeamAttitude::Type
    {
        uint8 teamA_ID = TeamA.GetId();
        uint8 teamB_ID = TeamB.GetId();

        TPair<uint8, uint8> key (teamA_ID, teamB_ID );
        if( !TeamAttitudes2.Find(key) )
        return ETeamAttitude::Neutral;

        return ( ETeamAttitude::Type)(*TeamAttitudes2.Find( key ));
});



타겟 찾았을 경우 팀 에티튜드에 따라 분기처리 

void AMonsterAIController::OnTargetFound( AActor* Actor, FAIStimulus Stimulus )
{
	ETeamAttitude::Type attitude = GetTeamAttitudeTowards( *Actor );

	// 감지성공
	if ( Stimulus.WasSuccessfullySensed() )
	{
		GEngine->AddOnScreenDebugMessage( 1, 10.f, FColor::Red, L"true" );

		if( attitude == ETeamAttitude::Hostile || attitude == ETeamAttitude::Neutral )
		{	
			// 기존 타겟과 비교
			AActor* foundActor = Cast<AActor>( Blackboard->GetValueAsObject( L"Target" ) );

			// 같은 플레이어 태그를 가졌으면 타겟 바꾸지 않기 
			if ( IsValid( foundActor ) )
			{
				if ( foundActor->ActorHasTag( L"Player" ) && Actor->ActorHasTag( L"Player" ) )
					return;
			}

			Blackboard->SetValueAsObject( L"Target", Actor );
		}		
	}
	else
	{
		GEngine->AddOnScreenDebugMessage( 1, 10.f, FColor::Red, L"false" );

		if ( attitude == ETeamAttitude::Hostile || attitude == ETeamAttitude::Neutral )
		{
			AActor* foundActor = Cast<AActor>( Blackboard->GetValueAsObject( L"Target" ) );
			if ( IsValid( foundActor ) )
			{
				if ( foundActor->GetUniqueID() == Actor->GetUniqueID() )
					Blackboard->SetValueAsObject( L"Target", nullptr );

				return;
			}

			Blackboard->SetValueAsObject( L"Target", nullptr );
		}
	}
}

 

 


 

 

3. 이동 태스크 c++처리 

 

1) moveto 상속받아서 클래스 하나 만들기

Task폴더에 BTTask_Main_MoveTo_Trace생성

 

2)

생성자 추가 

UBTTask_MoveTo에서 ExecuteTask(), AbortTask(), OnTaskFinished(), 

UBTTaskNode에서 TickTask() 가져와서 오버라이딩

class RESTART0501_API UBTTask_Main_MoveTo_Trace : public UBTTask_MoveTo
{
	GENERATED_BODY()
	
public :
	UBTTask_Main_MoveTo_Trace();

public:
	// 해당 테스크 노드가 실행될 때 해야할 일 구현 
	virtual EBTNodeResult::Type ExecuteTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory ) override;
	// 해당 테스크 노드가 중단 되었을 때 해야할 일 구현
	virtual EBTNodeResult::Type AbortTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory ) override;
	virtual void OnTaskFinished( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTNodeResult::Type TaskResult ) override;
	virtual void TickTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds ) override;
};

// 생성자에서 노드 이름 설정

UBTTask_Main_MoveTo_Trace::UBTTask_Main_MoveTo_Trace()
{
	NodeName = L"TRACE_TARGET";
}

EBTNodeResult::Type UBTTask_Main_MoveTo_Trace::ExecuteTask( UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory )
{
	EBTNodeResult::Type retVal = Super::ExecuteTask( OwnerComp, NodeMemory );
	return retVal;
}

EBTNodeResult::Type UBTTask_Main_MoveTo_Trace::AbortTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory )
{
	EBTNodeResult::Type retVal = Super::AbortTask( OwnerComp, NodeMemory );

	return retVal;
}

void UBTTask_Main_MoveTo_Trace::OnTaskFinished( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTNodeResult::Type TaskResult )
{
	Super::OnTaskFinished( OwnerComp, NodeMemory, TaskResult );
}

void UBTTask_Main_MoveTo_Trace::TickTask( UBehaviorTreeComponent & OwnerComp, uint8 * NodeMemory, float DeltaSeconds )
{
	Super::TickTask( OwnerComp, NodeMemory, DeltaSeconds );
}

 

 

3) BT_MonsterTree 에디터로 이동 

태스크 추가 시도 시, 방금 추가한 클래스명이 Main Move to Trace로 출력된다.

 

(1) 이전 Moveto를 제거하고, 방금만든 트레이스를 추가하자

(2) 생성 후, 디테일창에서 블랙보드키를 Target으로 변경해주자 

 

=> 실행하면 시야에 들어왔을 때 몬스터가 타겟 플레이어에게 잘 이동한다.



728x90

댓글