只使用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中那樣,我們做了反饋機制。