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 类)
如上图所示,Idle
、Move
、Jump
等都是继承自 PlayerState
,这些状态在某些功能需求上是相同的,比如在 Idle
、 Move
、Jump
、Fall
都可以接收输入进行移动,在 PlayerState
里我们需要对这些功能进行编写。(注意这些都是共用的功能,如果是共用一个数据,一般都写在 Blackboard
里)。
比如下面几个功能
Idle 状态里
Move 状态
Jump 状态
PlayerState 中,Player 可以执行的能力。
input 开头的方法是指可以执行的功能, 接收 移动 攀爬 跳跃 落下 等等行为,每个行为都会返回一个 布尔值 bool
,用以判断是否执行了这个功能。
如果功能是可以执行多个的,比如 input_move()
可以在移动的时候执行其他功能,则直接不在 if 里进行优先级判断,而在 if 中的,则会有一个优先级,只能执行其中的一个动作,不能同时执行,因为总不能说,在跳跃的时候又同时在爬楼梯。
把功能都写在这个类型的基础状态类里,全局的数据写在 Blackboard 节点里。
按照这样的设计与逻辑编写,状态机里添加状态节点,这样,我们的一个状态机就完成了。
以上是关于Godot状态机设计和使用方式的主要内容,如果未能解决你的问题,请参考以下文章
如果Godot脚本A和B都扩展了脚本C,A和B是否共享与C中相同的变量名?
FPGA 如果没有外部复位,如何产生复位信号,用来复位状态机,或者复位寄存器初值