当前位置: 首页 > news >正文

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

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

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));}
}

        但是,如果你有n中不会频繁切换的状态,那我我建议你写在ChangeState里,就像开关一样,那么就理所当然地使用枚举和switch表达式

C# & Unity 面向对象补全计划 之 Switch 表达式(c# 8.0++)-CSDN博客

        这时再写一个枚举切换的逻辑,就完美地写出一个状态机了,我就不过多赘述了,自己试试吧!


http://www.mrgr.cn/news/5376.html

相关文章:

  • 《基于 Spark 的平替药品智能推荐方法》
  • 开发团队应对突发的技术故障和危机
  • 【nvm】误操作npm install npm@latest -g如何回退
  • SQLserver中的索引以及创建主键,外键,唯一约束,自增
  • 康耐视相机与发那科机器人通过Ethernet I/P直连与程序编写
  • wpf VisualStateManager.VisualStateGroups 介绍和举例
  • 结构型模式之外观模式
  • 美国高防服务器到底怎么选
  • 一文教你正确打通WSL和win10宿主机网络全通道
  • 鸿蒙(API 12 Beta3版)【Image Kit简介】图片处理服务
  • 互动营销小程序怎么制作
  • 122-域信息收集应用网络凭据CS插件AdfindBloodHound
  • Nginx--代理与负载均衡(扩展nginx配置7层协议及4层协议方法、会话保持)
  • 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main
  • 43.x86游戏实战-XXX寻找吸怪坐标
  • 探索CSS的:placeholder-shown伪类:增强表单输入体验
  • 等保二级测评:中小企业如何快速合规
  • webpack中1个文件修改会全部更新吗
  • linux性能查看命令和工具
  • Spring-MVC 结合 Swagger2