using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Unity.Profiling;
namespace MBT
{
[DisallowMultipleComponent]
// [RequireComponent(typeof(Blackboard))]
public class MonoBehaviourTree : MonoBehaviour
{
private static readonly ProfilerMarker _TickMarker = new ProfilerMarker("MonoBehaviourTree.Tick");
[HideInInspector]
public Node selectedEditorNode;
public bool repeatOnFinish = false;
public int maxExecutionsPerTick = 1000;
public MonoBehaviourTree parent;
///
/// Event triggered when tree is about to be updated
///
public event UnityAction onTick;
private List tickListeners = new List();
private Root rootNode;
private List executionStack;
private List executionLog;
private List interruptingNodes = new List();
public float LastTick { get; internal set; }
void Awake()
{
rootNode = GetComponent();
if (rootNode == null) {
Debug.LogWarning("Missing Root node in behaviour tree.", this);
}
// Find master parent tree and all nodes
MonoBehaviourTree masterTree = this.GetMasterTree();
Node[] nodes = GetComponents();
if(masterTree == this)
{
// Create lists with capicity
executionStack = new List(8);
executionLog = new List(nodes.Length);
// Set start node when tree is created first time
executionStack.Add(rootNode);
executionLog.Add(rootNode);
}
// Initialize nodes of tree/subtree
for (int i = 0; i < nodes.Length; i++)
{
Node n = nodes[i];
n.behaviourTree = masterTree;
n.runningNodeResult = new NodeResult(Status.Running, n);
}
}
private void EvaluateInterruptions()
{
if (interruptingNodes.Count == 0) {
return;
}
// Find node with highest priority - closest to the root (the smallest number)
Decorator abortingNode = interruptingNodes[0];
for (int i = 1; i < interruptingNodes.Count; i++)
{
Decorator d = interruptingNodes[i];
if (d.runtimePriority < abortingNode.runtimePriority) {
abortingNode = d;
}
}
// Revert stack
executionStack.Clear();
executionStack.AddRange(abortingNode.GetStoredTreeSnapshot());
// Restore flow of events in nodes after abort
for (int i = 0; i < executionStack.Count; i++)
{
Node node = executionStack[i];
if (node.status == Status.Running)
{
// This node is still running and might need to restore the state
node.OnBehaviourTreeAbort();
}
else if (node.status == Status.Success || node.status == Status.Failure)
{
// This node returned failure or success, so reenter it and call OnEnter
node.OnEnter();
}
// All nodes in execution stack should be in running state
node.status = Status.Running;
}
int nodeIndex = abortingNode.runtimePriority - 1;
// Sanity check
if (abortingNode != executionLog[nodeIndex]) {
Debug.LogWarning("Priority of node does not match with exectuion log");
}
// Abort nodes in log
ResetNodesTo(abortingNode, true);
// Reset aborting node
abortingNode.status = Status.Ready;
// Reset list and wait for new interruptions
interruptingNodes.Clear();
}
///
/// Update tree state.
///
public void Tick()
{
_TickMarker.Begin();
// Fire Tick event and notify listeners
onTick?.Invoke();
for (int i = 0; i < tickListeners.Count; i++)
{
tickListeners[i].OnBehaviourTreeTick();
}
// Check if there are any interrupting nodes
EvaluateInterruptions();
// Max number of traversed nodes
int executionLimit = maxExecutionsPerTick;
// Traverse tree
while (executionStack.Count > 0)
{
if (executionLimit == 0) {
LastTick = Time.time;
_TickMarker.End();
return;
}
executionLimit -= 1;
// Execute last element in stack
Node currentNode = executionStack[executionStack.Count - 1];
NodeResult nodeResult = currentNode.Execute();
// Set new status
currentNode.status = nodeResult.status;
if (nodeResult.status == Status.Running) {
// If node is running, then stop execution or continue children
Node child = nodeResult.child;
if (child == null) {
// Stop execution and continue next tick
LastTick = Time.time;
_TickMarker.End();
return;
} else {
// Add child to execution stack and execute it in next loop
executionStack.Add(child);
executionLog.Add(child);
// IMPORTANT: Priority must be > 0 and assigned in this order
child.runtimePriority = executionLog.Count;
child.OnAllowInterrupt();
child.OnEnter();
#if UNITY_EDITOR
// Stop execution if breakpoint is set on this node
if (child.breakpoint)
{
Debug.Break();
UnityEditor.Selection.activeGameObject = this.gameObject;
Debug.Log("MBT Breakpoint: " + child.title, this);
LastTick = Time.time;
_TickMarker.End();
return;
}
#endif
continue;
}
} else {
// Remove last node from stack and move up (closer to root)
currentNode.OnExit();
executionStack.RemoveAt(executionStack.Count - 1);
}
}
// Run this when execution stack is empty and BT should repeat
if (repeatOnFinish) {
Restart();
}
LastTick = Time.time;
_TickMarker.End();
}
public void AddTickListener(IMonoBehaviourTreeTickListener listener)
{
#if UNITY_EDITOR
if (tickListeners.Contains(listener))
{
Debug.LogErrorFormat(this, "Tick listener {0} has been already added.", listener);
}
#endif
tickListeners.Add(listener);
}
public void RemoveTickListener(IMonoBehaviourTreeTickListener listener)
{
tickListeners.Remove(listener);
}
///
/// This method should be called to abort tree to given node
///
/// Abort and revert tree to this node
internal void Interrupt(Decorator node)
{
if (!interruptingNodes.Contains(node)) {
interruptingNodes.Add(node);
}
}
internal void ResetNodesTo(Node node, bool aborted = false)
{
int i = executionLog.Count - 1;
// Reset status and get index of node
while (i >= 0)
{
Node n = executionLog[i];
if (n == node) {
break;
}
// If node is running (on exec stack) then call exit
if (n.status == Status.Running) {
n.OnExit();
// IMPROVEMENT: Abort event can be added or abort param onExit
}
n.status = Status.Ready;
n.OnDisallowInterrupt();
i -= 1;
}
// Reset log
i += 1;
if (i >= executionLog.Count) {
return;
}
executionLog.RemoveRange(i, executionLog.Count - i);
}
private void ResetNodes()
{
for (int i = 0; i < executionLog.Count; i++)
{
Node node = executionLog[i];
if (node.status == Status.Running)
{
node.OnExit();
}
node.OnDisallowInterrupt();
node.status = Status.Ready;
}
executionLog.Clear();
executionStack.Clear();
}
///
/// Resets state to root node
///
public void Restart()
{
ResetNodes();
executionStack.Add(rootNode);
executionLog.Add(rootNode);
}
internal void GetStack(ref Node[] stack)
{
// Resize array when size is too small
if (executionStack.Count > stack.Length)
{
// Node should not change priority and position during runtime
// It means the array will be resized once during first call of this method
Array.Resize(ref stack, executionStack.Count);
}
#if UNITY_EDITOR
// Additional sanity check in case nodes are reordered or changed in editor
if (stack.Length > executionStack.Count)
{
Debug.LogError("Changing order of MBT nodes during runtime might cause errors or unpredictable results.");
}
#endif
// Copy elements to provided array
executionStack.CopyTo(stack);
}
public Node GetRoot()
{
return rootNode;
}
public MonoBehaviourTree GetMasterTree()
{
if (parent == null)
{
return this;
}
return parent.GetMasterTree();
}
#if UNITY_EDITOR
void OnValidate()
{
if (maxExecutionsPerTick <= 0)
{
maxExecutionsPerTick = 1;
}
if (parent != null)
{
if (parent == this)
{
parent = null;
Debug.LogWarning("This tree cannot be its own parent.");
return;
}
if (transform.parent == null || parent.gameObject != transform.parent.gameObject)
{
// parent = null;
Debug.LogWarning("Parent tree should be also parent of this gameobject.", this.gameObject);
}
}
}
#endif
}
}