Player Control System

Generates a complete player control system in Godot: movement, jump, attack, state machine, with 2D/3D support.

Sby Skills Guide Bot
DevelopmentAdvanced
1308/18/2026
Claude CodeCursorWindsurfCopilotCodex
#player-system#godot#game-development#state-machine#character-controller

Recommended for


name: player-system description: > 玩家控制系统技能。生成完整的玩家控制器,包括移动、跳跃、攻击、状态机等功能。 支持 2D 和 3D 多种控制模式。 version: 1.0.0 dependencies:

  • godot-core
  • code-generator
  • input-system triggers:
  • pattern: "玩家控制|角色控制|移动系统|Player|玩家移动" inputs:
  • name: player_type type: string enum: ["platformer_2d", "topdown_2d", "third_person_3d", "first_person_3d"] required: true
  • name: features type: array items: enum: ["movement", "jump", "double_jump", "dash", "attack", "interact", "crouch", "climb", "swim"] required: false outputs:
  • name: player_scene type: file path_pattern: "res://scenes/prefabs/player/player.tscn"
  • name: player_script type: file path_pattern: "res://scripts/entities/player/player.gd"
  • name: player_states type: directory path_pattern: "res://scripts/entities/player/states/"

玩家控制系统技能

生成完整的玩家控制系统,包含移动、状态机、输入处理等功能。

支持的控制器类型

Platformer 2D(平台跳跃)

适用游戏:横版动作、银河恶魔城、平台跳跃

特点

  • 基于 CharacterBody2D
  • 重力物理系统
  • 可配置浮空时间(Coyote Time)
  • 跳跃缓冲(Jump Buffer)
  • 可变跳跃高度

生成文件

res://scenes/prefabs/player/player_2d.tscn
res://scripts/entities/player/player_2d.gd
res://scripts/entities/player/player_2d_controller.gd
res://scripts/entities/player/states/
    ├── player_state.gd
    ├── idle_state.gd
    ├── run_state.gd
    ├── jump_state.gd
    ├── fall_state.gd
    └── ...

Top-down 2D(俯视角)

适用游戏:RPG、动作冒险、射击游戏

特点

  • 8 方向移动
  • 可选瞄准系统
  • 平滑旋转

Third Person 3D(第三人称)

适用游戏:动作 RPG、冒险游戏

特点

  • 与摄像机联动的移动方向
  • 斜坡处理
  • 阶梯检测

First Person 3D(第一人称)

适用游戏:FPS、探索游戏、恐怖游戏

特点

  • 鼠标视角控制
  • 头部摇摆
  • 可选武器系统

Platformer 2D 完整实现

玩家场景结构

Player (CharacterBody2D)
├── CollisionShape2D
├── Sprite2D / AnimatedSprite2D
├── AnimationPlayer
├── StateMachine
│   ├── IdleState
│   ├── RunState
│   ├── JumpState
│   ├── FallState
│   ├── DashState (可选)
│   └── AttackState (可选)
├── Hitbox (Area2D) - 攻击判定
├── Hurtbox (Area2D) - 受伤判定
├── CoyoteTimer (Timer)
├── JumpBufferTimer (Timer)
└── Camera2D (可选)

玩家主脚本 (player_2d.gd)

## 2D 平台跳跃玩家控制器
##
## 包含移动、跳跃、攻击等核心功能
## @author AI Generated
## @version 1.0.0
class_name Player2D
extends CharacterBody2D

# region 信号
signal died
signal health_changed(old_value: int, new_value: int)
signal state_changed(old_state: String, new_state: String)
signal jumped
signal landed
signal attacked
# endregion

# region 常量
const GRAVITY: float = 980.0
# endregion

# region 导出变量 - 移动
@export_group("Movement")
@export var move_speed: float = 200.0
@export var acceleration: float = 1500.0
@export var friction: float = 1200.0
@export var air_friction: float = 600.0
# endregion

# region 导出变量 - 跳跃
@export_group("Jump")
@export var jump_velocity: float = -350.0
@export var max_jumps: int = 1
@export var coyote_time: float = 0.1
@export var jump_buffer_time: float = 0.1
@export var variable_jump_multiplier: float = 0.5
# endregion

# region 导出变量 - 冲刺
@export_group("Dash")
@export var dash_enabled: bool = true
@export var dash_speed: float = 400.0
@export var dash_duration: float = 0.2
@export var dash_cooldown: float = 1.0
# endregion

# region 导出变量 - 属性
@export_group("Stats")
@export var max_health: int = 100
# endregion

