C# x Unity面向对象补全计划 设计模式 之 实现一个简单的有限状态机

一个简单的有限状态机可以有如下内容

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那我该上哪获取这些组件?所以要指明一个挂载到场景对象身上的脚本,也就是管理状态类

cs 复制代码
using 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之中,但是作为触发条件,我建议耦合在行走类的代码中,这也是切换状态的唯一桥梁

cs 复制代码
private void FixedUpdate() {
    currentState.Execute();

    // 示例:按下空格键时切换到另一个状态
    if (Input.GetKeyDown(KeyCode.Space)) {
        ChangeState(new IdleState(this));
    }
}

但是,如果你的是真的两种不会频繁切换的状态,那我我建议你写在ChangeState里,就像开关一样

相关推荐
vker1 小时前
第 1 天:单例模式(Singleton Pattern)—— 创建型模式
java·设计模式
晨米酱20 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机1 天前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤1 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript