Firstly, I guess we need an enum of behavior status to handle.
public enum BehaviorStatus
{
Pass,
Fail,
Running,
Error
}
The behavior base class.
public abstract class Behavior
{
/// The parent behavior
public Behavior Parent { get; private set; }
/// The children behaviors
public ListChildren = new List ();
/// The index of the active behavior
protected int activeBehaviorIndex = -1;
/// Add child and make sure parents are set up.
public void AddChildBehavior (Behavior child)
{
child.Parent = this;
Children.Add (child);
}
/// Process Behavior logic and return status.
public virtual BehaviorStatus Decide ()
{
return BehaviorStatus.Pass;
}
/// Resets active status of self and children
public void ResetActiveBehavior ()
{
if (-1 != activeBehaviorIndex)
{
Children [activeBehaviorIndex].ResetActiveBehavior ();
activeBehaviorIndex = -1;
}
}
}
A selector which is a list of behaviors that are tried one after the other until one succeeds.
public class Selector : Behavior
{
public override BehaviorStatus Decide ()
{
BehaviorStatus status = BehaviorStatus.Fail;
for (int i = 0;
i < (activeBehaviorIndex == -1 ? Children.Count : activeBehaviorIndex + 1);
++i)
{
status = Children [i].Decide ();
// Error,
Debug.Assert (BehaviorStatus.Error != status, "Error status in Priority.");
if (BehaviorStatus.Running == status)
{
// Running, Store active behavior
activeBehaviorIndex = i;
return status;
}
else
{
// Not running, Reset active behavior and their active children
ResetActiveBehavior ();
}
// Pass, Stop looking for behavior
if (BehaviorStatus.Pass == status)
{
return status;
}
// Fail, try another one.
}
// Fail, Everything
return status;
}
}
A sequence is a list of behaviors that are run one after another.
public class Sequence : Behavior
{
public override BehaviorStatus Decide ()
{
BehaviorStatus status = BehaviorStatus.Fail;
// Start making decision from the active index
for (int i = (activeBehaviorIndex == -1 ? 0 : activeBehaviorIndex);
i < Children.Count;
++i)
{
status = Children [i].Decide ();
// Error,
Debug.Assert (BehaviorStatus.Error != status, "Error status in Sequence.");
if (BehaviorStatus.Running == status)
{
// Running, Store active behavior
activeBehaviorIndex = i;
return status;
}
else
{
// Not running, Reset active behavior and their active children
ResetActiveBehavior ();
}
// Fail, Stop execution
if (BehaviorStatus.Fail == status)
{
return status;
}
// Pass, continue execution
}
return status;
}
}
Example condition and action.
public class ExampleCondition : Behavior
{
public override BehaviorStatus Decide ()
{
// Check condition
if (true)
{
return BehaviorStatus.Pass;
}
return BehaviorStatus.Fail;
}
}
public class ExampleAction : Behavior
{
public override BehaviorStatus Decide ()
{
// Check for any problems
if (true)
{
return BehaviorStatus.Fail;
}
// Check if action finished
if (true)
{
return BehaviorStatus.Pass;
}
// Do action
return BehaviorStatus.Running;
}
}
An example of how it is all setup
// Setup
Selector root = new Selector ();
Sequence talkToPlayer = new Sequence ();
Condition checkIfNearPlayer = new Condition ();
Action walkToPlayer = new Action ();
Action startConverstationWithPlayer () = new Action ();
Action walkAwayFromPlayer = new Action ();
Sequence chaseRatsAway = new Sequence ();
Condition checkIfNearRat = new Condition ();
Action chaseRat = new Action ();
Action wander = new Action ();
root.AddChildBehavior (talkToPlayer);
talkToPlayer.AddChildBehavior (checkIfNearPlayer);
talkToPlayer.AddChildBehavior (walkToPlayer);
talkToPlayer.AddChildBehavior (startConverstationWithPlayer);
talkToPlayer.AddChildBehavior (walkAwayFromPlayer);
root.AddChildBehavior (chaseRatsAway);
chaseRatsAway.AddChildBehavior (checkIfNearRat);
chaseRatsAway.AddChildBehavior (chaseRat);
root.AddChildBehavior (wander);
// Update
root.Decide ();
This works for now but I am sure there are better ways of setting up the behavior tree. Maybe with an editor or xml. I think I would also want to improve the way I am tracking the running behaviors by putting them into a queue.
Awfully technical this blog is. Speak like Yoda I am.
ReplyDelete