Project_P 행동트리 모델 AI Perception
블랙보드의 타겟정보를 업데이트 하기위해 언리얼에서 제공해주는 AI Perception기능을 사용했다
AI Perception은 AI(NPC)가 액터나 다른 Pawn을 인식할 수 있는 여러 감각들을 제공해준다.
AIPerception은 시각, 듣기 등 여러 감각이 있어 이를 활용하였다
공식문서 : https://dev.epicgames.com/documentation/en-us/unreal-engine/ai-perception-in-unreal-engine
AI Perception 시각으로 블랙보드의 타겟변수를 설정 및 해지를 할것이다
APPAIController 헤더파일
AI Perception을 사용하기 위해 AIController에 AIPerceptionComponent, UAISenseConfig를 생성을 해야된다
#include "CoreMinimal.h"
#include "AIController.h"
#include "Perception/AIPerceptionTypes.h"
#include "PPAIController.generated.h"
/**
*
*/
UCLASS()
class PROJECT_P_API APPAIController : public AAIController
{
//AI Section
protected:
UFUNCTION()
virtual void ActorPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
UFUNCTION()
virtual void ActorPerceptionForgetUpdated(AActor* Actor);
virtual void PerceptionSensedSight(APawn* Pawn_);
virtual void PerceptionSensedHearing(APawn* Pawn_);
virtual void PerceptionSensedDamage(APawn* Pawn_);
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAIPerceptionComponent> AIPerceptionComp;
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Sight> SenseConfig_Sight;
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Hearing> SenseConfig_Hearing;
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Damage> SenseConfig_Damage;
UPROPERTY(VisibleAnywhere, Category = "Data")
TObjectPtr<class UPPGruntAIData> GruntAIData;
};
- UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAIPerceptionComponent> AIPerceptionComp;- AI 인식 컴포넌트
- UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Sight> SenseConfig_Sight;- AI 시야 Config
- UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Hearing> SenseConfig_Hearing;- AI 청각 Config
- UPROPERTY(VisibleAnywhere)
TObjectPtr<class UAISenseConfig_Damage> SenseConfig_Damage;- AI 데미지 Config
- UPROPERTY(VisibleAnywhere, Category = "Data")
TObjectPtr<class UPPGruntAIData> GruntAIData;- AI Config파일의 변수들을 초기화할 데이터 애셋
- 데이터 애셋을 만들어 생성자에서 이를 활용해 Config를 초기화
- UFUNCTION()
virtual void ActorPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
UFUNCTION()
virtual void ActorPerceptionForgetUpdated(AActor* Actor);- AIPerception에 따라 실행되는 델리게이트에 등록할 콜백함수들
Cpp 파일
생성자
APPAIController::APPAIController()
{
static ConstructorHelpers::FObjectFinder<UPPGruntAIData> AIDataRef(TEXT("/Script/Project_P.PPGruntAIData'/Game/Project_P/Data/GruntAIData.GruntAIData'"));
if (AIDataRef.Object)
{
GruntAIData = AIDataRef.Object;
}
// AI Perception 설정
AIPerceptionComp = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerceptionComp"));
SetPerceptionComponent(*AIPerceptionComp);
// Sight Config
SenseConfig_Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SenseConfig_Sight"));
SenseConfig_Sight->SightRadius = GruntAIData->SightRadius;
SenseConfig_Sight->LoseSightRadius = GruntAIData->LoseSightRadius;
SenseConfig_Sight->PeripheralVisionAngleDegrees = GruntAIData->AIVisionAngleDeg;
//탐지하면 몇초(age)동안 탐지를 유지할지
SenseConfig_Sight->SetMaxAge(GruntAIData->AISenseAge);
//마지막으로 감지된 객체의 위치 탐지 성공 - 시야 범위를 벗어난 상태에서도 객체를 일정 거리 이내에서 추적할 수 있다.
SenseConfig_Sight->AutoSuccessRangeFromLastSeenLocation = GruntAIData->AutoSuccessRange;
SenseConfig_Sight->DetectionByAffiliation.bDetectEnemies = true;
SenseConfig_Sight->DetectionByAffiliation.bDetectFriendlies = true;
SenseConfig_Sight->DetectionByAffiliation.bDetectNeutrals = true;
AIPerceptionComp->ConfigureSense(*SenseConfig_Sight);
AIPerceptionComp->SetDominantSense(SenseConfig_Sight->GetSenseImplementation());
// Hearing Config
SenseConfig_Hearing = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("SenseConfig_Hearing"));
SenseConfig_Hearing->HearingRange = GruntAIData->HearingRange;
SenseConfig_Hearing->SetMaxAge(GruntAIData->AISenseAge);
SenseConfig_Hearing->DetectionByAffiliation.bDetectEnemies = true;
SenseConfig_Hearing->DetectionByAffiliation.bDetectFriendlies = true;
SenseConfig_Hearing->DetectionByAffiliation.bDetectNeutrals = true;
AIPerceptionComp->ConfigureSense(*SenseConfig_Hearing);
// Damage Config
SenseConfig_Damage = CreateDefaultSubobject<UAISenseConfig_Damage>(TEXT("SenseConfig_Damage"));
AIPerceptionComp->OnTargetPerceptionUpdated.AddDynamic(this, &APPAIController::ActorPerceptionUpdated);
AIPerceptionComp->OnTargetPerceptionForgotten.AddDynamic(this, &APPAIController::ActorPerceptionForgetUpdated);
}
AISenseConfig 관련 설정들은 생성자에서 초기화 되기때문에 다른 함수들(ex : BeginPlay, PostInitializeComponents)에서 설정을 해도 적용되지 않음
- static ConstructorHelpers::FObjectFinder<UBlackboardData> BlackboardRef(TEXT("~~~'"));
if (BlackboardRef.Object)
{
BBAsset = BlackboardRef.Object;
}- 데이터애셋 가져오기
- AIPerceptionComp = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerceptionComp"));
SetPerceptionComponent(*AIPerceptionComp);- AIPerception 생성 및 등록
- SenseConfig_Sight = CreateDefaultSubobject<UAISenseConfig_Sight>(TEXT("SenseConfig_Sight"));
- 시야 Config 생성
- SenseConfig_Sight->SightRadius = GruntAIData->SightRadius;
SenseConfig_Sight->LoseSightRadius = GruntAIData->LoseSightRadius;
SenseConfig_Sight->PeripheralVisionAngleDegrees = GruntAIData->AIVisionAngleDeg;- 각각 시야 반경, 시야에서 나갈 때 반경, 시야각 설정
- SenseConfig_Sight->SetMaxAge(GruntAIData->AISenseAge);
- 시야에서 나가면 데이터를 몇초 유지할 것인지 설정
- SenseConfig_Sight->AutoSuccessRangeFromLastSeenLocation = GruntAIData->AutoSuccessRange;
- 위치 탐지 성공하고 시야 범위를 벗어난 상태에서도 객체를 설정한 거리 이내에서 추적할 수 있다.
- SenseConfig_Sight->DetectionByAffiliation.bDetectEnemies = true;
SenseConfig_Sight->DetectionByAffiliation.bDetectFriendlies = true;
SenseConfig_Sight->DetectionByAffiliation.bDetectNeutrals = true;- AI가 적, 아군, 중립을 감지할지 설정
- GenericTeamAgentInterface를 활용해 사용하는 것 같음
- 액터를 인터페이스를 통해 지정하지 않으면 중립(Neutrals)
https://forums.unrealengine.com/t/ai-perception-mark-player-as-enemy/354383
AI Perception Mark player as enemy
How do I mark the player as an enemy for my AI so that they will sense him when using “Detection by Affiliation: Detect Enemies”? Also see this old topic: https://answers.unrealengine.com/questions/285849/aiperception-how-to-use-detection-by-affiliatio
forums.unrealengine.com
- AIPerceptionComp->ConfigureSense(*SenseConfig_Sight);
- PerceptionComp에 설정한 시각 Configure을 등록
- AIPerceptionComp->SetDominantSense(SenseConfig_Sight->GetSenseImplementation());
- 시야를 PerceptionComp의 메인감각으로 지정
- SenseConfig_Hearing = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("SenseConfig_Hearing"));
- 청각 Config 생성
- SenseConfig_Hearing->HearingRange = GruntAIData->HearingRange;
SenseConfig_Hearing->SetMaxAge(GruntAIData->AISenseAge);- 청각 범위, 정보 유효 시간 설정
- SenseConfig_Hearing->DetectionByAffiliation.bDetectEnemies = true;
SenseConfig_Hearing->DetectionByAffiliation.bDetectFriendlies = true;
SenseConfig_Hearing->DetectionByAffiliation.bDetectNeutrals = true;- 감지 설정
- AIPerceptionComp->ConfigureSense(*SenseConfig_Hearing);
- PerceptionComp에 청각 등록
- AIPerceptionComp->OnTargetPerceptionUpdated.
AddDynamic(this, &APPAIController::ActorPerceptionUpdated);
AIPerceptionComp->OnTargetPerceptionForgotten.
AddDynamic(this, &APPAIController::ActorPerceptionForgetUpdated);- PerceptionComp의 감각 업데이트, 감각 잊힘 델리게이트에 콜백함수 등록
ActorPerceptionUpdated
void APPAIController::ActorPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
APawn* PerceptionedPawn = Cast<APawn>(Actor);
if (PerceptionedPawn && PerceptionedPawn->GetController()->IsPlayerController())
{
TSubclassOf<UAISense> SensedStimulsClass = UAIPerceptionSystem::GetSenseClassForStimulus(this, Stimulus);
if (SensedStimulsClass == UAISense_Sight::StaticClass())
{
PerceptionSensedSight(PerceptionedPawn);
}
if (SensedStimulsClass == UAISense_Hearing::StaticClass())
{
PerceptionSensedHearing(PerceptionedPawn);
}
}
}
OnTargetPerceptionUpdated 델리게이트에 등록된 콜백함수
해당 함수에서 감각에 대한 처리를 해줘야 처리한 감각이 디버깅 화면에 드로우 된다
- APawn* PerceptionedPawn = Cast<APawn>(Actor);
- 감각으로 들어온 액터를 Pawn으로 캐스팅
- if (PerceptionedPawn && PerceptionedPawn->GetController()->IsPlayerController())
- Pawn이 캐스팅 되었고 해당폰이 플레이어라면(플레이어 컨트롤러가 있으면)
- TSubclassOf<UAISense> SensedStimulsClass =
UAIPerceptionSystem::GetSenseClassForStimulus(this, Stimulus);- UAIPerceptionSystem의 GetSenseClassForStimulus함수로 FAIStimulus의 감각 클래스를 얻을 수 있다
UAISense 서브클래스로 감각을 받아온다
- UAIPerceptionSystem의 GetSenseClassForStimulus함수로 FAIStimulus의 감각 클래스를 얻을 수 있다
- if (SensedStimulsClass == UAISense_Sight::StaticClass())
{
PerceptionSensedSight(PerceptionedPawn);
}
if (SensedStimulsClass == UAISense_Hearing::StaticClass())
{
PerceptionSensedHearing(PerceptionedPawn);
}- 감각별로 미리 생성한 함수로 처리를 해준다
ActorPerceptionForgetUpdated
void APPAIController::ActorPerceptionForgetUpdated(AActor* Actor)
{
APawn* PerceptionedPawn = Cast<APawn>(Actor);
if (PerceptionedPawn && PerceptionedPawn->GetController()->IsPlayerController())
{
UE_LOG(LogTemp, Log, TEXT("ActorPerceptionForgetUpdated : %s"), *Actor->GetName());
APawn* Target = Cast<APawn>(GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
if (PerceptionedPawn == Target)
{
GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, nullptr);
AActor::SetActorTickEnabled(false);
}
}
}
- APawn* PerceptionedPawn = Cast<APawn>(Actor);
- Pawn으로 캐스팅
- if (PerceptionedPawn && PerceptionedPawn->GetController()->IsPlayerController())
- 캐스팅 확인 및 플레이어 확인
- APawn* Target = Cast<APawn>(GetBlackboardComponent()->GetValueAsObject(BBKEY_TARGET));
- 블랙보드 타겟 가져오기
- if (PerceptionedPawn == Target)
- 타겟이 잊혀진 폰이랑 같으면
- GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, nullptr);
- 블랙보드 초기화
PerceptionSensedSight
void APPAIController::PerceptionSensedSight(APawn* PerceptionedPawn)
{
UE_LOG(LogTemp, Log, TEXT("ActorPerceptionUpdated : %s"), *PerceptionedPawn->GetName());
UAbilitySystemComponent* ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(PerceptionedPawn);
if (ASC)
{
GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, PerceptionedPawn);
AActor::SetActorTickEnabled(true);
}
}
시각처리 함수
- UAbilitySystemComponent* ASC =
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(PerceptionedPawn);- ASC 가져오기
- GetBlackboardComponent()->SetValueAsObject(BBKEY_TARGET, PerceptionedPawn);
- 타겟 설정
추가적으로
청각처리 함수는 지금은 단순 로그만 나오게 구현함
시각함수는 Config만 설정하여 등록해주면 자동으로 감지하지만 청각, 데미지 감지는 따로 스테이틱 함수를 실행해야 됨
ex) 청각 리포트 함수 UAISense_Hearing::ReportNoiseEvent