代码:https://github.com/HushengStudent/myGameFramework
这里主要是介绍一下自己写的一个简单的行为树。
①行为树是常见的游戏ai解决方案;常见做法就是程序实现好工具,由策划配置实现ai功能;这里就涉及到一个是行为树的框架实现和行为树编辑器实现;
②行为树节点分为两类,一是常见业务节点,可以有一个子节点;二是组合节点,一般没有具体业务逻辑,一般有多个子节点,只对子节点进行处理;以下是常见组合节点:
BTParallel 并行执行节点;
BTRandom 随机执行节点;
BTSelector 选择执行节点;
BTSequence 顺序执行节点;
首先我们实现一个行为节点基类:
public abstract class AbsBehavior
{
private bool _awake = false;
private int _id;
private BaseEntity _entity = null;
private BehaviorState _reslut = BehaviorState.Reset;
public int Id { get { return _id; } set { _id = value; } }
public BaseEntity Entity { get { return _entity; } }
public BehaviorState Reslut { get { return _reslut; } set { _reslut = value; } }
public virtual bool IsComposite { get { return false; } }
public BehaviorState Behave(BaseEntity entity)
{
if (!_awake)
{
_awake = true;
_entity = entity;
Reslut = BehaviorState.Running;
AwakeEx();
}
Update();
return Reslut;
}
public bool ResetBehavior()
{
if (Reslut == BehaviorState.Running)
return false;
Reset();
_awake = false;
_entity = null;
Reslut = BehaviorState.Reset;
return true;
}
public AbsBehavior(Hashtable table) { }
protected abstract void AwakeEx();
protected abstract void Update();
protected abstract void UpdateEx();
protected abstract void Reset();
}
Behave是行为树调用该节点时,执行的方法,调用时会初始化节点,并调用子类的UpdateEx方法,具体的业务逻辑写在子类的UpdateEx方法中;
所以节点都对应一个构造函数,初始化参数为HashTable;
public abstract class AbsDecorator : AbsBehavior
{
protected AbsBehavior _nextNode = null;
public AbsBehavior NextNode { get { return _nextNode; } }
public AbsDecorator(Hashtable table) : base(table)
{
_nextNode = null;
}
public void Serialize(AbsBehavior node)
{
_nextNode = node;
}
protected sealed override void Update()
{
if (Reslut == BehaviorState.Running)
{
UpdateEx();
}
else if (Reslut == BehaviorState.Finish)
{
if (_nextNode == null)
{
Reslut = BehaviorState.Success;
return;
}
if (_nextNode != null && _nextNode.Behave(Entity) == BehaviorState.Success)
{
Reslut = BehaviorState.Success;
}
}
}
}
以上是业务节点基类,提供Serialize(AbsBehavior node)方法
public abstract class AbsComposite : AbsBehavior
{
protected List _list = new List();
public override bool IsComposite
{
get
{
return true;
}
}
public AbsComposite(Hashtable table) : base(table)
{
_list.Clear();
}
public void Serialize(List behaviorList)
{
_list = behaviorList;
}
protected sealed override void Update()
{
if (Reslut == BehaviorState.Running)
{
UpdateEx();
}
else if (Reslut == BehaviorState.Finish)
{
Reslut = BehaviorState.Success;
}
}
}
以上是组合节点基类,提供Serialize(List节点存在了,那接下来我们写一下行为树类,该类比较简单,就是从根节点开始,在Update中不断的调用根节点的Behave,具体可参看代码中的Framework.BehaviorTree;
行为树编辑器,可参考其他常见的行为树编辑器设计,这里我采用了NodeCanvas这个插件,效果图如下:
该插件支持导出json文件,我们把json文件在Unity后缀改成.bytes,运行时解析json文件;
解析过程参见BehaviorTreeFactory.cs:
public static class BehaviorTreeFactory
{
private static AbsBehavior _rootBehavior = null;
private static Dictionary _behaviorDict = new Dictionary();
private static Dictionary> _connectionDict = new Dictionary>();
public static BehaviorTree CreateBehaviorTree(BaseEntity entity, string path)
{
InitDict(path);
if (_rootBehavior == null)
{
LogUtil.LogUtility.PrintError("[BehaviorTreeFactory]Root Behavior is null!");
return null;
}
GenerateConnect(new List() { _rootBehavior });
BehaviorTree tree = new BehaviorTree(_rootBehavior, entity);
_rootBehavior = null;
_behaviorDict.Clear();
_connectionDict.Clear();
return tree;
}
private static void InitDict(string path)
{
_rootBehavior = null;
_behaviorDict.Clear();
_connectionDict.Clear();
TextAsset json = Resources.Load(path);
string content = json.text.Replace("\r", "").Replace("\n", "");
Hashtable table = MiniJsonExtensions.hashtableFromJson(content);
ArrayList nodeList = table["nodes"] as ArrayList;
ArrayList connectionList = table["connections"] as ArrayList;
for (int i = 0; i < nodeList.Count; i++)
{
Hashtable nodeTable = nodeList[i] as Hashtable;
var id = 0;
if (int.TryParse(nodeTable["$id"].ToString(), out id))
{
AbsBehavior absBehavior = CreateBehavior(nodeTable, id);
_behaviorDict[id] = absBehavior;
if (_rootBehavior == null)
{
_rootBehavior = absBehavior;
}
else
{
if (absBehavior.Id < _rootBehavior.Id)
{
_rootBehavior = absBehavior;
}
}
}
else
{
LogUtil.LogUtility.PrintError("[BehaviorTreeFactory]try get node id error!");
}
}
for (int i = 0; i < connectionList.Count; i++)
{
Hashtable connectionTable = connectionList[i] as Hashtable;
int source = 0;
int target = 0;
Hashtable scurceNode = connectionTable["_sourceNode"] as Hashtable;
Hashtable targetNode = connectionTable["_targetNode"] as Hashtable;
if (int.TryParse(scurceNode["$ref"].ToString(), out source)
&& int.TryParse(targetNode["$ref"].ToString(), out target))
{
List list;
if (!_connectionDict.TryGetValue(source, out list))
{
_connectionDict[source] = new List();
list = _connectionDict[source];
}
list.Add(target);
}
else
{
LogUtil.LogUtility.PrintError("[BehaviorTreeFactory]try get source id and target id error!");
}
}
}
private static void GenerateConnect(List list)
{
List nextList = new List();
AbsBehavior target;
for (int i = 0; i < list.Count; i++)
{
target = list[i];
int id = target.Id;
List connectList;
if(!_connectionDict.TryGetValue(id,out connectList))
{
continue;
}
List sonList = new List();
for (int j = 0; j < connectList.Count; j++)
{
int sonId = connectList[j];
AbsBehavior son;
if(!_behaviorDict.TryGetValue(sonId,out son))
{
continue;
}
if (son != null)
{
sonList.Add(son);
}
}
if (target.IsComposite)
{
AbsComposite composite = target as AbsComposite;
if (sonList.Count < 1)
{
composite.Serialize(null);
}
else
{
composite.Serialize(sonList);
nextList.AddRange(sonList);
}
}
else
{
AbsDecorator decorator = target as AbsDecorator;
if (sonList.Count < 1)
{
decorator.Serialize(null);
}
else
{
decorator.Serialize(sonList[0]);
nextList.Add(sonList[0]);
}
}
}
if (nextList.Count > 0)
{
GenerateConnect(nextList);
}
}
private static AbsBehavior CreateBehavior(Hashtable table, int id)
{
AbsBehavior behavior = null;
string type = table["$type"].ToString();
switch (type)
{
//顺序执行节点;
case "NodeCanvas.BehaviourTrees.BTSequence":
behavior = new BTSequence(table);
break;
//随机执行节点;
case "NodeCanvas.BehaviourTrees.BTRandom":
behavior = new BTRandom(table);
break;
//选择执行节点;
case "NodeCanvas.BehaviourTrees.BTSelector":
behavior = new BTSelector(table);
break;
//等级条件执行节点;
case "NodeCanvas.BehaviourTrees.BTLevel":
behavior = new BTLevel(table);
break;
//日志执行节点;
case "NodeCanvas.BehaviourTrees.BTLog":
behavior = new BTLog(table);
break;
default:
break;
}
if (behavior != null)
behavior.Id = id;
return behavior;
}
}
测试插件使用:
public void BehaviorTreeTest()
{
var entity = EntityMgr.Instance.GetEntity(ulong.MaxValue);
if (entity == null)
{
entity = EntityMgr.Instance.CreateEntity(null, ulong.MaxValue, "BehaviorTree", null);
}
BehaviorTreeMgr.Instance.CreateBehaviorTree(entity, "BehaviourTree.BT", true);
}
首先配置上图节点参数:
执行该行为树:
输出结果与设计所想一致!
自己编写业务节点,再编写编辑器节点,再由策划配置节点,运行!
最后,自己写行为树的原因,是因为自由度比较大,感觉有的行为树用起来不太好用,所以自己写了一下,游戏ai还是有很多东西可以深入研究下!