# region 状态变量
var current_health: int:
    set(value):
        var old = current_health
        current_health = clampi(value, 0, max_health)
        health_changed.emit(old, current_health)
        if current_health <= 0:
            _die()

var is_alive: bool:
    get: return current_health > 0

var facing_direction: int = 1  # 1 = 右, -1 = 左
var jumps_remaining: int = 0
var can_coyote_jump: bool = false
var jump_buffered: bool = false
var can_dash: bool = true
var is_dashing: bool = false
# endregion

# region 节点引用
@onready var sprite: Sprite2D = $Sprite2D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var state_machine: Node = $StateMachine
@onready var coyote_timer: Timer = $CoyoteTimer
@onready var jump_buffer_timer: Timer = $JumpBufferTimer
# endregion

# region 生命周期
func _ready() -> void:
    current_health = max_health
    jumps_remaining = max_jumps
    _setup_timers()

func _physics_process(delta: float) -> void:
    if not is_alive:
        return
    
    # 重力
    if not is_on_floor() and not is_dashing:
        velocity.y += GRAVITY * delta
    
    # 更新朝向
    var input_dir = Input.get_axis("move_left", "move_right")
    if input_dir != 0:
        facing_direction = signi(input_dir)
        sprite.flip_h = facing_direction < 0
    
    move_and_slide()
    
    # 检测落地
    if is_on_floor():
        jumps_remaining = max_jumps
        if jump_buffered:
            jump()

func _unhandled_input(event: InputEvent) -> void:
    if not is_alive:
        return
    
    if event.is_action_pressed("jump"):
        _handle_jump_input()
    
    if event.is_action_released("jump"):
        _handle_jump_release()
    
    if event.is_action_pressed("dash") and dash_enabled:
        _handle_dash_input()
    
    if event.is_action_pressed("attack"):
        attacked.emit()
# endregion

# region 移动
func apply_movement(delta: float, direction: float) -> void:
    if direction != 0:
        velocity.x = move_toward(velocity.x, direction * move_speed, acceleration * delta)
    else:
        var current_friction = friction if is_on_floor() else air_friction
        velocity.x = move_toward(velocity.x, 0, current_friction * delta)

func apply_gravity(delta: float, multiplier: float = 1.0) -> void:
    if not is_on_floor():
        velocity.y += GRAVITY * multiplier * delta
# endregion

# region 跳跃
func _handle_jump_input() -> void:
    if is_on_floor() or can_coyote_jump:
        jump()
    elif jumps_remaining > 0:
        jump()
    else:
        # 缓冲跳跃
        jump_buffered = true
        jump_buffer_timer.start(jump_buffer_time)

func _handle_jump_release() -> void:
    # 可变跳跃高度
    if velocity.y < 0:
        velocity.y *= variable_jump_multiplier

func jump() -> void:
    velocity.y = jump_velocity
    jumps_remaining -= 1
    jump_buffered = false
    can_coyote_jump = false
    jumped.emit()

func start_coyote_time() -> void:
    can_coyote_jump = true
    coyote_timer.start(coyote_time)
# endregion

# region 冲刺
func _handle_dash_input() -> void:
    if can_dash and not is_dashing:
        dash()

func dash() -> void:
    is_dashing = true
    can_dash = false
    velocity = Vector2(facing_direction * dash_speed, 0)
    
    await get_tree().create_timer(dash_duration).timeout
    is_dashing = false
    
    await get_tree().create_timer(dash_cooldown).timeout
    can_dash = true
# endregion

# region 伤害与死亡
func take_damage(amount: int, knockback: Vector2 = Vector2.ZERO) -> void:
    if not is_alive:
        return
    
    current_health -= amount
    
    if knockback != Vector2.ZERO:
        velocity = knockback

func heal(amount: int) -> void:
    current_health += amount

func _die() -> void:
    died.emit()
    # 播放死亡动画或处理
# endregion

# region 工具方法
func _setup_timers() -> void:
    coyote_timer.one_shot = true
    coyote_timer.timeout.connect(func(): can_coyote_jump = false)
    
    jump_buffer_timer.one_shot = true
    jump_buffer_timer.timeout.connect(func(): jump_buffered = false)

func get_input_direction() -> float:
    return Input.get_axis("move_left", "move_right")
# endregion

状态机基类 (state_machine.gd)

## 通用状态机
class_name StateMachine
extends Node

signal state_changed(old_state: State, new_state: State)

@export var initial_state: State

var current_state: State
var states: Dictionary = {}

