接上篇,AI预定义逻辑可以把条件和行为逻辑看成一个Task,一个AI有多个Task,行为树把单个Task中条件和行为、多个Task之间的关系进行细分成节点关系,通过树形结构来分层,系统每帧从树的根向下遍历,根据各节点的功能来执行子节点。

行为树主要由以下四种节点抽象而成组合节点、装饰节点、条件节点、行为节点。

组合节点(Composites)

主要包含:Sequence顺序条件,Selector选择条件,Parallel平行条件以及他们之间相互组合的条件。

②修饰节点(Decorator)

连接树叶的树枝,就是各种类型的修饰节点,这些节点决定了 AI 如何从树的顶端根据不同的情况,来沿着不同的路径来到最终的叶子这一过程。
如让子节点循环操作(LOOP)或者让子task一直运行直到其返回某个运行状态值(Util),或者将task的返回值取反(NOT)等等

③条件节点(Conditinals)

用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。

④行为节点(Action)

行为节点是真正做事的节点,行为节点在树的最末端,都是叶子节点(注意叶子节点是没有子节点的),就是这些 AI 实际上去做事情的命令;

通过使用行为树内的节点之间的关联来驱动角色的行为,比直接用具体的代码告诉一个角色去做什么事情,要来得有意思得多,这也是行为树最让人兴奋的一点。这样我们只要抽象好行为,就不用去理会战斗中具体发生了什么。

修饰节点条件节点可以放在组合节点中进行处理,因此组成三个节点:

1.根节点Root

2.逻辑节点:负责子节点的执行顺序和子节点能否执行。

Sequence          : 选择第一个成功计算的子对象作为活动子对象。

PrioritySelector  :计算算当前活动子节点,或者第一个子节点(如果没有活动子节点)。如果通过计算,标记当前活动子节点,或者第一个子节点(如果没有可用的活动子节点),如果结果是结束,那么将活动子节点更改为下一个。

Parallel               : 计算所有子节点,如果其中任何子节点计算失败,则当前节点失败。

3.行为节点


案例:一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。

条件:大于5五米、小于等于5米

行为:巡逻、追逐玩家

所有节点的根节点:NodeBase

