BossFight - 적 AI Controller 작성

김대겸·2025년 4월 23일

적 AI Controller 작성

이번에느 EnemyCharacter에 적용할 AIController를 작성 해보자 또한 AIController에 Perception을 추가하고 적용 해보자.

🎮 AEnemyAIController.h

class UAIPerceptionComponent;
class UAISenseConfig_Sight;

UCLASS()
class BOSSFIGHT_API AEnemyAIController : public AAIController
{
	GENERATED_BODY()
	
public:
	AEnemyAIController();

protected:
	virtual void BeginPlay() override;

	UFUNCTION()
	virtual void OnEnemyPerceptionUpdate(AActor* Actor, FAIStimulus Stimulus);

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	UAIPerceptionComponent* EnemyPerceptionComponent;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
	UAISenseConfig_Sight* AISenseConfig_Sight;

};

🎮 AEnemyAIController.cpp

AEnemyAIController::AEnemyAIController()
{
	AISenseConfig_Sight = CreateDefaultSubobject<UAISenseConfig_Sight>("EnemySenseConfig_Sight");
	AISenseConfig_Sight->DetectionByAffiliation.bDetectEnemies = true;
	AISenseConfig_Sight->DetectionByAffiliation.bDetectFriendlies = false;
	AISenseConfig_Sight->DetectionByAffiliation.bDetectNeutrals = false;
	AISenseConfig_Sight->SightRadius = 5000.0f;
	AISenseConfig_Sight->LoseSightRadius = 0.0f;
	AISenseConfig_Sight->PeripheralVisionAngleDegrees = 360.0f;

	EnemyPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>("EnemyPerceptionComponent");
	EnemyPerceptionComponent->ConfigureSense(*AISenseConfig_Sight);
	EnemyPerceptionComponent->SetDominantSense(UAISenseConfig_Sight::StaticClass());

	EnemyPerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &ThisClass::OnEnemyPerceptionUpdate);
}

void AEnemyAIController::BeginPlay()
{
	Super::BeginPlay();
}

void AEnemyAIController::OnEnemyPerceptionUpdate(AActor* Actor, FAIStimulus Stimulus)
{
	//TODO 플레이어 인식
}

위와 같이 UAIPerceptionComponent을 정의 하고 필요한 정보를 기입 해주었다. 이를통해 플레이어를 인식 시킬려고 한다.

그러나 지금은 플레이어를 인식 하지 않는데, 왜냐하면 귀속 탐지 옵션이 현재는 "적대적" 인 객체만 인식 하는데, 플레이어가 현재 적대적인지 아닌지 확인하지 못하기 때문이다.

위의 문제를 수정하기위해 FGenericTeamId를 활용해서 AIController 및 PlayerController에 GenericTeamId를 적용 시켜 주자

🎮 AEnemyAIController.h

UCLASS()
class BOSSFIGHT_API AEnemyAIController : public AAIController
{
	GENERATED_BODY()

public:
	AEnemyAIController();
    //GetTeamAttitudeTowards() 함수 추가
	virtual ETeamAttitude::Type GetTeamAttitudeTowards(const AActor& Other) const override; 
// 중략
};

🎮 AEnemyAIController.cpp

ETeamAttitude::Type AEnemyAIController::GetTeamAttitudeTowards(const AActor& Other) const
{
	const APawn* PawnToCheck = Cast<const APawn>(&Other);
	const IGenericTeamAgentInterface* OtherTeamAgent = Cast<const IGenericTeamAgentInterface>(PawnToCheck->GetController());

	if (OtherTeamAgent && OtherTeamAgent->GetGenericTeamId() < GetGenericTeamId())
	{
		return ETeamAttitude::Hostile;
	}
	return ETeamAttitude::Friendly;
}

GetTeamAttitudeTowards()함수를 통해 AIController를 소유한 Pawn이 다른 Pawn과 적대적인지 확인 할 수 있게 되었다. 허나 이 기능을 사용하기 위에서는 PlayerController에도 GenericTeamId를 설정 해주어야 한다.

