I'm attempting to get my head around behavior trees, so I'm spiking out some test code. One thing I'm struggling with is how to preempt a currently running node when something of higher priority comes along.
Consider the following simple, fictitious behavior tree for a soldier:
Suppose that some number of ticks have gone by and there was no enemy near by, the soldier was standing on grass, so the Sit down node is selected for execution:
Now the Sit down action takes time to execute because there is an animation to play, so it returns Running
as its status. A tick or two goes by, the animation is still running, but the Enemy near? condition node triggers. Now we need to preempt the Sit down node ASAP so we can execute the Attack node. Ideally the soldier wouldn't even finish sitting down - he might instead reverse his animation direction if he only just started to sit. For added realism, if he's past some tipping point in the animation, we might instead to choose to let him finish sitting down and then stand again, or perhaps have him stumble in his haste to react to the threat.
Try as I might, I have not been able to find guidance on how to handle this kind of situation. All the literature and videos I've consumed over the past few days (and it's been a lot) seem to skirt around this issue. The closest thing I've been able to find has been this concept of resetting running nodes, but that doesn't give nodes like Sit down a chance to say "hey, I've not finished yet!"
I thought of perhaps defining a Preempt()
or Interrupt()
method on my base Node
class. Different nodes can handle it how they see fit, but in this case we'd attempt to get the soldier back on his feet ASAP and then return Success
. I think this approach would also require that my base Node
has the concept of conditions separately to other actions. That way, the engine can check conditions only and, if they pass, preempt any currently executing node before starting the execution of the actions. If this differentiation wasn't established, the engine would need to execute nodes indiscriminately and could therefore trigger a new action before preempting the running one.
For reference, below are my current base classes. Again, this is a spike, so I've attempted to keep things as simple as possible and only add complexity when I need it, and when I understand it, which is what I'm struggling with right now.
public enum ExecuteResult
{
// node needs more time to run on next tick
Running,
// node completed successfully
Succeeded,
// node failed to complete
Failed
}
public abstract class Node<TAgent>
{
public abstract ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard);
}
public abstract class DecoratorNode<TAgent> : Node<TAgent>
{
private readonly Node<TAgent> child;
protected DecoratorNode(Node<TAgent> child)
{
this.child = child;
}
protected Node<TAgent> Child
{
get { return this.child; }
}
}
public abstract class CompositeNode<TAgent> : Node<TAgent>
{
private readonly Node<TAgent>[] children;
protected CompositeNode(IEnumerable<Node<TAgent>> children)
{
this.children = children.ToArray();
}
protected Node<TAgent>[] Children
{
get { return this.children; }
}
}
public abstract class ConditionNode<TAgent> : Node<TAgent>
{
private readonly bool invert;
protected ConditionNode()
: this(false)
{
}
protected ConditionNode(bool invert)
{
this.invert = invert;
}
public sealed override ExecuteResult Execute(TimeSpan elapsed, TAgent agent, Blackboard blackboard)
{
var result = this.CheckCondition(agent, blackboard);
if (this.invert)
{
result = !result;
}
return result ? ExecuteResult.Succeeded : ExecuteResult.Failed;
}
protected abstract bool CheckCondition(TAgent agent, Blackboard blackboard);
}
public abstract class ActionNode<TAgent> : Node<TAgent>
{
}
Does anyone have any insight that could steer me in the right direction? Is my thinking along the right lines, or is it as naive as I fear?
Stop()
callback before exiting the active nodes) \$\endgroup\$