【置顶】XYGame-AI设计4-行为树-第2版本
基于上篇 XYGame-AI设计3-行为树-第1版本 的 AI 丰富版本
也是 XYGame-AI设计2-FSM 的 行为树 重构版本
根据下下图的策划文档 得出以下行为树:
先说总结:
相比版本1 只是单纯的AI规则复杂化了, 每帧更新 还是从root节点开始遍历,通过不同的决策 找出执行的叶子节点,并且执行Action行为逻辑,而现在设计的这种树形结构 彼此是有依赖关系的,因为 看似可以简单粗暴的每次从root 开始遍历,更好的一种设计是保存当前节点,下次更新直接从当前节点继续执行,这个过程有点像函数的调用过程,地址保存 和恢复。
这样做虽然能省去每次从root遍历的性能开销,但是带来了几个难点:
1.行为节点的恢复和保存,什么时候开始执行行为,什么时候执行行为的条件等问题
2.行为的条件评定,严格看来虽然从root开始遍历,但是得出的行为始终是在严格的条件下执行的,只不过部分条件重合评定 导致的性能开销,而不是从root遍历本身带来的开销
3.节点将会携带执行状态,比如Running Complete Looping 等,
策划文档:
以下是Action 和Condition 的 实现代码
/* * Author: caoshanshan * Email: me@dreamyouxi.com Behavior Tree 's Actions * 行为树游戏逻辑部分 */ using UnityEngine; using System.Collections; namespace BehaviorTree.Action { //--------------------------------------------------游戏逻辑实际的 Action public class SearchNearestTarget : ActionBase {//寻找最近的玩家作为目标 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; float minDis = float.MaxValue; Entity t = null; foreach (Entity h in HeroMgr.ins.GetHeros()) {//找出一个最近的玩家 作为锁定目标 if (h.IsMaxTarget()) { continue; } float dis = h.ClaculateDistance(host.x, host.y); if (dis < minDis) { t = h; minDis = dis; } } if (t != null) { host.target = t; host.is_hit_tower = false; return true; } return false; } } public class MoveToTarget : ActionBase { public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; host.dir = (int)Utils.GetAngle(host.pos, host.target.pos);//委托给Run状态去做 return true; } } public class AttackTarget : ActionBase { public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target.IsHero && host.IsHeroRandomAtk(0.03f)) { // 随机化攻击 减弱攻击强度 target.atk = true; return true; } return false; } } public class SearchNearestTower : ActionBase {//仇恨指向最近的塔 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; float minDis = float.MaxValue; Entity t = null; foreach (Entity h in BuildingMgr.ins.GetBuildings()) {//找出一个最近的玩家 作为锁定目标 if (h.IsMaxTarget()) { continue; } float dis = h.ClaculateDistance(host.x, host.y); if (dis < minDis) { t = h; minDis = dis; } } if (t != null) { host.target = t; host.is_hit_tower = false; // Debug.LogError("目标指向最近的塔"); return true; } return false; } } public class SearchNearestHero : ActionBase {//寻找最近的玩家作为目标 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; float minDis = float.MaxValue; Entity t = null; foreach (Entity h in HeroMgr.ins.GetHeros()) {//找出一个最近的玩家 作为锁定目标 if (h.IsMaxTarget()) { continue; } float dis = h.ClaculateDistance(host.x, host.y); if (dis < minDis) { t = h; minDis = dis; } } if (t != null) { host.target = t; host.is_hit_tower = false; // Debug.LogError("目标指向最近的玩家"); return true; } return false; } } public class SetTarget : ActionBase { public override bool Visit(Entity target) { Enemy host = target as Enemy; var obj = host.GetTagPairOnce("BT_HitHero"); if (obj == null) { host.target = null; return false; } host.target = obj.value as Entity; // Debug.LogError("重新设置目标BT_HitHero"); return true; } } public class NotHitTower : ConditionBase {//重置没有攻击塔 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; host.is_hit_tower = false; return true; } } public class MoveRandom : ActionBase { int dir = 0; public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.target.isDie) return false; if (Utils.random_frameMS.Next(0, 300) == 5) { // 地图范围内的随机目标点 Terrain terrain = AppMgr.GetCurrentApp<BattleApp>().GetCurrentWorldMap().GetTerrain(); Vector2 to = new Vector2(Utils.random_frameMS.Next((int)(terrain.limit_x_left * 1000), (int)(terrain.limit_x_right * 1000)) / 1000f, Utils.random_frameMS.Next((int)(terrain.limit_z_down * 1000), (int)(terrain.limit_z_up * 1000)) / 1000f); this.dir = (int)Utils.GetAngle(host.pos, to); } host.dir = this.dir; return true; } } public class MoveRandomInAtkRange : ActionBase { int dir = 0; public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.target.isDie) return false; if (Utils.random_frameMS.Next(1, 200) == 2 || Mathf.Abs(host.atk_range - host.target.ClaculateDistance(host)) < 0.2f) {//一定概率 随机方向 dir = (int)Utils.GetAngle(host.pos, host.target.pos); // Debug.LogError("随机攻击范围内运动 " + dir); } host.dir = this.dir; return true; } } } /* * Author: caoshanshan * Email: me@dreamyouxi.com Behavior Tree 's Conditions * 行为树游戏逻辑部分 */ using UnityEngine; using System.Collections; namespace BehaviorTree.Condition { //--------------------------------------------------游戏逻辑实际的 Condition public class TargetHasNotInAtkRange : ConditionBase {//目标不在否在攻击范围内 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.atk_range > host.target.ClaculateDistance(host)) {//范围内 return false; } return true; } } public class IsCDMax : ConditionBase {//CD是否结束 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.cd.IsMax()) { host.cd.Reset(); return true; } return false; } } public class NotTargetOrDie : ConditionBase {//没有目标或者死亡 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return true; if (host.target == null) return true; if (host.target.isDie) return true; return false; } } public class HasTower : ConditionBase {//存在塔 public override bool Visit(Entity target) { if (BuildingMgr.ins.GetBuildings().Count > 0) { // Debug.LogError("存在塔"); return true; } return false; } } public class HasHitTower : ConditionBase {//攻击了塔 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return true; return host.is_hit_tower; } } public class HasNotHitTower : ConditionBase {//没有攻击塔 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return true; return !host.is_hit_tower; } } public class HasHitByHero : ConditionBase {//被玩家攻击 bool ret = false; Entity hit_target = null;//被攻击的目标 public override bool Visit(Entity target) { _host = target; if (ret) { // Debug.LogError("被玩家攻击"); ret = false; return true; } return false; } public override void OnEvent(int type, object userData) { if (this.IsInValid()) return; if (type == Events.ID_BATTLE_ENTITY_BEFORE_TAKEATTACKED) { AttackInfo info = userData as AttackInfo; Enemy host = _host as Enemy; if (host == null) return; if (info.target as Enemy != host) return; //自己被命中 if (info.ownner.IsMaxTarget() == false) { // host.target = info.ownner; ret = true; host.SetTag("BT_HitHero", info.ownner); // 信息 写入黑板 } } } public override void OnEnter() { EventDispatcher.ins.AddEventListener(this, Events.ID_BATTLE_ENTITY_BEFORE_TAKEATTACKED); } public override void OnExit() { EventDispatcher.ins.RemoveEventListener(this, Events.ID_BATTLE_ENTITY_BEFORE_TAKEATTACKED); } } public class TargetIsHero : ConditionBase {//目标是玩家 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.target.isDie) return false; return host.target.IsHero; } } public class TargetIsTower : ConditionBase {//目标是塔 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; if (host.target.isDie) return false; if (host.target.IsTower) { // Debug.LogError("目标是塔"); return true; } return false; } } public class HasHeroInTargetRange : ConditionBase {//目标不在否在攻击范围内 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return false; if (host.target == null) return false; foreach (Entity h in HeroMgr.ins.GetHeros()) { if (host.target_distance > host.ClaculateDistance(h.x, 0, h.z)) {//范围内 return true; } } return false; } } public class HasNotTarget : ConditionBase {//没有目标 public override bool Visit(Entity target) { Enemy host = target as Enemy; if (host == null) return true; if (host.target == null) return true; return false; } } }