서의 공간
AIController에서 BehaviorTreeComponent 본문
AI Blackboard 생성 관련 질문있어요 : 네이버 카페 (naver.com)
위 글을 먼저 참고하여 두 번째 경우에서 조금 다른 경우를 살펴보자.
BehaviorTree를 컨트롤러가 아닌 캐릭터에 설정하는 방식이다.
Enemy 클래스(.h)에 다음과 같이 변수를 정의하고, 블루프린트에서 BehaviorTree 애셋을 설정할 것이다.
/** Behavior tree for the AI Character*/
UPROPERTY(EditAnywhere, Category = "Behavior Tree", meta = (AllowPrivateAccess = "true"))
class UBehaviorTree* BehaviorTree;
다음은 AEnemy 클래스에서 BeginPlay 함수 부분이다.
void AEnemy::BeginPlay()
{
...
// Get the AI Controller
EnemyController = Cast<AEnemyController>(GetController());
...
if (EnemyController)
{
...
EnemyController->RunBehaviorTree(BehaviorTree);
...
}
}
다음은 이어지는 RunBehaviorTree 함수 코드이다.
bool AAIController::RunBehaviorTree(UBehaviorTree* BTAsset)
{
...
if (bSuccess)
{
// 이 부분을 주목한다.
UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(BrainComponent);
// BTComp가 NULL이면 UBehaviorTreeComponent 인스턴스를 새롭게 생성한다.
if (BTComp == NULL)
{
UE_VLOG(this, LogBehaviorTree, Log, TEXT("RunBehaviorTree: spawning BehaviorTreeComponent.."));
BTComp = NewObject<UBehaviorTreeComponent>(this, TEXT("BTComponent"));
BTComp->RegisterComponent();
}
// 앞서 BrainComponent는 NULL일 수도 있고, 아닐 수도 있다.
// 새로 만든 BTComp는 기존 BrainComponent의 값과 같거나, 새롭게 할당한 값이다.
// make sure BrainComponent points at the newly created BT component
BrainComponent = BTComp;
check(BTComp != NULL);
BTComp->StartTree(*BTAsset, EBTExecutionMode::Looped);
}
return bSuccess;
}
호출 순서를 확인해본다. 컨트롤러와 캐릭터 사이에서 초기 함수 호출 순서는 다음과 같다.
EnemyController::OnPossess -> EnemyController::PostInitializeComponents -> EnemyController::BeginPlay -> Enemy::BeginPlay
다음 코드 OnPossess에서는 캐릭터에 설정된 BehaviorTree 애셋이 결정되지 않으므로, BehaviorTreeComponent의 애셋은 아직 없다. BlackboardComponent->InitializeBlackboard(..) 함수를 통해 BlackboardComponent는 초기화된다.
void AEnemyController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (InPawn == nullptr) return;
AEnemy* Enemy = Cast<AEnemy>(InPawn);
if (Enemy)
{
if (Enemy->GetBehaviorTree())
{
BlackboardComponent->InitializeBlackboard(*(Enemy->GetBehaviorTree()->BlackboardAsset));
}
}
}
그 다음 EnemyController::PostInitializeComponents 호출이 되는데, 다음은 부모 클래스의 구현부분이다. 주석 부분을 보자.
void AAIController::PostInitializeComponents()
{
Super::PostInitializeComponents();
...
// 이 부분을 주목. BrainComponent가 nullptr이라면, FindComponentByClass를 통해
// Component를 생성하고 초기화한다.
if (BrainComponent == nullptr)
{
BrainComponent = FindComponentByClass<UBrainComponent>();
}
if (Blackboard == nullptr)
{
Blackboard = FindComponentByClass<UBlackboardComponent>();
}
...
}
이어서 FindComponentByClass 함수를 살펴보자.
// 다음은 AIController.cpp의 일부
/** Templatized version of FindComponentByClass that handles casting for you */
template<class T>
T* FindComponentByClass() const
{
static_assert(TPointerIsConvertibleFromTo<T, const UActorComponent>::Value, "'T' template parameter to FindComponentByClass must be derived from UActorComponent");
return (T*)FindComponentByClass(T::StaticClass());
}
// 다음은 Actor.cpp의 일부
UActorComponent* AActor::FindComponentByClass(const TSubclassOf<UActorComponent> ComponentClass) const
{
UActorComponent* FoundComponent = nullptr;
if (UClass* TargetClass = ComponentClass.Get())
{
// 현재 actor가 가지고 있는 모든 컴포넌트는 OwnedComponents에 저장되어있다.
for (UActorComponent* Component : OwnedComponents)
{
if (Component && Component->IsA(TargetClass))
{
FoundComponent = Component;
break;
}
}
}
return FoundComponent;
}
결론:
BrainComponent는 컨트롤러 클래스에 정의된 BehaviorTreeComponent와 같은 주소를 참조한다. 이 부분은 AAIController::PostInitializeComponent에서 확인할 수 있다. 만약 컨트롤러 클래스에 BehaviorTreeComponent가 없다면, 새로 컴포넌트를 생성한다. 이 과정은 RunBehaviorTree 함수에서 확인 가능하다.
최종적으로 BehaviorTreeComponent는 캐릭터에 설정된 BehaviorTree 애셋을 통해 StartTree를 실행한다.
'Unreal Engine' 카테고리의 다른 글
컨트롤러 방향 (2) | 2021.12.16 |
---|---|
Set Timer 사용법 (0) | 2021.12.12 |
속도, MakeFromX, MakeRotFromX (0) | 2021.11.09 |
Pawn (0) | 2021.11.09 |
[UMG] Drag & Drop (0) | 2021.10.29 |