【分析】
行为树是否足够灵活强大依赖于足够丰富的各类条件节点和动作节点,在实现这些节点时,不可避免的,节点本身需要有一些参数供配置。
这些参数可以分为静态的固定值的参数以及动态读取设置的参数。
静态参数直接设置为Public即可,动态参数的难点在于可能需要获取不同模块的数据。(这里认为数据是这些模块的某个属性或字段)
节点是通用的,不可能在获取模块A的数据时在Update中调用A方法,在获取模块B的数据时调用B方法,这里需要一个统一的调用方式。
获取属性值的关键在于获取实例类和属性名,在代码上就可以通过实例类.属性名获取到属性值
因此,动态参数的配置内容为类名和字段名,需要通过代码自动生成[实例类.属性名]的调用,这必须要依赖反射。
理论上我们可以通过类A.类B.类C这样生成嵌套的调用。但这样通过程序去实现是成本较高,出现Bug容易导致错乱。
需要通过流程做规范,最多通过某个类就可以获取到值。
例如,在Unity中,脚本挂在GameObject上,可以通过统一的GetComponet获取;有个属性系统,可以通过一个的单例获取等等,这于具体的项目相关。这里我们用GetComponet
为了实现动态的设置和获取值,我们需要将这些动态值做封装,设置Getter和Setter
【行为树数据初始化】
参数配置是整个行为树配置的一部分,在此之前,我们需要补充实现前文缺省的初始化数据部分
我们需要有一个结构来描述行为树的配置数据,这个结构是用于运行时初始化数据的,可以将编辑数据和运行时数据结构分隔开来。
数据显而易见的可以分为三个层次:行为树的数据、节点的数据、参数的数据
比较难解决的是最下层的参数数据,这在很多配置数据中是通用的,其主要问题是参数类型、数量、值等各不相同,如何做一个统一的描述?
方式一是采用Json描述不同节点的参数,每个参数各自解析,便于任意扩展,可以用Unity提供的JsonUtility.ToJson和JsonUtility.FromJson做编码和解析。更进一步的,可以看到这和网络协议的编码和解码类似,在追求性能和效率时可以考虑用ProtoBuffer
方式二是使用固定的结构来存储所有类型数据,每个参数各自解析。
行为树节点基本参数是可以做明确的,这里采用方式二
【生命周期】
行为树需要初始化读取配置数据,节点也需要有初始化
行为树在Update中执行,轮询所有节点,节点需要有Update方法。行为树本身也不一定需要每帧都执行,可以根据实际情况定期执行等
大多数节点只在Update中执行即可,有些节点自身包含参数数据,需要重置。
有些特殊的节点在首次执行时,有特殊的逻辑处理,需要有特殊的Start方法
行为树销毁时,节点数据也需要销毁
【代码实现】
配置数据结构
#region BTConfigData
public class BehaviourTreeData:ScriptableObject
{
public string btName;
public UpdateType updateType;
public float updateTime;
public List<NodeInfo> nodes = new List<NodeInfo>();
}
[Serializable]
public class NodeInfo
{
public int nodeId;
public string nodeName;
public string nodeType;
public List<NodeStaticParams> staticParams = new List<NodeStaticParams>();
public List<NodeDynamicParams> dynamicParams = new List<NodeDynamicParams>();
public List<int> subNodes = new List<int>();
}
[Serializable]
public class NodeStaticParams
{
public string paramType;
public string paramName;
public List<int> intParams;
public List<string> strParams;
//节点类型是有限的,直接对每个类型做编码解码
public void Encode(string name,bool value)
{
paramType = "bool";
paramName = name;
intParams = new List<int>();
intParams.Add(value ? 1 : 0);
}
public void Encode(string name,int value)
{
paramType = "int";
paramName = name;
intParams = new List<int>();
intParams.Add(value);
}
public void Encode(string name, float value)
{
paramType = "float";
paramName = name;
intParams = new List<int>();
intParams.Add((int)value*1000);
}
public void Encode(string name, string value)
{
paramType = "string";
paramName = name;
strParams = new List<string>();
st