参考

原文地址:https://www.bilibili.com/read/cv15683152?spm_id_from=333.999.0.0
本篇用语雀“备份”了@张学徒(zhangxuetu)的状态机写法,在部分代码排版和注释风格上做了一定修改。并添加了原文没有的一些注释,方便查看和会意。
可以参照9.状态机初步一文,对照学习。

状态机

  1. #============================================================
  2. # State Machine
  3. #============================================================
  4. # 状态机
  5. #============================================================
  6. # @datetime: 2022-3-15 17:02:55
  7. #============================================================
  8. class_name StateMachine
  9. extends Node
  10. signal state_changed(last_state, current_state)
  11. var current_state # 当前状态
  12. var current_state_node : State # 当前状态对应的节点
  13. var blackboard : StateBlackboard setget , get_blackboard # 状态黑板
  14. var states := {} # 所有可用状态的字典
  15. #============================================================
  16. # Set/Get
  17. #============================================================
  18. func get_state_node_list() -> Array: # 获取所有可用状态的节点的数组
  19. return states.values()
  20. func get_blackboard(): # 获取状态黑板
  21. if blackboard == null:
  22. for child in get_children():
  23. if child is StateBlackboard:
  24. blackboard = child
  25. return blackboard
  26. func get_state_node(state) -> State: # 返回状态名称对应的节点
  27. return states[state] as State
  28. #============================================================
  29. # 内置
  30. #============================================================
  31. func _ready():
  32. # 找到所有子状态机记录到states字典
  33. for child in get_children():
  34. if child is State:
  35. states[child.name] = child
  36. # 如果存在至少1个状态 -- 默认进入第一个状态
  37. if states.size() > 0:
  38. current_state = states.keys()[0]
  39. current_state_node = get_state_node(current_state)
  40. current_state_node.enter(null)
  41. else:
  42. set_physics_process(false)
  43. printerr("没有添加状态节点,状态机没有启动")
  44. # 只执行当前状态的物理帧处理过程
  45. func state_process(delta):
  46. current_state_node.state_process(delta)
  47. #============================================================
  48. # 自定义
  49. #============================================================
  50. # 切换状态
  51. func switch_to(state, data: Dictionary = {}):
  52. if state != current_state: # 如果新的状态不是现在的状态
  53. current_state_node.exit() # 判断当前状态的节点存在
  54. emit_signal("state_changed", current_state, state)
  55. current_state = state # 将状态记录为当前状态
  56. current_state_node = get_state_node(current_state) # 记录状态节点为当前状态的节点
  57. current_state_node.enter(data) # 调用当前状态的进入

状态基类

  1. #============================================================
  2. # State 状态基类 @datetime: 2022-3-15 17:03:14 张学徒 巽星石部分修改,用于展示
  3. #============================================================
  4. class_name State
  5. extends Node
  6. onready var state_machine = get_parent() # 获取和记录StateMachine对象
  7. #============================================================
  8. # Set/Get
  9. #============================================================
  10. # 获取状态机对象
  11. func get_state_machine():
  12. return state_machine
  13. # 获取状态机的当前状态
  14. func get_current_state() -> State:
  15. return state_machine.get_current_state() as State
  16. # 获取状态黑板
  17. func get_blackboard():
  18. return get_state_machine().get_blackboard() as StateBlackboard
  19. #============================================================
  20. # 核心代码
  21. #============================================================
  22. # 物理帧处理
  23. func state_process(delta):
  24. pass
  25. # 进入状态
  26. func enter(data):
  27. pass
  28. # 退出状态
  29. func exit():
  30. pass
  31. # 切换状态
  32. func switch_to(state, data: Dictionary = {}):
  33. state_machine.switch_to(state, data)

可以看到,张学徒的代码更灵活,给予了状态基类更多的权限,比如自己切换状态,获取当前状态等等。

状态黑板类

另外张学徒还引入了一个叫做“StateBlackboard(状态黑板)”的额外类型,说是用于存储状态机的全局数据。

  1. #============================================================
  2. # Blackboard 状态黑板 @datetime: 2022-3-15 17:31:58
  3. #============================================================
  4. class_name StateBlackboard
  5. extends Node
  6. var data : Dictionary = {}
  7. func _enter_tree():
  8. get_parent().blackboard = self # 赋值状态机的blackboard属性为自己

在做的时候,我会创建对应类型的状态机、状态和对应的黑板。比如我要给 Player 节点设计状态机,我会先创建对应的 PlayerStateMachine(Player状态机)、PlayerState(Player状态基类)、PlayerBlackboard(Player状态机黑板)。 ——@张学徒(zhangxuetu)

image.png

PlayerState的推测

