参考
原文地址:https://www.bilibili.com/read/cv15683152?spm_id_from=333.999.0.0
本篇用语雀“备份”了@张学徒(zhangxuetu)的状态机写法,在部分代码排版和注释风格上做了一定修改。并添加了原文没有的一些注释,方便查看和会意。
可以参照9.状态机初步一文,对照学习。
状态机
#============================================================# State Machine#============================================================# 状态机#============================================================# @datetime: 2022-3-15 17:02:55#============================================================class_name StateMachineextends Nodesignal 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 = childreturn blackboardfunc get_state_node(state) -> State: # 返回状态名称对应的节点return states[state] as State#============================================================# 内置#============================================================func _ready():# 找到所有子状态机记录到states字典for child in get_children():if child is State:states[child.name] = child# 如果存在至少1个状态 -- 默认进入第一个状态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 state_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) # 调用当前状态的进入
状态基类
#============================================================# State 状态基类 @datetime: 2022-3-15 17:03:14 张学徒 巽星石部分修改,用于展示#============================================================class_name Stateextends Nodeonready var state_machine = get_parent() # 获取和记录StateMachine对象#============================================================# 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 state_process(delta):pass# 进入状态func enter(data):pass# 退出状态func exit():pass# 切换状态func switch_to(state, data: Dictionary = {}):state_machine.switch_to(state, data)
可以看到,张学徒的代码更灵活,给予了状态基类更多的权限,比如自己切换状态,获取当前状态等等。
状态黑板类
另外张学徒还引入了一个叫做“StateBlackboard(状态黑板)”的额外类型,说是用于存储状态机的全局数据。
#============================================================# Blackboard 状态黑板 @datetime: 2022-3-15 17:31:58#============================================================class_name StateBlackboardextends Nodevar data : Dictionary = {}func _enter_tree():get_parent().blackboard = self # 赋值状态机的blackboard属性为自己
在做的时候,我会创建对应类型的状态机、状态和对应的黑板。比如我要给 Player 节点设计状态机,我会先创建对应的 PlayerStateMachine(Player状态机)、PlayerState(Player状态基类)、PlayerBlackboard(Player状态机黑板)。 ——@张学徒(zhangxuetu)
PlayerState的推测
原文的下半部分没有贴出完整的代码,所以联系作者要了项目源码,然而,部分设计已经修改,所以类名也有所修改,所以只好贴出项目中的源码。
#============================================================# PlayerBaseState#============================================================# * 这是这个状态机的基础状态类,默认添加到场景中,便于修改这个类型中的属性# 和方法。#============================================================# @datetime: 2022-5-1 22:35:53#============================================================class_name PlayerBaseStateextends BaseExecuteStatevar controller : PlayerControllervar input : InputControllervar attack_wrapper : AttackWrappervar equipment_wrapper : EquipmentWrappervar property_wrapper : PropertyWrappervar type_layer : PlayerTypeLayer#============================================================# Set/Get#============================================================func get_host() -> Player:return owner as Playerfunc get_blackboard() -> PlayerStateBlackboard:return .get_blackboard() as PlayerStateBlackboardfunc set_gravity_enabled(value: bool):get_move_controller().gravity_enabled = valuefunc get_layer() -> PlayerTypeLayer:return owner.type_layer as PlayerTypeLayerfunc get_input_controller() -> InputController:return inputfunc get_move_controller() -> SimplePlatformController:return controller.move_controllerfunc is_moving() -> bool:return controller.move_controller.is_moving()func is_on_floor() -> bool:return get_host().is_on_floor()func is_falling():return (not get_host().is_on_floor()or controller.move_controller.get_last_velocity().y > 0)func is_can_jump() -> bool:return controller.move_controller.jump_enabledfunc is_can_move() -> bool:return controller.move_controller.move_enabledfunc is_can_fall() -> bool:return (get_host() as KinematicBody2D).is_on_floor()func is_can_attack() -> bool:return (attack_wrapper.is_enabled()and equipment_wrapper.get_type_items("weapon").size() > 0)func is_on_stairs() -> bool:return type_layer.collision.is_can_climb_stairs()func is_on_stairs_top() -> bool:return type_layer.collision.is_on_stairs_top()func is_can_climb_stairs() -> bool:return is_on_stairs() or is_on_stairs_top()func is_can_climb_wall() -> bool:return get_host().is_on_wall()#============================================================# 内置#============================================================func _ready():# 等待 Player 中的 onready 节点都加载完成yield(owner, "ready")controller = get_layer().controllerinput = get_layer().controller.input_controllertype_layer = get_host().type_layerattack_wrapper = WrapperHelper.get_attack(owner)equipment_wrapper = WrapperHelper.get_equipment(owner)property_wrapper = WrapperHelper.get_property(owner)#============================================================# 状态方法#============================================================#(override)func enter():# printerr("进入到状态:", name)pass#(override)func exit():# printerr("退出状态:", name)pass#============================================================# 自定义#============================================================## 移动func input_move() -> bool:var direction : float = Input.get_axis(input.input_left, input.input_right)if direction and is_can_move():controller.move(direction)return truereturn false## 跳跃func input_jump() -> bool:if (Input.is_action_pressed(input.input_up)and is_can_jump()):if controller.jump():return truereturn false## 下落func input_fall() -> bool:if (Input.is_action_pressed(input.input_down)and is_can_fall()):controller.fall()return truereturn false## 发出攻击func input_attack() -> bool:if (Input.is_action_pressed(get_blackboard().input_attack)and is_can_attack()):attack_wrapper.attack()return truereturn false## 爬楼梯func input_climb_stairs() -> bool:var dir = Input.get_axis(input.input_up, input.input_down)if is_can_climb_stairs():if ((dir < 0 and not is_on_stairs_top()) # 向上移动or dir > 0 # 向下移动):get_host().move_and_slide(Vector2(0, property_wrapper.get_property("move_speed") * dir), Vector2.UP)return truereturn false## 爬墙func input_climb_wall():if (is_can_climb_wall()and Input.is_action_pressed(input.input_up)):get_move_controller().jump(# 爬墙速度为 jump_height 的 0.60.6 * property_wrapper.get_property('jump_height'), true)return truereturn false
#============================================================# PlayerIdle#============================================================# @datetime: 2022-4-26 00:59:06#============================================================class_name PlayerIdleextends PlayerBaseState#(override)func state_process(_arg0):set_gravity_enabled(not is_on_stairs_top())if input_fall():if is_on_stairs_top():switch_to(PlayerStateBlackboard.States.CLIMB_STARIS)if input_jump():switch_to(PlayerStateBlackboard.States.JUMP)elif input_attack():switch_to(PlayerStateBlackboard.States.ATTACK)elif input_climb_stairs():switch_to(PlayerStateBlackboard.States.CLIMB_STARIS)elif input_climb_wall():switch_to(PlayerStateBlackboard.States.CLIMB_WALL)elif input_move():switch_to(PlayerStateBlackboard.States.MOVE)
