【杂谈】Godot 游戏开发:有限状态机

学博而后可约,事历而后知要。

文章目录

零、引言

假设你正在编写玩家角色控制逻辑:当未按下方向键时保持空闲状态,一旦触发方向键输入就切换为移动状态,而按下跳跃键时则执行跳跃动作。这样的代码逻辑该如何实现呢?

一个常规思路是采用多重if-else条件判断结构------对于简单的应用场景,这种实现方式的确可行。但想要在此基础上扩展更多状态(如滑墙、二段跳、翻滚等),问题出现了,多个状态相互耦合,形成了网状结构。代码将不可避免地陷入条件嵌套的泥潭,变得臃肿复杂,难以维护。

为了解决状态间复杂的一对多关联问题,避免状态之间的高度耦合,简化系统结构。故引入了"状态管理器"来封装和协调多个状态之间的转换,从而降低状态间的耦合度,而这就是------"有限状态机"。

一、有限状态机

在引入了"状态管理器"封装一系列状态之间的交互后,从一个状态到另一个状态的转换必须经过状态管理器。当前的结构更加清晰,状态间错综复杂的网状结构得以消解,多个状态间的一对多关系,被转换成状态与状态管理器间的一对一关系。这也符合了迪米特原则: 状态只需知道状态管理器,不需要知道其他状态。

现在,当玩家在空闲状态中按下方向键时,该状态将立即发射包含"目标状态"的上下文。状态管理器接收后,将变更当前状态为目标状态,并执行目标状态的行为。

这使得各状态之间不再需要显式地相互引用,降低了彼此的耦合度,便于增减状态。

二、如何实现

有限状态机的核心由 ​​状态基类 ​​(State) 和 ​​状态管理器​​(StateMachine) 构成,二者通过信号机制实现解耦。

(一)状态基类

作为所有具体状态(如空闲、行走、跳跃等)的抽象基类:

python 复制代码
class_name State
extends Node
## 状态基类
## 用于状态模式中表示一个状态,子类应继承此类并实现具体状态逻辑

@warning_ignore("unused_signal")
signal transition  # 当前状态发出转换信号 
  1. 进入/退出状态;
python 复制代码
# 状态进入时回调
# 当状态到此状态时自动调用,用于初始化状态相关逻辑
func _on_enter() -> void:
	pass


# 状态退出时回调
# 当状态机切换出此状态时自动调用,用于清理状态相关资源
func _on_exit() -> void:
	pass
  1. 状态行为逻辑;
python 复制代码
# 状态处理函数(每帧调用)
func _on_process(_delta : float) -> void:
	pass


# 物理处理函数(固定时间步长调用)
func _on_physics_process(_delta : float) -> void:
	pass
  1. 信号驱动。
python 复制代码
# 状态转换条件检测(由状态管理器定期调用)
func _on_next_transitions() -> void:
	pass

(二)状态管理器类

状态管理器负责协调各状态转换。

python 复制代码
class_name StateMachine
extends Node
## 状态管理器类

@export var initial_state : State  # 初始状态

var states : Dictionary = {}  # 状态
var current_state : State  # 当前状态
var current_state_name : String  # 当前状态名
var parent_name: String 
  1. 初始化;
python 复制代码
func _ready() -> void:
	parent_name = get_parent().name
	
	# 获取状态管理器下所有状态
	# 加入字典、连接信号
	for child in get_children():
		if child is State:
			states[child.name.to_lower()] = child
			child.transition.connect(transition_to)
	
	# 进入初始状态
	if initial_state:
		initial_state._on_enter()
		current_state = initial_state
		current_state_name = current_state.name.to_lower()
  1. 执行当前状态下的行为逻辑;
python 复制代码
# _process 和 _physics_process
# 执行当前状态的行为(如空闲:播放空闲动画)
func _process(delta : float) -> void:
	if current_state:
		current_state._on_process(delta)


func _physics_process(delta: float) -> void:
	if current_state:
		current_state._on_physics_process(delta)
		current_state._on_next_transitions()
		#print(parent_name, " Current State: ", current_state_name)
  1. 监听状态转换信号,变更当前状态。
python 复制代码
# 状态转变信号的回调函数
# 状态转换
func transition_to(state_name : String) -> void:
	if state_name == current_state.name.to_lower():
		return
	
	var new_state = states.get(state_name.to_lower())
	
	if !new_state:
		return
	
	if current_state:
		current_state._on_exit()
	
	new_state._on_enter()
	
	current_state = new_state
	current_state_name = current_state.name.to_lower()
	#print("Current State: ", current_state_name)

(三)新增状态

当需要新增状态(如跳跃Jump)时,只需以下三步操作,极大提升了开发效率和系统可维护性。
创建继承State类的Jump类 实现特有逻辑 添加到StateMachine子节点

(四)具体实例

详见:【极简】Godot 4.4 有限状态机

相关推荐
top_designer13 小时前
游戏优化“屠龙技”:AI+Substance Painter LOD烘焙工作流
人工智能·游戏·prompt·aigc·设计师·substance painter·游戏美术
m0_5522008214 小时前
《UE5_C++多人TPS完整教程》学习笔记59 ——《P60 投射物武器(Projectile Weapons)》
c++·游戏·ue5
玉龙202514 小时前
使用虚幻引擎|UE5制作自动开关门
ue5·游戏引擎·虚幻·虚幻引擎教程
liu****15 小时前
负载均衡式的在线OJ项目编写(二)
c++·负载均衡·个人开发
锦***林18 小时前
ABCTorrents官网入口 – 磁力搜索引擎网站
游戏·搜索引擎·娱乐
wanhengidc1 天前
BGP高防服务器具体是指什么
运维·服务器·网络·安全·游戏·智能手机
m0_552200821 天前
《UE5_C++多人TPS完整教程》学习笔记58 ——《P58 旋转奔跑动画(Rotate Running Animations)》
c++·游戏·ue5
wanhengidc1 天前
云手机性能会受到哪些因素的影响?
运维·服务器·网络·游戏·智能手机
般若Neo1 天前
人工智能与数字艺术 - AI技术创意应用(多模态、数字展演、游戏、元宇宙)
游戏·元宇宙·多模态
雪域迷影1 天前
使用C++编写的一款射击五彩敌人的游戏
开发语言·c++·游戏