一个简单的游戏框架:行为树设计

代码:https://github.com/HushengStudent/myGameFramework

这里主要是介绍一下自己写的一个简单的行为树。

1、行为树?

①行为树是常见的游戏ai解决方案;常见做法就是程序实现好工具,由策划配置实现ai功能;这里就涉及到一个是行为树的框架实现和行为树编辑器实现;

②行为树节点分为两类,一是常见业务节点,可以有一个子节点;二是组合节点,一般没有具体业务逻辑,一般有多个子节点,只对子节点进行处理;以下是常见组合节点:

BTParallel 并行执行节点;

BTRandom 随机执行节点;

BTSelector 选择执行节点;

BTSequence 顺序执行节点;


2、行为树的框架

首先我们实现一个行为节点基类:

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<AbsBehavior> _list = new List<AbsBehavior>();

        public override bool IsComposite
        {
            get
            {
                return true;
            }
        }

        public AbsComposite(Hashtable table) : base(table)
        {
            _list.Clear();
        }

        public void Serialize(List<AbsBehavior> behaviorList)
        {
            _list = behaviorList;
        }

        protected sealed override void Update()
        {
            if (Reslut == BehaviorState.Running)
            {
                UpdateEx();
            }
            else if (Reslut == BehaviorState.Finish)
            {
                Reslut = BehaviorState.Success;
            }
        }
    }
以上是组合节点基类,提供Serialize(List<AbsBehavior> behaviorList)方法;


节点存在了,那接下来我们写一下行为树类,该类比较简单,就是从根节点开始,在Update中不断的调用根节点的Behave,具体可参看代码中的Framework.BehaviorTree;


3、行为树编辑器

行为树编辑器,可参考其他常见的行为树编辑器设计,这里我采用了NodeCanvas这个插件,效果图如下:


该插件支持导出json文件,我们把json文件在Unity后缀改成.bytes,运行时解析json文件;

解析过程参见BehaviorTreeFactory.cs:

public static class BehaviorTreeFactory
    {
        private static AbsBehavior _rootBehavior = null;
        private static Dictionary<int, AbsBehavior> _behaviorDict = new Dictionary<int, AbsBehavior>();
        private static Dictionary<int, List<int>> _connectionDict = new Dictionary<int, List<int>>();

        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<AbsBehavior>() { _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<TextAsset>(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<int> list;
                    if (!_connectionDict.TryGetValue(source, out list))
                    {
                        _connectionDict[source] = new List<int>();
                        list = _connectionDict[source];
                    }
                    list.Add(target);
                }
                else
                {
                    LogUtil.LogUtility.PrintError("[BehaviorTreeFactory]try get source id and target id error!");
                }
            }
        }

        private static void GenerateConnect(List<AbsBehavior> list)
        {
            List<AbsBehavior> nextList = new List<AbsBehavior>();
            AbsBehavior target;
            for (int i = 0; i < list.Count; i++)
            {
                target = list[i];
                int id = target.Id;
                List<int> connectList;
                if(!_connectionDict.TryGetValue(id,out connectList))
                {
                    continue;
                }
                List<AbsBehavior> sonList = new List<AbsBehavior>();
                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<RoleEntity>(ulong.MaxValue);
        if (entity == null)
        {
            entity = EntityMgr.Instance.CreateEntity<RoleEntity>(null, ulong.MaxValue, "BehaviorTree", null);
        }
        BehaviorTreeMgr.Instance.CreateBehaviorTree(entity, "BehaviourTree.BT", true);
    }

首先配置上图节点参数:


执行该行为树:


输出结果与设计所想一致!


4、工作流程

自己编写业务节点,再编写编辑器节点,再由策划配置节点,运行!

最后,自己写行为树的原因,是因为自由度比较大,感觉有的行为树用起来不太好用,所以自己写了一下,游戏ai还是有很多东西可以深入研究下!

猜你喜欢

转载自blog.csdn.net/husheng0/article/details/81056673