AEnemyAIController::AEnemyAIController()
{
BehaviorTreeComponent = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("BehaviorTreeComponent"));
BlackboardComponent = CreateDefaultSubobject<UBlackboardComponent>(TEXT("BlackboardComponent"));
}
void AEnemyAIController::BeginPlay()
{
Super::BeginPlay();
if (BehaviorTree && BehaviorTree->BlackboardAsset)
{
UseBlackboard(BehaviorTree->BlackboardAsset, BlackboardComponent);
RunBehaviorTree(BehaviorTree); // 비헤비어 트리 컴포넌트가 실행됨
}
}
// 보통 AIController에서 다음과 같이 설정합니다:
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("Sight Config"));
SightConfig->SightRadius = 1000.0f;
SightConfig->LoseSightRadius = 1200.0f;
SightConfig->PeripheralVisionAngleDegrees = 90.0f;
SightConfig->SetMaxAge(5.0f);
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
GetPerceptionComponent()->ConfigureSense(*SightConfig);
GetPerceptionComponent()->SetDominantSense(SightConfig->GetSenseImplementation());
UAISenseConfig_Sight는 "눈을 어떻게 사용할 것인지"에 대한 매뉴얼UAIPerceptionComponent는 그 매뉴얼을 보고 실제로 눈을 사용하는 AI의 두뇌에디터에서 C++ 클래스를 만든다. (None)
아래와 같이 DataTable에 들어갈 Row(행)을 구조체로 만든다
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataTable.h" // FTableRowBase 정의되어 있음
#include "MonsterSightData.generated.h"
USTRUCT(BlueprintType)
struct FMonsterSightData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SightRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float LoseSightRadius;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float PeripheralVisionAngle;
};
사용하고 싶은 클래스에 UDataTable* TableName 이런식으로 만들어서 에디터에서 할당시킬 수 있도록 Reflection 시킨다.
자식클래스에서도 사용이 가능하도록 하고 싶으면 DefaultOnly로 설정하는 것이 바람직하다.
아래와 같이 코드를 작성
// MonsterAIController.h
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Sight.h"
#include "MonsterSightData.h"
#include "MonsterAIController.generated.h"
UCLASS()
class AMonsterAIController : public AAIController
{
GENERATED_BODY()
public:
AMonsterAIController();
protected:
virtual void BeginPlay() override;
void LoadSightDataFromTable();
protected:
UPROPERTY(EditDefaultsOnly, Category = "AI")
UDataTable* SightDataTable;
UPROPERTY(EditDefaultsOnly, Category = "AI")
FName MonsterId;
UPROPERTY()
UAIPerceptionComponent* PerceptionComponent;
UPROPERTY()
UAISenseConfig_Sight* SightConfig;
};
// MonsterAIController.cpp
#include "MonsterAIController.h"
AMonsterAIController::AMonsterAIController()
{
PerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("PerceptionComponent"));
SetPerceptionComponent(*PerceptionComponent);
SightConfig = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SightConfig"));
SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectFriendlies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
PerceptionComponent->ConfigureSense(*SightConfig);
PerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
}
void AMonsterAIController::BeginPlay()
{
Super::BeginPlay();
LoadSightDataFromTable();
}
void AMonsterAIController::LoadSightDataFromTable()
{
if (SightDataTable == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("SightDataTable is not assigned!"));
return;
}
const FMonsterSightData* SightRow = SightDataTable->FindRow<FMonsterSightData>(MonsterId, TEXT("MonsterSight"));
if (SightRow && SightConfig)
{
SightConfig->SightRadius = SightRow->SightRadius;
SightConfig->LoseSightRadius = SightRow->LoseSightRadius;
SightConfig->PeripheralVisionAngleDegrees = SightRow->PeripheralVisionAngleDegrees;
SightConfig->SetMaxAge(SightRow->SenseInterval);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("No matching row found for MonsterId: %s"), *MonsterId.ToString());
}
}
자식클래스에서 DataTable 사용 예시
// TentacleAIController.h
#pragma once
#include "CoreMinimal.h"
#include "MonsterAIController.h"
#include "TentacleAIController.generated.h"
UCLASS()
class ATentacleAIController : public AMonsterAIController
{
GENERATED_BODY()
public:
ATentacleAIController();
};
// TentacleAIController.cpp
#include "TentacleAIController.h"
ATentacleAIController::ATentacleAIController()
{
MonsterId = FName("Tentacle");
}
UBehaviorTreeComponent, UBlackboardComponent는 ActorComponent지만 SceneComponent는 아니다.SetPerceptionComponent(*AIPerceptionComponent);StimuliSource 등록 등 기능이 동작하지 않을 가능성이 있다.단순히 Tag나 이름으로 되는게 아니라, FAISenseAffiliationFilter는 TeamID 기반으로 판단
GetPerceptionComponent()->SetSenseImplementation(UAISense_Sight::StaticClass());
// 예: AIController에서 팀 ID 설정
SetGenericTeamId(FGenericTeamId(1)); // 예
PlayerCharacter->SetGenericTeamId(FGenericTeamId(0));
관계 판별 로직
ETeamAttitude::Type AAIController::GetTeamAttitudeTowards(const AActor& Other) const
이 함수는 AI와 대상 Actor의 TeamID를 비교해서 어떤 관계인지 자동으로 리턴함.
if (IsValid(BehaviorTree))
{
UseBlackboard(BehaviorTree->BlackboardAsset, (UBlackboardComponent*&)BlackboardComponent);
RunBehaviorTree(BehaviorTree);
LOG(TEXT("AIController Possess"));
}
}
UBlackboardComponent*& 즉, “UBlackboardComponent에 대한 포인터 참조를 요구한다. 따라서 직접적 호환이 안됌