用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/infometa/workbuddyskills --skill player-system命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| 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":"[Truncated]"},"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/"}] |
生成完整的玩家控制系统,包含移动、状态机、输入处理等功能。
适用游戏:横版动作、银河恶魔城、平台跳跃
特点:
生成文件:
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
└── ...
适用游戏:RPG、动作冒险、射击游戏
特点:
适用游戏:动作 RPG、冒险游戏
特点:
适用游戏:FPS、探索游戏、恐怖游戏
特点:
Player (CharacterBody2D)
├── CollisionShape2D
├── Sprite2D / AnimatedSprite2D
├── AnimationPlayer
├── StateMachine
│ ├── IdleState
│ ├── RunState
│ ├── JumpState
│ ├── FallState
│ ├── DashState (可选)
│ └── AttackState (可选)
├── Hitbox (Area2D) - 攻击判定
├── Hurtbox (Area2D) - 受伤判定
├── CoyoteTimer (Timer)
├── JumpBufferTimer (Timer)
└── Camera2D (可选)
## 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
## 通用状态机
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)
## 状态基类
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 &""
## 待机状态
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 &""
## 奔跑状态
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 &""
## 跳跃状态
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 &""
## 下落状态
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 &""
## 俯视角 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()
## 第三人称 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)
]
}
={
"deadzone": 0.5,
"events":
}
创建一个 2D 平台跳跃玩家控制器:
- 支持移动、跳跃、二段跳
- 支持冲刺
- 支持攻击
- 包含完整状态机
创建一个第三人称 3D 玩家:
- WASD 移动,空格跳跃
- 与摄像机方向联动
- 支持冲刺
- 简单的攻击动作