Unity游戏开发———行为树(Part2)

一.行为树节点代码示例

衔接Part1的节点实现代码如下(简易框架)

状态类型枚举

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum E_NodeState 
{
   Success,//成功
   Failed,//失败
   Running//进行中
}

节点基类

namespace BT
{
    /// <summary>
    /// 行为节点基类
    /// </summary>
    public abstract class BTBaseNode
    {
        protected List<BTBaseNode> childList = new List<BTBaseNode>();

        public BTBaseNode parent;

        //当前执行逻辑的子节点序号
        protected int NowIndex = 0;

        /// <summary>
        /// 添加子节点方法
        /// </summary>
        /// <param name="nodes"></param>
        public virtual void AddChild(params BTBaseNode[] nodes)
        {
            for (int i = 0; i < nodes.Length; i++)
            {
                nodes[i].parent = this;
                childList.Add(nodes[i]);
            }
        }

        public virtual void RemoveChild(int index)
        {

        }

        /// <summary>
        /// 执行节点逻辑 (Evaluate)
        /// </summary>
        /// <returns></returns>
        public abstract E_NodeState Excute();
    }
}

行为节点(ActionNode)

namespace BT
{
    /// <summary>
    /// 动作节点 (执行具体逻辑)此动作节点 是将逻辑推给 外部执行
    /// </summary>
    public class BTActionNode : BTBaseNode
    {
        public Func<E_NodeState> action;
        //使用委托承接外部函数,其中外部的逻辑需要一个E_NodeState返回值的函数了
        public E_NodeState nodeState;
        public BTActionNode(Func<E_NodeState> action)
        {
            this.action = action;
        }

        public override E_NodeState Excute()
        {
            if (action == null)
            {
                nodeState = E_NodeState.Failed;
                return nodeState;
            }

            switch (action.Invoke())
            {
                case E_NodeState.Failed:
                    nodeState = E_NodeState.Failed;
                    return E_NodeState.Failed;
                case E_NodeState.Running:
                    nodeState = E_NodeState.Running;
                    return E_NodeState.Running;
            }
            nodeState = E_NodeState.Success;
            return nodeState;
        }
    }
}

条件节点(ConditionNode)


namespace BT
{
    /// <summary>
    /// 条件节点 最终去进行一个条件判断
    /// </summary>
    public class BTConditionNode : BTBaseNode
    {

        public Func<bool> action;

        public BTConditionNode(Func<bool> action)
        {
            this.action = action;
        }

        public override E_NodeState Excute()
        {
            if (action == null)
            {
                return E_NodeState.Failed;
            }

            return action.Invoke() ? E_NodeState.Success : E_NodeState.Failed;
        }

    }
}

选择节点(SelectorNode)

namespace BT
{
    /// <summary>
    /// 选择节点 选择其中一个子节点执行,通常是第一个成功的子节点如果没有子节点成功
    /// </summary>
    public class BTSelectNode : BTBaseNode
    {
        public BTSelectNode() : base() { }

        public override E_NodeState Excute()
        {
            BTBaseNode childNode;
            if (childList.Count != 0)
            {
                childNode = childList[NowIndex];
                switch (childNode.Excute())//返回执行子节点的结果
                {
                    case E_NodeState.Success:
                        NowIndex = 0;
                        return E_NodeState.Success;
                    case E_NodeState.Failed:
                        ++NowIndex;
                        if (NowIndex == childList.Count)
                        {
                            NowIndex = 0;
                            return E_NodeState.Failed;
                        }
                        break;
                    case E_NodeState.Running:
                        return E_NodeState.Running;
                }
            }
            return E_NodeState.Failed;
        }
    }
}

顺序节点(SequenceNode)

