Godot状态机设计和使用方式

Posted 张学徒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Godot状态机设计和使用方式相关的知识,希望对你有一定的参考价值。

Godot 3.4.2

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

状态机是一个状态模式的绝佳展示,你可以多做以理解状态模式。

State.gd

#============================================================
#	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

#============================================================
#	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(存储状态机的全局数据)

#============================================================
#	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 节点设计状态机,我会先创建对应的 PlayerStateMachine(Player状态机)、PlayerState(Player状态基类)、PlayerBlackboard(Player状态机黑板)

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

  • PlayerStateMachine
    • PlayerBlackboard
    • 多个 PlayerState (如下图中的 Blackboard 之后的节点,都是扩展自 PlayerState 类)


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

比如下面几个功能

Idle 状态里

Move 状态

Jump 状态

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

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

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

把功能都写在这个类型的基础状态类里,全局的数据写在 Blackboard 节点里。

按照这样的设计与逻辑编写,状态机里添加状态节点,这样,我们的一个状态机就完成了。

以上是关于Godot状态机设计和使用方式的主要内容,如果未能解决你的问题,请参考以下文章

如果Godot脚本A和B都扩展了脚本C,A和B是否共享与C中相同的变量名?

Godot Engine可以用python语言开发吗

FPGA 如果没有外部复位,如何产生复位信号,用来复位状态机,或者复位寄存器初值

如何用verilog设计有限状态机

电梯控制系统基于VHDL语言和状态机实现的电梯控制系统的设计,使用了状态机

成都仪器开发:用状态机的方法进行串口触摸屏的界面设计