简单的玩家控制器
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
pid=10416 name=Godot_v4.7.2-stable_mono_win64 title='' responding=True
|
||||
pid=12060 name=Godot_v4.7.2-stable_mono_win64 title='main.tscn - agame - Godot Engine' responding=True
|
||||
pid=26164 name=godot-ai title='' responding=True
|
||||
pid=27664 name=godot-ai title='' responding=True
|
||||
@@ -0,0 +1,43 @@
|
||||
# 2026-09-21
|
||||
|
||||
## 接入 godot-ai MCP(已完成并验证)
|
||||
|
||||
- 把 Claude Desktop 的 `godot-ai` MCP 迁入 WorkBuddy 配置 `C:\Users\DHXY\.workbuddy\mcp.json`,与原有 `unityMCP` 并存。
|
||||
- 启动方式:`uvx.exe` + `--isolated --no-config ... --from godot-ai==4.1.0 godot-ai attach --port 8001 --ws-port 8002`。
|
||||
- 去掉了原配置中 `pythonw.exe` 包一层隐藏控制台的写法,直接走 uvx stdio。
|
||||
- 环境核实:`uvx.exe` 存在;Godot v4.7.2 mono 运行中,8001/8002 均 LISTEN;`addons/godot_ai/plugin.cfg` 版本 4.1.0 与服务端 pin 版本一致。
|
||||
- **已信任并实测通过**:`project_manage(op="settings_get", key="application/config/name")` → `agame`。
|
||||
|
||||
## 经验:MCP 信任入口位置
|
||||
|
||||
- 「自定义连接器 / 信任 MCP」不在侧边栏连接器列表里,而在 **「连接器」页顶栏 →「MCP 服务管理」弹窗 →「我的 MCP」列表**,每个服务右侧是启用开关(绿色圆点=已连接)。
|
||||
- 相关 i18n key(从 app.asar 反查得到):`connectorPanel.customConnector`=自定义连接器、`unifiedMarket.customConnector`=自定义连接器(市场页顶部「+」下拉里)。
|
||||
|
||||
## 本机环境补充(重要)
|
||||
|
||||
- `.workbuddy/memory` 下的文件在本会话中被外部清理过一次,写入后需复核是否仍在。
|
||||
- Bash 工具完全不可用(`mkdir`/`dirname` 都不存在),PowerShell 可用但需把输出写文件再 Read;Glob/Grep 对 `.workbuddy` 这类点目录不可靠,用 Read 直接读或 PowerShell 列目录。
|
||||
- PowerShell 里 `Add-Type` 被安全策略拦截,无法用 .NET API 激活窗口。
|
||||
|
||||
## 新建:第三人称控制器(胶囊体占位)
|
||||
|
||||
- 文件:`res://player/player.gd`、`res://player/player.tscn`、`res://world/main.tscn`(测试场地,已设为 main scene)。
|
||||
- 结构:Player(CharacterBody3D,不旋转) / Collision + Body(Node3D,视觉层朝移动方向转) / Mesh + CameraRig / SpringArm / Camera。
|
||||
- **关键教训**:相机臂不要挂在会旋转的本体下,否则按方向键会产生「相机跟着转」的螺旋轨迹(实测偏航 90°、直行无横向位移)。
|
||||
- 输入动作(已写入 project.godot):move_forward/back/left/right(WASD+方向键)、jump(Space)、sprint(Shift)。
|
||||
- 参数:走 5.0 / 跑 8.5 m/s,加速度 45(地面)/12(空中),跳跃初速 5.0(实测最高点 1.318 m)。
|
||||
|
||||
## 本机/工程改造记录
|
||||
|
||||
- `autoload/steamworks.gd`:Steam 初始化失败时原来直接 `get_tree().quit()`,导致没开 Steam 客户端时游戏秒退;已加 `OS.has_feature("editor")` 豁免(编辑器内返回不退出,发布版行为不变)。
|
||||
|
||||
## godot-ai MCP 使用要点(踩坑记录)
|
||||
|
||||
1. **路径是相对「当前编辑场景根」**,且要带根节点名,如 `/Player/CameraRig/SpringArm`;`parent_path: ""` 表示当前场景根。切场景后所有路径的含义会变,写操作前先确认 `editor_state.current_scene`。
|
||||
2. **`is_action_just_pressed()` 在 `_physics_process` 里对调试器注入的输入无效**(注入发生在渲染帧,物理帧号已 +1)。要么用 `_unhandled_input` 收事件 + 输入缓冲,要么只能读 `is_action_pressed()`。
|
||||
3. `game_eval` 里 `var x := Engine.get_main_loop().current_scene` 会编译不过(类型推断),要 `var tree := Engine.get_main_loop() as SceneTree` 并且给节点变量显式类型。
|
||||
4. `game_eval` 里的语法错误会把游戏卡进调试器断点(`game_status.status == "break"`),此后工具全部失效,必须 `project_manage(op="stop")` 后重启。
|
||||
5. 布尔属性写入会报 `WRONG_TYPE: Cannot write String to a bool property`(`Camera3D.current` 试过),改用其他方式或接受默认行为(只有一台相机时 Godot 会自动启用)。
|
||||
6. `SpringArm3D` 只在运行时摆放子节点;编辑器里预览要在场景里把相机 position.z 写成 spring_length 的值(已写入 player.tscn = 4.0)。
|
||||
7. 验证玩法用 `game_manage(op="input_sequence")`;涉及 `just_pressed` 的场景改用 `game_eval` + `Input.parse_input_event` + `await tree.physics_frame` 循环采样。
|
||||
|
||||
@@ -14,5 +14,9 @@ func initialize_steam() -> void:
|
||||
print("Did Steam initialize?: %s" % initialize_response)
|
||||
|
||||
if initialize_response['status'] > Steam.STEAM_API_INIT_RESULT_OK:
|
||||
print("Failed to initialize Steam: %s" % initialize_response)
|
||||
if OS.has_feature("editor"):
|
||||
# 编辑器内调试时 Steam 未必在运行,不退出,方便单独调试玩法
|
||||
return
|
||||
print("Failed to initialize Steam, shutting down: %s" % initialize_response)
|
||||
get_tree().quit()
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
extends CharacterBody3D
|
||||
## 第三人称控制器(胶囊体占位)
|
||||
##
|
||||
## 场景结构:
|
||||
## Player (CharacterBody3D) ← 只负责移动与碰撞,自身不旋转
|
||||
## ├─ Collision (CollisionShape3D, CapsuleShape3D)
|
||||
## ├─ Body (Node3D) ← 视觉层,朝移动方向转身;换角色模型时挂在它下面
|
||||
## │ └─ Mesh (MeshInstance3D)
|
||||
## └─ CameraRig (Node3D) ← 承担偏航 yaw,独立于本体旋转
|
||||
## └─ SpringArm (SpringArm3D) ← 承担俯仰 pitch,自动避障
|
||||
## └─ Camera (Camera3D)
|
||||
##
|
||||
## 操作:WASD/方向键移动,鼠标转视角,Shift 疾跑,空格跳跃,Esc 释放/锁定鼠标
|
||||
|
||||
## 跳跃输入缓冲帧数(输入事件在渲染帧到达,物理帧读取,避免漏跳)
|
||||
const JUMP_BUFFER_FRAMES: int = 6
|
||||
|
||||
@export_group("移动")
|
||||
## 常规移动速度(米/秒)
|
||||
@export var walk_speed: float = 5.0
|
||||
## 疾跑速度(米/秒)
|
||||
@export var sprint_speed: float = 8.5
|
||||
## 地面加速度(米/秒²)
|
||||
@export var acceleration: float = 45.0
|
||||
## 空中加速度(米/秒²)
|
||||
@export var air_acceleration: float = 12.0
|
||||
## 起跳初速度(米/秒)
|
||||
@export var jump_velocity: float = 5.0
|
||||
## 视觉层转身平滑速度
|
||||
@export var turn_speed: float = 10.0
|
||||
|
||||
@export_group("相机")
|
||||
## 鼠标灵敏度(弧度/像素)
|
||||
@export var mouse_sensitivity: float = 0.0025
|
||||
## 最低俯角(向下看)
|
||||
@export var pitch_min_deg: float = -80.0
|
||||
## 最高仰角(向上看)
|
||||
@export var pitch_max_deg: float = 45.0
|
||||
|
||||
@onready var body: Node3D = $Body
|
||||
@onready var camera_rig: Node3D = $CameraRig
|
||||
@onready var spring_arm: SpringArm3D = $CameraRig/SpringArm
|
||||
|
||||
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
|
||||
var _yaw: float = 0.0
|
||||
var _pitch: float = 0.0
|
||||
var _jump_buffer: int = 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_yaw = camera_rig.rotation.y
|
||||
_pitch = clampf(spring_arm.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
|
||||
spring_arm.rotation.x = _pitch
|
||||
set_mouse_captured(true)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
|
||||
_yaw -= event.relative.x * mouse_sensitivity
|
||||
_pitch = clampf(
|
||||
_pitch - event.relative.y * mouse_sensitivity,
|
||||
deg_to_rad(pitch_min_deg),
|
||||
deg_to_rad(pitch_max_deg)
|
||||
)
|
||||
camera_rig.rotation.y = _yaw
|
||||
spring_arm.rotation.x = _pitch
|
||||
elif event.is_action_pressed("jump"):
|
||||
_jump_buffer = JUMP_BUFFER_FRAMES
|
||||
elif event.is_action_pressed("ui_cancel"):
|
||||
set_mouse_captured(Input.mouse_mode != Input.MOUSE_MODE_CAPTURED)
|
||||
elif event is InputEventMouseButton and event.pressed and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
|
||||
set_mouse_captured(true)
|
||||
|
||||
|
||||
func set_mouse_captured(captured: bool) -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if captured else Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
# --- 重力 ---
|
||||
if is_on_floor():
|
||||
if velocity.y < 0.0:
|
||||
velocity.y = 0.0
|
||||
else:
|
||||
velocity.y -= _gravity * delta
|
||||
|
||||
# --- 跳跃(读取输入缓冲)---
|
||||
if _jump_buffer > 0:
|
||||
_jump_buffer -= 1
|
||||
if is_on_floor():
|
||||
velocity.y = jump_velocity
|
||||
_jump_buffer = 0
|
||||
|
||||
# --- 相对相机的水平输入 ---
|
||||
var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
|
||||
var cam_basis := camera_rig.global_transform.basis
|
||||
var wish := cam_basis.x * input_dir.x + (-cam_basis.z) * input_dir.y
|
||||
wish.y = 0.0
|
||||
|
||||
var wants_move := wish.length() > 0.001
|
||||
if wants_move:
|
||||
wish = wish.normalized()
|
||||
# 只转视觉层,不动本体,也不影响相机(Godot 前进方向为 -Z)
|
||||
var target_yaw := atan2(-wish.x, -wish.z)
|
||||
body.rotation.y = lerp_angle(body.rotation.y, target_yaw, clampf(turn_speed * delta, 0.0, 1.0))
|
||||
|
||||
var speed := sprint_speed if Input.is_action_pressed("sprint") else walk_speed
|
||||
if not wants_move:
|
||||
speed = 0.0
|
||||
var accel := acceleration if is_on_floor() else air_acceleration
|
||||
|
||||
var horizontal := Vector2(velocity.x, velocity.z)
|
||||
horizontal = horizontal.move_toward(Vector2(wish.x, wish.z) * speed, accel * delta)
|
||||
velocity.x = horizontal.x
|
||||
velocity.z = horizontal.y
|
||||
|
||||
move_and_slide()
|
||||
@@ -0,0 +1 @@
|
||||
uid://f5ptp3is4agf
|
||||
@@ -0,0 +1,41 @@
|
||||
[gd_scene format=3 uid="uid://bekuuw153mv03"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://f5ptp3is4agf" path="res://player/player.gd" id="1_oul6g"]
|
||||
|
||||
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_rkbax"]
|
||||
radius = 0.4
|
||||
height = 1.8
|
||||
|
||||
[sub_resource type="CapsuleMesh" id="CapsuleMesh_g1dw6"]
|
||||
radius = 0.4
|
||||
height = 1.8
|
||||
radial_segments = 24
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yw30f"]
|
||||
albedo_color = Color(0.23137255, 0.50980395, 0.9647059, 1)
|
||||
roughness = 0.6
|
||||
|
||||
[node name="Player" type="CharacterBody3D" unique_id=869687860]
|
||||
script = ExtResource("1_oul6g")
|
||||
|
||||
[node name="Collision" type="CollisionShape3D" parent="." unique_id=215132582]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
|
||||
shape = SubResource("CapsuleShape3D_rkbax")
|
||||
|
||||
[node name="CameraRig" type="Node3D" parent="." unique_id=1774966304]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.45, 0)
|
||||
|
||||
[node name="SpringArm" type="SpringArm3D" parent="CameraRig" unique_id=917938711]
|
||||
transform = Transform3D(1, 0, 0, 0, 0.9659257, 0.25881964, 0, -0.25881964, 0.9659257, 0, 0, 0)
|
||||
spring_length = 4.0
|
||||
|
||||
[node name="Camera" type="Camera3D" parent="CameraRig/SpringArm" unique_id=52294557]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
|
||||
fov = 70.0
|
||||
|
||||
[node name="Body" type="Node3D" parent="." unique_id=394283421]
|
||||
|
||||
[node name="Mesh" type="MeshInstance3D" parent="Body" unique_id=1230094944]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0)
|
||||
mesh = SubResource("CapsuleMesh_g1dw6")
|
||||
surface_material_override/0 = SubResource("StandardMaterial3D_yw30f")
|
||||
@@ -11,6 +11,7 @@ config_version=5
|
||||
[application]
|
||||
|
||||
config/name="agame"
|
||||
run/main_scene="res://world/main.tscn"
|
||||
config/features=PackedStringArray("4.7", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
@@ -36,6 +37,43 @@ project/assembly_name="agame"
|
||||
|
||||
enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg", "res://addons/godotsteam/plugin.cfg")
|
||||
|
||||
[input]
|
||||
|
||||
move_forward={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":87,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194320,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_back={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194322,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_left={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"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,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194319,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
move_right={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":68,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194321,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
jump={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":32,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
sprint={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194325,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[physics]
|
||||
|
||||
3d/physics_engine="Jolt Physics"
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
[gd_scene format=3 uid="uid://bp8l1uwgshc86"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://bekuuw153mv03" path="res://player/player.tscn" id="1_gjxf3"]
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_hyim0"]
|
||||
size = Vector3(40, 0.5, 40)
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_4f7fj"]
|
||||
size = Vector2(40, 40)
|
||||
subdivide_width = 8
|
||||
subdivide_depth = 8
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_spvjk"]
|
||||
albedo_color = Color(0.5529412, 0.5803922, 0.61960787, 1)
|
||||
roughness = 0.9
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_cmo1i"]
|
||||
sky_top_color = Color(0.38, 0.45, 0.55, 1)
|
||||
sky_horizon_color = Color(0.65, 0.67, 0.7, 1)
|
||||
ground_bottom_color = Color(0.2, 0.17, 0.13, 1)
|
||||
ground_horizon_color = Color(0.65, 0.67, 0.7, 1)
|
||||
|
||||
[sub_resource type="Sky" id="Sky_i1eo7"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_cmo1i")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_gu74f"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_i1eo7")
|
||||
ambient_light_source = 3
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=58155864]
|
||||
|
||||
[node name="Ground" type="StaticBody3D" parent="." unique_id=305091665]
|
||||
|
||||
[node name="GroundShape" type="CollisionShape3D" parent="Ground" unique_id=8315700]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
|
||||
shape = SubResource("BoxShape3D_hyim0")
|
||||
|
||||
[node name="GroundMesh" type="MeshInstance3D" parent="Ground" unique_id=51344137]
|
||||
mesh = SubResource("PlaneMesh_4f7fj")
|
||||
surface_material_override/0 = SubResource("StandardMaterial3D_spvjk")
|
||||
|
||||
[node name="Sun" type="DirectionalLight3D" parent="." unique_id=910400852]
|
||||
transform = Transform3D(0.8660248, 0.3830344, -0.32138094, 0, 0.6427605, 0.76606715, 0.5000011, -0.66343313, 0.5566465, 0, 0, 0)
|
||||
|
||||
[node name="Player" parent="." unique_id=869687860 instance=ExtResource("1_gjxf3")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.3, 0)
|
||||
|
||||
[node name="Environment" type="WorldEnvironment" parent="." unique_id=794103784]
|
||||
environment = SubResource("Environment_gu74f")
|
||||
Reference in New Issue
Block a user