Godot游戏练习01-第16节-游戏中的状态机

本节主要了解状态机是什么, 以及在游戏开发中的使用

为什么需要状态机

如果我们想表达一个复杂的状态转换流程, 一般会绘制流程图, 比如给之前的敌人添加"充能"和"攻击"两种状态, 整个状态变换的过程可能如下

plaintext 复制代码
  ┌─────────┐      ┌─────────┐            
  │  spawn  ├─────►│ normal  ├────┐       
  └─────────┘      └─▲──┬────┘    │       
                     │  │         │       
                     │  │         │       
         ┌───────────┘  │      ┌──▼──────┐
         │ attack  ◄────┼──────┤ charge  │
         └──────┬──┘    │      └──┬──────┘
                │       │         │       
                │    ┌──▼───┐     │       
                └────► died ◄─────┘       
                     └──────┘             

在之前的实现中, 我使用了一个is_spawning标志位, 来表达状态, 并且多处判断该标志位, 来进行不同状态下的逻辑处理

如果是上面这种状态流程, 需要增加的标志位会增加好几个, 并且依据标志位来判断的逻辑会变得异常复杂, 这是一个非常好的使用状态机的情形

使用状态机有以下好处:

  • 解耦各种状态下的运行逻辑, 每个状态只关心该状态下的逻辑
  • 每个状态具有自己的生命周期, 有明确的 "进入" - "运行" - "退出" 生命事件
  • 状态机的切换比较简单, 如果使用标志位, 则需要同时管理多个标志位的状态, 容易出错
  • 同一时刻只有一个状态, 避免出现使用标志位可能出现的"多状态"重叠, 逻辑混乱

游戏开发中的状态机

在Godot中我们一般使用基于Node节点的状态机, 方便管理

先看看基础状态State的实现, 非常简单: 继承于Node方便放入场景节点树, 表达生命周期的三个函数, 以及一个状态转换的信号(供StateMachine监听切换状态)

gdscript 复制代码
class_name State
extends Node

@warning_ignore("unused_signal")
signal transitioned(next: String)

## 当进入状态时调用
func enter() -> void:
	pass


## 该状态的更新调用
func update() -> void:
	pass


# 退出状态时调用
func exit() -> void:
	pass

再看看状态机StateMachine的实现

gdscript 复制代码
class_name StateMachine
extends Node

signal state_changed(old: String, new: String)

@export var initial_state: String

# 当前状态, 空字符串表示没有状态
var current_state: String = String():
	get:
		return current_state
	set(value):
		_state_transition(value)

var states: Dictionary[String,State] = {}

func _ready() -> void:
	for child in get_children():
		if child is State:
			states[child.name.to_lower()] = child
			child.transitioned.connect(_on_state_transition)
	if initial_state:
		_state_transition(initial_state)


func _process(_delta: float) -> void:
	if current_state:
		states[current_state].update()


func _state_transition(next: String) -> void:
	if next not in states:
		push_error("Transition to a non-exists state: %s" % next)
		return
	if current_state == next:
		push_error("Transition to a same state: %s" % next)
		return
	if current_state in states:
		states[current_state].exit()
	var old_state = current_state
	current_state = next
	states[current_state].enter()
	state_changed.emit(old_state, current_state)


func _on_state_transition(next: String) -> void:
	current_state = next

整体也不复杂, ready时遍历所有子节点, 将State类型的子节点纳入管理中, 在process中执行当前状态的update生命函数, 处理State之间的切换, 以及State的enter/exit生命函数执行

整个StateMachine实现不到50行, 但是能极大解耦复杂状态转换, 对于简单的敌人逻辑相当好用

本节对状态机做一个整体介绍, 下次就使用这个状态机改进敌人, 让敌人实现最开始流程图中的状态转换

相关推荐
Nil20816 分钟前
leetcode 189轮转数组
数据结构·算法·leetcode
-dzk-29 分钟前
【动态规划】LC 118.杨辉三角
算法·动态规划
恣逍信点33 分钟前
《凌微经》静态自悖——变化之自因
人工智能·科技·算法·机器学习·业界资讯·拓扑学·哲学
吃着火锅x唱着歌1 小时前
LeetCode 3885.设计事件管理器
算法·leetcode·职场和发展
土司大王1 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode
Eloudy1 小时前
预词力:LLM 的唯一形式化能力
人工智能·算法·agent
依然鸣2 小时前
PTA团体程序设计天梯赛L1真题讲解L1-085-088
数据结构·c++·经验分享·算法·深度优先·pat考试
听取WA声一片(无恶意)2 小时前
CSP-J/S 初赛图论完全讲义
算法·csp-s初赛·csp-j初赛
Brilliantwxx2 小时前
【算法从零到千】【51-54】逆序对(分治+递归算法)
数据结构·算法·排序算法
小星星闪亮登场2 小时前
2026河南萌新联赛第四场--南阳理工学院
数据结构·算法·贪心算法·动态规划·哈希算法·广度优先