using System;
namespace AI.FSM
{
public enum FSMTriggerID
{
NoHealth = 1,
FoundTarget = 2,
ReachTarget = 3,
LoseTarget = 4,
CompletePatrol = 5,
KilledTarget = 6
WithoutAttackRange = 7
}
public enum FSMStateID
{
None = 1,
Idle = 2,
Dead = 3,
Pursuit = 4,
Attacking = 5,
Default = 6,
Patrolling = 7
}
public enum PatrolMode
{
Once,
Loop,
PingPong
}
public abstract class FSMBase: MonoBehvaviour
{
public FSMStateID test_currentStateID;
[Tooltip("当前状态机使用的配置文件")]public string configFile = "AI_01.txt";
private List<FSMState> states;
private FSMState currentState;
private FSMState defaultState;
private NavMeshAgent navAgent;
[Tooltip("默认状态编号")]public FSMStateID defaultStateID;
[HideInInSpector]public Animator anim;
[HideInInSpector]public CharacterStatus chStatus;
[HideInInSpector]public Transform targetTF;
[HideInInSpector]public CharacterSkillSystem skillSystem;
[HideInInSpector]public bool isPatrolComplete;
[Tooltip("攻击目标标签")]public string[] targetTags = {"Player"};
[Tooltip("视野距离")]public float sightDistance = 10;
[Tooltip("跑动速度")]public float moveSpeed = 2;
[Tooltip("走动速度")]public float walkSpeed = 1;
[Tooltip("路点")]public Transform[] wayPoints;
[Tooltip("巡逻模式")]ublic PatrolMode patrolMode;
#region //脚本生命周期
private void Start()
{
InitComponent();
ConfigFSM();
InitDefaultState();
}
private void Update()
{
test_currentStateID = currentState.StateID;
currentState.Reason(this);
currentState.ActionState(this);
SearchTarget();
}
#endregion
#region //状态机自身成员
private void ConfigFSM()
{
states = new List<FSMState>();
var map = AIConfigurationFactory.GetMap(fileName);
foreach(var state in map)
{
Type type = Type.GetType("AI.FSM." + state.Key + "State");
FSMState stateObj = Activator.CreateInstance(type) as FSMState
states.Add(stateObj);
foreach(var dic in state.Value)
{
FSMTriggerID triggerID = (FSMTriggerID)Enum.Parse(typeof(FSMTriggerID), dic.Key);
FSMStateID stateID = (FSMStateID)Enum.Parse(typeof(FSMStateID), dic.Value);
stateObj.AddMap(triggerID, stateID);
}
}
}
public void ChangeActiveState(FSMStateID stateID)
{
currentState.ExitState(this);
currentState = stateID == FSMStateID.Default? defaultState: states.Find(s => s.StateID == stateID);
currentState.EnterState(this);
}
#endregion
#region //状态机自身成员
private void InitDefaultState()
{
defaultState = states.Find(s => s.StateID == defaultStateID);
currentState = defaultState;
currentState.EnterState(this);
}
public void InitComponent()
{
anim = GetComponentInChilderen<Animator>();
chStatus = GetComponent<CharacterStatus>();
navAgent = GetComponent<NavMeshAgent>();
skillSystem = GetComponent<CharacterSkillSystem>();
}
private void SearchTarget()
{
SkillData data = new SkillData()
{
attackTargets = targetTags,
attackDistance = sightDistance;
attackAngle = 360,
attackType = SkillAttackType.Single
};
Transform[] targetArr = new SectorAttackSelector().SelectTarget(data, transform);
targetTF = targetArr.Length == 0 ? null : targetArr[0];
}
public void MoveToTarget(Vector3 position, float stopDistance, float moveSpeed)
{
navAgent.SetDestination(position);
navAgent.stoppingDistance = stopDistance;
navAgent.speed = moveSpeed;
}
public void StopMove()
{
navAgent.SetDestination(transform.position);
}
#endregion
}
public abstract class FSMState
{
private Dictionary<FSMTriggerID, FSMStateID> map;
private List<FSMTrigger> Triggers;
public FSMStateID StateID{get; set;}
public FSMState()
{
map = new Dictionary<FSMState, FSMStateID>();
Triggers = new List<FSMTrigger>();
Init();
}
public abstract void Init();
public void Reason(FSMBase fsm)
{
for(int i=0; i<Triggers.Count; i++)
{
if(Trigger[i].HandleTrigger(fsm))
{
FSMStateID stateID = map[Triggers[i].TriggerID]
fsm.ChangeActiveState(stateID);
return;
}
}
}
public void AddMap(FSMTriggerID triggerID, FSMStateID stateID)
{
map.Add(triggerID, stateID);
CreateTrigger(triggerID);
}
private void CreateTrigger(FSMTriggerID triggerID)
{
Type type = Type.GetType("AI.FSM." + triggerID + "Trigger");
FSMTrigger trigger = Activator.CreateInstance(type) as FSMTrigger;
Triggers.Add(trigger);
}
public virtual void EnterState(FSMBase fsm){}
public virtual void ActionState(FSMBase fsm){}
public virtual void ExitState(FSMBase fsm){}
}
public abstract class FSMTrigger
{
public FSMTriggerID TriggerID{get; set;}
public FSMTrigger()
{
Init();
};
public abstract void Init();
public abstract bool HandleTrigger(FSMBase fsm);
}
}
namespace AI.FSM
{
public class DeadState: FSMState
{
public override void Init()
{
StateID = FSMStateID.Dead;
}
public override void EnterState(FSMBase fsm)
{
base.EnterState(fsm);
fsm.enabled = false;
}
}
public class PursuitState: FSMState
{
public override void Init()
{
StateID = FSMStateID.Pursuit;
}
public override void EnterState(fsm)
{
base.EnterState(fsm);
fsm.anim.SetBool(fsm.chStatus.chParams.run, true);
}
public override void ActionState(FSMBase fsm)
{
base.ActionState(fsm);
fsm.MoveToTarget(fsm.targetTF.position, fsm.chStatus.attackDistance, fsm.moveSpeed)
}
public override void ExitState(FSMBase fsm)
{
base.ExitState(fsm);
fsm.StopMove();
fsm.anim.SetBool(fsm.chStatus.chParams.run, false);
}
}
public class AttackingState: FSMState
{
private float atkTime;
public override void Init()
{
StateID = FSMStateID.Attacking;
}
public override void ActionState(FSMBase fsm)
{
base.ActionState(fsm);
if(atkTime <= Time.time)
{
fsm.skillSystem.UseRandomSkill();
atkTime = Time.time + fsm.chStatus.attackInterval;
}
}
}
public class PatrollingState: FSMState
{
private int index;
public override void Init()
{
StateID = FSMStateID.Patrolling;
}
public override void EnterState(FSMBase fsm)
{
base.EnterState(fsm);
fsm.isPatrolComplete = false;
fsm.anime.SetBool(fsm.chStatus.chParams.walk, true);
}
public override void ActionState(FSMBase fsm)
{
base.ActionState(fsm);
switch(fsm.patrolMode)
{
case PatrolMode.Once:
OncePatrolling(fsm);
break;
case PatrolMode.Loop:
LoopPatrolling(fsm);
break;
case PatrolMode.PingPong:
PingPongPatrolling(fsm);
break;
}
}
public override void ExitState(FSMBase fsm)
{
base.ExitState(fsm);
fsm.anim.SetBool(fsm.chStatus.chParams.walk, false);
}
private void OncePatrolling(FSMBase fsm)
{
if(Vector3.Distance(fsm.transform.position, fsm.wayPoints[index].position) < 0.5f)
{
if(index == fsm.wayPoints.Length-1)
{
fsm.isPatrolComplete = true;
return;
}
index++;
}
fsm.MoveToTarget(fsm.wayPoints[index].position, 0, fsm.walkSpeed);
}
private void LoopPatrolling(FSMBase fsm)
{
if(Vector3.Distance(fsm.transform.position, fsm.wayPoints[index].position) < 0.5f)
{
index = (index + 1) % fsm.wayPoints.Length;
}
fsm.MoveToTarget(fsm.wayPoints[index].position, 0, fsm.walkSpeed);
}
private void PingPongPatrolling(FSMBase fsm)
{
if(Vector3.Distance(fsm.transform.position, fsm.wayPoints[index].position) < 0.5f)
{
if(index == fsm.wayPoints.Length-1)
{
Array.Reverse(fsm.wayPoints);
}
index = (index + 1) % fsm.wayPoints.Length;
}
fsm.MoveToTarget(fsm.wayPoints[index].position, 0, fsm.walkSpeed);
}
}
public class TargetFoundTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
return fsm.targetTF != null;
}
public override void Init()
{
TriggerID = FSMTriggerID.FoundTarget;
}
}
public class ReachTargetTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
if(fsm.targetTF == null) return false;
return Vector3.Distance(fsm.transform.position, fsm.targetTF.position) <= fsm.chStatus.attackDistance;
}
public override void Init()
{
TriggerID = FSMTriggerID.ReachTarget;
}
}
public class LoseTargetTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
return fsm.targetTF == null;
}
public override void Init()
{
TriggerID = FSMTriggerID.LoseTarget;
}
}
public class WithoutAttackRangeTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
return Vector3.Distance(fsm.transform.position, fsm.targetTF.position) > fsm.chStatus.attackDistance;
}
public override void Init()
{
TriggerID = FSMTriggerID.WithoutAttackRange;
}
}
public class KilledTargetTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
return fsm.TargetTF.GetComponent<CharacterStatus>().HP <= 0;
}
public override void Init()
{
TriggerID = FSMTriggerID.KilledTarget;
}
}
public class CompletePatrolTrigger: FSMTrigger
{
public override bool HandleTrigger(FSMBase fsm)
{
return fsm.isPatrolComplete;
}
public override void Init()
{
TriggerID = FSMTriggerID.CompletePatrol;
}
}
}
namespace AI.FSM{}
public class AIConfigurationReader
{
public Dictionary<string, Dictionary<string, string>> _map{get; private set;};
private string mainKey;
public AIConfigurationReader(string fileName)
{
_map = new Dictionary<string, Dictionary<string, string>>();
string content = ConfigurationReader.GetConfigFile(fileName);
ConfigurationReader.Reader(content, BuildMap)
}
private void BuildMap(string line)
{
line = line.Trim();
if(string.IsNullOrEmpty(line)) return;
if(line.StartsWith("["))
{
mainKey = line.Substring(1,line.Length-2);
_map.Add(mainKey, new Dictionary<string, string());
}
else
{
string[] keyValue = line.Split('>');
_map[mainKey].Add(keyValue[0], keyValue[1]);
}
}
}
public class AIConfigurationFactory
{
private static Dictionary<string, AIConfigurationReader> cache;
static AIConfigurationFactory()
{
cache = new Dictionary<string, AIConfigurationReader>();
}
public static Dictionary<string, Dictionary<string, string>> GetMap(string fileName)
{
if(!cache.ContainsKey(fileName))
{
cache.Add(fileName, new AIConfigurationReader(fileName));
}
return cache[fileName]._map;
}
}