基于上篇 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 的 实现代码
Action.cs
/*
* 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;
}
}
}
Condition.cs
/*
* 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;
}
}
}