namespace BT
{
    /// <summary>
    /// 顺序节点 按顺序执行他的子节点,直到一个子节点返回失败 
    /// </summary>
    public class BTSequenceNode : BTBaseNode
    {
        public BTSequenceNode() : base() { }
        public override E_NodeState Excute()
        {
            BTBaseNode childNode;
            if (childList.Count != 0)
            {
                childNode = childList[NowIndex];
                switch (childNode.Excute())
                {
                    case E_NodeState.Success:
                        ++NowIndex;
                        if (NowIndex == childList.Count)
                        {
                            NowIndex = 0;
                            return E_NodeState.Success;
                        }
                        break;
                    case E_NodeState.Failed:
                        NowIndex = 0;
                        return E_NodeState.Failed;
                    case E_NodeState.Running:
                        return E_NodeState.Running;
                    default:
                        break;
                }
            }
            return E_NodeState.Failed;
        }
    }
}

反向修饰节点(DecoratorNotNode)

namespace BT
{
    /// <summary>
    /// 反向修饰节点
    /// </summary>
    public class BTDecoratorNot : BTBaseNode
    {
        public Func<bool> action;

        public BTDecoratorNot(Func<bool> action)
        {
            this.action = action;
        }

        public override E_NodeState Excute()
        {
            if (action == null)
            {
                return E_NodeState.Failed;
            }

            return action.Invoke() ? E_NodeState.Failed : E_NodeState.Success;
        }
    }
}

黑板节点

Part1没有提到黑板节点,黑板节点用于读取和写入公共数据,以便行为树节点之间共享信息,比如存储和传递状态信息,或者存储敌人的位置,其他的环境变量等。

namespace BT
{
    public class DataBase 
    {
        public Dictionary<string, object> _dataContext = new Dictionary<string, object>();


        public void Setdata(string key, object value)
        {
            if (_dataContext.ContainsKey(key))
            {
                _dataContext[key] = value;
            }
            else
            {
                _dataContext.Add(key, value);
            }
        }

        public object GetData(string key)
        {
            if (_dataContext.ContainsKey(key))
            {
                return _dataContext[key];
            }
            Debug.LogWarning("没有此数据,无法获取");
            return null;
        }
        public bool ClearData(string key)
        {
            if (_dataContext.ContainsKey(key))
            {
                _dataContext.Remove(key);

                return true;
            }
            Debug.LogWarning("没有此数据,无法清除");
            return false;
        }
    }
}

其他的节点以此类推,而且每个人的书写风格和逻辑不同,本人就不一一贴代码了~

一.构建树型结构以及例子逻辑

树基类

namespace BT
{
    public abstract class BTTree : MonoBehaviour
    {
        protected BTBaseNode _root;

        protected DataBase _database = new DataBase();//每个树声明一个黑板节点

        public virtual void Start()
        {
            _root = SetUpTree();        
        }

        private void Update()
        {
            if(_root != null)
            {
                _root.Excute();
            }
        }

        public abstract BTBaseNode SetUpTree();

    }
}

敌人代码

在这里插入图片描述

public class Moster : BTTree
{
    public float moveSpeed;
    public float potrolRange;
    public float radius;
    private Vector3 waypoint;
    private Vector3 gardPos;
    private GameObject attackTarget;
    public bool isFound;
    public Vector3 currentPatrolPos;