using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace VAE.BehaviourTree
{public abstract class NodeBase{public enum NodeResult              //节点返回状态 {Ended = 1,Running = 2,}public PreconditionBase preconditionBase;    // 用来检查节点是否可以输入public Database database;public bool activated;                                      //节点激活状态public float interval;                                        //节点更新间隔private float lastTimeEvaluated = 0;protected List<NodeBase> children;public List<NodeBase> Children { get { return this.children; } }public NodeBase() : this(null) { }public NodeBase(PreconditionBase preconditionBase){this.preconditionBase = preconditionBase;}//激活节点public virtual void Active(Database database){if (this.activated) return;this.database = database;if (this.preconditionBase != null){this.preconditionBase.Active(database);}if (this.children != null){foreach (var child in this.children){child.Active(database);}}this.activated = true;}//没帧进行校验public virtual NodeResult Update(){return NodeResult.Ended;}protected virtual bool DoEvaluate() { return true; }public bool Evaluate(){bool coolDownOK = this.CheckTimer();return this.activated && coolDownOK && (this.preconditionBase == null || preconditionBase.Check()) && this.DoEvaluate();}public virtual void Clear() { }public virtual void AddChild(NodeBase nodeBase){if (this.children == null){this.children = new List<NodeBase>();}if (nodeBase != null){this.children.Add(nodeBase);}}public virtual void RemoveChild(NodeBase nodeBase){if (this.children != null && nodeBase != null){this.children.Remove(nodeBase);}}//用来处理每个节点的检查间隔private bool CheckTimer(){if (Time.time - this.lastTimeEvaluated > this.interval){this.lastTimeEvaluated = Time.time;return true;}return false;}}
}

条件基类:PreconditionBase        条件会注册到逻辑节点进行判定当前节点是否可以进行

using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace VAE.BehaviourTree
{public abstract class PreconditionBase : NodeBase{public PreconditionBase() : base(null) { }public abstract bool Check();       // 条件检测public override NodeResult Update(){bool success = this.Check();if (success){return NodeResult.Ended;}else{return NodeResult.Running;}}}
}

逻辑节点:PrioritySelector

using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace VAE.BehaviourTree
{/** 优先级选择器选择第一个成功计算的子对象作为活动子对象。*/public class PrioritySelector : NodeBase{private NodeBase activeChild;       //活动子对象public PrioritySelector(PreconditionBase preconditionBase = null) : base(preconditionBase){}protected override bool DoEvaluate(){foreach (var child in this.children){if (child.Evaluate())       //选择计算成功的子对象{if (this.activeChild != null && this.activeChild != child){this.activeChild.Clear();}this.activeChild = child;return true;}}this.activeChild = null;return false;}public override NodeResult Update(){if (this.activeChild == null){return NodeResult.Ended;}var result = this.activeChild.Update();if (result != NodeResult.Running){this.activeChild.Clear();this.activeChild = null;}return result;}}}

行为节点:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace VAE.BehaviourTree
{/*行为节点基类该节点不能进行增加和删除子节点,只用来处理行为*/public class ActionBase : NodeBase{private enum ActionStatus{Ready = 1,Running = 2,}private ActionStatus actionStatus = ActionStatus.Ready;public ActionBase(PreconditionBase preconditionBase = null) : base(preconditionBase){}protected virtual void Enter(){// 当行为进入时触发的函数   一般用来处理:播放动画等}protected virtual void Exit(){// 当行为退出时触发的函数}// 执行逻辑protected virtual NodeResult Execute(){return NodeResult.Running;}public override NodeResult Update(){NodeResult result = NodeResult.Ended;if (this.actionStatus == ActionStatus.Ready){this.Enter();                                                       // 进入执行行为this.actionStatus = ActionStatus.Running;}if (this.actionStatus == ActionStatus.Running){result = this.Execute();if (result != NodeResult.Running)       //代表执行结束{this.Exit();this.actionStatus = ActionStatus.Ready;}}return result;}public override void Clear(){if (this.actionStatus != ActionStatus.Ready){this.Exit();this.actionStatus = ActionStatus.Ready;}}public override void AddChild(NodeBase nodeBase){Debug.LogError("Action: Cannot add a node into Action.");}public override void RemoveChild(NodeBase nodeBase){Debug.LogError("Action: Cannot remove a node into Action.");}}
}

用于AI行为树,各节点之间数据修改与获取的:Database

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;namespace VAE.BehaviourTree
{public class Database : MonoBehaviour{private List<object> _database = new List<object>();private List<string> _dataNames = new List<string>();public T GetData<T>(string dataName){int dataId = IndexOfDataId(dataName);if (dataId == -1) Debug.LogError("Database: Data for " + dataName + " does not exist!");return (T)_database[dataId];}// Should use this function to get data!public T GetData<T>(int dataId){return (T)_database[dataId];}public void SetData<T>(string dataName, T data){int dataId = GetDataId(dataName);_database[dataId] = (object)data;}public void SetData<T>(int dataId, T data){_database[dataId] = (object)data;}public int GetDataId(string dataName){int dataId = IndexOfDataId(dataName);if (dataId == -1){_dataNames.Add(dataName);_database.Add(null);dataId = _dataNames.Count - 1;}return dataId;}private int IndexOfDataId(string dataName){for (int i = 0; i < _dataNames.Count; i++){if (_dataNames[i].Equals(dataName)) return i;}return -1;}public bool ContainsData(string dataName){return IndexOfDataId(dataName) != -1;}}
}

行为树驱动脚本:BTTreeBase     处理行为树初始化,条件判定和更新

using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace VAE.BehaviourTree
{/**/public class BTTreeBase : MonoBehaviour{protected NodeBase root = null;[HideInInspector]public Database database;[HideInInspector]public bool isRunning = true;public const string RESET = "RESET";private static int resetId;private void Awake(){this.Init();//当所有 限定节点 逻辑条件 添加完毕 再进行数据激活this.root.Active(this.database);}private void Update(){if (!this.isRunning) return;if (this.database.GetData<bool>(BTTreeBase.RESET)){this.Reset();this.database.SetData<bool>(BTTreeBase.RESET, false);}if (this.root.Evaluate()){this.root.Update();}}protected virtual void Init(){this.database = this.GetComponent<Database>();if (this.database == null){this.database = this.gameObject.AddComponent<Database>();}BTTreeBase.resetId = this.database.GetDataId(BTTreeBase.RESET);this.database.SetData<bool>(BTTreeBase.resetId, false);}protected void Reset(){this.root.Clear();}}
}

外部逻辑:

处理限制条件:大于5五米、小于等于5米

using System.Collections;
using System.Collections.Generic;
using UnityEngine;using VAE.BehaviourTree;public class Precdontion_TransformDistance : PreconditionBase
{public enum precdFunction{LessThan = 1,GreaterThan = 2,}private string minChaseDistanceStr;private int minChaseDistanceId;private Transform itemTran;                     // 目标对象private Transform enemyAI;private precdFunction func;public Precdontion_TransformDistance(string minChaseDistanceStr, Transform itemTran, precdFunction func){this.minChaseDistanceStr = minChaseDistanceStr;this.itemTran = itemTran;this.func = func;}public override void Active(Database database){base.Active(database);this.minChaseDistanceId = this.database.GetDataId(this.minChaseDistanceStr);this.enemyAI = this.database.transform;}public override bool Check(){if (itemTran == null) return false;Vector3 offset = itemTran.position - this.enemyAI.position;var minDistance = this.database.GetData<float>(this.minChaseDistanceId);if (this.func == precdFunction.GreaterThan){return (offset.sqrMagnitude >= minDistance);}else{return (offset.sqrMagnitude <= minDistance);}}}

行为逻辑:

巡逻:Action_Partoll

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VAE.BehaviourTree;public class Action_Partoll : ActionBase
{private List<Transform> partollPos;private int index = 0;private Transform enemyAI;private float moveSpeed;public Action_Partoll(List<Transform> partollPos, float speed){this.partollPos = partollPos;this.moveSpeed = speed;}public override void Active(Database database){base.Active(database);this.enemyAI = this.database.transform;}protected override NodeResult Execute(){var distance = Vector3.Distance(this.enemyAI.position, this.partollPos[this.index].position);if (distance <= 0.1f){this.index++;this.index %= this.partollPos.Count;}Vector3 direction = (this.partollPos[this.index].position - this.enemyAI.position).normalized;this.enemyAI.position += direction * this.moveSpeed * Time.deltaTime;return NodeResult.Running;}}

追逐指定目标:Action_ChaseItem

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VAE.BehaviourTree;public class Action_ChaseItem : ActionBase
{private Transform itemTran;      //目标private float moveSpeed;private Transform enemyAI;private string minChaseDistanceStr;private int minChaseDistanceId;public Action_ChaseItem(string minChaseDistanceStr, Transform itemTran, float speed){this.itemTran = itemTran;this.moveSpeed = speed;this.minChaseDistanceStr = minChaseDistanceStr;}public override void Active(Database database){base.Active(database);      // 基类函数一定要执行this.minChaseDistanceId = this.database.GetDataId(this.minChaseDistanceStr);this.enemyAI = this.database.transform;}protected override NodeResult Execute(){if (this.CheckArrived()){return NodeResult.Ended;}Vector3 direction = (this.itemTran.position - this.enemyAI.position).normalized;this.enemyAI.position += direction * this.moveSpeed * Time.deltaTime ;return NodeResult.Running;}//检验是否应超出追逐范围private bool CheckArrived(){Vector3 offset = this.itemTran.position - this.enemyAI.position;       // 怪物AI -》目的地的向量float tmpMinDistance = this.database.GetData<float>(this.minChaseDistanceId);return offset.sqrMagnitude > tmpMinDistance * tmpMinDistance;}}

EnemyAI:AI初始化

using System.Collections;
using System.Collections.Generic;
using VAE.BehaviourTree;
using UnityEngine;public class EnemyAI : BTTreeBase
{public Transform itemTran;public float speed = 3;public List<Transform> partollPos;public float partollSpeed = 4;public float minChaseDistance = 5;      // 最小追逐距离public const string MINCHASEDISTANCE = "MINCHASEDISTANCE";protected override void Init(){base.Init();        // 一定要调用基类函数this.root = new PrioritySelector();         // 创建行为树根节点//一个怪物在指定路径巡逻,当玩家怪物与玩家距离小于等于5米时会追逐玩家,当大于5米时会继续巡逻。这里就分为了巡逻、追逐两种状态。//this.database.SetData<float>(MINCHASEDISTANCE, this.minChaseDistance);Precdontion_TransformDistance precd_TransformDistance_greater = new Precdontion_TransformDistance(MINCHASEDISTANCE, itemTran, Precdontion_TransformDistance.precdFunction.GreaterThan);Precdontion_TransformDistance precd_TransformDistance_less = new Precdontion_TransformDistance(MINCHASEDISTANCE, itemTran, Precdontion_TransformDistance.precdFunction.LessThan);PrioritySelector tree2_1 = new PrioritySelector(precd_TransformDistance_greater);Action_Partoll action_Partoll = new Action_Partoll(this.partollPos, this.partollSpeed);tree2_1.AddChild(action_Partoll);PrioritySelector tree2_2 = new PrioritySelector(precd_TransformDistance_less);Action_ChaseItem action_ChaseItem = new Action_ChaseItem(MINCHASEDISTANCE, itemTran, this.speed);tree2_2.AddChild(action_ChaseItem);this.root.AddChild(tree2_1);this.root.AddChild(tree2_2);}
}


像上述,条件节点,行为节点还可以继续进行细划分,分的越详细代码复用越高。比如:AI巡逻行为接着可划分为朝指定位置移动行为,这就需要新的逻辑节点来处理,从而达到复用追逐和巡逻两个地方的行为功能。

行为树缺点:为树是依赖设计者的固定架构的,很不灵活,做的选择不一定是最优选择,而且每次都要经过大量的逻辑判断,性能消耗严重。

而后另一种AI算法:GOAP(目标导向型行动计划)更好的解决问题。

目前对行为树理解还不太深刻,后续会继续增加。。。

主要参考:

https://zhuanlan.zhihu.com/p/94850561

https://www.jianshu.com/p/23f79a365c10

【AI】行为树(Behaviour Tree)相关推荐

  1. 使用行为树(Behavior Tree)实现游戏AI

    注意:本文版权归Csdn AKara所有,此处纯粹转载,如有再转,请严格按如下方式显示标明原创作者及出处,以示尊重!! 关注公众号 风色年代(itfantasycc) 200G Unity资料合集送上 ...

  2. AI行为树的基础运作原理

    欢迎捉虫! 之前我研究了一下基于switch case语句的FSM状态机的使用,后来遇到了很多问题. 比如当角色的行为很多时,代码结构相当混乱(你需要考虑每一种状态之间的联系). 所以,当角色的行为愈 ...

  3. 使用行为树(Behavior Tree)实现网游奖励掉落系统

    原地址:http://blog.csdn.net/akara/article/details/6165421 [原创]使用行为树(Behavior Tree)实现网游奖励掉落系统 by AKara 2 ...

  4. 行为树 Behavior Tree 原理

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接: https://blog.csdn.net/LIQIANGEASTSUN/arti ...

  5. 行为树 Behavior Tree 原理 一

    行为树 Behavior Tree 原理 一 行为树 结构图如下,一棵倒置的树 行为树采用节点描述行为逻辑,主要节点类型有: 组合节点:选择节点.顺序节点.随机选择节点.随机顺序节点.随机权重节点.并 ...

  6. Unity 可视化编辑工具 树节点 Tree Node Editor 四

    Unity 可视化编辑工具 树节点 Tree Node Editor 四 接上一篇Unity Behavior Tree Editor 行为树编辑器实现 三 上一篇主要讲解编辑器的使用,像关于自定义条 ...

  7. 游戏AI - 行为树Part2:框架

    上次提到,行为树可以让代码更加模块化,也可以提高重用性.这次我们就来看看一个行为树框架是什么样的. 如果你对行为树比较陌生,可以先浏览一下游戏AI - 行为树Part1:简介. 关键词 在展开之前,我 ...

  8. Tianchi发布最新AI知识树!

    ↑↑↑关注后"星标"Datawhale 每日干货 & 每月组队学习,不错过 Datawhale干货 来源:Tianchi,方向:AI内容 近期Tianchi开放了9大训练营 ...

  9. 数据结构与算法(C++)– 树(Tree)

    数据结构与算法(C++)– 树(Tree) 1.树的基础知识 树(tree): 一些节点的集合,可以为空集 子树(sub tree): 树的子集 根(root): 树的第一个节点 孩子和父亲(Chil ...

  10. C语言实现段树segment tree(附完整源码)

    C语言实现段树segment tree 段树结构体定义 实现以下6个接口 完整实现和main测试源码 段树结构体定义 typedef struct segment_tree {void *root; ...

最新文章

  1. 数字图像处理:腐蚀与膨胀操作
  2. Zookeeper 的典型应用场景场景
  3. 被Zoom逼疯的歪果仁,造出了视频会议机器人,同事已笑疯丨开源
  4. 可持久化-可持久化字典树
  5. flex 实现图片播放 方案二 把临时3张图片预加载放入内存
  6. android java语言_android可以用java语言开发吗
  7. 如何批量打印Excel文件
  8. 香橙派OrangePi Zero开发板的WiFi连接测试
  9. uniapp调用微信小程序人脸识别步骤
  10. 关于投入产出表中的一些原则和方法论
  11. 如何使用CubeMx生成一个DFU工程
  12. Get busy living--or get busy dying
  13. 2、Class和Subclass
  14. mysql 存储视频_数据库中怎样存储视频?谢谢各位
  15. JAVA点餐系统计算机毕业设计Mybatis+系统+数据库+调试部署
  16. openwrt pptpd start 报错validation filed
  17. Go语言slice详解
  18. 用户6.5亿 墨迹天气难舍现金贷广告:合作方仅小米贷款
  19. Opencv学习笔记 - 频域手段添加盲水印
  20. 【系统集成项目管理刷题专题】第12章—项目沟通管理和干系人管理

热门文章

  1. 微软账号登陆不上_微软待办(todo)如何跟Outlook任务同步?
  2. 记Thinkpad的一次扩容升级经历
  3. 深度解析 | 炎症,肠道菌群以及抗炎饮食
  4. 网络技术人员要知道的100个安全工具
  5. 微信下载多媒体文件 java_java微信开发之上传下载多媒体文件_php实例
  6. Error: unable to open database “BookStore.db“: unable to open database file的解决方法
  7. 网站服务器 南阳,河南南阳DNS服务器地址
  8. Linux系统环境:DM8数据库安装
  9. dvi黑屏解决方法_DVI线导致黑屏故障处理全攻略
  10. 信道估计之LMMSE估计