Snippets
For those interested in reading some of my code, I’ve made a collection of the more interesting scripts I’ve written over the years: https://github.com/Roboolet/snippets
Below is an example of one of my more comprehensive systems. This is a custom behaviour system for use by AI agents in Unity. It’s somewhat based on Unreal Engine’s behaviour tree, which I personally like.
Agent class
This is the only MonoBehaviour used. It creates a predefined behaviour tree and runs it.
// ... hidden for brevity ...
private void Awake(){
root = behaviour.BuildTree();
blackboard = new Blackboard();
blackboard.Set(CommonBB.AGENT, this);
blackboard.Set(CommonBB.AGENT_GAMEOBJECT, gameObject);
Blackboard.Global.ListAdd(CommonBB.AGENTS_LIST, this);
// set values of pre-defined blackboard entries
for (int i = 0; i < behaviour.blackboardValues.Length; i++)
{
BlackboardInitialValue bbVal = behaviour.blackboardValues[i];
switch (bbVal.type)
{
case BlackboardValueType.INT: blackboard.Set(bbVal.key, bbVal._int); break;
case BlackboardValueType.FLOAT: blackboard.Set(bbVal.key, bbVal._float); break;
case BlackboardValueType.STRING: blackboard.Set(bbVal.key, bbVal._string); break;
case BlackboardValueType.VECTOR3: blackboard.Set(bbVal.key, bbVal._vector3); break;
}
}
}
// ... hidden for brevity ...
private void Update(){
if (active && lastTickTime + (1 / ticksPerSecond) < Time.time)
{
// set commonly used variables in the blackboard
float tickDelta = Time.time - lastTickTime;
lastTickTime = Time.time;
tickCounter++;
blackboard.Set(CommonBB.TICK_DELTA, tickDelta);
blackboard.Set(CommonBB.TICK_TOTAL, tickCounter);
// every implementation of ANode appends their node name to this,
// giving the "path" of the current node when the tick finishes.
// it is necessary to clear this every tick beforehand.
blackboard.Set(CommonBB.CURRENT_NODE, "");
// run the root, thereby stepping forward in the tree
NodeReturnState rootReturn = root.Execute(blackboard);
if (logCurrentNode)
{
Debug.Log(GetNodeLog());
}
if (rootReturn == NodeReturnState.ERROR)
{
Debug.LogError("Behaviour tree of " + transform.name +
" returns ERROR, stopping execution");
active = false;
}
}
}
ANode class
A default implementation of the INode interface, which is the basic building block for everything in the behaviour tree. ANode can be inherited from directly to creating “Action” type nodes (Jump, Wait, etc). It is also inherited from by the NDecorator and NComposite node templates, which have a single child and multiple children respectively.
public abstract class ANode : INode
{
private readonly string nodeName;
protected ANode()
{
nodeName = this.GetType().Name;
}
public NodeReturnState Execute(Blackboard bb)
{
if (bb == null)
{
Debug.LogError(nodeName + " does not have a Blackboard reference");
return NodeReturnState.ERROR;
}
else
{
NodeReturnState ret = OnExecute(bb);
// log the current node, used for debugging only
string newPath;
if (bb.TryGet(CommonBB.CURRENT_NODE, out string oldPath))
{
newPath = oldPath + nodeName + "=" + ret.ToString() + " <<-- ";
}
else
{
newPath = nodeName;
}
bb.Set(CommonBB.CURRENT_NODE, newPath);
return ret;
}
}
protected virtual NodeReturnState OnExecute(Blackboard bb)
{
Debug.LogWarning("This node has no implemented behaviour");
return NodeReturnState.ERROR;
}
}
Blackboard class
The blackboard is how communication between nodes happens. You can for example use a node to find a specific object in the scene, save it to the blackboard, and now other nodes can use that reference to do things (like move towards it)
public bool TryGet<T>(string _key, out T _value)
{
if (String.IsNullOrEmpty(_key) || !objects.ContainsKey(_key))
{
_value = default(T);
Debug.LogWarning("Blackboard does not contain item with key \""+_key + "\"");
return false;
}
else
{
object obj = objects[_key];
if (obj.GetType() == typeof(T))
{
_value = (T)objects[_key];
return true;
}
else
{
Debug.LogWarning("Blackboard does not contain item with key \""+_key + "\" that matches Type "+ typeof(T).Name);
_value = default(T);
return false;
}
}
}