Snippets
Find here useful code snippets that can be used with Bot SDK.
Compound Agents
Sometimes it might be useful to have more than one AI agent component operating in the same entity.
For example, maybe there is one HFSM which is responsible uniquely for moving the entity and another HFSM which is only responsible for attacking targets.
Even mixing different technologies in the same entity is possible: perhaps part of the work is done by with an HFSM and the other part if done with a BT.
When such setups are needed, it is also useful to provide more than one memory storage for the entity. In Bot SDK, it is common (but not mandatory) to use the AIBlackboard component.
Here are then code snippets which showcases how multiple components can be added to a single entity to create what we are calling here an entity with Compounds Agents:
Creating the compound component
In the DSL (any .qtn
file), add the component below.
Feel free to give them meaningful game-specific names to the components, or to add them in a generic-way to a list.
In this example, there is HFSM responsible only for Moving the entity and another one to perform Attacks (just to illustrate):
C#
component CompoundAgents
{
HFSMAgent MovementHFSMAgent;
HFSMAgent AttackHFSMAgent;
AIBlackboardComponent MovementBlackboard;
AIBlackboardComponent AttackBlackboard;
AssetRefAIBlackboardInitializer MovementBBInitializer;
AssetRefAIBlackboardInitializer AttackBBInitializer;
}
Extending the AIContext
Create a partial implementation of AIContext
. The context here is: "given the updated entity, which of its HFSMs is being updated? Which of its Blackboards does that HFSM uses?", etc:
C#
namespace Quantum
{
public unsafe partial struct AIContext
{
public readonly HFSMAgent* HfsmAgent;
public readonly AIBlackboardComponent* Blackboard;
public AIContext(HFSMAgent* hfsmAgent, AIBlackboardComponent* blackboard)
{
this.HfsmAgent = hfsmAgent;
this.Blackboard = blackboard;
}
}
}
Initialising the Agents and Blackboards
This is exemplified with a ISignalOnComponentAdded
C#
public void OnAdded(Frame frame, EntityRef entity, CompoundAgents* compoundAgents)
{
// Initialise Agents
HFSMRoot hfsmRoot = frame.FindAsset<HFSMRoot>(compoundAgents->MovementHFSMAgent.Data.Root.Id);
HFSMManager.Init(frame, &compoundAgents->MovementHFSMAgent.Data, entity, hfsmRoot);
hfsmRoot = frame.FindAsset<HFSMRoot>(compoundAgents->AttackHFSMAgent.Data.Root.Id);
HFSMManager.Init(frame, &compoundAgents->AttackHFSMAgent.Data, entity, hfsmRoot);
// Initialise Blackboards
AIBlackboardInitializer initializer = frame.FindAsset<AIBlackboardInitializer>(compoundAgents->MovementBBInitializer.Id);
AIBlackboardInitializer.InitializeBlackboard(frame, &compoundAgents->MovementBlackboard, initializer);
initializer = frame.FindAsset<AIBlackboardInitializer>(compoundAgents->AttackBBInitializer.Id);
AIBlackboardInitializer.InitializeBlackboard(frame, &compoundAgents->AttackBlackboard, initializer);
}
Updating all Agents
Create the AI Context and fill it with each HFSM and Blackboard, then perform the Update:
C#
public override void Update(Frame frame, ref Filter filter)
{
HFSMData* movementData = &filter.CompoundAgents->MovementHFSMAgent.Data;
HFSMData* rotationData = &filter.CompoundAgents->RotationHFSMAgent.Data;
HFSMData* attackData = &filter.CompoundAgents->AttackHFSMAgent.Data;
AIContext aiContext = new AIContext(&filter.CompoundAgents->MovementHFSMAgent, &filter.CompoundAgents->MovementBlackboard);
HFSMManager.Update(frame, frame.DeltaTime, movementData, filter.EntityRef, ref aiContext);
aiContext = new AIContext(&filter.CompoundAgents->RotationHFSMAgent, &filter.CompoundAgents->RotationBlackboard);
HFSMManager.Update(frame, frame.DeltaTime, rotationData, filter.EntityRef, ref aiContext);
aiContext = new AIContext(&filter.CompoundAgents->AttackHFSMAgent, &filter.CompoundAgents->AttackBlackboard);
HFSMManager.Update(frame, frame.DeltaTime, attackData, filter.EntityRef, ref aiContext);
}
Getting the HFSM-specific blackboard from the AIContext object
Here is a snippet of an Action which writes to the context-specific Blackboard. Notice it is agnostic to which one it is, so the code is decoupled, there is no need to add lots of boilerplate code in the Actions to figure out which Blackboard should be used:
C#
namespace Quantum
{
[System.Serializable]
public unsafe class WriteBlackboardCompound : AIAction
{
public int Value;
public override void Update(Frame frame, EntityRef entity, ref AIContext aiContext)
{
aiContext.Blackboard->Set(frame, "TestInteger", Value);
}
}
}
Back to top