PART 6 - 강의 3/3

BT와 Smart Object 통합

FindAndUseSmartObject Task, GameplayBehavior 연동, 실전 BT 패턴을 구현합니다

01

BTTask_FindAndUseSmartObject

BT에서 Smart Object를 자동으로 검색하고 사용

BTTask_FindAndUseSmartObject는 UE5가 제공하는 빌트인 BT Task로, Smart Object 검색, Claim, 이동, Use를 한 번에 처리합니다. 가장 간편한 통합 방법입니다.

C++// BTTask_FindAndUseSmartObject 설정 (BT 에디터) // Task: FindAndUseSmartObject // - ActivityRequirements: "Activity.Sit" // - Radius: 2000 // - (선택) BB Key: 결과 저장 // 내부 실행 흐름: // 1. 주변 Smart Object 검색 (ActivityTag 기반) // 2. 가장 적합한 Slot Claim // 3. Slot 위치로 AI 이동 // 4. 도착 시 Use 실행 (GameplayBehavior) // 5. Behavior 완료 시 Release → Task 성공 // BT 구조 예시: // Selector // ├─ Sequence [전투] // │ ├─ Decorator: HasTarget // │ └─ Task: AttackTarget // ├─ Sequence [휴식] // │ ├─ Decorator: IsEnergyLow // │ └─ Task: FindAndUseSmartObject // │ Activity: "Activity.Sit" // │ Radius: 3000 // └─ Sequence [순찰] // └─ Task: Patrol // 실패 조건: // - 범위 내 적합한 Smart Object 없음 // - 모든 Slot이 이미 Claimed/Occupied // - 이동 실패 (경로 없음) // - GameplayBehavior 실행 실패

FindAndUseSmartObject가 실패하면 BT가 자동으로 다음 브랜치로 전환합니다. Selector 아래에 배치하면, Smart Object를 찾지 못할 경우 폴백 행동(순찰 등)으로 자연스럽게 전환됩니다.

02

커스텀 BT Task로 Smart Object 사용

세밀한 제어를 위한 커스텀 구현

FindAndUseSmartObject보다 세밀한 제어가 필요할 때, 커스텀 BT Task를 만들어 Find/Claim/Use를 분리 제어할 수 있습니다. 이동 중 조건 변경 시 Claim 해제 등의 복잡한 시나리오에 유용합니다.

C++// 커스텀 Task: Smart Object 검색 + Claim UCLASS() class UBTTask_ClaimSmartObject : public UBTTaskNode { GENERATED_BODY() public: virtual EBTNodeResult::Type ExecuteTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override { USmartObjectSubsystem* SOSub = USmartObjectSubsystem::GetCurrent(GetWorld()); AAIController* Controller = OwnerComp.GetAIOwner(); APawn* Pawn = Controller->GetPawn(); // 검색 FSmartObjectRequest Request; Request.Filter.ActivityRequirements.RequiredTags = ActivityTags; Request.QueryBox = FBox( Pawn->GetActorLocation() - FVector(SearchRadius), Pawn->GetActorLocation() + FVector(SearchRadius)); TArray<FSmartObjectRequestResult> Results; SOSub->FindSmartObjects(Request, Results); if (Results.Num() == 0) return EBTNodeResult::Failed; // Claim FSmartObjectClaimHandle Handle = SOSub->Claim(Results[0].SmartObjectHandle, Results[0].SlotHandle); if (!Handle.IsValid()) return EBTNodeResult::Failed; // BB에 ClaimHandle과 Slot 위치 저장 UBlackboardComponent* BB = OwnerComp.GetBlackboardComponent(); FVector SlotLoc; FRotator SlotRot; SOSub->GetSlotLocation(Handle, SlotLoc, SlotRot); BB->SetValueAsVector(SlotLocationKey.SelectedKeyName, SlotLoc); // ClaimHandle은 별도 관리 필요 return EBTNodeResult::Succeeded; } virtual EBTNodeResult::Type AbortTask( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override { // 중단 시 Claim 해제 ReleaseClaim(); return EBTNodeResult::Aborted; } protected: UPROPERTY(EditAnywhere) FGameplayTagContainer ActivityTags; UPROPERTY(EditAnywhere) float SearchRadius = 2000.f; UPROPERTY(EditAnywhere) FBlackboardKeySelector SlotLocationKey; }; // BT 구조: // Sequence // ├─ Task: ClaimSmartObject → BB에 위치 저장 // ├─ Task: MoveTo(BB: SlotLocation) // ├─ Task: UseSmartObject → Claim을 Use로 전환 // └─ Task: WaitForBehavior → 행동 완료 대기
주의

Find/Claim/MoveTo/Use를 분리하면 AbortTask()에서 반드시 Claim을 해제해야 합니다. Observer Aborts로 이동 중 브랜치가 전환될 때 Claim이 남아있으면 다른 AI도 사용 불가합니다.

03

EQS와 Smart Object 연동

EQS로 최적의 Smart Object 선택

EQS Generator를 커스텀하여 Smart Object Slot을 후보 아이템으로 사용하면, 거리/시야/경로 비용 등 복합적인 기준으로 최적의 Smart Object를 선택할 수 있습니다.

