Last active
March 11, 2021 10:37
-
-
Save NovemberDev/ae50135e29be45a8aa6b95f411583506 to your computer and use it in GitHub Desktop.
C# Behaviour Tree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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