一个简单的有限状态机可以有如下内容
1.状态基类(定义基本状态的方法,如进入(Enter)、执行(Execute)和退出(Exit),同时可以在此声明需要被管理的对象)
2.具体状态类(定义具体状态,如:跳跃,行走,待机,每个具体状态类继承自状态基类)
3.管理状态类(负责管理状态的切换逻辑,确保在不同状态之间进行正确的转换)
很好,那么就可以理论与实际结合了
我就按照上述内容创建一个控制角色行走,跳跃,待机可以不同切换的状态机
1.状态基类
cs
public abstract void Enter();
public abstract void Execute();
public abstract void Exit();
但是这还不够,因为没有控制角色的具体信息,试想一下,如果我想获取角色身上的刚体组件,动画组件等等基础组件,那我应该写在哪里?
具体状态类?还是管理状态类?
如果写在这两个类之中,你可能会将相同的代码多写N遍,这违背了合成复用原则
C# & Unity 面向对象补全计划 七大原则 之 合成/聚合复用原则( CARP)难度:☆☆☆☆ 总结:在类中使用类,而不是继承类-CSDN博客
So,就写在状态基类之中吧
尤其要注意的一点就是没有继承MoNo那我该上哪获取这些组件?所以要指明一个挂载到场景对象身上的脚本,也就是管理状态类
csusing System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public abstract class State { //Player身上的基础组件变量 protected Rigidbody2D rb; protected PlayerInputAction action; protected Animator animator; protected SpriteRenderer spriteRenderer; //基础变量 [Header("控制移动的变量")] public Vector2 adValue; public float playerSpeed; [Header("控制跳跃的变量")] public float jumpSpeed; public bool isJump; //尤其要说明这一点,这个是继承mono的管理类,所以要指明对象是人物的管理类 protected PlayerControl playercntrol; //可以在构造函数进行初始化 protected State(PlayerControl playerControl) { this.playercntrol = playerControl; Instance(); } //获取基础组件的函数 protected void Instance() { rb= playercntrol.GetComponent<Rigidbody2D>(); animator = playercntrol.GetComponent<Animator>(); action = new PlayerInputAction(); spriteRenderer = playercntrol.GetComponent<SpriteRenderer>(); } public abstract void Enter(); public abstract void Execute(); public abstract void Exit(); }
2.具体状态类
具体状态就直接继承状态基类写逻辑好了,虽然是核心功能
但是在状态机中,是最简单的,最清晰明了的部分
PS:我拿走路和跳跃举例其实不是太恰当,因为二者之间的切换条件过于简单,想象一下,如果是Boss从100血降低到50以后触发二阶段从而有一套全新的动作的话,利用状态机是不是会很清晰
行走状态
cs
public class WalkStae : State {
public WalkStae(PlayerControl playerControl) : base(playerControl) {
}
public override void Enter() {
playerSpeed = 200;
action.Enable();
}
public override void Execute() {
//执行行走逻辑
adValue = action.Player.Move.ReadValue<Vector2>();
rb.velocity = new Vector2(adValue.x * Time.deltaTime * playerSpeed * 1.6f, rb.velocity.y);
//设置动画
animator.SetFloat("walk", Mathf.Abs(rb.velocity.x));
//翻转逻辑
if (adValue.x > 0) {
spriteRenderer.flipX = false;
}
if (adValue.x < 0) {
spriteRenderer.flipX = true;
}
}
public override void Exit() {
action.Disable();
}
}
跳跃状态
注意我没写地面检测,所以用协程函数模拟了一下跳跃切换的过程 (1s)
cs
public class JumpState : State {
public JumpState(PlayerControl playerControl) : base(playerControl) {
}
public override void Enter() {
jumpSpeed = 200;
action.Enable();
}
public override void Execute() {
//订阅跳跃事件
action.Player.Jump.started += OnJumpStarted;
}
public void OnJumpStarted(InputAction.CallbackContext context) {
isJump = true;
animator.SetBool("Jump", isJump);
rb.AddForce(playercntrol.transform.up * jumpSpeed, ForceMode2D.Impulse);
Debug.Log(rb.velocity);
playercntrol.StartCoroutine(WaitJumpOver(isJump));
}
public IEnumerator WaitJumpOver(bool isJump)
{
yield return new WaitForSeconds(1.0f);
isJump =false;
}
public override void Exit() {
action.Player.Jump.started -= OnJumpStarted;
action.Disable();
}
}
3.管理状态类
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem.LowLevel;
public class PlayerControl : MonoBehaviour
{
//设置一个当前状态用于执行与存储
private State currentState;
public void Start() {
ChangeState(new WalkStae(this));
}
private void FixedUpdate() {
currentState.Execute();
}
//切换状态的逻辑
public void ChangeState(State state)
{
//如果当前状态部不为空则退出,不然会报错
if (currentState != null) {
currentState.Exit();
}
//记录下一个状态并且开启下一个状态
currentState = state;
currentState.Enter();
}
}
最后一个问题,也是最重要的问题,我该怎么切换不同的状态?不然写那么多状态不能切换由什么用?
比如上面行走切换跳跃,你可以写在ChangeState之中,但是作为触发条件,我建议耦合在行走类的代码中,这也是切换状态的唯一桥梁
csprivate void FixedUpdate() { currentState.Execute(); // 示例:按下空格键时切换到另一个状态 if (Input.GetKeyDown(KeyCode.Space)) { ChangeState(new IdleState(this)); } }
但是,如果你的是真的两种不会频繁切换的状态,那我我建议你写在ChangeState里,就像开关一样