学习 Unity Unity之有限状态机(1.01版) 理理 2024-12-15 2024-12-15 本版本是对有限状态机的优化,并讲了有限状态机的具体使用,要比之前的版本更好。主要是借鉴了博主向宇it 的文章。
一、编写框架
首先我们新建一个Parameter.cs,用于存储我们怪物的各种参数,比如动画啊、伤害啊,并加上[Serializable]属性使其可以编辑。
1 2 3 4 5 6 7 8 9 10 11 [Serializable] public class Parameter { //定义我们需要的参数 public Animator animator; public float attack; public NavMeshAgent nav; public AnimatorStateInfo animatorStateInfo;//当前动画信息 public float idleTime; public Transform targetObj; }
之后写一个IState.cs脚本,我们所有的状态都要继承它,相等于一个规范
1 2 3 4 5 6 7 8 public abstract class IState { protected FSM fsm;// 当前状态机 protected Parameter parameter;// 参数 public abstract void Enter(); public abstract void Update(); public abstract void Exit(); }
之后写一个FSM.cs脚本,继承MonoBehaviour,这就是我们状态机的主体框架了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public enum StateType { idle, stand, run, attack } public class FSM : MonoBehaviour { private IState currentIState; protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>(); //我们需要的参数 public Parameter parameter; protected virtual void Awake() { //注册方法 } protected virtual void OnEnable() { parameter.animator = transform.GetComponent<Animator>(); // 获取角色上的动画控制器组件 TransformState(StateType.idle); // 初始状态为Idle currentIState.Enter(); } //添加状态 public virtual void AddState(StateType state,IState istate) { if (states.ContainsKey(state)) { Debug.Log("请勿重复添加状态"); return; } states.Add(state, istate); } //转换状态 public virtual void TransformState(StateType state) { if (!states.ContainsKey(state)) { Debug.Log("没有找到此状态"); return; } if (currentIState != null) { currentIState.Exit(); } currentIState = states[state]; currentIState.Enter(); } public void Update() { parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息 currentIState.Update(); } }
二、编写状态
在上面的FSM.cs脚本,我声明了四个状态,因此写四个脚本:
先是Idle.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using UnityEngine; public class Idle : IState { public Idle(FSM fsm) { this.fsm = fsm; this.parameter = fsm.parameter; } public override void Enter() { parameter.animator.Play("Idle"); } public override void Exit() { } public override void Update() { if(Vector3.Distance(fsm.transform.position,parameter.targetObj.transform.position) < 20f) { fsm.TransformState(StateType.stand); } } }
再是stand.cs。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using UnityEngine; public class Stand : IState { float time = 5.0f; public Stand(FSM fsm) { this.fsm = fsm; this.parameter = fsm.parameter; } public override void Enter() { int x = Random.Range(1, 3); if(x == 1) { parameter.animator.CrossFade("Walk_Left", 0.2f); } else { parameter.animator.CrossFade("Walk_Right", 0.2f); } } public override void Exit() { time = 5.0f; } public override void Update() { fsm.transform.rotation = Quaternion.LookRotation(parameter.targetObj.transform.position - fsm.transform.position); time -= Time.deltaTime; if (time < 0f) { fsm.TransformState(StateType.run); } } }
接着是Run.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Run : IState { public Run(FSM fsm) { this.fsm = fsm; this.parameter = fsm.parameter; } public override void Enter() { parameter.animator.CrossFade("Run", 0.2f); } public override void Exit() { } public override void Update() { parameter.nav.SetDestination(parameter.targetObj.transform.position); if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) < 3f) { fsm.TransformState(StateType.attack); } } }
最后是Attack.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using UnityEngine; public class Attack : IState { float time = 5.0f; public Attack(FSM fsm) { this.fsm = fsm; this.parameter = fsm.parameter; } public override void Enter() { parameter.animator.CrossFade("Attack", 0.2f); } public override void Exit() { time = 5.0f; } public override void Update() { if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) > 3f && Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) < 20f) { fsm.TransformState(StateType.run); } if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) > 20f) { fsm.TransformState(StateType.idle); } } }
三、状态机的使用
每一个怪物对象、都给它单独写一个脚本作为状态机控制器,我这里拿的是yousa的模型,于是新建一个yousa.cs继承FSM.cs
1 2 3 4 5 6 7 8 9 10 11 12 using UnityEngine; public class yousa : FSM { protected override void Awake() { states.Add(StateType.idle,new Idle(this)); states.Add(StateType.stand, new Stand(this)); states.Add(StateType.run, new Run(this)); states.Add(StateType.attack, new Attack(this)); } }
在Awake方法里注册状态就好。把这个脚本和要用到的组件挂载到怪物身上。 图片
后续维护
后续的维护并不困难,假如我们现在又有新状态Skill了,就写一个Skill.cs的脚本,仿照之前几种状态的脚本去写,然后在FSM.cs最上边的枚举,加上这个状态,再在yousa.cs注册一下新状态就好了。