原文的下半部分没有贴出完整的代码,所以联系作者要了项目源码,然而,部分设计已经修改,所以类名也有所修改,所以只好贴出项目中的源码。

  1. #============================================================
  2. # PlayerBaseState
  3. #============================================================
  4. # * 这是这个状态机的基础状态类,默认添加到场景中,便于修改这个类型中的属性
  5. # 和方法。
  6. #============================================================
  7. # @datetime: 2022-5-1 22:35:53
  8. #============================================================
  9. class_name PlayerBaseState
  10. extends BaseExecuteState
  11. var controller : PlayerController
  12. var input : InputController
  13. var attack_wrapper : AttackWrapper
  14. var equipment_wrapper : EquipmentWrapper
  15. var property_wrapper : PropertyWrapper
  16. var type_layer : PlayerTypeLayer
  17. #============================================================
  18. # Set/Get
  19. #============================================================
  20. func get_host() -> Player:
  21. return owner as Player
  22. func get_blackboard() -> PlayerStateBlackboard:
  23. return .get_blackboard() as PlayerStateBlackboard
  24. func set_gravity_enabled(value: bool):
  25. get_move_controller().gravity_enabled = value
  26. func get_layer() -> PlayerTypeLayer:
  27. return owner.type_layer as PlayerTypeLayer
  28. func get_input_controller() -> InputController:
  29. return input
  30. func get_move_controller() -> SimplePlatformController:
  31. return controller.move_controller
  32. func is_moving() -> bool:
  33. return controller.move_controller.is_moving()
  34. func is_on_floor() -> bool:
  35. return get_host().is_on_floor()
  36. func is_falling():
  37. return (
  38. not get_host().is_on_floor()
  39. or controller.move_controller.get_last_velocity().y > 0
  40. )
  41. func is_can_jump() -> bool:
  42. return controller.move_controller.jump_enabled
  43. func is_can_move() -> bool:
  44. return controller.move_controller.move_enabled
  45. func is_can_fall() -> bool:
  46. return (get_host() as KinematicBody2D).is_on_floor()
  47. func is_can_attack() -> bool:
  48. return (attack_wrapper.is_enabled()
  49. and equipment_wrapper.get_type_items("weapon").size() > 0
  50. )
  51. func is_on_stairs() -> bool:
  52. return type_layer.collision.is_can_climb_stairs()
  53. func is_on_stairs_top() -> bool:
  54. return type_layer.collision.is_on_stairs_top()
  55. func is_can_climb_stairs() -> bool:
  56. return is_on_stairs() or is_on_stairs_top()
  57. func is_can_climb_wall() -> bool:
  58. return get_host().is_on_wall()
  59. #============================================================
  60. # 内置
  61. #============================================================
  62. func _ready():
  63. # 等待 Player 中的 onready 节点都加载完成
  64. yield(owner, "ready")
  65. controller = get_layer().controller
  66. input = get_layer().controller.input_controller
  67. type_layer = get_host().type_layer
  68. attack_wrapper = WrapperHelper.get_attack(owner)
  69. equipment_wrapper = WrapperHelper.get_equipment(owner)
  70. property_wrapper = WrapperHelper.get_property(owner)
  71. #============================================================
  72. # 状态方法
  73. #============================================================
  74. #(override)
  75. func enter():
  76. # printerr("进入到状态:", name)
  77. pass
  78. #(override)
  79. func exit():
  80. # printerr("退出状态:", name)
  81. pass
  82. #============================================================
  83. # 自定义
  84. #============================================================
  85. ## 移动
  86. func input_move() -> bool:
  87. var direction : float = Input.get_axis(input.input_left, input.input_right)
  88. if direction and is_can_move():
  89. controller.move(direction)
  90. return true
  91. return false
  92. ## 跳跃
  93. func input_jump() -> bool:
  94. if (Input.is_action_pressed(input.input_up)
  95. and is_can_jump()
  96. ):
  97. if controller.jump():
  98. return true
  99. return false
  100. ## 下落
  101. func input_fall() -> bool:
  102. if (Input.is_action_pressed(input.input_down)
  103. and is_can_fall()
  104. ):
  105. controller.fall()
  106. return true
  107. return false
  108. ## 发出攻击
  109. func input_attack() -> bool:
  110. if (Input.is_action_pressed(get_blackboard().input_attack)
  111. and is_can_attack()
  112. ):
  113. attack_wrapper.attack()
  114. return true
  115. return false
  116. ## 爬楼梯
  117. func input_climb_stairs() -> bool:
  118. var dir = Input.get_axis(input.input_up, input.input_down)
  119. if is_can_climb_stairs():
  120. if ((dir < 0 and not is_on_stairs_top()) # 向上移动
  121. or dir > 0 # 向下移动
  122. ):
  123. get_host().move_and_slide(
  124. Vector2(0, property_wrapper.get_property("move_speed") * dir)
  125. , Vector2.UP
  126. )
  127. return true
  128. return false
  129. ## 爬墙
  130. func input_climb_wall():
  131. if (is_can_climb_wall()
  132. and Input.is_action_pressed(input.input_up)
  133. ):
  134. get_move_controller().jump(
  135. # 爬墙速度为 jump_height 的 0.6
  136. 0.6 * property_wrapper.get_property('jump_height')
  137. , true
  138. )
  139. return true
  140. return false
  1. #============================================================
  2. # PlayerIdle
  3. #============================================================
  4. # @datetime: 2022-4-26 00:59:06
  5. #============================================================
  6. class_name PlayerIdle
  7. extends PlayerBaseState
  8. #(override)
  9. func state_process(_arg0):
  10. set_gravity_enabled(not is_on_stairs_top())
  11. if input_fall():
  12. if is_on_stairs_top():
  13. switch_to(PlayerStateBlackboard.States.CLIMB_STARIS)
  14. if input_jump():
  15. switch_to(PlayerStateBlackboard.States.JUMP)
  16. elif input_attack():
  17. switch_to(PlayerStateBlackboard.States.ATTACK)
  18. elif input_climb_stairs():
  19. switch_to(PlayerStateBlackboard.States.CLIMB_STARIS)
  20. elif input_climb_wall():
  21. switch_to(PlayerStateBlackboard.States.CLIMB_WALL)
  22. elif input_move():
  23. switch_to(PlayerStateBlackboard.States.MOVE)