【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 节点里。

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

在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值