🎮 ABFPlayerController.h

UCLASS()
class BOSSFIGHT_API ABFPlayerController : public APlayerController, public IGenericTeamAgentInterface
{
	GENERATED_BODY()

public:
	ABFPlayerController();

	//~Begin IGenericTeamAgentInterface
	virtual FGenericTeamId GetGenericTeamId() const override;
	//~End IGenericTeamAgentInterface

private:
	FGenericTeamId TeamID;
};

🎮 ABFPlayerController.cpp

ABFPlayerController::ABFPlayerController()
{
	TeamID = FGenericTeamId(0);
}

FGenericTeamId ABFPlayerController::GetGenericTeamId() const
{
	return TeamID;
}

PlayerController에 TeamID를 추가하고 생성자에서FGenericTeamId(0)로 초기화, GetGenericTeamId()함수에서 TeamID 를 반환 하도록 구현 하였다.

작동 영상

위와같이 Perception이 플레이어를 감지 할수 있게 되었다.

다음으로 블랙보드와 비헤이비어 트리를 작성 하고, OnEnemyPerceptionUpdate()함수 에서 감지한 플레이어를 블랙보드에 저장 하도록 하자.

🎮 AEnemyAIController.cpp

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

	if (IsValid(BehaviorTree))
	{
		RunBehaviorTree(BehaviorTree);
	}
}

void AEnemyAIController::OnEnemyPerceptionUpdate(AActor* Actor, FAIStimulus Stimulus)
{
	if (UBlackboardComponent* BlackboardComponent = GetBlackboardComponent())
	{
		if (!BlackboardComponent->GetValueAsObject(TargetActor))
		{
			if (Stimulus.WasSuccessfullySensed() && Actor)
			{
				BlackboardComponent->SetValueAsObject(TargetActor, Actor);
			}
		}
	}
}

OnPossess()함수를 오버라이드 하여 비헤이비어 트리를 작동 시키고, OnEnemyPerceptionUpdate를통해 Perception이 업데이트가 되면, 블랙보드의TargetActor값을 확인, nullptr이면 감지된 Actor를 TargetActor에 저장하도록 하였다.

다음으로 AIState를 애님인스턴스에서 블랙보드로 변경 해주도록 하자

위 사진 처럼 열거형으로 키값을 추가하고, 열겨형 이름을 EBFEnemyAIState로 지정 해주자.

다음으로 AEnemyAIController에서 AIState를 반환 하는 함수를 작성 해주자

🎮 AEnemyAIController.cpp

EBFEnemyAIState AEnemyAIController::GetAIState()
{
	if (UBlackboardComponent* BlackboardComponent = GetBlackboardComponent())
	{
		return EBFEnemyAIState(GetBlackboardComponent()->GetValueAsEnum(AIState));
	}
	else
	{
		EBFEnemyAIState::None;
	}
}

그 다음 애님인스턴스에서 AIController를 통해 AIState를 받아오도록 하자.

🎮 UBFEnemyAnimInstance.cpp

void UBFEnemyAnimInstance::NativeThreadSafeUpdateAnimation(float DeltaSeconds)
{
	Super::NativeThreadSafeUpdateAnimation(DeltaSeconds);

	if (OwningEnemyCharacter)
	{
		bIsStrafing = (OwningAIController->GetAIState() == EBFEnemyAIState::Strafing);
	}
}

앞으로 AIState를 통해 비헤이비어 트리를 제어 하면 될 듯 하다

비헤이비어 트리에 MoveTo노드를 추가하여, 인식된 플레이어를 추적 하도록 간단하게 구현해보고 태스트 해보자.

플레이어를 감지하고 잘 추적하는 것을 확인 하였다.

다음에는 플레이어가 무기를 장착 하였을때, 필요한 스탯을 적용 하고 UI를 출력 하도록 해보겠다.

0개의 댓글