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


阅读更多
文章标签: Game AI Unity FuSM
个人分类: Unity FreedomAI
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