func _ready() -> void:
    # 收集所有子状态
    for child in get_children():
        if child is State:
            states[child.name.to_lower()] = child
            child.state_machine = self
            child.player = get_parent()
    
    # 设置初始状态
    if initial_state:
        current_state = initial_state
        current_state.enter()

func _process(delta: float) -> void:
    if current_state:
        current_state.update(delta)

func _physics_process(delta: float) -> void:
    if current_state:
        current_state.physics_update(delta)
        
        # 检查状态转换
        var next_state_name = current_state.get_transition()
        if next_state_name != &"":
            transition_to(next_state_name)

func _unhandled_input(event: InputEvent) -> void:
    if current_state:
        current_state.handle_input(event)

func transition_to(state_name: StringName) -> void:
    var new_state = states.get(str(state_name).to_lower())
    if new_state == null:
        push_error("State not found: " + str(state_name))
        return
    
    if new_state == current_state:
        return
    
    var old_state = current_state
    current_state.exit()
    current_state = new_state
    current_state.enter()
    
    state_changed.emit(old_state, new_state)

状态基类 (state.gd)

## 状态基类
class_name State
extends Node

var state_machine: StateMachine
var player: CharacterBody2D

func enter() -> void:
    pass

func exit() -> void:
    pass

func update(_delta: float) -> void:
    pass

func physics_update(_delta: float) -> void:
    pass

func handle_input(_event: InputEvent) -> void:
    pass

func get_transition() -> StringName:
    return &""

Idle 状态 (idle_state.gd)

## 待机状态
class_name IdleState
extends State

func enter() -> void:
    player.animation_player.play("idle")

func physics_update(delta: float) -> void:
    player.apply_movement(delta, 0)

func get_transition() -> StringName:
    if not player.is_on_floor():
        return &"fall"
    
    if player.get_input_direction() != 0:
        return &"run"
    
    return &""

Run 状态 (run_state.gd)

## 奔跑状态
class_name RunState
extends State

func enter() -> void:
    player.animation_player.play("run")

func physics_update(delta: float) -> void:
    var direction = player.get_input_direction()
    player.apply_movement(delta, direction)

func get_transition() -> StringName:
    if not player.is_on_floor():
        player.start_coyote_time()
        return &"fall"
    
    if player.get_input_direction() == 0:
        return &"idle"
    
    return &""

Jump 状态 (jump_state.gd)

## 跳跃状态
class_name JumpState
extends State

func enter() -> void:
    player.animation_player.play("jump")

func physics_update(delta: float) -> void:
    var direction = player.get_input_direction()
    player.apply_movement(delta, direction)
    player.apply_gravity(delta)

func get_transition() -> StringName:
    if player.velocity.y >= 0:
        return &"fall"
    
    return &""

Fall 状态 (fall_state.gd)

## 下落状态
class_name FallState
extends State

func enter() -> void:
    player.animation_player.play("fall")

func physics_update(delta: float) -> void:
    var direction = player.get_input_direction()
    player.apply_movement(delta, direction)
    player.apply_gravity(delta)

func get_transition() -> StringName:
    if player.is_on_floor():
        player.landed.emit()
        if player.get_input_direction() != 0:
            return &"run"
        else:
            return &"idle"
    
    return &""

Top-down 2D 实现

玩家脚本 (player_topdown.gd)

## 俯视角 2D 玩家控制器
class_name PlayerTopdown
extends CharacterBody2D

# region 信号
signal died
signal health_changed(old_value: int, new_value: int)
# endregion

# region 导出变量
@export_group("Movement")
@export var move_speed: float = 200.0
@export var acceleration: float = 2000.0
@export var friction: float = 1800.0

@export_group("Stats")
@export var max_health: int = 100
# endregion

# region 状态
var current_health: int:
    set(value):
        var old = current_health
        current_health = clampi(value, 0, max_health)
        health_changed.emit(old, current_health)
        if current_health <= 0:
            _die()

var look_direction: Vector2 = Vector2.RIGHT
# endregion

# region 节点引用
@onready var sprite: Sprite2D = $Sprite2D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
# endregion

func _ready() -> void:
    current_health = max_health

func _physics_process(delta: float) -> void:
    var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    
    if input_dir != Vector2.ZERO:
        look_direction = input_dir.normalized()
        velocity = velocity.move_toward(input_dir * move_speed, acceleration * delta)
        _update_animation("walk")
    else:
        velocity = velocity.move_toward(Vector2.ZERO, friction * delta)
        _update_animation("idle")
    
    # 更新朝向
    _update_sprite_direction()
    
    move_and_slide()

