Skip to content

Instantly share code, notes, and snippets.

@NovemberDev
Last active March 11, 2021 10:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NovemberDev/ae50135e29be45a8aa6b95f411583506 to your computer and use it in GitHub Desktop.
Save NovemberDev/ae50135e29be45a8aa6b95f411583506 to your computer and use it in GitHub Desktop.
C# Behaviour Tree
using System;
using System.Linq;
using System.Collections.Generic;
public enum BehaviourTreeState
{
Success,
Failure,
Running
}
public abstract class BehaviourTreeNode
{
public string Name;
public abstract BehaviourTreeState Tick(float deltaTime);
public List<BehaviourTreeNode> ChildNodes = new List<BehaviourTreeNode>();
}
public class ParallelNode : BehaviourTreeNode
{
public int CountToFail, CountToSucceed;
public ParallelNode(string name, int countToFail, int countToSucceed)
{
Name = name;
CountToFail = countToFail;
CountToSucceed = countToSucceed;
}
public override BehaviourTreeState Tick(float deltaTime)
{
int succeeded = 0,
failed = 0;
foreach (BehaviourTreeNode child in ChildNodes)
{
BehaviourTreeState status = child.Tick(deltaTime);
switch (status)
{
case BehaviourTreeState.Success:
++succeeded;
break;
case BehaviourTreeState.Failure:
++failed;
break;
}
}
if (CountToSucceed > 0 && succeeded >= CountToSucceed)
return BehaviourTreeState.Success;
if (CountToFail > 0 && failed >= CountToFail)
return BehaviourTreeState.Failure;
return BehaviourTreeState.Running;
}
}
public class LeafNode : BehaviourTreeNode
{
public Func<float, BehaviourTreeState> Function;
public LeafNode(string name, Func<float, BehaviourTreeState> function)
{
Name = name;
Function = function;
}
public override BehaviourTreeState Tick(float deltaTime) => Function(deltaTime);
}
public class InverterNode : BehaviourTreeNode
{
public InverterNode(string name)
{
Name = name;
}
public override BehaviourTreeState Tick(float deltaTime)
{
BehaviourTreeState status = ChildNodes.First().Tick(deltaTime);
switch (status)
{
case BehaviourTreeState.Failure:
return BehaviourTreeState.Success;
case BehaviourTreeState.Success:
return BehaviourTreeState.Failure;
default:
return status;
}
}
}
public class SelectorNode : BehaviourTreeNode
{
public SelectorNode(string name)
{
Name = name;
}
public override BehaviourTreeState Tick(float deltaTime)
{
foreach (BehaviourTreeNode child in ChildNodes)
{
BehaviourTreeState status = child.Tick(deltaTime);
if (status != BehaviourTreeState.Failure)
return status;
}
return BehaviourTreeState.Failure;
}
}
public class SequenceNode : BehaviourTreeNode
{
public SequenceNode(string name)
{
Name = name;
}
public override BehaviourTreeState Tick(float deltaTime)
{
foreach (BehaviourTreeNode node in ChildNodes)
{
BehaviourTreeState status = node.Tick(deltaTime);
if (status != BehaviourTreeState.Success)
return status;
}
return BehaviourTreeState.Success;
}
}
public class BehaviourTree
{
public BehaviourTreeNode CurrentNode;
public Stack<BehaviourTreeNode> ParentNodes = new Stack<BehaviourTreeNode>();
/// <summary>
/// Method to execute logic at the leaf of a node, can be stacked
/// </summary>
public BehaviourTree Do(string name, Func<float, BehaviourTreeState> function)
{
ParentNodes.Peek().ChildNodes.Add(new LeafNode(name, function));
return this;
}
/// <summary>
/// Does the same as Do, but returns a bool for convenience
/// </summary>
public BehaviourTree Condition(string name, Func<float, bool> function)
{
Do(name, t => function(t) ? BehaviourTreeState.Success : BehaviourTreeState.Failure);
return this;
}
/// <summary>
/// Inverts success to failure and vice versa
/// </summary>
public BehaviourTree Inverter(string name)
{
ParentNodes.Push(new InverterNode(name));
return this;
}
/// <summary>
/// Runs every child node in sequence as long as they succeed, stops on failure
/// </summary>
public BehaviourTree Sequence(string name)
{
SequenceNode sequenceNode = new SequenceNode(name);
if (ParentNodes.Count > 0)
ParentNodes.Peek().ChildNodes.Add(sequenceNode);
ParentNodes.Push(sequenceNode);
return this;
}
/// <summary>
/// Runs every node in parallel depending on how many can succeed or fail until it is able to return
/// </summary>
public BehaviourTree Parallel(string name, int countToFail, int countToSucceed)
{
ParentNodes.Push(new ParallelNode(name, countToFail, countToSucceed));
return this;
}
/// <summary>
/// Runs childnodes until one succeeds, failing means going to the next node
/// </summary>
public BehaviourTree Selector(string name)
{
ParentNodes.Push(new SelectorNode(name));
return this;
}
/// <summary>
/// Finishes the creation of this level of subnodes
/// </summary>
public BehaviourTree End()
{
CurrentNode = ParentNodes.Pop();
return this;
}
public BehaviourTreeNode Return() => CurrentNode;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment