[Unreal] Node Memory
BT를 사용하는 인스턴스의 데이터 캐싱
- AI 캐릭터들이 BT를 사용하면서, 인스턴스 별로 가져야 할 데이터가 있다
- Target과의 거리를 기록해두어야 한다든가..
- Timer를 재기 위해 체크하는 현재 남은 시간이라든가..
- 즉, 캐싱(Cache)을 해야 하는 데이터가 있을 것이다
Member 변수로 저장해도 되는가?
UCLASS()
class MYGAME_API UBTTask_MyCustomTask : public UBTTaskNode
{
GENERATED_BODY()
public:
// ...
private:
// ...
// 남은 시간
float ElapsedTimer = 0;
}
EBTNodeResult::Type UBTTask_MyCustomTask::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
// ...
// 이렇게 해도 되나??
ElapsedTimer -= DeltaSeconds;
// ...
return EBTNodeResult::InProgress;
}
- BT를 사용하는 Instance가 ElapsedTimer를 체크하고 특정 행동을 해야한다고 하자
- 위와 같이 Task의 멤버변수로 두고 관리하는 게 맞을까?
- 당연히 안된다
- 여러 AI가 같은 BTTask 인스턴스를 공유하기 때문
- member 변수 값이 덮어씌울 수 있다
- 이로써 다른 AI의 행동에 의도치 않은 영향을 미칠 수 있다
올바른 방법은?
Blackboard
- Blackboard의 데이터는 객체 별로 관리되므로 안전한 방법이다
- 단, 캐싱해야 하는 데이터가 너무 많은 경우에는 관리가 힘들어질 수 있다
Pawn/Controller
- AI Controller나 Controlled Pawn에 필요한 변수들을 선언하고 접근할 수도 있다
- 데이터가 원래 해당 클래스에 있다면 상관없겠지만, 상관없는 데이터를 넣어둔다면 구조적으로 별로다
NodeMemory 사용
UCLASS()
class MYGAME_API UBTTask_MyCustomTask : public UBTTaskNode
{
GENERATED_BODY()
public:
// NodeMemory 구조체 정의
struct FMyTaskMemory
{
AActor* TargetActor;
float StartTime;
bool bIsInitialized;
FMyTaskMemory()
{
TargetActor = nullptr;
StartTime = 0.0f;
bIsInitialized = false;
}
};
virtual uint16 GetInstanceMemorySize() const override;
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
};
// BTTask_MyCustomTask.cpp
uint16 UBTTask_MyCustomTask::GetInstanceMemorySize() const
{
return sizeof(FMyTaskMemory);
}
EBTNodeResult::Type UBTTask_MyCustomTask::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
FMyTaskMemory* MyMemory = reinterpret_cast<FMyTaskMemory*>(NodeMemory);
if (!MyMemory->bIsInitialized)
{
// 초기화 로직
MyMemory->TargetActor = /* 타겟 찾기 로직 */;
MyMemory->StartTime = GetWorld()->GetTimeSeconds();
MyMemory->bIsInitialized = true;
}
// 메모리의 데이터 사용
if (MyMemory->TargetActor)
{
// 작업 수행
}
return EBTNodeResult::InProgress;
}
- C++을 사용할 수 있다면 이것이 가장 일반적이고 안전한 방법이다
- Blueprint로는 사용할 수 없는 듯하다
- BT의 Task, Decorator, Service 등 여러 타입에 대해서 NodeMemory를 인스턴스 별로 관리할 수 있다
- 복잡도에 따라 위의 3가지 중 1가지 방법을 선택해 사용하면 된다고 하는데, Blueprint로만 작업한느 환경이 아니라면 기능 의존성을 고려해 NodeMemory 기능을 구현하는 것이 가장 적절해 보인다