Tuesday, 21 June 2011

Behavior Tree

Recently, I have been utterly intrigued by behavior trees. I have always been struck by how difficult it is to design and tweak entity behaviors using finite state machines. I have been using nested state machines in previous projects and it is always a pain when there are changes in the game flow even when it is just small menu changes. After reading about the advantages of easy to design decision flow and reusable actions/conditions in some articles online, I decided to try and implement something simple to test it out.

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 List Children = 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.

1 comments:

  1. Awfully technical this blog is. Speak like Yoda I am.

    ReplyDelete