【分析】
如果行为树的节点很多,那么会存在要经过很多节点才会走到动作节点的情况。显然,性能上不如状态机。
每帧都需要重新遍历一系列节点才会走到动作节点,而实际上很多条件节点在数帧内不会有变化,这是造成性能问题的重要原因。
行为树每帧的Update其实就是在轮询这些条件,为避免轮询,我们自然而然的希望在一帧直接执行动作节点。
为此,需要将在Running状态的动作节点缓存,直接执行直到成功。
这种方式引起的问题是动作节点不一定总是会执行成功,可能在某一帧,某些条件改变了,动作需要被打断。
为了解决该问题,就需要给动作节点引入一些事件,当特定事件发生时,在动作节点内接收该事件,直接结束动作节点的运行,返回失败。
随后重新开始整个行为树的轮询,找到下一个动作节点。
这就是事件和轮询混合驱动的行为树。
事件本质上就是直接调用,当经过动作节点的路径较长,也即影响该动作节点的条件过多时,不可避免要通过代码调用发多种消息,这就失去了配置的意义。
需要将打断动作节点及节点的逻辑做成单独的Conditional节点,这些节点接收消息或监听变化,以决定是否打断其子节点。
通过轮询这些Conditional节点以简化发多种消息的调用。
当打断发生时,需要重新运行被打断节点的父节点确定新的分支走向。为此,需要将路径上所有节点做缓存。
这里要对控制节点做特殊处理,控制节点会控制自身的子节点运行,其状态受子节点影响,当打断存在时,子节点可能不需要再次运行,因此控制节点在控制子节点运行前需要知道当前是否被打断。
【代码实现】
public class BehaviorTree
{
public string btName;
public int btId;
public UpdateType updateType;
public DrivenType drivenType;
public float updateTime;
private float curTime;
public GameObject owner;
private Node rootNode;
private List<Node> allNodes = new List<Node>();
private Dictionary<int,Node> id2Node = new Dictionary<int,Node>();
private Dictionary<int,NodeInfo> id2NodeInfo = new Dictionary<int,NodeInfo>();
private Dictionary<Type, Dictionary<string, Delegate>> eventDic = new Dictionary<Type, Dictionary<string, Delegate>>();
private Node curActionNode;
private Stack<Node> activeNodes = new Stack<Node>();
private List<ConditionalNode> conditionalNodes = new List<ConditionalNode>();
public static Func<string, BehaviourTreeData> loadFunc = null;
public void Init(GameObject owner, string path, Func<string, BehaviourTreeData> loadFunc)
{
this.owner = owner;
//这里省略部分边界情况检查的逻辑,
if (!string.IsNullOrEmpty(path))
{
var data = loadFunc?.Invoke(path);
btId = owner.GetInstanceID();
updateType = data.updateType;
updateTime = data.updateTime;
drivenType = data.drivenType;
//通常初始化有两种写法,一种是在管理者中完成管理者自身以及被管理者的数据初始化;另一种是管理者完成自身的初始化后将被数据传递给被管理者,被管理者自身完成初始化
//第二种方式更加灵活可变,且不需要关注行为树结构是怎么样的,只需每个节点做好自身的初始化
//在第二种方式下涉及如何将数据递归传递的问题,为统一获取,将数据记录在管理者上,被管理者根据Id从管理者中获取,初始化后可选择将数据释放
foreach (var item in data.nodes)
{
id2NodeInfo[item.nodeId] = item;
}
var rootData = data.nodes[0];//默认第一个是rootNode数据
rootNode = new RootNode();
rootNode.Init(rootData, this);
id2NodeInfo.Clear();//也可以保留
}
}
public void Update(float time)
{
if(drivenType == DrivenType.Polling)
{
PollingUpdate(time);
}
else if(drivenType == DrivenType.PollingAndEvent)
{
if (curActionNode != null)
{
var actionStatus = curActionNode.Update();
if(actionStatus != NodeStatus.Running)
{
curActionNode = null;
PollingUpdate(time);
}
else
{
if (updateType == UpdateType.FixedTime)
{
curTime += time;
}
}
}
else
{
PollingUpdate(time);
}
}
else if(drivenType == DrivenType.EventDriven)
{
EventUpdate(time);
}
}
private void PollingUpdate(float time)
{
if (updateType == UpdateType.EveryFrame)
{
Update();
}
else if (updateType == UpdateType.FixedTime)
{
curTime += time;
if (curTime > updateTime)
{
curTime = 0;
Update();
}
}
}
private void EventUpdate(float time)
{
int index = -1;
for (int i = 0; i < conditionalNodes.Count; i++)
{
if (conditionalNodes[i].ConditionChanged()) //当条件节点条件改变,其后所有节点都需要重新计算,动作节点需要被打断
{
index = i;
break;
}
}
if (index != -1)
{
while(activeNodes.Count> 0 && activeNodes.Peek().nodeId != conditionalNodes[index].nodeId)//移除其后所有Active节点
{
PopNode(activeNodes.Peek());
}
conditionalNodes[index].Update();//重新运行该节点
if (conditionalNodes[index].StatusChanged(out var curStatus))
{
var curNode = (Node)conditionalNodes[index];
var parent = conditionalNodes[index].parent;
while (parent is ControlNode)//如果父节点是控制节点,其状态会与子节点相关,这里简化处理,直接重新运行
{
curNode = parent;
parent = parent.parent;
}
if(curNode != conditionalNodes[index])
{
while (activeNodes.Count > 0 && activeNodes.Peek().nodeId != curNode.nodeId)
{
PopNode(activeNodes.Peek());
}
curNode.Update();
}
}
}
else
{
var state = activeNodes.Peek().Update();//没有打断,再次运行末尾的动作节点
if(state != NodeStatus.Running)//运行结束,重新从根节点开始运行,也可以选择回退到上一个节点开始运行
{
activeNodes.Clear();
conditionalNodes.Clear();
rootNode.Update();
}
}
}
public void PushNode(Node node)
{
if (drivenType != DrivenType.EventDriven)
return;
activeNodes.Push(node);
if(node is ConditionalNode conditionalNode)
{
conditionalNodes.Add(conditionalNode);
}
}
private void PopNode(Node node)
{
if (drivenType != DrivenType.EventDriven)
return;
node.End();
activeNodes.Pop();
if(node is ConditionalNode conditionalNode)
{
conditionalNodes.Remove(conditionalNode);
}
}
public NodeStatus Update()
{
var status = rootNode.Update();
if(status != NodeStatus.Running)
{
rootNode.End();
}
return status;
}
public void Destroy()
{
foreach (var item in allNodes)
{
item.Destroy();
}
allNodes.Clear();
id2Node.Clear();
id2NodeInfo.Clear();
}
public NodeInfo GetNodeInfo(int nodeId)
{
return id2NodeInfo[nodeId];
}
public void AddNode(Node node)
{
allNodes.Add(node);
id2Node[node.nodeId] = node;
}
public void SetActionNode(Node node)
{
curActionNode = node;
}
private void RegisterEvent(string eventName, Delegate handler)
{
if (drivenType == DrivenType.Polling)
return;
if (!eventDic.TryGetValue(handler.GetType(), out var dic))
{
dic = new Dictionary<string, Delegate>();
eventDic[handler.GetType()] = dic;
}
if (!dic.TryGetValue(eventName, out var value))
{
dic[eventName] = handler;
}
else
{
dic[eventName] = Delegate.Combine(value, handler);
}
}
public void RegisterEvent(string eventNmae,Action action)
{
RegisterEvent(eventNmae, (Delegate)action);
}
public void RegisterEvent<T>(string eventNmae, Action<T> action)
{
RegisterEvent(eventNmae, (Delegate)action);
}
public void RegisterEvent<T1,T2>(string eventNmae, Action<T1,T2> action)
{
RegisterEvent(eventNmae, (Delegate)action);
}
public void RegisterEvent<T1, T2,T3>(string eventNmae, Action<T1, T2,T3> action)
{
RegisterEvent(eventNmae, (Delegate)action);
}
private Delegate GetDelegate(string eventName,Type type)
{
if (drivenType == DrivenType.Polling)
return null;
if (eventDic.TryGetValue(type, out var dic))
{
if (dic.TryGetValue(eventName, out var handler))
{
return handler;
}
}
return null;
}
public void SendEvent(string eventName)
{
if(GetDelegate(eventName, typeof(Action)) is Action action)
{
action();
}
}
public void SendEvent<T>(string eventName, T t)
{
if (GetDelegate(eventName, typeof(Action<T>)) is Action<T> action)
{
action(t);
}
}
public void SendEvent<T1,T2>(string eventName, T1 t1,T2 t2)
{
if (GetDelegate(eventName, typeof(Action<T1,T2>)) is Action<T1,T2> action)
{
action(t1,t2);
}
}
public void SendEvent<T1, T2,T3>(string eventName, T1 t1, T2 t2,T3 t3)
{
if (GetDelegate(eventName, typeof(Action<T1, T2,T3>)) is Action<T1, T2,T3> action)
{
action(t1, t2,t3);
}
}
}
public enum DrivenType
{
Polling,
PollingAndEvent,
EventDriven
}
public class Node
{
public string nodeName;
public int nodeId;
public NodeStatus status;
public BehaviorTree owner;
public Node parent;
public void Init(NodeInfo nodeInfo, BehaviorTree owner)
{
this.owner = owner;
this.nodeName = nodeInfo.nodeName;
this.nodeId = nodeInfo.nodeId;
OnInit(nodeInfo);
owner.AddNode(this);
//对于字段的配置可以通过字段名反射获取字段然后设置值,但这里允许的值是有限的,我们直接在每个节点写对应的处理
}
public NodeStatus Update()
{
owner.PushNode(this);
return OnUpdate();
}
public void End()//方法名字叫Exit也是一样的
{
OnEnd();
}
protected virtual void OnInit(NodeInfo nodeInfo)
{
}
protected virtual NodeStatus OnUpdate()
{
return NodeStatus.Success;
}
protected virtual void OnEnd()
{
}
public virtual void Destroy()
{
}
}
public class ConditionalNode:Node
{
public Node subNode;
protected override void OnInit(NodeInfo nodeInfo)
{
if (nodeInfo.subNodes.Count > 0)
{
var subNodeInfo = owner.GetNodeInfo(nodeInfo.subNodes[0]);
Type type = Type.GetType(subNodeInfo.nodeType);
subNode = (Node)Activator.CreateInstance(type);
subNode.Init(subNodeInfo, owner);
subNode.parent = this;
}
}
protected override NodeStatus OnUpdate()
{
return subNode.Update();
}
public virtual bool ConditionChanged()
{
return false;
}
public virtual bool StatusChanged(out NodeStatus status)
{
status = NodeStatus.Success;
return false;
}
public class ActionNode:Node
{
private bool start;
private bool stop;
protected override NodeStatus OnUpdate()
{
if (!start)
{
Start();
start = true;
}
var res = base.OnUpdate();
if(stop)
{
res = NodeStatus.Failure;
}
if (res == NodeStatus.Running)
{
owner.SetActionNode(this);
}
return res;
}
private void Start()
{
OnStart();
}
protected virtual void OnStart()
{
//各动作节点自定义注册事件
}
protected override void OnEnd()
{
start = false;
stop = false;
}
}