Behavior
Important methods
Start
This method is invoked once the Behavior is assigned to entity. Its implementation is not required.
Update
This method is invoked during each AI Manager update.
Finish
This method is invoked once IsFinished
method returns true
, before the next behavior is chosen.
Its implementation is not required. Your implementation may behave differently depending on behavior status.
IsFinished
This method returns true
if the behavior should terminate.
Its implementation is not required. The default implementation returns true.
IsLegal
This method indicates whether it’s possible to execute the behavior and if it should be considered by decision making algorithms. If a behavior turns illegal during its execution, it will be automatically finished with DELEGALIZED status.
Its implementation is not required. The default implementation returns true.
IsInterruptible
This method indicates whether it’s possible to interrupt behavior execution. If it is Reasoner may decide to finish behavior immediately with SUSPENDED status.
Its implementation is not required. The default implementation returns true.
Behavior Status
-
ACTIVE - behavior is currently assigned to entity
-
SUSPENDED - behavior was assigned to entity but its execution was interrupted
-
FINISHED - behavior finished properly
-
DELEGALIZED - behavior is no longer legal
Simple behavior implementation
An exemplar implementation of a simple behavior looks like this:
-
C++
-
C#
class GoTo : public grail::Behavior
{
private:
Location target;
Unit* controlledUnit;
public:
GoTo(const Location& target)
: target{target}
{
}
void Start(grail::AIEntity& owner) override
{
controlledUnit = owner.GetBlackboard().GetValue<Unit*>("controlled_unit");
if (controlledUnit->GetCurrentLocation() != target)
{
controlledUnit->GetMovement().GoTo(target);
}
}
void Update(grail::AIEntity& owner, float deltaTime) override
{
//Place any time-dependent or durable logic in behavior's update
}
void Finish(grail::AIEntity& owner, grail::BehaviorStatus status) override
{
controlledUnit->ReportMovementFinished();
//We may also place any cleanup logic in this method
}
bool IsFinished(const grail::AIEntity& owner) const override
{
return controlledUnit->Location == target;
}
bool IsLegal(const grail::AIEntity& owner) const override
{
//if IsLegal returns false, the behavior will be omitted by decision-making algorithms or interrupted
return true;
}
}
public class GoTo : Grail.Behavior
{
private Location target;
private Unit controlledUnit;
public GoTo(Location target)
{
this.target = target;
}
public override void Start(AIControlledEntity owner)
{
controlledUnit = owner.Blackboard.GetValue<Unit>("controlled_unit");
if (controlledUnit.CurrentLocation != target)
{
currentUnit.Movement.GoTo(target);
}
}
public override void Update(AIControlledEntity owner, float deltaTime)
{
//Place any time-dependent or durable logic in behavior's update
}
public override void Finish(AIControlledEntity owner)
{
controlledUnit.ReportMovementFinished();
//We may also place any cleanup logic in this method
}
public override bool IsFinished(AIControlledEntity owner)
{
return controlledUnit.Location == target;
}
public override bool IsLegal(AIControlledEntity owner)
{
//if IsLegal returns false, the behavior will be omitted by decision-making algorithms
return true;
}
}
Recovery Behavior
Recovery behavior is an advanced feature that allows you to perform a behavior right after finishing the current one. It can be used to execute any necessary cleanup logic. For example, if you have a behavior that causes your character to assume a non-standard pose (like lying down), you ofter want it to play some wind-down animation before any other behavior can be started (like standing up before starting movement). There two ways to define a recovery behavior:
-
via the constructor; this method should be used if you want the same recovery behavior to always execute after finishing the parent behavior,
-
by overriding a getter; this method should be used if you want to use different recovery behaviors depending on the parent behavior’s stage.
It’s a good practice to make recovery behaviors non-interruptible. |
Recovery Behavior Passed by Constructor
-
C++
-
C#
auto behavior = std::make_unique<grail::MyParentBehaviorBehavior>(std::make_unique<MyRecoveryBehavior>());
MyParentBehavior behavior = new(new MyRecoveryBehavior());
Recovery Behavior Returned by the Getter
After a behavior is finished, the executing AI Entity calls the recovery behavior getter. It the getter returns a non-null behavior, it will be immediately executed, regardless of whether the parent behavior was delegalized, suspended or finished normally. If you want to dynamically change the recovery behavior based on the parent behavior’s state, you can do so by overriding the getter (see the code snippets below).
-
C++
-
C#
class MyParentBehavior : public grail::Behavior
{
public:
MyParentBehavior(std::unique_ptr<Behavior> recoveryBehavior)
: grail::Behavior(std::move(recoveryBehavior))
{
}
std::unique_ptr<Behavior> GetRecoveryBehavior() override
{
if(timer < 1.0f)
{
return nullptr;
}
return std::move(recoveryBehavior);
}
void Update(grail::AIEntity& owner, float deltaTime) override
{
timer += deltaTime;
}
private:
float timer = 0;
};
public class MyParentBehavior : Behavior
{
private float timer = 0;
public override Behavior RecoveryBehavior
{
get
{
if(timer < 1.0f)
{
return null;
}
return recoveryBehavior;
}
}
public MyParentBehavior(Behavior recoveryBehavior = null)
: base(recoveryBehavior)
{
}
public override void Update(AIEntity owner, float deltaTime)
{
timer += deltaTime;
}
}