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

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

时间:2024-08-19 16:26:32浏览次数:15  
标签:状态 补全 void 状态机 切换 action using 设计模式 public

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

1.状态基类(定义基本状态的方法,如进入(Enter)、执行(Execute)和退出(Exit),同时可以在此声明需要被管理的对象)

2.具体状态类(定义具体状态,如:跳跃,行走,待机,每个具体状态类继承自状态基类)

3.管理状态类(负责管理状态的切换逻辑,确保在不同状态之间进行正确的转换)

很好,那么就可以理论与实际结合了

我就按照上述内容创建一个控制角色行走,跳跃,待机可以不同切换的状态机

1.状态基类

 public abstract void Enter();
 public abstract void Execute();
 public abstract void Exit();

但是这还不够,因为没有控制角色的具体信息,试想一下,如果我想获取角色身上的刚体组件,动画组件等等基础组件,那我应该写在哪里?

具体状态类?还是管理状态类?

如果写在这两个类之中,你可能会将相同的代码多写N遍,这违背了合成复用原则

C# & Unity 面向对象补全计划 七大原则 之 合成/聚合复用原则( CARP)难度:☆☆☆☆ 总结:在类中使用类,而不是继承类-CSDN博客

So,就写在状态基类之中吧

尤其要注意的一点就是没有继承MoNo那我该上哪获取这些组件?所以要指明一个挂载到场景对象身上的脚本,也就是管理状态类

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以后触发二阶段从而有一套全新的动作的话,利用状态机是不是会很清晰

行走状态

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)

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.管理状态类

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

private void FixedUpdate() {
    currentState.Execute();

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

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

 

标签:状态,补全,void,状态机,切换,action,using,设计模式,public
From: https://blog.csdn.net/2301_77947509/article/details/141322919

相关文章

  • 设计模式六大原则(二)--开闭原则
    1.简介1.1.概述开闭原则(Open/ClosedPrinciple,简称OCP)是软件设计原则中的一个重要原则,它指出:“软件实体(如类、模块、函数等)应该对扩展开放,对修改封闭。”这意味着我们应该设计出这样的软件实体,它们可以在不改变原有代码的基础上进行扩展和修改。开闭原则的核心思想是将......
  • Unity 麦扣 x 勇士传说 全解析 之 有限状态机(附各模块知识的链接,零基础也包学会的牢弟
            在编码前我一直有个疑问,为什么不是将方法写在一个一个类中,或者用的单例模式写个管理器来继承的方式来做怪物脚本,玩家控制和玩家动画控制的代码混在一起不说,与其他脚本之间的交互,让过于冗杂的代码不易阅读        这节开始应用的有限状态机,似乎一定......
  • C++实现设计模式——Builder模式
    C++实现设计模式——Builder模式建造者模式定义建造者(Builder)模式的定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品......
  • 设计模式 单例模式
    单例模式的定义单例模式是指确保一个类在任何情况下都只有一个实例,并且提供一个访问该单例的全局访问点。如何创建一个单例模式构造函数需要是private访问权限的,这样才能避免外部通过new创建实例;考虑对象创建时的线程安全问题;考虑是否支持延迟加载;考虑getInstanc......
  • 设计模式 适配器模式
    适配器模式适配器模式(Adapter)的定义如下:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式的结构适配器模式通常涉及以下几个角色:目标(Target):定义客户端所期望的接口。适配者(Adaptee):定义一个已经存在......
  • C++ 设计模式——建造者模式
    建造者模式建造者模式组成部分建造者模式使用步骤1.定义产品类2.创建具体产品类3.创建建造者接口4.实现具体建造者5.创建指挥者类6.客户端代码建造者模式UML图建造者模式UML图解析建造者模式的优缺点建造者模式的适用场景完整代码建造者模式建造者模式(B......
  • 工厂设计模式:深入解析与应用
    工厂设计模式:深入解析与应用在软件开发领域,设计模式是解决常见问题的最佳实践。工厂设计模式(FactoryDesignPattern)作为一种创建型设计模式,提供了一种创建对象的接口,但由子类决定要实例化的类是哪一个。本文将深入探讨工厂设计模式的定义、分类、实现方式、优缺点以及应用......
  • 【项目】多设计模式下的同步&&异步日志系统(二)
    继上文对日志系统的介绍,并且实现了日志等级、日志消息的定义、以及格式化消息等。由这三个模块就能完整的输出一条消息。但是考虑到日志消息不可能全部都通过显示器展示。本项目针对落地方式,也进行多种编写,以供使用。消息落地类(简单工厂模式)消息落地的方式标准输出文件按......
  • 【项目】多设计模式下的同步&&异步日志系统(三)
    继前俩次完成了日志系统的等级类、消息结构以及格式化消息,并且将格式化的数据实现落地。落地存在同步和异步的落地方式。同步:本线程生成消息,并且进行IO写。异步:线程生成消息,交给子线程写。为此实现了双缓冲区用来减少异步带来的频繁申请锁释放锁减低效率。本文继续实现异步......
  • Roslyn 简单实现代码智能提示补全功能
    相信有很多伙伴热衷于编写IDE应用,在dotnet系下,通过Roslyn友好的API和强大的能力,实现一个代码智能提示是非常简单的事情。本文将和大家简单介绍一下如何使用Roslyn实现简单的代码智能提示补全功能现在的dotnetC#核心构建工具链是非常完善的且开放的,基于dotnet完善......