Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@m039
Created August 11, 2019 16:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save m039/7988e715e5a9ad4795cc1b8209b350a9 to your computer and use it in GitHub Desktop.
Save m039/7988e715e5a9ad4795cc1b8209b350a9 to your computer and use it in GitHub Desktop.
The Behavior Tree source code file from Unity's 2D Game Kit project.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace BTAI
{
public enum BTState
{
Failure,
Success,
Continue,
Abort
}
public static class BT
{
public static Root Root() { return new Root(); }
public static Sequence Sequence() { return new Sequence(); }
public static Selector Selector(bool shuffle = false) { return new Selector(shuffle); }
public static Action RunCoroutine(System.Func<IEnumerator<BTState>> coroutine) { return new Action(coroutine); }
public static Action Call(System.Action fn) { return new Action(fn); }
public static ConditionalBranch If(System.Func<bool> fn) { return new ConditionalBranch(fn); }
public static While While(System.Func<bool> fn) { return new While(fn); }
public static Condition Condition(System.Func<bool> fn) { return new Condition(fn); }
public static Repeat Repeat(int count) { return new Repeat(count); }
public static Wait Wait(float seconds) { return new Wait(seconds); }
public static Trigger Trigger(Animator animator, string name, bool set = true) { return new Trigger(animator, name, set); }
public static WaitForAnimatorState WaitForAnimatorState(Animator animator, string name, int layer = 0) { return new WaitForAnimatorState(animator, name, layer); }
public static SetBool SetBool(Animator animator, string name, bool value) { return new SetBool(animator, name, value); }
public static SetActive SetActive(GameObject gameObject, bool active) { return new SetActive(gameObject, active); }
public static WaitForAnimatorSignal WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0) { return new WaitForAnimatorSignal(animator, name, state, layer); }
public static Terminate Terminate() { return new Terminate(); }
public static Log Log(string msg) { return new Log(msg); }
public static RandomSequence RandomSequence(int[] weights = null) { return new BTAI.RandomSequence(weights); }
}
public abstract class BTNode
{
public abstract BTState Tick();
}
public abstract class Branch : BTNode
{
protected int activeChild;
protected List<BTNode> children = new List<BTNode>();
public virtual Branch OpenBranch(params BTNode[] children)
{
for (var i = 0; i < children.Length; i++)
this.children.Add(children[i]);
return this;
}
public List<BTNode> Children()
{
return children;
}
public int ActiveChild()
{
return activeChild;
}
public virtual void ResetChildren()
{
activeChild = 0;
for (var i = 0; i < children.Count; i++)
{
Branch b = children[i] as Branch;
if (b != null)
{
b.ResetChildren();
}
}
}
}
public abstract class Decorator : BTNode
{
protected BTNode child;
public Decorator Do(BTNode child)
{
this.child = child;
return this;
}
}
public class Sequence : Branch
{
public override BTState Tick()
{
var childState = children[activeChild].Tick();
switch (childState)
{
case BTState.Success:
activeChild++;
if (activeChild == children.Count)
{
activeChild = 0;
return BTState.Success;
}
else
return BTState.Continue;
case BTState.Failure:
activeChild = 0;
return BTState.Failure;
case BTState.Continue:
return BTState.Continue;
case BTState.Abort:
activeChild = 0;
return BTState.Abort;
}
throw new System.Exception("This should never happen, but clearly it has.");
}
}
/// <summary>
/// Execute each child until a child succeeds, then return success.
/// If no child succeeds, return a failure.
/// </summary>
public class Selector : Branch
{
public Selector(bool shuffle)
{
if (shuffle)
{
var n = children.Count;
while (n > 1)
{
n--;
var k = Mathf.FloorToInt(Random.value * (n + 1));
var value = children[k];
children[k] = children[n];
children[n] = value;
}
}
}
public override BTState Tick()
{
var childState = children[activeChild].Tick();
switch (childState)
{
case BTState.Success:
activeChild = 0;
return BTState.Success;
case BTState.Failure:
activeChild++;
if (activeChild == children.Count)
{
activeChild = 0;
return BTState.Failure;
}
else
return BTState.Continue;
case BTState.Continue:
return BTState.Continue;
case BTState.Abort:
activeChild = 0;
return BTState.Abort;
}
throw new System.Exception("This should never happen, but clearly it has.");
}
}
/// <summary>
/// Call a method, or run a coroutine.
/// </summary>
public class Action : BTNode
{
System.Action fn;
System.Func<IEnumerator<BTState>> coroutineFactory;
IEnumerator<BTState> coroutine;
public Action(System.Action fn)
{
this.fn = fn;
}
public Action(System.Func<IEnumerator<BTState>> coroutineFactory)
{
this.coroutineFactory = coroutineFactory;
}
public override BTState Tick()
{
if (fn != null)
{
fn();
return BTState.Success;
}
else
{
if (coroutine == null)
coroutine = coroutineFactory();
if (!coroutine.MoveNext())
{
coroutine = null;
return BTState.Success;
}
var result = coroutine.Current;
if (result == BTState.Continue)
return BTState.Continue;
else
{
coroutine = null;
return result;
}
}
}
public override string ToString()
{
return "Action : " + fn.Method.ToString();
}
}
/// <summary>
/// Call a method, returns success if method returns true, else returns failure.
/// </summary>
public class Condition : BTNode
{
public System.Func<bool> fn;
public Condition(System.Func<bool> fn)
{
this.fn = fn;
}
public override BTState Tick()
{
return fn() ? BTState.Success : BTState.Failure;
}
public override string ToString()
{
return "Condition : " + fn.Method.ToString();
}
}
public class ConditionalBranch : Block
{
public System.Func<bool> fn;
bool tested = false;
public ConditionalBranch(System.Func<bool> fn)
{
this.fn = fn;
}
public override BTState Tick()
{
if (!tested)
{
tested = fn();
}
if (tested)
{
var result = base.Tick();
if (result == BTState.Continue)
return BTState.Continue;
else
{
tested = false;
return result;
}
}
else
{
return BTState.Failure;
}
}
public override string ToString()
{
return "ConditionalBranch : " + fn.Method.ToString();
}
}
/// <summary>
/// Run all children, while method returns true.
/// </summary>
public class While : Block
{
public System.Func<bool> fn;
public While(System.Func<bool> fn)
{
this.fn = fn;
}
public override BTState Tick()
{
if (fn())
base.Tick();
else
{
//if we exit the loop
ResetChildren();
return BTState.Failure;
}
return BTState.Continue;
}
public override string ToString()
{
return "While : " + fn.Method.ToString();
}
}
public abstract class Block : Branch
{
public override BTState Tick()
{
switch (children[activeChild].Tick())
{
case BTState.Continue:
return BTState.Continue;
default:
activeChild++;
if (activeChild == children.Count)
{
activeChild = 0;
return BTState.Success;
}
return BTState.Continue;
}
}
}
public class Root : Block
{
public bool isTerminated = false;
public override BTState Tick()
{
if (isTerminated) return BTState.Abort;
while (true)
{
switch (children[activeChild].Tick())
{
case BTState.Continue:
return BTState.Continue;
case BTState.Abort:
isTerminated = true;
return BTState.Abort;
default:
activeChild++;
if (activeChild == children.Count)
{
activeChild = 0;
return BTState.Success;
}
continue;
}
}
}
}
/// <summary>
/// Run a block of children a number of times.
/// </summary>
public class Repeat : Block
{
public int count = 1;
int currentCount = 0;
public Repeat(int count)
{
this.count = count;
}
public override BTState Tick()
{
if (count > 0 && currentCount < count)
{
var result = base.Tick();
switch (result)
{
case BTState.Continue:
return BTState.Continue;
default:
currentCount++;
if (currentCount == count)
{
currentCount = 0;
return BTState.Success;
}
return BTState.Continue;
}
}
return BTState.Success;
}
public override string ToString()
{
return "Repeat Until : " + currentCount + " / " + count;
}
}
public class RandomSequence : Block
{
int[] m_Weight = null;
int[] m_AddedWeight = null;
/// <summary>
/// Will select one random child everytime it get triggered again
/// </summary>
/// <param name="weight">Leave null so that all child node have the same weight.
/// If there is less weight than children, all subsequent child will have weight = 1</param>
public RandomSequence(int[] weight = null)
{
activeChild = -1;
m_Weight = weight;
}
public override Branch OpenBranch(params BTNode[] children)
{
m_AddedWeight = new int[children.Length];
for (int i = 0; i < children.Length; ++i)
{
int weight = 0;
int previousWeight = 0;
if (m_Weight == null || m_Weight.Length <= i)
{//if we don't have weight for that one, we set the weight to one
weight = 1;
}
else
weight = m_Weight[i];
if (i > 0)
previousWeight = m_AddedWeight[i - 1];
m_AddedWeight[i] = weight + previousWeight;
}
return base.OpenBranch(children);
}
public override BTState Tick()
{
if (activeChild == -1)
PickNewChild();
var result = children[activeChild].Tick();
switch (result)
{
case BTState.Continue:
return BTState.Continue;
default:
PickNewChild();
return result;
}
}
void PickNewChild()
{
int choice = Random.Range(0, m_AddedWeight[m_AddedWeight.Length - 1]);
for (int i = 0; i < m_AddedWeight.Length; ++i)
{
if (choice - m_AddedWeight[i] <= 0)
{
activeChild = i;
break;
}
}
}
public override string ToString()
{
return "Random Sequence : " + activeChild + "/" + children.Count;
}
}
/// <summary>
/// Pause execution for a number of seconds.
/// </summary>
public class Wait : BTNode
{
public float seconds = 0;
float future = -1;
public Wait(float seconds)
{
this.seconds = seconds;
}
public override BTState Tick()
{
if (future < 0)
future = Time.time + seconds;
if (Time.time >= future)
{
future = -1;
return BTState.Success;
}
else
return BTState.Continue;
}
public override string ToString()
{
return "Wait : " + (future - Time.time) + " / " + seconds;
}
}
/// <summary>
/// Activate a trigger on an animator.
/// </summary>
public class Trigger : BTNode
{
Animator animator;
int id;
string triggerName;
bool set = true;
//if set == false, it reset the trigger istead of setting it.
public Trigger(Animator animator, string name, bool set = true)
{
this.id = Animator.StringToHash(name);
this.animator = animator;
this.triggerName = name;
this.set = set;
}
public override BTState Tick()
{
if (set)
animator.SetTrigger(id);
else
animator.ResetTrigger(id);
return BTState.Success;
}
public override string ToString()
{
return "Trigger : " + triggerName;
}
}
/// <summary>
/// Set a boolean on an animator.
/// </summary>
public class SetBool : BTNode
{
Animator animator;
int id;
bool value;
string triggerName;
public SetBool(Animator animator, string name, bool value)
{
this.id = Animator.StringToHash(name);
this.animator = animator;
this.value = value;
this.triggerName = name;
}
public override BTState Tick()
{
animator.SetBool(id, value);
return BTState.Success;
}
public override string ToString()
{
return "SetBool : " + triggerName + " = " + value.ToString();
}
}
/// <summary>
/// Wait for an animator to reach a state.
/// </summary>
public class WaitForAnimatorState : BTNode
{
Animator animator;
int id;
int layer;
string stateName;
public WaitForAnimatorState(Animator animator, string name, int layer = 0)
{
this.id = Animator.StringToHash(name);
if (!animator.HasState(layer, this.id))
{
Debug.LogError("The animator does not have state: " + name);
}
this.animator = animator;
this.layer = layer;
this.stateName = name;
}
public override BTState Tick()
{
var state = animator.GetCurrentAnimatorStateInfo(layer);
if (state.fullPathHash == this.id || state.shortNameHash == this.id)
return BTState.Success;
return BTState.Continue;
}
public override string ToString()
{
return "Wait For State : " + stateName;
}
}
/// <summary>
/// Set a gameobject active flag.
/// </summary>
public class SetActive : BTNode
{
GameObject gameObject;
bool active;
public SetActive(GameObject gameObject, bool active)
{
this.gameObject = gameObject;
this.active = active;
}
public override BTState Tick()
{
gameObject.SetActive(this.active);
return BTState.Success;
}
public override string ToString()
{
return "Set Active : " + gameObject.name + " = " + active;
}
}
/*
/// <summary>
/// Wait for a signal to be received from a SendSignal state machine behaviour on an animator.
/// </summary>
public class WaitForAnimatorSignal : BTNode
{
internal bool isSet = false;
string name;
int id;
public WaitForAnimatorSignal(Animator animator, string name, string state, int layer = 0)
{
this.name = name;
this.id = Animator.StringToHash(name);
if (!animator.HasState(layer, this.id))
{
Debug.LogError("The animator does not have state: " + name);
}
else
{
SendSignal.Register(animator, name, this);
}
}
public override BTState Tick()
{
if (!isSet)
return BTState.Continue;
else
{
isSet = false;
return BTState.Success;
}
}
public override string ToString()
{
return "Wait For Animator Signal : " + name;
}
}
*/
public class Terminate : BTNode
{
public override BTState Tick()
{
return BTState.Abort;
}
}
public class Log : BTNode
{
string msg;
public Log(string msg)
{
this.msg = msg;
}
public override BTState Tick()
{
Debug.Log(msg);
return BTState.Success;
}
}
}
#if UNITY_EDITOR
namespace BTAI
{
public interface IBTDebugable
{
Root GetAIRoot();
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment