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行, 但是能极大解耦复杂状态转换, 对于简单的敌人逻辑相当好用

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

相关推荐
budingxiaomoli2 小时前
优选算法--优先级队列(堆)
算法
Trouvaille ~2 小时前
【优选算法篇】哈希表——空间换时间的极致艺术
c++·算法·leetcode·青少年编程·蓝桥杯·哈希算法·散列表
bbbb3652 小时前
算法调优的多目标优化与性能平衡模型的技术8
算法
Fcy6482 小时前
与二叉树有关算法题
算法·深度优先
️是782 小时前
信息奥赛一本通—编程启蒙(3346:【例60.3】 找素数)
数据结构·c++·算法
captain3762 小时前
map和set
数据结构·算法
qq_416018722 小时前
实时数据可视化库
开发语言·c++·算法
格林威2 小时前
工业相机参数解析:曝光时间与运动模糊的“生死博弈”
c++·人工智能·数码相机·opencv·算法·计算机视觉·工业相机
2401_873204652 小时前
C++中的策略模式进阶
开发语言·c++·算法