Godot 2D动作平台游戏玩家有限状态机实现方法
Godot 2D平台玩家有限状态机完整实现方案
以下实现完全匹配GDQuest的FSM架构设计,可直接基于你已有的代码框架补全,兼容你已经写完的KinematicBody2D移动、跳跃/二段跳核心逻辑。
1. 补全核心基类代码
所有状态、状态机的通用逻辑全部抽离到基类,避免重复代码。
State虚基类
所有状态的顶层父类,定义统一的生命周期接口:
# State.gd extends Node class_name State var state_machine: Node # 持有所属状态机的引用 var host: Node # 持有状态挂载对象(玩家)的引用 # 生命周期虚方法,子类按需重写 func _enter(msg: Dictionary = {}) -> void: pass # 进入状态时触发,用于初始化、接收传参 func _exit() -> void: pass # 离开状态时触发,用于重置状态、释放资源 func _physics_process(delta: float) -> void: pass # 物理帧更新,放移动、碰撞检测逻辑 func _unhandled_input(event: InputEvent) -> void: pass # 输入监听,放按键触发的状态跳转逻辑
StateMachine通用管理类
挂载在玩家节点下,负责状态注册、切换、生命周期事件转发:
# StateMachine.gd extends Node class_name StateMachine export var initial_state: NodePath # 编辑器里指定初始状态 var current_state: State var states: Dictionary = {} func _ready() -> void: # 注册所有子节点为可用状态 for child in get_children(): states[child.name] = child child.state_machine = self child.host = get_parent() # 启动初始状态 current_state = get_node(initial_state) current_state._enter() # 统一状态切换接口 func transition_to(target_state_name: String, msg: Dictionary = {}) -> void: if not states.has(target_state_name): push_warning("目标状态不存在:%s" % target_state_name) return if current_state: current_state._exit() current_state = states[target_state_name] current_state._enter(msg) # 把引擎回调转发给当前激活状态 func _physics_process(delta: float) -> void: if current_state: current_state._physics_process(delta) func _unhandled_input(event: InputEvent) -> void: if current_state: current_state._unhandled_input(event)
PlayerState玩家状态基类
抽离所有玩家状态共用的属性、移动逻辑:
# PlayerState.gd extends State class_name PlayerState # 提前引用玩家节点下的常用组件 onready var player: KinematicBody2D = host onready var sprite: Sprite = player.get_node("Sprite") onready var animation_player: AnimationPlayer = player.get_node("AnimationPlayer") # 移动参数(和你之前的配置对齐即可) export var move_speed: float = 320.0 export var jump_force: float = -550.0 export var double_jump_force: float = -480.0 var velocity: Vector2 = Vector2.ZERO var can_double_jump: bool = true # 通用水平移动逻辑 func _apply_horizontal_movement(delta: float) -> void: var input_dir = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") velocity.x = input_dir * move_speed if input_dir != 0: sprite.flip_h = input_dir < 0
2. 补全具体玩家状态逻辑
PlayerIdle 待机状态
# PlayerIdle.gd extends PlayerState func _enter(msg: Dictionary = {}) -> void: animation_player.play("idle") velocity.x = 0 can_double_jump = true # 落地重置二段跳标记 func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("jump"): velocity.y = jump_force state_machine.transition_to("PlayerJump", {"velocity": velocity}) return var input_dir = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") if abs(input_dir) > 0.1: state_machine.transition_to("PlayerMove") func _physics_process(delta: float) -> void: velocity.y += player.gravity_scale * ProjectSettings.get_setting("physics/2d/default_gravity") * delta velocity = player.move_and_slide(velocity, Vector2.UP) if not player.is_on_floor(): state_machine.transition_to("PlayerFall", {"velocity": velocity})
PlayerMove 地面移动状态
# PlayerMove.gd extends PlayerState func _enter(msg: Dictionary = {}) -> void: animation_player.play("move") can_double_jump = true func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("jump"): velocity.y = jump_force state_machine.transition_to("PlayerJump", {"velocity": velocity}) func _physics_process(delta: float) -> void: _apply_horizontal_movement(delta) velocity.y += player.gravity_scale * ProjectSettings.get_setting("physics/2d/default_gravity") * delta velocity = player.move_and_slide(velocity, Vector2.UP) if not player.is_on_floor(): state_machine.transition_to("PlayerFall", {"velocity": velocity}) elif abs(velocity.x) < 10: state_machine.transition_to("PlayerIdle")
PlayerJump 跳跃/二段跳状态
# PlayerJump.gd extends PlayerState func _enter(msg: Dictionary = {}) -> void: animation_player.play("jump") if msg.has("velocity"): velocity = msg["velocity"] func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("jump") and can_double_jump: velocity.y = double_jump_force can_double_jump = false animation_player.play("double_jump") func _physics_process(delta: float) -> void: _apply_horizontal_movement(delta) velocity.y += player.gravity_scale * ProjectSettings.get_setting("physics/2d/default_gravity") * delta velocity = player.move_and_slide(velocity, Vector2.UP) if velocity.y > 0: state_machine.transition_to("PlayerFall", {"velocity": velocity})
PlayerFall 下落状态
# PlayerFall.gd extends PlayerState func _enter(msg: Dictionary = {}) -> void: animation_player.play("fall") if msg.has("velocity"): velocity = msg["velocity"] func _unhandled_input(event: InputEvent) -> void: if event.is_action_pressed("jump") and can_double_jump: velocity.y = double_jump_force can_double_jump = false state_machine.transition_to("PlayerJump", {"velocity": velocity}) func _physics_process(delta: float) -> void: _apply_horizontal_movement(delta) velocity.y += player.gravity_scale * ProjectSettings.get_setting("physics/2d/default_gravity") * delta velocity = player.move_and_slide(velocity, Vector2.UP) if player.is_on_floor(): if abs(velocity.x) > 10: state_machine.transition_to("PlayerMove") else: state_machine.transition_to("PlayerIdle")
3. 场景配置步骤
- 打开玩家KinematicBody2D场景,添加一个
StateMachine类型子节点,命名为StateMachine - 在StateMachine节点下添加4个普通Node子节点,分别挂载上述4个玩家状态脚本,节点命名必须和代码里
transition_to传入的字符串完全一致:PlayerIdle、PlayerMove、PlayerJump、PlayerFall - 选中StateMachine节点,在Inspector面板把
Initial State属性指定为PlayerIdle节点路径 - 删除玩家根节点上之前写的移动、跳跃逻辑代码,所有逻辑已经拆分到对应状态中
常见问题排查
- 状态跳转无响应:检查状态节点命名大小写是否匹配,
initial_state路径是否配置正确 - 二段跳失效:确认Idle、Move状态进入时
can_double_jump是否重置为true - 移动手感不对:直接调整PlayerState里的速度、跳跃力参数,和你之前的配置对齐即可
内容的提问来源于stack exchange,提问作者DeruDeru
相关产品推荐
相关产品推荐