func _update_animation(anim_base: String) -> void:
    var direction_suffix = _get_direction_suffix()
    var anim_name = anim_base + "_" + direction_suffix
    if animation_player.has_animation(anim_name):
        animation_player.play(anim_name)

func _get_direction_suffix() -> String:
    # 8方向动画
    var angle = look_direction.angle()
    if angle < -2.75 or angle > 2.75:
        return "left"
    elif angle < -1.96:
        return "up_left"
    elif angle < -1.18:
        return "up"
    elif angle < -0.39:
        return "up_right"
    elif angle < 0.39:
        return "right"
    elif angle < 1.18:
        return "down_right"
    elif angle < 1.96:
        return "down"
    else:
        return "down_left"

func _update_sprite_direction() -> void:
    sprite.flip_h = look_direction.x < 0

func take_damage(amount: int) -> void:
    current_health -= amount

func _die() -> void:
    died.emit()

Third Person 3D 实现

玩家脚本 (player_3d.gd)

## 第三人称 3D 玩家控制器
class_name Player3D
extends CharacterBody3D

# region 信号
signal died
signal health_changed(old_value: int, new_value: int)
# endregion

# region 导出变量
@export_group("Movement")
@export var move_speed: float = 5.0
@export var sprint_speed: float = 8.0
@export var acceleration: float = 10.0
@export var rotation_speed: float = 10.0

@export_group("Jump")
@export var jump_velocity: float = 4.5
@export var gravity_multiplier: float = 1.0

@export_group("Stats")
@export var max_health: int = 100

@export_group("References")
@export var camera_pivot: Node3D
# endregion

# region 状态
var current_health: int
var is_sprinting: bool = false
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
# endregion

# region 节点引用
@onready var mesh: MeshInstance3D = $MeshInstance3D
@onready var animation_tree: AnimationTree = $AnimationTree
# endregion

func _ready() -> void:
    current_health = max_health
    Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _physics_process(delta: float) -> void:
    # 重力
    if not is_on_floor():
        velocity.y -= gravity * gravity_multiplier * delta
    
    # 跳跃
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity
    
    # 冲刺
    is_sprinting = Input.is_action_pressed("sprint") and is_on_floor()
    
    # 移动输入
    var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    
    # 相对于摄像机的移动方向
    var camera_basis = camera_pivot.global_transform.basis
    var direction = (camera_basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    direction.y = 0
    
    if direction:
        var target_speed = sprint_speed if is_sprinting else move_speed
        velocity.x = move_toward(velocity.x, direction.x * target_speed, acceleration * delta)
        velocity.z = move_toward(velocity.z, direction.z * target_speed, acceleration * delta)
        
        # 旋转角色朝向移动方向
        var target_rotation = atan2(-direction.x, -direction.z)
        rotation.y = lerp_angle(rotation.y, target_rotation, rotation_speed * delta)
    else:
        velocity.x = move_toward(velocity.x, 0, acceleration * delta)
        velocity.z = move_toward(velocity.z, 0, acceleration * delta)
    
    move_and_slide()
    
    # 更新动画
    _update_animations()

func _update_animations() -> void:
    if animation_tree:
        var speed = Vector2(velocity.x, velocity.z).length() / sprint_speed
        animation_tree.set("parameters/blend_position", speed)

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_pressed("ui_cancel"):
        Input.mouse_mode = Input.MOUSE_MODE_VISIBLE

func take_damage(amount: int) -> void:
    var old = current_health
    current_health = clampi(current_health - amount, 0, max_health)
    health_changed.emit(old, current_health)
    if current_health <= 0:
        died.emit()

输入映射配置

生成玩家系统时,自动在 project.godot 中添加输入映射:

[input]

move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":65,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"keycode":68,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null)
]
}
move_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":87,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":83,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null)
]
}
jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":32,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":false,"script":null)
]
}
dash={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":4194325,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":4,"script":null)
]
}
attack={
"deadzone": 0.5,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"button_index":1,"pressed":false,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":5,"script":null)
]
}
interact={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":69,"script":null)
, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":3,"script":null)
]
}
sprint={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"keycode":4194325,"script":null)
]
}

使用示例

生成 Platformer 2D 玩家

创建一个 2D 平台跳跃玩家控制器:
- 支持移动、跳跃、二段跳
- 支持冲刺
- 支持攻击
- 包含完整状态机

生成 Third Person 3D 玩家

创建一个第三人称 3D 玩家:
- WASD 移动,空格跳跃
- 与摄像机方向联动
- 支持冲刺
- 简单的攻击动作
Related skills