[FreedomAI]第二週——FuSM

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章