本文是 XYGame-AI设计3-行为树-第1版本 的 AI 丰富版本,同时也是 XYGame-AI设计2-FSM 的行为树重构版本。
根据下方策划文档,得出以下行为树结构:
总结
相比版本1,本版本只是让 AI 规则更加复杂。每帧更新仍然从 root 节点开始遍历,通过不同的决策找出需要执行的叶子节点并执行 Action 行为逻辑。但现在设计的树形结构各节点之间存在依赖关系,因此虽然看起来可以简单粗暴地每次从 root 开始遍历,更好的设计是保存当前节点,下次更新直接从当前节点继续执行——这个过程类似函数调用时的地址保存与恢复。
这样做虽然能省去每次从 root 遍历的性能开销,但带来了以下几个难点:
- 行为节点的恢复与保存:何时开始执行行为,何时执行行为的条件等问题。
- 行为的条件评定:严格来看,虽然从 root 开始遍历,但得出的行为始终是在严格条件下执行的。实际的性能开销来自部分条件的重合评定,而非 root 遍历本身。
- 节点将携带执行状态,例如 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;
}
}
}