【Godot】设计状态机和使用方式
学徒的游戏小窝
2022年03月16日 13:21
收录于文集
共33篇

Godot 3.4.2

下面是一个简单的有限状态机基类,之后是一个使用状态机的思路

State.gd

代码块
Swift
自动换行
复制代码
#============================================================
#	State
#============================================================
#  子状态
#============================================================
# @datetime: 2022-3-15 17:03:14
#============================================================

class_name State
extends Node


onready var state_machine = get_parent()


#============================================================
#   Set/Get
#============================================================
func get_state_machine():
	return state_machine

func get_current_state() -> State:
	return state_machine.get_current_state() as State

func get_blackboard():
	return get_state_machine().get_blackboard() as StateBlackboard



#============================================================
#   自定义
#============================================================
## 进入到这个状态时
func enter(data):
	pass

## 退出这个状态时
func exit():
	pass

## 执行到当前状态时,会切换为调用这个方法
func state_process(delta):
	pass

## 切换状态
func switch_to(state, data: Dictionary = {}):
	state_machine.switch_to(state, data)
复制成功

StateMachine.gd

代码块
Swift
自动换行
复制代码
#============================================================
#	State Machine
#============================================================
#  状态机
#============================================================
# @datetime: 2022-3-15 17:02:55
#============================================================

class_name StateMachine
extends Node


signal state_changed(last_state, current_state)


var current_state
var current_state_node : State
var blackboard : StateBlackboard setget , get_blackboard
var states := {}



#============================================================
#   Set/Get
#============================================================
func get_state_node_list() -> Array:
	return states.values()

func get_blackboard():
	if blackboard == null:
		for child in get_children():
			if child is StateBlackboard:
				blackboard = child
	return blackboard

func get_state_node(state) -> State:
	return states[state] as State



#============================================================
#   内置
#============================================================
func _ready():
	# 找到所有子状态机
	for child in get_children():
		if child is State:
			# 根据节点的 name 作为 key 添加状态
			states[child.name] = child
			#child.set_physics_process(false)
			#child.set_process(false)
	
	if states.size() > 0:
		# 第一个节点
		current_state = states.keys()[0]
		current_state_node = get_state_node(current_state)
		current_state_node.enter(null)
	else:
		set_physics_process(false)
		printerr("没有添加状态节点,状态机没有启动")


func _physics_process(delta):
	current_state_node.state_process(delta)



#============================================================
#   自定义
#============================================================
func switch_to(state, data: Dictionary = {}):
	if state != current_state:
		current_state_node.exit()
		emit_signal("state_changed", current_state, state)
		# 切换到当前
		current_state = state
		current_state_node = get_state_node(current_state)
		current_state_node.enter(data)
复制成功

Blackboard.gd(存储状态机的全局数据)

代码块
Swift
自动换行
复制代码
#============================================================
#	Blackboard
#============================================================
#  状态黑板
#============================================================
# @datetime: 2022-3-15 17:31:58
#============================================================

class_name StateBlackboard
extends Node


var data : Dictionary = {}


func _enter_tree():
	# 状态机根节点的 blackboard 属性为自身
	get_parent().blackboard = self
复制成功

在做的时候,我会创建对应类型的状态机、状态和对应的黑板

比如我要给 Player 节点设计状态机,我会先创建对应的 (Player状态机)、(Player状态基类)、(Player状态机黑板)

这个设计之后,我们开始添加如下节点结构

  • PlayerStateMachine  

    • PlayerBlackboard

    • 多个 PlayerState

如上图所示, 等都是继承自 ,这些状态在某些功能需求上是相同的,比如在 都可以接收输入进行移动,在 里我们需要对这些功能进行编写。(注意这些都是共用的功能,如果是共用一个数据,一般都写在 里)。

比如下面几个功能

Idle 状态里  

 Move 状态  

 Jump 状态  

PlayerState 中,Player 可以执行的能力。  

input 开头的方法是指可以执行的功能, 接收 移动 攀爬 跳跃 落下 等等行为,每个行为都会返回一个 布尔值 ,用以判断是否执行了这个功能。

如果功能是可以执行多个的,比如 可以在移动的时候执行其他功能,则直接不在 if 里进行优先级判断,而在 if 中的,则会有一个优先级,只能执行其中的一个动作,不能同时执行,因为总不能说,在跳跃的时候又同时在爬楼梯。

这样,我们的一个状态机就完成了。