    public override void Start()
    {
        base.Start();
        gardPos = transform.position;//设置巡逻点
        _database.Setdata("巡逻点", GetwayPoint());//一开始向设置好巡逻点
        currentPatrolPos = (Vector3)_database.GetData("巡逻点");
    }
    /// <summary>
    /// 构建行为树,将逻辑填入条件节点以及行为节点
    /// </summary>
    /// <returns></returns>
    public override BTBaseNode SetUpTree()
    {
        BTSelectNode bt_Select = new BTSelectNode();
        BTSequenceNode SeqPatrol = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//需要装饰节点
        BTSequenceNode Seqpursuit = SeqBTInit(FoundPlayer, Pursuit);
        BTSequenceNode SeqBack = SeqBTInitNot(FoundPlayer, MoveToPatrolPoint);//需要装饰节点
        BTSequenceNode SeqAtt = SeqBTInit(AttackRange, Attack);
        bt_Select.AddChild(SeqPatrol, Seqpursuit, SeqBack, SeqPatrol);
        return bt_Select;
    }
    //实例化顺序节点
    public BTSequenceNode SeqBTInit(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
    {
        BTSequenceNode seq = new BTSequenceNode();
        
        BTConditionNode bTCondition = new BTConditionNode(actionCondi);

        BTActionNode btActionNode = new BTActionNode(action);
        //将条件节点和行为节点添加至顺序节点作为子节点
        seq.AddChild(bTCondition, btActionNode);

        return seq;
    }

    public BTSequenceNode SeqBTInitNot(Func<bool> actionCondi = null, Func<E_NodeState> action = null)
    {
        BTSequenceNode seq = new BTSequenceNode();

        BTDecoratorNot bTCondition = new BTDecoratorNot(actionCondi);

        BTActionNode btActionNode = new BTActionNode(action);

        seq.AddChild(bTCondition, btActionNode);

        return seq;
    }
    //追击
    public E_NodeState Pursuit()
    {
        if (FoundPlayer())//在逻辑函数中,中断行为节点的Running状态
        {
            GameObject attTarget = _database.GetData("攻击对象") as GameObject;
            if (_database.GetData("攻击对象") != null)
            {
                MoveToTarget(attTarget.transform.position);
                return E_NodeState.Running;
            }
        }

        return E_NodeState.Failed;
    }

    bool FoundPlayer()//找到敌人
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
        foreach (var target in colliders)
        {
            if (target.CompareTag("Player"))
            {
                attackTarget = target.gameObject;
                _database.Setdata("攻击对象", attackTarget);
                isFound = true;
                return isFound;
            }
        }
        if (_database.GetData("攻击对象") != null)
        {
            _database.ClearData("攻击对象");
        }
        isFound = false;
        return isFound;
    }

    Vector3 GetwayPoint()//随机巡逻点
    {
        float RandomX = Random.Range(-potrolRange, potrolRange);
        float RandomZ = Random.Range(-potrolRange, potrolRange);
        Vector3 RandomPoint = new Vector3(gardPos.x + RandomX, transform.position.y, gardPos.z + RandomZ);
        waypoint = RandomPoint;
        _database.Setdata("巡逻点", waypoint);//
        return waypoint;
    }

    public E_NodeState MoveToPatrolPoint()//移动至巡逻点
    {
        if (!FoundPlayer())
        {
            if (Vector3.Distance(currentPatrolPos, transform.position) < 2f)
            {
                _database.Setdata("巡逻点", GetwayPoint());
                currentPatrolPos = (Vector3)_database.GetData("巡逻点");
                MoveToTarget(currentPatrolPos);
                Debug.Log("切换巡逻点");
                return E_NodeState.Running;
            }
            else
            {
                MoveToTarget(currentPatrolPos);
                return E_NodeState.Running;
            }
        }
        return E_NodeState.Failed;
    }

    public void MoveToTarget(Vector3 targetPos)
    {
        transform.position = Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);

        Debug.Log("移动方法");

    }


    private void OnDrawGizmos()
    {
        Gizmos.color = Color.green;
        Gizmos.DrawWireSphere(transform.position, radius);
        Gizmos.DrawWireSphere(transform.position, potrolRange);
    }
    //这里的攻击制作了一个打印hhhh
    public E_NodeState Attack()
    {
        Debug.Log("攻击玩家");
        return E_NodeState.Success;
    }
    //判断攻击范围
    bool AttackRange()
    {
        if ((_database.GetData("攻击对象") as GameObject) != null)
        {
            if (Vector3.Distance((_database.GetData("攻击对象") as GameObject).transform.position, transform.position) < 2f)
            {
                return true;
            }
        }

        return false;
    }
}

玩家移动的代码在这里就不贴了,实现方式简单且多种。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值