A decision-making framework where every possible action is evaluated by a set of considerations
A decision-making framework for Unreal Engine agents where every possible action is evaluated by a set of considerations. The resulting utility score selects which action to execute.
Utility AI evaluates many potential actions and ranks them based on their desirability. Each action gathers scores from considerations (for example health, distance or ammo) and the highest score wins. This approach results in more nuanced behaviour than simple state machines or behaviour trees.
Unreal Engine 5.2+
Plugins
folder: .../UEGame/Plugins/NsSynapse
.UEGame.uproject
and generate project files.UNsSynapseBrainComponent
β attach to an actor and call Think to evaluate actions.UNsSynapseAction
β base class for actions. Override ExecuteAction and provide Considerations.UNsSynapseConsideration
β base class for scoring logic with a customizable curve.Think()
β evaluates PossibleActions
.ThinkAndReact()
β evaluates PossibleActions
and executes the best action.ChooseAction
β given an array of actions, selects the highest scoring one.ScoreAction
β multiplies consideration scores to produce an action utility value.ExecuteAction
β override in your action subclass to perform behaviour.CalculateScore
β override in a consideration to return a raw 0β1 score.GetBestAction
β returns the action chosen by the last call to Think.UNsSynapseBrainComponent
.UNsSynapseAction
subclasses and assign them to the Brain Componentβs PossibleActions
array.CalculateScore()
in each UNsSynapseConsideration
to read your game data.ThinkAndReact()
whenever the AI should evaluate and execute an action.An enemy might provide actions like:
Each action is scored by its considerations:
Action | Considerations |
---|---|
Attack Player | Player in sight, Ammo amount, Health percentage |
Reload Weapon | Ammo amount, Safe to reload |
Find Cover | Nearby cover points, Under fire |
Retreat | Health critically low, No cover available |
// Actor with brain component
AMyBot::AMyBot()
{
Brain = CreateDefaultSubobject<UNsSynapseBrainComponent>(TEXT("Brain"));
}
void AMyBot::BeginPlay()
{
if (Brain != nullptr)
{
Brain->ThinkAndReact(); // Chose the best action and execute it
}
}
// Custom consideration
UCLASS()
class UHealthConsideration : public UNsSynapseConsideration
{
GENERATED_BODY()
public:
virtual float CalculateScore_Implementation(AActor* InOwner) const override
{
if (const AMyBot* const Bot = Cast<AMyBot>(InOwner))
{
const float HealthScore = Bot->GetCurrentHealth() / Bot->GetMaxtHealth();
return HealthScore;
}
}
};
// Custom action
UCLASS()
class UAttackAction : public UNsSynapseAction
{
GENERATED_BODY()
virtual void ExecuteAction_Implementation(AActor* InActor) override
{
UE_LOG(LogTemp, Log, TEXT("Firing at player"));
}
};