오늘 플레이어를 향해 AI가 쫓아와 공격을 하는 것 까지 구현하였다.
AIController에 "UAIPerceptionComponent"를 부착해 캐릭터를 시각적으로 인지할 수 있게 하였고, NavMesh를 월드에 배치해 장애물까지 인식하여 플레이어를 향해 움직일 수 있는 경로를 찾아 쫓아오도록 하였다.
공격하는 로직은 아직 구현이 안되어서 일단은 애니메이션 재생하는 것으로 채워놓은 상태이다.
📌 구현 코드
✅ AIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "BaseEnemyAIController.generated.h"
/**
* ABaseEnemyAIController: 적 AI를 제어하는 AI 컨트롤러 클래스
* AI Perception을 사용하여 플레이어를 감지하고, Behavior Tree와 블랙보드를 활용하여 AI의 행동을 결정함.
*/
UCLASS()
class GUNFIREPARAGON_API ABaseEnemyAIController : public AAIController
{
GENERATED_BODY()
public:
ABaseEnemyAIController();
protected:
// AI가 Pawn을 소유할 때 호출되고 BeginPlay보다 먼저 호출된다.
virtual void OnPossess(APawn* InPawn) override;
virtual void BeginPlay() override;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
class UBehaviorTree* BehaviorTree;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AI")
class UBlackboardData* BlackboardData;
// AI Perception Component: AI가 환경을 감지하는 컴포넌트
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class UAIPerceptionComponent* AIPerceptionComponent;
// SightConfig: 시각 인식을 위한 설정
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class UAISenseConfig_Sight* SightConfig;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI")
class UBlackboardComponent* BlackboardComponent;
// Actor -> 감지된 액터, Stimulus -> 감지된 자극(시야, 소리 등)
UFUNCTION()
void OnTargetPerceived(AActor* Actor, FAIStimulus Stimulus);
};
✅ AIController.cpp
#include "BaseEnemyAIController.h"
#include "BaseEnemy.h"
#include "Kismet/GameplayStatics.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BehaviorTree.h"
// AI 컨트롤러의 기본 생성자
ABaseEnemyAIController::ABaseEnemyAIController()
{
// AI Perception 설정
PerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("PerceptionComponent"));
SetPerceptionComponent(*PerceptionComponent);
// 시각 감지 설정
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));
SightConfig->SightRadius = 3000.0f; // AI의 감지 범위
SightConfig->LoseSightRadius = 3500.0f; // AI가 플레이어를 잃어버리는 거리
SightConfig->PeripheralVisionAngleDegrees = 100.0f; // 시야각 설정
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
// Perception 컴포넌트 설정
PerceptionComponent->ConfigureSense(*SightConfig);
PerceptionComponent->SetDominantSense(*SightConfig->GetSenseImplementation());
PerceptionComponent->OnTargetPerceptionUpdated.AddDynamic(this, &ABaseEnemyAIController::OnTargetPerceived);
// 기본 변수 초기화
BehaviorTree = nullptr;
BlackboardData = nullptr;
BlackboardComponent = nullptr;
}
// AI가 Pawn을 소유할 때 호출
void ABaseEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (BlackboardData)
{
if (UseBlackboard(BlackboardData, BlackboardComponent))
{
BlackboardComponent = GetBlackboardComponent();
BlackboardComponent->SetValueAsVector("SpawnLocation", InPawn->GetActorLocation());
}
}
if (BehaviorTree)
{
RunBehaviorTree(BehaviorTree);
}
}
void ABaseEnemyAIController::BeginPlay()
{
Super::BeginPlay();
}
// AI가 감지한 객체의 정보를 처리하는 함수
void ABaseEnemyAIController::OnTargetPerceived(AActor* Actor, FAIStimulus Stimulus)
{
if (!BlackboardComponent) return;
if (!Actor || !Actor->ActorHasTag("Player")) return;
if (Stimulus.WasSuccessfullySensed())
{
// 감지된 플레이어를 블랙보드에 저장하고 초점을 맞춤
BlackboardComponent->SetValueAsObject("TargetPlayer", Actor);
SetFocus(Actor);
}
else
{
// 플레이어가 사라지면 블랙보드에서 제거하고 초점을 해제
BlackboardComponent->ClearValue("TargetPlayer");
ClearFocus(EAIFocusPriority::Gameplay);
}
}
📌 Behavior Tree / Blackboard / BTService
플레이어가 시야에 들어오게 된다면 Blackboard의 TargetPlayer에 값이 저장되고, TargetPlayer에 값이 있다면 그 대상에게로 이동하도록 하였다.
그리고 추가로 BTService(Tick과 같이 지속적으로 실행되면서 Blackboard의 값을 업데이트 하는 역할하는 서비스 노드)를 만들어서 지속적으로 플레이어와의 거리 계산을 해준다. Interval을 직접 설정할 수 있어서 Tick처럼 매 프레임 호출되는 것이 아니라서 성능에 부담이 적다.
계산된 거리가 200 이하라면 아주 잠깐 기다렸다가 공격하는 애니메이션이 나오도록 하였다.
✅ BTService_UpdateDistance.cpp
#include "BTService_UpdateDistance.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BaseEnemyAIController.h"
UBTService_UpdateDistance::UBTService_UpdateDistance()
{
NodeName = TEXT("Update Distance to Player");
Interval = 0.3f;
}
void UBTService_UpdateDistance::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
UBlackboardComponent* BlackboardComponent = OwnerComp.GetBlackboardComponent();
if (!BlackboardComponent) return;
AActor* TargetPlayer = Cast<AActor>(BlackboardComponent->GetValueAsObject("TargetPlayer"));
AAIController* AIController = OwnerComp.GetAIOwner();
if (!TargetPlayer || !AIController) return;
float Distance = FVector::Dist(AIController->GetPawn()->GetActorLocation(), TargetPlayer->GetActorLocation());
BlackboardComponent->SetValueAsFloat("DistanceToPlayer", Distance);
}
📌 오류 상황 / 새로 알게된 점
🔎 NavMesh 오류
AIController와 Behavior Tree까지 완성한 시점에 첫 플레이를 해봤을 때,
AI가 플레이어를 인식하고 돌아보긴 하는데 플레이어를 향해 움직이지 않았다.. 계속 뭐가 잘못된건지 도통 알 수가 없어서 한참 동안 코드 이곳 저곳 뒤져보다 로그를 확인하고 아차 싶었다.
플레이어 스타트 위치가 처음 NavMesh위에 올라와있지 않아서 AI가 경로를 제대로 인식하지 못해서 다가오지 못한 것으로 확인되었다. 아직 Behavior Tree가 완성된게 아니라서 그런지 현 시점으로써는 NavMesh 위에서 플레이어가 시작되어야하는 것으로 보여진다.
🔎 AIPerception Stimuli Source 세팅
AI특강 시간에 알게된 내용인데 AIController에서 AIPerceptionComponent로 플레이어를 감지하려면
PlayerCharacter에 AIPerception Stimuli Source를 추가하고, 클릭해 Details 창에서 어떤 감각으로 인지되게 할 것인지 설정해주어야 정상적으로 감지가 된다.
현재 설정은 시각으로 플레이어를 인지할 수 있도록 설정한 것.
🔔 현재까지 진행상황
행동을 더 세분화해서 발견 ➡️ 추적 ➡️ 공격 or 놓쳤다면 주변을 수색하다 다시 원 위치로 가도록 해야될 것 같다.
'UE_FPS 슈터 게임 팀프로젝트 > 일반 AI' 카테고리의 다른 글
AIController와 BT 설계 (0) | 2025.03.06 |
---|---|
일반 AI_공격 구현 (0) | 2025.03.04 |
일반 AI 공격 Test/ UWorld::SweepSingleByChannel (0) | 2025.02.20 |