// © 2025 Naked People Team. All Rights Reserved. #include "NPC.h" #include "BrainComponent.h" #include "NPCTypeDefinition.h" #include "NPCAIController.h" #include "NPCTargetLocation.h" #include "BehaviorTree/BlackboardComponent.h" #include "Components/SkeletalMeshComponent.h" #include "GameFramework/CharacterMovementComponent.h" ANPC::ANPC() { PrimaryActorTick.bCanEverTick = false; GetCharacterMovement()->MaxWalkSpeed = 200.0f; GetCharacterMovement()->bUseControllerDesiredRotation = true; GetCharacterMovement()->bOrientRotationToMovement = false; bUseControllerRotationYaw = false; AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned; // Crowd anim hygiene (mesh-agnostic): only evaluate the pose while the NPC is on screen and // throttle the eval rate by distance. CharacterMovement still ticks off-screen so pooled-in // NPCs can keep walking; only the visible pose freezes when unrendered. if (USkeletalMeshComponent* MeshComp = GetMesh()) { MeshComp->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered; MeshComp->bEnableUpdateRateOptimizations = true; } } ENPCType ANPC::GetNPCType() const { return NPCTypeDefinition ? NPCTypeDefinition->Type : ENPCType::Walker; } float ANPC::GetObservationWeight() const { return NPCTypeDefinition ? NPCTypeDefinition->ObservationWeight : 1.0f; } bool ANPC::ShouldStopToObserve() const { return NPCTypeDefinition ? NPCTypeDefinition->bStopsToObserve : false; } float ANPC::GetObserveDuration() const { return NPCTypeDefinition ? NPCTypeDefinition->ObserveDurationSeconds : 0.0f; } void ANPC::ActivateFromPool(const FVector& Location, const FRotator& Rotation) { SetActorLocationAndRotation(Location, Rotation, false, nullptr, ETeleportType::TeleportPhysics); SetActorHiddenInGame(false); SetActorEnableCollision(true); if (UCharacterMovementComponent* Move = GetCharacterMovement()) { Move->SetComponentTickEnabled(true); Move->SetMovementMode(MOVE_Walking); } if (ANPCAIController* NPCController = Cast(GetController())) { NPCController->RunCustomBehaviorTree(); NPCController->GetBlackboardComponent()->SetValueAsBool(TEXT("bHasReacted"), false); } } void ANPC::DeactivateToPool() { // Drop any active observation so a pooled-out NPC stops contributing to the player's embarrassment. if (ANPCAIController* NPCController = Cast(GetController())) { NPCController->ClearObservation(); if (UBrainComponent* Brain = NPCController->GetBrainComponent()) { Brain->StopLogic(TEXT("Moved to pool")); } NPCController->ClearFocus(EAIFocusPriority::Default); } if (UCharacterMovementComponent* Move = GetCharacterMovement()) { Move->StopMovementImmediately(); Move->DisableMovement(); Move->SetComponentTickEnabled(false); } if (TargetLocationActor) { TargetLocationActor->Release(this); } SetActorHiddenInGame(true); SetActorEnableCollision(false); }