为什么你的if-else越写越乱?聊聊状态机

状态机是什么?

一句话定义:状态机:把对象的所有可能情况分成互斥的几种"状态",明确规定状态之间如何切换。

本质上是对实际情况的一种分类讨论。

为什么需要状态机

状态机解决的是:同一个对象,在不同状态下,对同一输入的不同响应问题。

想象一个游戏场景,你操作的角色需要进行不同的动作。 假设角色有这些状态:站立、跑步、跳跃、攻击、受击

刚开始的时候,你打算用if-else来解决

先来定义几个变量保存这些状态:

python 复制代码
is_jumping = False
is_attacking = False
is_running = False
is_hurt = False
is_standing  = False

根据不同状态进行不同形态的攻击:

python 复制代码
def on_attack_button():
    if is_jumping and not is_attacking and not is_hurt:
        do_air_attack()
    elif is_running and not is_attacking and not is_hurt:
        do_dash_attack()
    elif is_standing and not is_attacking and not is_hurt:
        do_normal_attack()
    # 还要考虑29种其他组合的合理性

问题来了:

  • 5个布尔变量,理论上有32(2^5)种组合
  • 但"跳跃中同时受击同时攻击"合法吗?
  • 每加一个状态,组合数翻倍
  • 漏掉一个判断就出bug

状态机要素

状态机的要素分为4个要素,即:现态、条件、动作、次态。 "现态"和"条件"是因,"动作"和"次态"是果。

(1)现态:是指当前所处状态;

(2)条件:又称为"事件"。当条件被满足时,将会触发一个动作,或者执行一次状态的迁移。

(3)动作:条件满足后执行的动作。动作不是必须的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

(4)次态:条件满足后要迁移往的新状态。"次态"是相对于"现态"而言的,"次态"一旦被激活,就转变成新的"现态"了。

用状态机来解决这个问题

核心思路:任何时刻,角色只能处于一个状态

因此很自然的,我们定义一个变量表示角色的状态:

python 复制代码
state = "站立"  # 只有一个变量

从状态到状态的转换,我们也需要定义:

状态(现态) 状态描述 条件(事件) 动作 次态
站立 默认待机状态 按方向键 播放跑步动画 跑步
按跳跃键 播放跳跃动画 跳跃
按攻击键 播放攻击动画 攻击
被打中 播放受击动画 受击
跑步 水平移动中 松开方向键 - 站立
按跳跃键 播放跳跃动画 跳跃
按攻击键 播放攻击动画 攻击
被打中 播放受击动画 受击
跳跃 空中状态 落地 - 站立
按攻击键 播放空中攻击动画 攻击
被打中 播放受击动画 受击
攻击 攻击动作中 动画结束 - 站立
被打中 播放受击动画 受击
受击 受击硬直中 硬直结束 - 站立
flowchart LR subgraph 基础状态 站立[站立] 跑步[跑步] end subgraph 动作状态 跳跃[跳跃] 攻击[攻击] 受击[受击] end 站立 -->|方向键| 跑步 跑步 -->|松开| 站立 站立 -->|跳跃键| 跳跃 跑步 -->|跳跃键| 跳跃 跳跃 -->|落地| 站立 站立 -->|攻击键| 攻击 跑步 -->|攻击键| 攻击 跳跃 -->|攻击键| 攻击 攻击 -->|动画结束| 站立 站立 -->|被打中| 受击 跑步 -->|被打中| 受击 跳跃 -->|被打中| 受击 攻击 -->|被打中| 受击 受击 -->|硬直结束| 站立

最后我们来实现不同形态的攻击(按攻击键):

python 复制代码
def on_attack_button():
    if state == "站立":
	    # 站立时的攻击
        do_normal_attack()
        state = "攻击"
    elif state == "跑步":
	    # 跑步时的攻击
        do_dash_attack()
        state = "攻击"
    elif state == "跳跃":
	    # 跳跃时的攻击
        do_air_attack()
        state = "攻击"
    # 其他状态按攻击键无效,不用写

对比

布尔变量组合 状态机
5个状态 最多32种组合要考虑 只有5种情况
加新状态 所有if都要检查 只加新状态的转换
非法情况 要手动排除 根本不存在

总结

代码的核心思路转变就是:

从"用多个变量组合描述状态"到"用单一变量明确状态"

核心难点是 如何定义状态 以做到互斥。

延伸

  • 状态很多时(几十个),考虑分层状态机
  • 需要同时处于多个状态时,考虑并行状态机
  • 实际项目中,状态机常用字典映射或状态模式实现,比if-elif更易维护
相关推荐
凌览3 小时前
2026年1月编程语言排行榜|C#拿下年度语言,Python稳居第一
前端·后端·程序员
码事漫谈3 小时前
【深度解析】为什么C++有了malloc,还需要new?
后端
晴虹3 小时前
lecen:一个更好的开源可视化系统搭建项目--组件和功能按钮的权限控制--全低代码|所见即所得|利用可视化设计器构建你的应用系统-做一
前端·后端·低代码
Java编程爱好者3 小时前
Java 并发编程:JUC 包中原子操作类的原理和用法
后端
爱分享的鱼鱼3 小时前
Pinia 深度解析:现代Vue应用状态管理最佳实践
前端·后端
JOEH603 小时前
🚀 别再用 Future.get() 傻等了!CompletableFuture 异步编排实战,性能提升 300%!
后端·程序员
神奇小汤圆3 小时前
原来可以搭建一个HTTP服务
后端
Hooray113 小时前
前后端分离_案例学习_Python+Flask+VUE3
后端·python·学习·flask
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
计算机毕设VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue博物馆展览与服务一体化系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计