只使用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中那样,我们做了反馈机制。