Unreal AIController 제작
이번에 BehaviorTree에 쓰일 AIContoroller(AIC)를 직접 제작해보았다.
UPawnSensingComponent에서 UAIPerceptionComponent로 변경
UPawnSensingComponent: 간단한 AI 감지 시스템에 필요한 경우 사용된다.
UAIPerceptionComponent: PawnSensingComponent에서 제공하는 시각, 청각 감지 기능뿐만 아니라 손상 등 다양한 감지 기능을 지원한다.
이렇기에 이후에 더 확장성이 높은 UAIPerceptionComponent로 바꾸었다.
AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AI Perception"));
AIC의 생성자에 다음과 같이 추가했다.
OnPossess
위 도큐먼트를 들어가보면 OnPossess의 매개변수인 InPawn은 AI Controller를 소유하고 있는 Pawn이 들어온다.
Enemy AIC의 OnPossess를 간단하게 아래와 같이 작성했다.
void ABaseEnemyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
UE_LOG(LogTemp, Display, TEXT("OnPossess Call"));
Enemy = Cast<AEnemy>(InPawn);
if (!Enemy)
{
UE_LOG(LogTemp, Display, TEXT("AI Controller Cast Fail"));
return;
}
Enemy->OnEnemyDeath.AddDynamic(this, &ABaseEnemyAIController::OnEnemyDied);
if (AIPerceptionComponent)
{
AIPerceptionComponent->OnPerceptionUpdated.AddDynamic(this, &ABaseEnemyAIController::OnPerceptionUpdated);
}
BlackboardComponent = Enemy->GetBlackboardComponent();
if (!BlackboardComponent)
{
UE_LOG(LogTemp, Display, TEXT("Blackboard Component cann't find"));
return;
}
BlackboardAsset = Enemy->GetBlackboardComponent()->GetBlackboardAsset();
if(!BlackboardAsset){
UE_LOG(LogTemp, Display, TEXT("BlackboardAsset cann't find"));
}
//UseBlackboard를 사용하지 않고 RunBehavior를 사용하면 BlackboardKey에 값이 들어가지 않음.
//RunBehaviorTree 전, 후 상관없이 사용해야 정상적으로 값이 들어간다.
UseBlackboard(BlackboardAsset, BlackboardComponent);
RunBehaviorTree(Enemy->GetBehaviorTree());
SetEnemyState(EEnemyState::EES_Passive);
FName AttackRadiusKeyName = TEXT("AttackRadius");
FName DefendRadiusKeyName = TEXT("DefendRadius");
BlackboardComponent->SetValueAsFloat(AttackRadiusKeyName, Enemy->GetAttackRadius());
BlackboardComponent->SetValueAsFloat(DefendRadiusKeyName, Enemy->GetDefendRadius());
}
위 코드에서 중요한 점이 UseBlackboard이다.
여기서 작성된 AIC는 BlackboardKey를 사용하는데 위치가 중요하다.
RunBehavior의 전 후는 상관 없지만 가장 하단에 있는
BlackboardComponent->SetValueAs~~~와 같은 BlackboardKey값을 정해줄 때 보다는 먼저 선언이 되어있어야
Blackboard에 값이 들어간다.
처음 AIC를 작성하는 것이라서 UseBlackboard의 존재를 모르고 SetValue만 사용했다가 많이 헤맸다.
OnPerceptionUpdated
void ABaseEnemyAIController::OnPerceptionUpdated(const TArray<AActor*>& UpdatedActors)
{
//UE_LOG(LogTemp, Display, TEXT("OnPerceptionUpdated called. Updated Actors count: %d"), UpdatedActors.Num());
for (AActor* Actor : UpdatedActors)
{
GetPerceptionInfo(Actor);
}
}
OnPerceptionUpdated에 대해서 설명하려면 좀 길다.
AIPerceptionComponent에 있는 ProcessStimuli함수가 매 프레임(Tick과 비슷한 형식으로)마다 실행이 된다.
이때 새로운 자극(Stimuli)이 들어오면 OnTargetPerceptionComponent가 실행된다.
void ABaseEnemyAIController::OnTargetPerceptionUpdated(AActor* Actor, FActorPerceptionUpdateInfo PerceptionInfo)
{
if (PerceptionInfo.Stimulus.WasSuccessfullySensed())
{
UE_LOG(LogTemp, Display, TEXT("Actor %s was sensed using sense %d"), *Actor->GetName(), PerceptionInfo.Stimulus.Type);
}
else
{
UE_LOG(LogTemp, Display, TEXT("Actor %s was no longer sensed using sense %d"), *Actor->GetName(), PerceptionInfo.Stimulus.Type);
}
}
예시로 이렇게 생겼다.
매개변수로 AActor*와 어떤 자극이 들어왔는지(FActorPerceptionUpdateInfo)가 들어온다.
FActorPerceptionUpdateInfo는 어떠한 자극(시각, 청각, 손상)을 나타낸다.
그렇다면 자극이 총 5가지가 들어왔다고 가정을 하면 OnTargetPerception은 5번 호출이 된다는 의미이다.
당연하겠지만 선언되어있지 않다면 실행하지 않는다.
그리고 나서 OnPerceptionUpdated가 실행된다. OnTargerPerception이 있던 없던 자극을 한번에 TArray에 모아서 매개변수로 전달된다.
여기서도
FActorPerceptionBlueprintInfo PerceptionInfo;
AIPerceptionComponent->GetActorsPerception(Actor, PerceptionInfo);
와 같이 개별적으로 어떤 자극을 통해 인식되었는지 확인 가능하다. 또한
WasSuccessfullySensed()
해당 함수를 통해서 자극이 현재 감지된 상태인지 아닌지 확인할 수 있다.
위에 말이 좀 이상한 것처럼 보일 수 있다.
OnPerceptionUpdated는 자극을 TArray에 모아서 매개변수로 전달하는 것인데 왜 다시 감지된 것인지 확인을 하지?
왜냐면 자극이 활성화 되었다가 다시 사라질 수 있기 때문이다. 간단하게 AI가 감지했지만 지금은 못하는 상황인 것이다.
예를 들어 Sight(시각)로 A를 감지하고 있다가 A가 AI의 시야에서 벗어나면 위 함수의 실행 결과는 false가 나오게 된다.
위와 같이 자극을 모아서 한번에 처리하는 것이 OnPerceptionUpdated다.
OnTargetPerceptionUpdated은 그럼 왜 쓰냐?
OnTargetPerceptionUpdated는 즉시 실행되는 콜백함수이기 때문이다.
감각 자극이 발생할 때 즉각적으로 AI가 반응을 할 수 있도록 해준다. 하지만 OnPerceptionUpdated의 GetActorsPerception은 자극 정보를 수동으로 요청하는 방법이기에 AI가 주기적으로 상태를 확인해야하기 때문에 OnTargetPerceptionUpdated보단 반응 속도가 떨어질 수 있다.
혹시 AIController를 더 만들다가 추가적으로 공부하거나 작성해야할 내용이 있다면 작성해야겠다.