Advanced AI plugin with a ready to use perception system, runtime control of Behavior Trees and State Trees from a single component, token based NPC task prioritization, and example tasks for both Behavior Tree and State Tree.
- Token Based prioritization - Flexible token system for controlling execution orders for NPCs.
- Advanced Patrol Spline - Per actor patrol data with automatic direction switching.
- Single Component AI Management - Configure Perception, Behavior Tree, State Tree, Patrol data, etc. all from one component.
- Flexible Perception System - Adjust Sight, Hearing, and Damage sensing directly in the component and receive stimuli without extra setup.
- Example Tasks - Some simple example tasks for Behavior and State Tree.
This guide will help you set up and use the HAIPro Plugin.
To use the HAIController for your NPCs:
- Open the NPC Blueprint.
- In the Class Defaults, locate the AI Controller Class property.
- Select HAIController from the dropdown list.
- Save the Blueprint.
- You can assign your patrol point into the component
- Assign your Behavior or State Tree into the component and if you want you can trigger them to start at Begin Play
- You can enable or disable perception and also you can decide to dominant sense and sense's values
- !!! Both the giver (e.g., NPC, player, or object) and the receiver (e.g., NPC or another entity) must have the HToken System Component.
Script Example(AI Controller)
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "StateTree/HStateTreeAIComponent.h"
#include "Navigation/CrowdFollowingComponent.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Damage.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Sight.h"
#include "Perception/AIPerceptionTypes.h"
#include "HAIController.generated.h"
class UHAIBaseComponent;
/** for Filtering the perception types */
UENUM(BlueprintType)
enum class E_SenseType : uint8
{
NoneType UMETA(DisplayName = "NoneType"),
SightConfig UMETA(DisplayName = "SightConfig"),
HearingConfig UMETA(DisplayName = "HearingConfig"),
DamageConfig UMETA(DisplayName = "DamageConfig")
};
/** for Adjusting the BehaviorTree and StateTree */
UENUM(BlueprintType)
enum class E_TreeAdjustments : uint8
{
StartTree UMETA(DisplayName = "StartTree"),
PauseTree UMETA(DisplayName = "PauseTree"),
ResumeTree UMETA(DisplayName = "ResumeTree"),
StopTree UMETA(DisplayName = "StopTree"),
RestartTree UMETA(DisplayName = "RestartTree"),
DestroyTree UMETA(DisplayName = "DestroyTree")
};
/** Struct for storing sense data */
USTRUCT(BlueprintType)
struct F_SenseData
{
GENERATED_BODY()
F_SenseData() : sensedActor(nullptr), senseType(E_SenseType::NoneType), stimulus() {}
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HAI|Perception")
TWeakObjectPtr<AActor> sensedActor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HAI|Perception")
E_SenseType senseType;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HAI|Perception")
FAIStimulus stimulus;
};
/** Delegate for updating percaption data */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPeraptionConfigUpdated, const F_SenseData&, SenseData);
/** Delegate for when actor is forgotten */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSenseActorForgotten, const AActor*, ForgottenActor);
UCLASS()
class HAIPRO_API AHAIController : public AAIController
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, Category = "Components")
TObjectPtr<UCrowdFollowingComponent> CrowdFollowing;
UPROPERTY(EditDefaultsOnly, Category = "Components")
TObjectPtr<UAIPerceptionComponent> AIPerceptionComponent;
UPROPERTY(EditDefaultsOnly, Category = "Components")
TObjectPtr<UBehaviorTreeComponent> BehaviorTreeComponent;
UPROPERTY(EditDefaultsOnly, Category = "Components")
TObjectPtr<UHStateTreeAIComponent> StateTreeAIComponent;
UPROPERTY(BlueprintAssignable, BlueprintCallable, Category="HAI|Perception")
FOnPeraptionConfigUpdated OnPerceptionConfigUpdated;
UPROPERTY(BlueprintAssignable, BlueprintCallable, Category ="HAI|Perception")
FOnSenseActorForgotten OnSenseActorForgotten;
UPROPERTY()
TObjectPtr<UHAIBaseComponent> HAIBaseComponent;
UPROPERTY()
TObjectPtr<UAISenseConfig_Sight> SightConfig;
UPROPERTY()
TObjectPtr<UAISenseConfig_Hearing> HearingConfig;
UPROPERTY()
TObjectPtr<UAISenseConfig_Damage> DamageConfig;
AHAIController(const FObjectInitializer& ObjectInitializer);
virtual void OnPossess(APawn* InPawn) override;
/** Setting up percaption configs */
void OpenSightConfig();
void OpenHearingConfig();
void OpenDamageConfig();
/** Setting the dominant sense */
void SetDominantSense();
virtual void OnUnPossess() override;
UFUNCTION()
void OnTargetPerceptionForgotten(AActor* Actor);
UFUNCTION()
void OnTargetPerceptionInfoUpdated(const FActorPerceptionUpdateInfo& UpdateInfo);
public:
/** Target Values you can set and you can get wherever you want like state tree */
UPROPERTY()
TWeakObjectPtr<AActor> TargetActor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HAI|Perception")
FVector TargetLocation = FVector::ZeroVector;
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "HAI|Perception")
FAIStimulus TargetStimulus;
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "HAI|Perception")
E_SenseType TargetSenseType = E_SenseType::NoneType;
/** Setting target values */
UFUNCTION(BlueprintCallable, Category="HAI")
void SetTargetActor(AActor* newTarget, E_SenseType newSenseType, FAIStimulus newStimulus);
UFUNCTION(BlueprintPure, Category="HAI")
FORCEINLINE AActor* GetTargetActor() const { return TargetActor.Get(); }
UFUNCTION()
void OnTargetActorDestroyed(AActor* DestroyedActor);
UFUNCTION(BlueprintCallable, Category="HAI")
void SetTargetLocation(const FVector& newLocation, E_SenseType newSenseType, FAIStimulus newStimulus);
UFUNCTION(BlueprintCallable, Category="HAI")
void SetNullTargetActor();
UFUNCTION(BlueprintCallable, Category="HAI")
void SetNullTargetLocation();
/** Adjusting the BehaviorTree and StateTree */
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void StartBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void StopBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void PauseBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void ResumeBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void RestartBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|BehaviorTree")
void DestroyBehaviorTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void StartStateTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void StopStateTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void PauseStateTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void ResumeStateTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void RestartStateTree();
UFUNCTION(BlueprintCallable, Category="HAI|StateTree")
void DestroyStateTree();
};#include "HAIController.h"
#include "Perception/AIPerceptionComponent.h"
#include "HAIBaseComponent.h"
#include "Components/StateTreeAIComponent.h"
AHAIController::AHAIController(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer.SetDefaultSubobjectClass<UCrowdFollowingComponent>(TEXT("CrowdFollowingComponent")))
{
CrowdFollowing = CreateDefaultSubobject<UCrowdFollowingComponent>(TEXT("CrowdManager"));
AIPerceptionComponent = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("PerceptionComponent"));
SetPerceptionComponent(*AIPerceptionComponent);
BehaviorTreeComponent = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("BehaviorTreeComponent"));
StateTreeAIComponent = CreateDefaultSubobject<UHStateTreeAIComponent>(TEXT("StateTreeAIComponent"));
}
void AHAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
HAIBaseComponent = Cast<UHAIBaseComponent>(InPawn->GetComponentByClass(UHAIBaseComponent::StaticClass()));
if (!HAIBaseComponent)
return;
AIPerceptionComponent->OnTargetPerceptionForgotten.AddDynamic(this, &AHAIController::OnTargetPerceptionForgotten);
AIPerceptionComponent->OnTargetPerceptionInfoUpdated.AddDynamic(this, &AHAIController::OnTargetPerceptionInfoUpdated);
if (HAIBaseComponent->bEnableSight)
OpenSightConfig();
if (HAIBaseComponent->bEnableHearing)
OpenHearingConfig();
if (HAIBaseComponent->bEnableDamage)
OpenDamageConfig();
AIPerceptionComponent->RequestStimuliListenerUpdate();
SetDominantSense();
if (HAIBaseComponent->bStartBehaviorTreeOnBeginPlay)
StartBehaviorTree();
if (HAIBaseComponent->bStartStateTreeOnBeginPlay)
StartStateTree();
}
void AHAIController::OpenSightConfig()
{
SightConfig = NewObject<UAISenseConfig_Sight>(AIPerceptionComponent, TEXT("SightConfig"));
if (!SightConfig)
return;
SightConfig->SightRadius = HAIBaseComponent->SightRadius;
SightConfig->LoseSightRadius = HAIBaseComponent->LoseSightRadius;
SightConfig->SetMaxAge(HAIBaseComponent->SightAge);
SightConfig->DetectionByAffiliation.bDetectEnemies = HAIBaseComponent->bDetectEnemies;
SightConfig->DetectionByAffiliation.bDetectNeutrals = HAIBaseComponent->bDetectNeutrals;
SightConfig->DetectionByAffiliation.bDetectFriendlies = HAIBaseComponent->bDetectFriendlies;
AIPerceptionComponent->ConfigureSense(*SightConfig);
}
void AHAIController::OpenHearingConfig()
{
HearingConfig = NewObject<UAISenseConfig_Hearing>(AIPerceptionComponent, TEXT("HearingConfig"));
if (!HearingConfig)
return;
HearingConfig->HearingRange = HAIBaseComponent->HearingRange;
HearingConfig->SetMaxAge(HAIBaseComponent->HearingAge);
HearingConfig->DetectionByAffiliation.bDetectEnemies = HAIBaseComponent->bDetectHearingEnemies;
HearingConfig->DetectionByAffiliation.bDetectNeutrals = HAIBaseComponent->bDetectHearingNeutrals;
HearingConfig->DetectionByAffiliation.bDetectFriendlies = HAIBaseComponent->bDetectHearingFriendlies;
AIPerceptionComponent->ConfigureSense(*HearingConfig);
}
void AHAIController::OpenDamageConfig()
{
DamageConfig = NewObject<UAISenseConfig_Damage>(AIPerceptionComponent, TEXT("DamageConfig"));
if (!DamageConfig)
return;
DamageConfig->SetMaxAge(HAIBaseComponent->DamageAge);
AIPerceptionComponent->ConfigureSense(*DamageConfig);
}
void AHAIController::SetDominantSense()
{
switch (HAIBaseComponent->DominantSense)
{
case E_DominantSense::Sight:
if (SightConfig)
AIPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
break;
case E_DominantSense::Hearing:
if (HearingConfig)
AIPerceptionComponent->SetDominantSense(HearingConfig->GetSenseImplementation());
break;
case E_DominantSense::Damage:
if (DamageConfig)
AIPerceptionComponent->SetDominantSense(DamageConfig->GetSenseImplementation());
break;
default:
if (SightConfig)
AIPerceptionComponent->SetDominantSense(SightConfig->GetSenseImplementation());
break;
}
}
void AHAIController::OnUnPossess()
{
Super::OnUnPossess();
AIPerceptionComponent->OnTargetPerceptionForgotten.RemoveDynamic(this, &AHAIController::OnTargetPerceptionForgotten);
AIPerceptionComponent->OnTargetPerceptionInfoUpdated.RemoveDynamic(this, &AHAIController::OnTargetPerceptionInfoUpdated);
}
//-------------------------------------------------
void AHAIController::OnTargetPerceptionForgotten(AActor* Actor)
{
HAIBaseComponent->OnSenseActorForgotten.Broadcast(Actor);
}
void AHAIController::OnTargetPerceptionInfoUpdated(const FActorPerceptionUpdateInfo& UpdateInfo)
{
F_SenseData SenseData;
SenseData.sensedActor = UpdateInfo.Target;
SenseData.stimulus = UpdateInfo.Stimulus;
FAISenseID SenseID = UpdateInfo.Stimulus.Type;
if (SenseID == UAISense::GetSenseID<UAISense_Sight>())
SenseData.senseType = E_SenseType::SightConfig;
else if (SenseID == UAISense::GetSenseID<UAISense_Hearing>())
SenseData.senseType = E_SenseType::HearingConfig;
else if (SenseID == UAISense::GetSenseID<UAISense_Damage>())
SenseData.senseType = E_SenseType::DamageConfig;
HAIBaseComponent->OnPerceptionConfigUpdated.Broadcast(SenseData);
}
//-------------------------------------------------
void AHAIController::SetTargetActor(AActor* newTarget, E_SenseType newSenseType, FAIStimulus newStimulus)
{
if (!newTarget)
return;
TargetActor = newTarget;
TargetStimulus = newStimulus;
if (newSenseType == E_SenseType::NoneType)
newSenseType = newStimulus.Type == UAISense::GetSenseID<UAISense_Sight>() ? E_SenseType::SightConfig :
newStimulus.Type == UAISense::GetSenseID<UAISense_Hearing>() ? E_SenseType::HearingConfig :
newStimulus.Type == UAISense::GetSenseID<UAISense_Damage>() ? E_SenseType::DamageConfig : E_SenseType::NoneType;
TargetSenseType = newSenseType;
if (TargetActor.Get())
TargetActor->OnDestroyed.AddDynamic(this, &AHAIController::OnTargetActorDestroyed);
}
void AHAIController::OnTargetActorDestroyed(AActor* DestroyedActor)
{
if (DestroyedActor == TargetActor.Get())
{
TargetStimulus = FAIStimulus();
TargetLocation = FVector::ZeroVector;
TargetSenseType = E_SenseType::NoneType;
}
}
void AHAIController::SetTargetLocation(const FVector& newLocation, E_SenseType newSenseType, FAIStimulus newStimulus)
{
TargetLocation = newLocation;
TargetStimulus = newStimulus;
if (newSenseType == E_SenseType::NoneType)
newSenseType = newStimulus.Type == UAISense::GetSenseID<UAISense_Sight>() ? E_SenseType::SightConfig :
newStimulus.Type == UAISense::GetSenseID<UAISense_Hearing>() ? E_SenseType::HearingConfig :
newStimulus.Type == UAISense::GetSenseID<UAISense_Damage>() ? E_SenseType::DamageConfig : E_SenseType::NoneType;
TargetSenseType = newSenseType;
}
void AHAIController::SetNullTargetLocation()
{
TargetLocation = FVector::ZeroVector;
TargetStimulus = FAIStimulus();
TargetSenseType = E_SenseType::NoneType;
}
void AHAIController::SetNullTargetActor()
{
TargetActor = nullptr;
TargetStimulus = FAIStimulus();
TargetSenseType = E_SenseType::NoneType;
}
void AHAIController::StartBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
RunBehaviorTree(HAIBaseComponent->BehaviorTree);
}
void AHAIController::StopBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
BehaviorTreeComponent->StopLogic(TEXT("Stopped by AHAIController"));
}
void AHAIController::PauseBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
BehaviorTreeComponent->PauseLogic(TEXT("Paused by AHAIController"));
}
void AHAIController::ResumeBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
BehaviorTreeComponent->ResumeLogic(TEXT("Resumed by AHAIController"));
}
void AHAIController::RestartBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
BehaviorTreeComponent->RestartLogic();
}
void AHAIController::DestroyBehaviorTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->BehaviorTree || !BehaviorTreeComponent)
return;
BehaviorTreeComponent->Cleanup();
}
void AHAIController::StartStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;
FStateTreeReference* StateTree = StateTreeAIComponent->GetStateTree();
StateTree->SetStateTree(HAIBaseComponent->StateTree);
StateTreeAIComponent->StartLogic();
}
void AHAIController::StopStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;
StateTreeAIComponent->StopLogic(TEXT("Stopped by AHAIController"));
}
void AHAIController::PauseStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;
StateTreeAIComponent->PauseLogic(TEXT("Paused by AHAIController"));
}
void AHAIController::ResumeStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;
StateTreeAIComponent->ResumeLogic(TEXT("Resumed by AHAIController"));
}
void AHAIController::RestartStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;;
StateTreeAIComponent->RestartLogic();
}
void AHAIController::DestroyStateTree()
{
if (!HAIBaseComponent || !HAIBaseComponent->StateTree || !StateTreeAIComponent)
return;
StateTreeAIComponent->Cleanup();
}