C++// EQS Generator: Smart Object Slots UCLASS() class UEnvQueryGenerator_SmartObjectSlots : public UEnvQueryGenerator { GENERATED_BODY() public: virtual void GenerateItems( FEnvQueryInstance& QueryInstance) const override { USmartObjectSubsystem* SOSub = USmartObjectSubsystem::GetCurrent( QueryInstance.World); TArray<FVector> ContextLocations; QueryInstance.PrepareContext( UEnvQueryContext_Querier::StaticClass(), ContextLocations); for (const FVector& Center : ContextLocations) { FSmartObjectRequest Request; Request.Filter.ActivityRequirements.RequiredTags = RequiredTags; Request.QueryBox = FBox( Center - FVector(SearchRadius), Center + FVector(SearchRadius)); TArray<FSmartObjectRequestResult> Results; SOSub->FindSmartObjects(Request, Results); for (const auto& Result : Results) { FVector SlotLoc; SOSub->GetSlotLocation( Result.SlotHandle, SlotLoc); // 위치 아이템으로 추가 QueryInstance.AddItemData< UEnvQueryItemType_Point>(SlotLoc); } } } protected: UPROPERTY(EditAnywhere) FGameplayTagContainer RequiredTags; UPROPERTY(EditAnywhere) float SearchRadius = 3000.f; }; // EQS 쿼리 구성: // Generator: SmartObjectSlots (Activity: "Activity.Sit") // Test 1: Distance to Enemy → Score(Linear) // → 적에게서 먼 Smart Object 선호 // Test 2: Trace to Enemy → Filter(실패만) // → 적 시야에서 차단된 위치 // Test 3: Pathfinding → Filter(PathExist) // → 도달 가능한 위치만

EQS + Smart Object 조합은 "안전한 의자에 앉기", "적에게서 숨을 수 있는 엄폐물 사용" 등 전술적 환경 상호작용에 매우 강력합니다.

04

실전 Smart Object 패턴

마을 NPC, 상점 주인, 경비병 AI

Smart Object를 활용한 실전 AI 패턴을 살펴봅니다. 마을 NPC의 일상 행동, 상점 주인의 자리 지키기, 경비병의 초소 배치 등 다양한 시나리오에 적용할 수 있습니다.

C++// 패턴 1: 마을 NPC 일상 BT // Selector // ├─ Sequence [위험 회피] // │ ├─ Decorator: IsThreatNearby // │ └─ Task: Flee // ├─ Sequence [식사 시간] // │ ├─ Decorator: IsHungry AND IsMealTime // │ └─ Task: FindAndUseSmartObject // │ Activity: "Activity.Eat" // │ Radius: 5000 // ├─ Sequence [작업] // │ ├─ Decorator: IsWorkTime // │ └─ Task: FindAndUseSmartObject // │ Activity: "Activity.Work" // │ Radius: 3000 // └─ Sequence [여가] // └─ Task: FindAndUseSmartObject // Activity: "Activity.Social" // Radius: 4000 // 패턴 2: 경비병 초소 BT // Selector // ├─ Sequence [전투] // │ ├─ Decorator: HasTarget (ObserverAborts: LowerPriority) // │ └─ Task: CombatBehavior // ├─ Sequence [초소 복귀] // │ ├─ Decorator: NOT IsAtGuardPost // │ └─ Task: FindAndUseSmartObject // │ Activity: "Activity.Guard" // │ Radius: 100 (자기 초소만) // └─ Sequence [초소 대기] // └─ Task: FindAndUseSmartObject // Activity: "Activity.Guard.Idle" // 패턴 3: 일정 시간 사용 후 해제 // Service에서 타이머 관리 void UBTService_SmartObjectTimer::TickNode( UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) { FSmartObjectTimerMemory* Memory = reinterpret_cast<FSmartObjectTimerMemory*>(NodeMemory); Memory->ElapsedTime += DeltaSeconds; if (Memory->ElapsedTime > MaxUseTime) { // 사용 시간 초과 → Release SOSubsystem->Release(ClaimHandle); // BB 초기화 → BT가 다음 행동 선택 BB->ClearValue(TEXT("CurrentSmartObject")); } }
설계

마을 NPC의 일상을 구현할 때, 시간대별 Activity Tag를 변경하면 됩니다. 아침에는 "Activity.Work", 점심에는 "Activity.Eat", 저녁에는 "Activity.Social"을 검색하도록 Service에서 BB를 업데이트하세요.

SUMMARY

핵심 요약

  • BTTask_FindAndUseSmartObject는 검색/Claim/이동/Use를 한 번에 처리하는 가장 간편한 통합 방법이다
  • 커스텀 BT Task로 Find/Claim/Use를 분리하면 세밀한 제어가 가능하지만, AbortTask에서 Claim 해제가 필수이다
  • EQS Generator로 Smart Object Slot을 후보에 포함하면 전술적 환경 상호작용이 가능하다
  • 시간대별 Activity Tag 전환으로 NPC 일상 행동 패턴을 자연스럽게 구현할 수 있다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: BT에서 Smart Object 사용

BTTask_FindAndUseSmartObject 태스크를 BT에 배치하세요. 순찰 중 주변 Smart Object(의자, 관찰 포인트)를 찾아 사용하고, 완료 후 순찰을 재개하는 행동 패턴을 구현합니다.

실습 2: EQS와 Smart Object 연동

EQS 쿼리에서 Smart Object 위치를 Generator로 사용하세요. 거리와 슬롯 가용성을 기준으로 최적의 Smart Object를 선택하는 쿼리를 구현합니다.

심화 과제

시간대별 스케줄(아침: 식사 Smart Object, 낮: 작업 Smart Object, 밤: 수면 Smart Object)을 기반으로 NPC가 자연스러운 일상을 수행하는 시스템을 BT + Smart Object로 구축하세요.