只使用FSM及其变种是很难应用在复杂的AI中的,因为其复杂度的扩展性很差,就比如,如果怪物有20种状态,你就需要考虑20*20种可能性的连接。
所以这时我们必须建立分层次的状态机设计,试想一下,把AI的所有行为再分个类,每一类都认为是一种策略(事实上我们人的大脑也很类似这样,比如我们去攻击一个人,我们选择什么方式攻击、什么方式假动作是大脑思考好的,但之下的出拳这些动作确是自然而然做出来,虽然攻击策略不同,但是下面的行为确实一样的——这就是把策略看作了行为的组合),这样首先第一个好处就是,用很少的行为就可以表示很复杂的策略,因为同是出拳,在不同的策略中有不同的用法。这样,原本是增加状态节点->复杂度线性增加->难度指数增加,现在是增加状态节点和策略节点->复杂度平方增加->难度平方增加。
然后再来看看在框架中我们要如何做,很明显,一个策略要和一个状态机绑定。这里有一个问题出现了,策略还用FSM来做么?显然是不好的,FSM强调状态的转化,但是对于策略来说,策略的转化没有什么衔接和前后关系,所以我们使用了FuSM。FuSM的整体思想也很简单,他为每个策略确定了一个激活水平,选取最高激活水平的策略进入。
来看看代码:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityECS; using FreedomAI; namespace FreedomAI { public delegate float StrategyActioner(AIEntity pEntity); public delegate float StrategyFeedbacker(AIEntity pEntity); public delegate void StrategyExit(AIEntity pEntity); public delegate void StrategyEnter(AIEntity pEntity); public class ObstacleComponent:UComponent { public GameObject hitObject; public float hitDistance; public Vector3 target; } public class ObstacleAvoidance { public static float ActionFunc(AIEntity pEntity) { GameObject tAIObject = pEntity.GetComponent<BaseAIComponent> ().mAIRT; Vector3 tDir = pEntity.GetComponent<AIMove> ().mDirection; float maxDis = 2.0f; RaycastHit hit = new RaycastHit(); int layoutmask = 1 << LayerMask.NameToLayer ("Collision"); if (Physics.Raycast (tAIObject.transform.position, tDir,out hit,maxDis,layoutmask)) { Vector3 hitPos = hit.transform.position; float tDis = Vector3.Distance (hitPos,tAIObject.transform.position); pEntity.GetComponent<ObstacleComponent> ().hitObject = hit.transform.gameObject; pEntity.GetComponent<ObstacleComponent> ().hitDistance = tDis; if (tDis < 1.0f) { return 1.0f; } return 2.0f-tDis; } else { return 0.0f; } } public static void Strategy_Enter(AIEntity pEntity) { pEntity.GetComponent<ObstacleComponent> ().target = Vector3.zero; } public static void FSM_Avoid(AIEntity pEntity) { if (pEntity.GetComponent<ObstacleComponent> ().target == Vector3.zero) { Vector3 v1 = pEntity.GetComponent<ObstacleComponent> ().hitObject.transform.position - pEntity.AIPos; v1.y = 0.0f; Vector3 v2 = new Vector3 (1.0f,0.0f,-v1.x/v1.z); v2.Normalize (); Vector3 v3 = -v2; for (int i = 0; i <=10; i++) { float tempRate = (float)i / 10.0f; Vector3 vdir1 = Vector3.Lerp (v1, v2, tempRate); vdir1.Normalize (); Vector3 vdir2 = Vector3.Lerp (v1,v3,tempRate); vdir2.Normalize (); float maxDis = 2.0f; LayerMask layoutmask = 1 << LayerMask.NameToLayer ("Collision"); RaycastHit hit = new RaycastHit (); if (!Physics.Raycast (pEntity.GetComponent<BaseAIComponent> ().mAIRT.transform.position, vdir1, out hit, maxDis, layoutmask)) { pEntity.GetComponent<ObstacleComponent> ().target = pEntity.GetComponent<BaseAIComponent> ().mAIRT.transform.position + vdir1 * maxDis; break; } if (!Physics.Raycast (pEntity.GetComponent<BaseAIComponent> ().mAIRT.transform.position, vdir2, out hit, maxDis, layoutmask)) { pEntity.GetComponent<ObstacleComponent> ().target = pEntity.GetComponent<BaseAIComponent> ().mAIRT.transform.position + vdir2 * maxDis; break; } } } float tdis = Vector3.Distance (pEntity.GetComponent<ObstacleComponent>().target,pEntity.AIPos); if (tdis < 0.15f) { pEntity.GetComponent<AIMove> ().mDirection = Vector3.zero; pEntity.GetComponent<AIMove> ().mVelocity = 0.0f; return; } Vector3 tdir = pEntity.GetComponent<ObstacleComponent> ().target - pEntity.AIPos; tdir.y = 0.0f; pEntity.GetComponent<AIMove> ().mDirection = tdir.normalized; pEntity.GetComponent<AIMove> ().mVelocity = 5.0f; } public static float FSM_Battle_Avoid(AIEntity pEntity) { if (pEntity.GetComponent<ObstacleComponent> ().hitObject.tag != "Battleable") { return 1.0f; } else { return 0.0f; } } public static float FSM_Avoid_Battle(AIEntity pEntity) { if (pEntity.GetComponent<ObstacleComponent> ().hitObject.tag == "Battleable") { return 1.0f; } else { return 0.0f; } } }; public class EmptyStrategyFeedbacker { public static float Run(AIEntity pEntity) { return 0.0f; } }; public class EmptyStrategyEnter { public static void Run(AIEntity pEntity) { } }; public class EmptyStrategyExit { public static void Run(AIEntity pEntity) { } }; public class AIStrategy:UComponent { public StrategyActioner[] mStrategyActioner; public StrategyFeedbacker[] mStrategyFeedbacker; public StrategyEnter[] mStrategyEnter; public StrategyExit[] mStrategyExit; public AIState[] mAIState; public float[] mPower; private int maxCount = 25; public int tempCount =0; public int tempID; public int IDBuffer; public int BufferFrame = 0; public int mFrameCaptureCounter = 10; public float[] bufferdata = new float[10]; public bool mFrameCaptureStart = false; public int LastID; public float timer; public override void Init () { base.Init (); mStrategyActioner = new StrategyActioner[maxCount]; mStrategyFeedbacker = new StrategyFeedbacker[maxCount]; mStrategyEnter = new StrategyEnter[maxCount]; mStrategyExit = new StrategyExit[maxCount]; mAIState = new AIState[maxCount]; mPower = new float[maxCount]; for (int i = 0; i < maxCount; i++) { mPower[i] = 1.0f; } IDBuffer = -1; //InitAvoid (); } public int AddStrategy(StrategyActioner pStrategyActioner,StrategyEnter pStrategyEnter,StrategyExit pStrategyExit,StrategyFeedbacker pStrategyFeedbacker,AIState pAIState) { if (tempCount < maxCount) { mStrategyActioner [tempCount] = pStrategyActioner; mStrategyFeedbacker [tempCount] = pStrategyFeedbacker; mStrategyEnter [tempCount] = pStrategyEnter; mStrategyExit [tempCount] = pStrategyExit; mAIState[tempCount] = pAIState; tempCount++; return tempCount-1; } return -1; } public int AddStrategy(StrategyActioner pStrategyActioner,AIState aiState) { return AddStrategy (pStrategyActioner,EmptyStrategyEnter.Run,EmptyStrategyExit.Run,EmptyStrategyFeedbacker.Run,aiState); } public void SetEntry(int pID) { tempID = pID; mUEntity.GetComponent<AIState> ().SimpleClone (mAIState[tempID]); } public void InitAvoid(StateExecuter pStateExecuter,StateEnter pStateEnter,StateExit pStateExit,StateRecorder pStateRecorder,AIEntity pLast) { StrategyActioner AvoidActioner = ObstacleAvoidance.ActionFunc; StateExecuter AvoidState = ObstacleAvoidance.FSM_Avoid; AIState aiState = new AIState (); aiState.Init (); int id_battle = aiState.AddExecuter (pStateExecuter,pStateExit,pStateEnter); int id_avoid = aiState.AddExecuter (AvoidState,EmptyExitAndEnter.EmptyExit,EmptyExitAndEnter.EmptyEnter); aiState.AddAnimation (pStateExecuter,"Attack"); aiState.AddAnimation (AvoidState,"Walk"); aiState.tempID = id_avoid; StateTranfer tAvoid_Battle = ObstacleAvoidance.FSM_Avoid_Battle; StateTranfer tBattle_Avoid = ObstacleAvoidance.FSM_Battle_Avoid; aiState.AddEdge (tAvoid_Battle,EmptyStrategyFeedbacker.Run,id_avoid,id_battle); aiState.AddEdge (tBattle_Avoid,EmptyStrategyFeedbacker.Run,id_battle,id_avoid); StrategyEnter tAvoidEnter = ObstacleAvoidance.Strategy_Enter; aiState.mStateRecorder = pStateRecorder; aiState.LastEntityData = pLast; AddStrategy (AvoidActioner,tAvoidEnter,EmptyStrategyExit.Run,EmptyStrategyFeedbacker.Run,aiState); } }; public struct actionNode { public int mid; public float action; }; public class StrategyController:USystem { public override void Init () { base.Init (); this.AddRequestComponent (typeof(AIStrategy)); this.AddRequestComponent (typeof(AIState)); } public override void Update (UEntity uEntity) { base.Update (uEntity); AIEntity pEntity = (AIEntity)uEntity; if (pEntity.GetComponent<AIStrategy> ().timer <= 1.0f) { pEntity.GetComponent<AIStrategy> ().timer += Time.deltaTime; return; } pEntity.GetComponent<AIStrategy> ().timer = 0.0f; if (pEntity.GetComponent<AIStrategy> ().IDBuffer != -1) { if (pEntity.GetComponent<AIStrategy> ().BufferFrame != 0) { pEntity.GetComponent<AIStrategy> ().BufferFrame--; } else { pEntity.GetComponent<AIStrategy> ().IDBuffer = -1; } return; } float minValue = 0.5f; actionNode tActionNode1 = new actionNode (); tActionNode1.action = 0.0f; tActionNode1.mid = -1; actionNode tActionNode2 = new actionNode (); tActionNode2.action = 0.0f; tActionNode2.mid = -1; //Debug.Log ("update"); for (int i = 0; i < pEntity.GetComponent<AIStrategy> ().tempCount; i++) { float tempRate = pEntity.GetComponent<AIStrategy> ().mStrategyActioner [i](pEntity); tempRate *= pEntity.GetComponent<AIStrategy> ().mPower [i]; if (tempRate > tActionNode1.action) { tActionNode2.action = tActionNode1.action; tActionNode2.mid = tActionNode1.mid; tActionNode1.action = tempRate; tActionNode1.mid = i; } else if (tempRate > tActionNode2.action) { tActionNode2.action = tempRate; tActionNode2.mid = i; } } if (tActionNode1.action > minValue) { if (tActionNode1.mid == pEntity.GetComponent<AIStrategy> ().tempID) { return; } if (pEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter != 10) { float sum = 0.0f; for (int i = 0; i < 10 - pEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter; i++) { sum += pEntity.GetComponent<AIStrategy> ().bufferdata [i]; } sum /= 10 - pEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter; pEntity.GetComponent<AIStrategy> ().mPower [pEntity.GetComponent<AIStrategy> ().LastID] += sum; if (pEntity.GetComponent<AIStrategy> ().mPower [pEntity.GetComponent<AIStrategy> ().LastID] > 3.0f) pEntity.GetComponent<AIStrategy> ().mPower [pEntity.GetComponent<AIStrategy> ().LastID] = 3.0f; if (pEntity.GetComponent<AIStrategy> ().mPower [pEntity.GetComponent<AIStrategy> ().LastID] < 0.3f) pEntity.GetComponent<AIStrategy> ().mPower [pEntity.GetComponent<AIStrategy> ().LastID] = 0.3f; pEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter = 10; } pEntity.GetComponent<AIStrategy> ().LastID = pEntity.GetComponent<AIStrategy> ().tempID; pEntity.GetComponent<AIStrategy> ().mFrameCaptureStart = true; for (int i = 0; i < pEntity.GetComponent<AIState> ().mtempCount; i++) { for (int j = 0; j < pEntity.GetComponent<AIStrategy> ().mAIState [pEntity.GetComponent<AIStrategy> ().tempID].mPowerEdge [i].Count; j++) { PowerNode pnt = new PowerNode (); pnt.id = pEntity.GetComponent<AIState> ().mPowerEdge [i] [j].id; pnt.power = pEntity.GetComponent<AIState> ().mPowerEdge [i] [j].power; pEntity.GetComponent<AIStrategy> ().mAIState [pEntity.GetComponent<AIStrategy> ().tempID].mPowerEdge [i] [j] = pnt; } } //Debug.Log (tActionNode1.mid+" "+pEntity.GetComponent<AIStrategy>().tempID); pEntity.GetComponent<AIStrategy> ().mStrategyExit[pEntity.GetComponent<AIStrategy>().tempID](pEntity); pEntity.GetComponent<AIStrategy> ().SetEntry (tActionNode1.mid); pEntity.GetComponent<AIStrategy> ().mStrategyEnter[pEntity.GetComponent<AIStrategy>().tempID](pEntity); if (tActionNode1.action - tActionNode2.action > 0.3f) { pEntity.GetComponent<AIStrategy> ().IDBuffer = pEntity.GetComponent<AIStrategy> ().tempID; pEntity.GetComponent<AIStrategy> ().BufferFrame = 6; } } } } public class StrategyCapturer:USystem { public override void Init () { base.Init (); this.AddRequestComponent (typeof(AIStrategy)); this.AddRequestComponent (typeof(AIState)); } public override void Update (UEntity uEntity) { base.Update (uEntity); if (uEntity.GetComponent<AIStrategy> ().mFrameCaptureStart) { if (uEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter == 0) { uEntity.GetComponent<AIStrategy> ().mFrameCaptureStart = false; return; } int tempID = uEntity.GetComponent<AIStrategy> ().LastID; StrategyFeedbacker tempFeedbacker = uEntity.GetComponent<AIStrategy> ().mStrategyFeedbacker [tempID]; float rate1 = tempFeedbacker ((AIEntity)uEntity); float rate2 = tempFeedbacker (uEntity.GetComponent<AIState>().LastEntityData); uEntity.GetComponent<AIStrategy> ().bufferdata [10 - uEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter] = rate1 - rate2; } } }; public class StrategyComputer:USystem { public override void Init () { base.Init (); this.AddRequestComponent (typeof(AIStrategy)); } public override void Update (UEntity uEntity) { base.Update (uEntity); if (uEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter == 0) { float sum = 0.0f; for (int i = 0; i < 10; i++) { sum += uEntity.GetComponent<AIStrategy> ().bufferdata [i]; } sum /= 10.0f; uEntity.GetComponent<AIStrategy> ().mPower [uEntity.GetComponent<AIStrategy> ().LastID] += sum; uEntity.GetComponent<AIStrategy> ().mFrameCaptureCounter = 10; uEntity.GetComponent<AIStrategy> ().mFrameCaptureStart = false; } } } };
这里说一点做的优化:
每次做检测的时候,我们除了保存了最高激活水平,还保存了第二高激活水平,如果最高激活水平高于第二高激活水平一个阈值,就说明这个策略暂时具有很大优势,我们将其保存进缓存,在接下来的若干时间不做检测,直接使用这个策略。
然后其他部分,这里同样如我们在FSM中那样,我们做了反馈机制。