构建易扩展的AI基类和数据结构
有限状态机FSM控制小兵AI行为状态
NavMesh自动寻路系统控制小兵移动
缓存池CatchTool 控制小兵动态创建和销毁
……
直接上代码…………………………………………
public class AI : MonoBehaviour
{
///
/// AI自身对象
///
public GameObject obj;
///
/// 自身ID
///
public int AIID;
///
/// 名字
///
public string Name;
///
/// 唯一识别ID
///
public int UniqueID;
///
/// AI类型
///
public AIType AIType;
///
/// 阵营
///
public CampEnum CampEnum;
///
/// 是否存活
///
public bool isAlive;
///
/// AI皮肤
///
public AISkin skin = new AISkin();
///
/// AI属性
///
public AIAttribute attribute = new AIAttribute();
///
/// AI属性变化事件
///
public AttributeEvent attributeEvent = new AttributeEvent();
///
/// AI成长属性
///
public AIGrowUp growUp = new AIGrowUp();
///
/// 播放动画
///
public AIAnimation aiAnimation = new AIAnimation();
AI小兵派生类:
///
/// 小兵AI派生类
///
public class SoldierAI : AI
{
///
/// 自动寻路组件
///
public AINaveMesh aINaveMesh;
///
/// 小兵状态机组件
///
public SoldierBehaviour soldierBehaviour;
///
/// 小兵侦察距离检测类
///
public SoldierDetectionCollider soldierDetectionCollider;
///
/// 小兵战斗距离检测类
///
public SoldierAttackCollider soldierAttackCollider;
///
/// 小兵技能
///
public SoldierSkill soldierSkill;
///
/// 小兵UI画布
///
public SoldierCanvas soldierCanvas;
///
/// 小兵类型
///
public string SoldierType;
///
/// 是否死亡
///
private bool isDeath = false;
定义小兵AI管理器类,用于管理小兵AI的创建、销毁、属性成长等等
///
/// 小兵管理器类
///
public class SoldierManager : MonoSingle
{
///
/// 小兵加载波次
///
private int Frequency = 0;
///
/// 加载小兵间隔
///
private float LoadInterval = 0;
///
/// 倒计时
///
private float Countdown = 1f;
///
/// 间隔时间
///
private float IntervalTime = 30f;
///
/// 判定是否开始小兵系统
///
private bool isStart = false;
///
///
///
private TerrainEnum GameTerrain;
///
/// 对象池小兵预制体
///
public Dictionary SoldierPrefabs = new Dictionary();
//小兵临时对象
public Queue Temp_SoldierMelee_Blue = new Queue();
public Queue Temp_SoldierRemote_Blue = new Queue();
public Queue Temp_GunTruck_Blue = new Queue();
public Queue Temp_SoldierMelee_Red = new Queue();
public Queue Temp_SoldierRemote_Red = new Queue();
public Queue Temp_GunTruck_Red = new Queue();
///
/// 所有小兵容器
/// Key:小兵唯一识别符
/// Value:小兵AI
///
public Dictionary Soldiers_Dic = new Dictionary();
///
/// 是否刷新小兵
///
private bool isRefresh = false;
///
/// 当前刷新帧
///
private float refreshCDNow = 0;
///
/// 每个小兵刷新间隔
///
private float refreshCD = 0.7f;
///
///
///
private int refreshNumber = 0;
///
/// 小兵类型列表
///
private string[] refreshList = new string[7];
定义小兵缓存池,并创建小兵缓存
//根据游戏模式加载小兵缓存池
int SoldierRemote = 0;
int SoldierMelee = 0;
int GunTruck = 0;
switch (GameTerrain)
{
case TerrainEnum.Gorge:
SoldierRemote = 27;
SoldierMelee = 27;
GunTruck = 9;
break;
case TerrainEnum.Bridge:
SoldierRemote = 9;
SoldierMelee = 9;
GunTruck = 3;
break;
case TerrainEnum.Jungle:
SoldierRemote = 27;
SoldierMelee = 27;
GunTruck = 9;
break;
default:
break;
}
//创建小兵缓存池
ToolManager.objectPool_C.Init_Public("SoldierRemote_Blue", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("SoldierRemote_Blue", SoldierPrefabs["SoldierRemote_Blue"].obj, SoldierRemote);
ToolManager.objectPool_C.Init_Public("SoldierMelee_Blue", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("SoldierMelee_Blue", SoldierPrefabs["SoldierMelee_Blue"].obj, SoldierMelee);
ToolManager.objectPool_C.Init_Public("GunTruck_Blue", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("GunTruck_Blue", SoldierPrefabs["GunTruck_Blue"].obj, GunTruck);
ToolManager.objectPool_C.Init_Public("SoldierRemote_Red", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("SoldierRemote_Red", SoldierPrefabs["SoldierRemote_Red"].obj, SoldierRemote);
ToolManager.objectPool_C.Init_Public("SoldierMelee_Red", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("SoldierMelee_Red", SoldierPrefabs["SoldierMelee_Red"].obj, SoldierMelee);
ToolManager.objectPool_C.Init_Public("GunTruck_Red", PoolParentType.Model);
ToolManager.objectPool_C.SetPool_Public("GunTruck_Red", SoldierPrefabs["GunTruck_Red"].obj, GunTruck);
打包ab包方法:
///
/// 编译资源
///
/// 目标位置
/// 预制体
/// 场景
/// 目标平台
[MenuItem("开始打包/打包Obj")]
public static void BuildingObj()
{
AssetBundleManifest prs = null;
try
{
UnityEngine.Object[] selects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.DeepAssets);
//添加资源
foreach (UnityEngine.Object obj in selects)
{
List prefabBuilds = new List(); //预制体资源
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = obj.name + ".assetBundle";
string assetPath = AssetDatabase.GetAssetPath(obj);
build.assetNames = new string[] { assetPath };
prefabBuilds.Add(build);
string prefabPath = Environment.CurrentDirectory + "/DownLoad/Assetbundle/" + obj.name;
if (!Directory.Exists(prefabPath)) Directory.CreateDirectory(prefabPath);
try
{
prs = BuildPipeline.BuildAssetBundles(prefabPath, prefabBuilds.ToArray(), BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
}
catch (System.Exception ex)
{
Debug.Log("预制体 打包 失败:" + ex.ToString());
}
}
}
catch (UnityException e)
{
Debug.Log("预制体 打包 失败:" + e.ToString());
prs = null;
}
if (prs != null)
{
Debug.Log("预制体 打包 成功");
}
else
{
Debug.Log("预制体 打包 失败");
}
//刷新编辑器(不写的话要手动刷新,否则打包的资源不能及时在Project视图内显示)
AssetDatabase.Refresh();
}
加载ab包方法:
UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(URL);
www.SendWebRequest();
while (!www.isDone)
{
yield return null;
}
yield return www;
assetBundle = DownloadHandlerAssetBundle.GetContent(www);
if (assetBundle != null)
{
GameObject[] objects = assetBundle.LoadAllAssets();
yield return new WaitForEndOfFrame();
GameObject model;
if (objects[0])
{
try
{
model = Instantiate(objects[0]);
action(model);
}
catch (System.Exception EX)
{
iDebug.YiYan("克隆模型物体失败!" + EX, DebugColor.red);
model = GameObject.CreatePrimitive(PrimitiveType.Cube);
action(model);
}
}
else
{
iDebug.YiYan("AB包为空!", DebugColor.red);
model = GameObject.CreatePrimitive(PrimitiveType.Cube);
action(model);
}
}
else
{
iDebug.YiYan("AB包为空", DebugColor.red);
}
if (assetBundle != null)
{
assetBundle.Unload(false);
}
www.Dispose();
这个是AI系统中比较重要的环节,双方小兵按照规定移动轨迹走到线上,然后开始自动战斗,其中AI状态大概分为:
自动寻路状态 对线状态 攻击状态 防守状态
死亡状态 停止状态 追击状态 强制攻击状态
小兵的行为应该在遇到对应变化时适时切换这几种状态,而切换的方法则要用到设计模式中常用的模式:状态模式,也就是FSM有限状态机,代码如下:
状态机基类:
///
/// 状态机基类
///
public class BaseState
{
///
/// 状态名称
///
private string m_StateName = "BaseState";
public string StateName
{
get { return m_StateName; }
set { m_StateName = value; }
}
///
/// 控制器
///
protected BaseStateController m_Controller = null;
///
/// AI行为逻辑
///
public StateBehaviour m_Behaviour = null;
///
/// 构造函数——建造者
///
///
public BaseState(BaseStateController Controller, StateBehaviour behaviour = null)
{
m_Controller = Controller;
if (behaviour != null)
m_Behaviour = behaviour;
}
///
/// 状态开始
///
public virtual void StateBegin() { }
///
/// 状态更新
///
public virtual void StateUpdate() { }
///
/// 状态结束
///
public virtual void StateEnd() { }
///
/// 输出当前状态
///
///
public override string ToString()
{
return string.Format("[StateName = {0}]", StateName);
}
}
状态机控制器类:
///
/// 状态机控制器类
///
public class BaseStateController
{
///
/// 切换的当前状态
///
private BaseState m_State;
///
/// 是否已经开始执行当前状态
///
private bool m_bRunBegin = false;
///
/// 构造
///
public BaseStateController() { }
///
/// 状态机操作方法——执行设置状态方法,执行结束状态方法
/// 1、执行上一个状态的结束方法
/// 2、设置新的状态
///
///
///
public void SetState(BaseState State)
{
m_bRunBegin = false;
//通知前一个状态结束
if (m_State != null)
{
m_State.StateEnd();
}
m_State = State;
}
///
/// 状态机操作方法——执行开始状态方法
///
public void StateBegin()
{
if (m_State != null && m_bRunBegin == false)
{
m_State.StateBegin();
m_bRunBegin = true;
}
}
///
/// 状态机操作方法——执行更新状态方法
///
public void StateUpdate()
{
if (m_State != null && m_bRunBegin == true)
{
m_State.StateUpdate();
}
}
}
状态机控制器:
public class StateBehaviour : MonoBehaviour
{
///
/// ID
///
public int ID;
///
/// 名字
///
public string Name;
///
/// 状态集合
///
public Dictionary StateDic;
///
/// 状态机控制器
///
public BaseStateController StateController;
public virtual void Awake()
{
StateDic = new Dictionary();
StateController = new BaseStateController();
}
public virtual void Start()
{
}
public virtual void Update()
{
StateController.StateUpdate();
}
///
/// 初始化状态机
///
public virtual void Init(int id, string name)
{
ID = id;
Name = name;
}
///
/// 设置状态
///
///
public virtual void SetState(string state)
{
StateController.SetState(StateDic[state]);
StateController.StateBegin();
}
}
小兵AI状态机管理器
public class SoldierBehaviour : StateBehaviour
{
///
/// 本身物体
///
public GameObject Local;
///
/// 目标物体
///
public GameObject Target;
///
/// 自身AI对象
///
public SoldierAI AI;
public override void Awake()
{
base.Awake();
StateDic.Add("SoldierState_Alignment", new SoldierState_Alignment(StateController, this));
StateDic.Add("SoldierState_Attack", new SoldierState_Attack(StateController, this));
StateDic.Add("SoldierState_CrazyAttack", new SoldierState_CrazyAttack(StateController, this));
StateDic.Add("SoldierState_Defense", new SoldierState_Defense(StateController, this));
StateDic.Add("SoldierState_Death", new SoldierState_Death(StateController, this));
StateDic.Add("SoldierState_Pathfinding", new SoldierState_Pathfinding(StateController, this));
StateDic.Add("SoldierState_Pursuit", new SoldierState_Pursuit(StateController, this));
StateDic.Add("SoldierState_Stop", new SoldierState_Stop(StateController, this));
}
public override void Start()
{
base.Start();
}
public override void Update()
{
base.Update();
}
///
/// 初始化状态机
///
public void Init(int id, string name, SoldierAI ai)
{
base.Init(id, name);
AI = ai;
// iDebug.YiYan("小兵状态机初始化");
}
///
///
///
///
public void SetGameState(string name)
{
SetState(name);
}
///
/// 结束系统
///
public void End()
{
Target = null;
}
}
然后,就是在小兵进行途中,在小兵不同的状态下写好当前状态应该做的行为,和判定切换状态的条件行为,举个例子:
小兵在自动寻路状态下在兵线上走,突然攻击预警范围内有敌人出现,则自动切换到追击状态。
等走到攻击范围内,则切换到攻击状态,开始攻击敌人。
当敌人离开攻击范围但尚未离开预警范围,则继续切换到追击状态。
当敌人离开预警范围,则切换到自动寻路状态。
自动寻路状态代码:
///
/// 状态开始
///
public override void StateBegin()
{
// iDebug.YiYan("开始State:SoldierState_Pathfinding" + soldierBehaviour.AI.obj.GetInstanceID());
//播放行走动画
//获取寻路目标
soldierBehaviour.AI.aINaveMesh.SetState(true);
soldierBehaviour.AI.aINaveMesh.SetFinalTarget();
}
///
/// 状态更新
///
public override void StateUpdate()
{
//判断自身区域触发器是否有敌人
if (soldierBehaviour.AI.soldierDetectionCollider.EnemyList.Count > 0)
{
soldierBehaviour.AI.aINaveMesh.Target = soldierBehaviour.AI.soldierDetectionCollider.EnemyList[0];
soldierBehaviour.SetState("SoldierState_Pursuit");
}
}
追击状态代码:
///
/// 状态开始
///
public override void StateBegin()
{
// iDebug.YiYan("开始State:SoldierState_Pursuit" + soldierBehaviour.AI.obj.GetInstanceID());
//播放行走动画
if (soldierBehaviour.AI.aINaveMesh.Target != null && soldierBehaviour.AI.soldierDetectionCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target))
{
soldierBehaviour.AI.aINaveMesh.SetDestinationTarget(soldierBehaviour.AI.aINaveMesh.Target.obj);
}
}
///
/// 状态更新
///
public override void StateUpdate()
{
//判断自身区域触发器是否有敌人
if (soldierBehaviour.AI.soldierAttackCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target))
{
soldierBehaviour.AI.aINaveMesh.SetState(false);
soldierBehaviour.SetState("SoldierState_Attack");
}
if (!soldierBehaviour.AI.soldierDetectionCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target))
{
soldierBehaviour.SetState("SoldierState_Pathfinding");
}
}
攻击状态代码:
///
/// 状态开始
///
public override void StateBegin()
{
// iDebug.YiYan("开始State:SoldierState_Attack" + soldierBehaviour.AI.obj.GetInstanceID());
if (soldierBehaviour.AI.aINaveMesh.Target != null && soldierBehaviour.AI.soldierAttackCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target))
{
soldierBehaviour.AI.soldierSkill.SetTarget(soldierBehaviour.AI, soldierBehaviour.AI.aINaveMesh.Target);
soldierBehaviour.AI.soldierSkill.AutoAttack(true);
}
}
///
/// 状态更新
///
public override void StateUpdate()
{
//持续对目标释放技能
//播放攻击动画
if (!soldierBehaviour.AI.soldierAttackCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target) && soldierBehaviour.AI.soldierDetectionCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target))
{
soldierBehaviour.AI.aINaveMesh.SetState(true);
soldierBehaviour.SetState("SoldierState_Pursuit");
}
else if (!soldierBehaviour.AI.soldierDetectionCollider.EnemyList.Contains(soldierBehaviour.AI.aINaveMesh.Target)|| soldierBehaviour.AI.aINaveMesh.Target.attribute.attributeValueNow.HP <= 0)
{
soldierBehaviour.AI.aINaveMesh.SetFinalTarget();
soldierBehaviour.AI.aINaveMesh.SetState(true);
soldierBehaviour.SetState("SoldierState_Pathfinding");
// iDebug.YiYan("切换目标");
}
else
{
// iDebug.YiYan("不断攻击");
}
}
是不是非常简单易懂(笑脸+手动狗头~~)
(为了连贯性,所以把UI界面和资源导入的效果都录入了)
这里有的小伙伴会有疑问了,小兵的战斗数值和防御塔的属性数值是多少,如何设置呢?
哈~ 当然是在数据库中,由服务器统一分发了
数值就动态读取,然后赋予到AI基类里即可,后面的篇章会介绍技能系统和战斗计算,那时候再详细介绍数值调用的详细逻辑吧
↓↓↓↓↓↓
文末福利: