From 3dd9d2ec71dc0e9a17f7a5b263ae3fd71d4e30fa Mon Sep 17 00:00:00 2001 From: yvmou_ Date: Fri, 9 Oct 2026 16:36:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=91=84=E5=83=8F=E6=9C=BA?= =?UTF-8?q?=E7=A9=BF=E6=A8=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .workbuddy/memory/2026-10-09.md | 41 +++++++++++++++++++++ character/player/player.gd | 8 ++++ character/player/player.tscn | 1 + character/player/player_sync.tres | 4 +- ui/paint_panel.tscn | 61 +++++++++++++++---------------- 5 files changed, 82 insertions(+), 33 deletions(-) diff --git a/.workbuddy/memory/2026-10-09.md b/.workbuddy/memory/2026-10-09.md index b6ab31c..7bdbf97 100644 --- a/.workbuddy/memory/2026-10-09.md +++ b/.workbuddy/memory/2026-10-09.md @@ -136,3 +136,44 @@ - 选 C 不选 A(只提 jump_velocity、保持 g=9.8)的理由:同样 0.23 m 的高度,A 只有 0.43 s 滞空,落地仍嫌仓促;C 多出 0.18 s。而且 C 让 jump 片段能播完约六成(A 只有四成)。 - 没选等比方案(`gravity_scale = 0.0857`):那比月球(1.62)还飘。 - 代码注释已整理:`jump_velocity` 与 `gravity_scale` 标注为**配对参数**,并写入反解公式 `g = 8h/T²`、`v = 4h/T`;`player_animator.gd` 里那句引用旧值的上升时间注释也改准了(1.5 / 4.9 ≈ 0.31 s)。 + +### 贴墙时摄像机穿模(已修) +- **根因(纯几何,差 8 毫米)**: + | 项 | 值 | 来源 | + |---|---|---| + | 摄像机 `near` | **0.05 m** | Godot 默认,`player.tscn` 未覆盖 | + | 摄像机前移量 | **0.01074 m** | `Camera` 本地 Z = −0.1118 × Player 缩放 0.096096 | + | 贴墙时身体离墙 | **0.05241 m** | 胶囊局部半径 0.5454 × 0.096096 | + | **摄像机到墙** | **0.04167 m** | 0.05241 − 0.01074 | + → `0.0417 < 0.05`,墙面落在近裁剪面**内部**被整个裁掉,于是看到墙的背面/内部。 +- **修复 1**:`player.tscn` 的 `Camera` 加 `near = 0.01`(留 4 倍余量)。项目是 Forward+(反向 Z 深度),near 缩小对深度精度影响很小。`far` 保持默认未动。 +- **修复 2**:`player.gd` 新增 `@onready var model: Node3D = get_node_or_null("Body/mouse") as Node3D`,在权威分支里 `model.visible = false` —— 原来正是靠 near=0.05 把自己的身体裁掉才看不见,near 调小后身体会露出来。只影响本地玩家,远端玩家各自的模型实例不受影响。 + - 代价:自己也看不到自己的影子(`visible=false` 会关掉阴影)。想保留影子可改用「本地玩家的 MeshInstance3D 放到独立视觉层 + 本地摄像机 cull_mask 排除该层」,但要注意该层不能和远端玩家共用,否则本地相机也看不到别人。 +- **备选方案(未采用)**:把胶囊半径加大到 > 0.06 m 让摄像机永远离墙更远 —— 会改变玩法(体型变胖);或把 `Camera` 的前移量改小 —— 会动视角位置。 + +### 附带发现的 bug:复制配置里的节点路径过期(已修) +- 用户此前把 `AnimationPlayer` 从 `Body/AnimationPlayer` 移到 Player 根节点(`$AnimationPlayer`),但 `player_sync.tres` 里两条路径仍写着 `Body/AnimationPlayer:anim_state` / `Body/AnimationPlayer:anim_speed` → 这两个属性根本没被复制,**远端玩家的动画状态不会同步**。 +- 已改为 `AnimationPlayer:anim_state` / `AnimationPlayer:anim_speed`。 + +## ⚠ 项目约定(用户明确要求) +**改 Godot 的 .tscn / .tres / .gd,一律走 godot-ai MCP,不要直接写文件。** 原因见下:外部直接写 .tscn 会和编辑器的文件监视重载(`_reload_modified_scenes`)竞态,导致编辑器读到中间状态、报出**假的解析错误**。 +- 可用工具:`scene_open` / `scene_save` / `node_set_property` / `node_get_properties` / `node_create` / `node_manage` / `script_manage(find_symbols 可当解析校验)` / `script_create`(带诊断) / `filesystem_manage(read_text/write_text/scan/reimport)` / `resource_manage` / `logs_read` / `editor_manage(state)` / `project_run`。 +- 调用形式:多数工具是「扁平参数」(如 `scene_open {path}`),管理类工具是 `{op, params}` 双层(如 `editor_manage {op:"state"}`)。`node_*` 的 path 是**相对当前编辑场景根**(如 `/Player/CameraRig/Camera`),不是 `/root/...`。 + +## player.tscn 解析错误事件(已解决) +- 现象:`Parse Error ... player.tscn:38` in `_parse_node_tag`,场景彻底打不开。 +- **真实原因:不是文件语法错误。** 我用 MCP `node_set_property` 设 `Camera.near = 0.01` 后 `scene_save`,编辑器写出的内容与我手写的**字节完全一致**(同为 5106 字节),且 `scene_open(force_reload=true)` 立即 `reloaded_from_disk: true` 加载成功。 + → 根因是**外部直接写 .tscn 与编辑器文件监视及重载的竞态**。 +- 诊断过程中的有效手段(可复用): + 1. `editor_manage(op="state")` 看当前场景与就绪状态 + 2. `scene_open` 做**对照实验**(打开一个已知正常场景,若 `switched:true` 则说明工具可靠、目标文件真的坏了) + 3. `logs_read(source="editor", include_details=true)` 拿准确报错函数与行号 + 4. 用 `node_create` 在**内存里**加临时节点 + `node_get_properties` 探测属性真名(**不保存、不落盘**,随后 `node_manage(op="delete")` 清掉)—— 这次用它确认了 `Camera3D.near` 是合法 float 属性(默认 0.05,`far` 默认 4000) + 5. `script_manage(op="find_symbols")` 可当脚本**解析校验**器;`filesystem_manage(op="scan")` 重建 `class_name` 全局表 +- 修复结果:`player.tscn` 的 `Camera` 现有 `near = 0.01`(MCP 设置并保存),场景可正常加载。 + +### 日志里发现的其它线索(多为历史记录,非当前问题) +- `Could not find type "PlayerLook/PlayerMotor/PlayerPickup/Crosshair"`、`Identifier "animator" not declared` —— 都是**旧状态的历史日志**;`find_symbols` 证明 player.gd 现已正常,且 `filesystem_manage(scan)` 返回 `global_classes_registered_delta: 0`(类名表本是全的,共 75 个)。 +- `crosshair.gd:133 _draw 里 texture_for_state 收到 Nil` —— `@tool` 脚本热重载时旧实例被新代码调用的一次性产物,非当前 bug。 +- `player.tscn` 的 **Crosshair 纹理疑似接反**:`hover_texture = grab_hover.png`、`hold_texture = grab.png`。按我的语义(hover=指着可拖拽物 / hold=正抓着)应互换;但若按光标包命名(`_hover` 后缀表示悬停态)则现状正确。**未擅自改动,已向用户指出。** +- 遗留警告(非本次引入):`face_painter.gd:153` 整数除法、`face_rig.gd:190` 局部变量 `name` 遮蔽 Node.name、`paint_panel.gd:69` 局部变量 `scale` 遮蔽 Control.scale、`Cannot load shader: res://shader/stylized.gdshader`。 diff --git a/character/player/player.gd b/character/player/player.gd index 12b5325..a0c6a2b 100644 --- a/character/player/player.gd +++ b/character/player/player.gd @@ -10,6 +10,8 @@ extends CharacterBody3D ## 本脚本不实现任何操作,只负责「谁先谁后」和「谁有权跑」。 @onready var body: Node3D = $Body +## 本地玩家自己的模型(鼠)。第一人称要藏掉它,见 _ready()。 +@onready var model: Node3D = get_node_or_null("Body/mouse") as Node3D @onready var camera_rig: Node3D = $CameraRig @onready var camera: Camera3D = $CameraRig/Camera ## 准心(挂在 CameraRig/HUD 下)。远端玩家的 HUD 会随 camera_rig 一起被释放。 @@ -37,6 +39,12 @@ func _ready() -> void: pickup.set_process_unhandled_input(false) return + # 第一人称:藏掉自己的模型。 + # 摄像机的近裁剪面被调小了(见 player.tscn 的 near = 0.01),已经盖不住自己的身体 —— + # 不藏就会从内部看到模型的内壁。远端玩家的模型是各自独立的实例,不受影响。 + if model != null: + model.visible = false + # 依赖注入:组件不自己去 get_node,由这里接线。 # 好处是组件不认识玩家的场景结构,换场景 / 单测时注入别的节点即可。 look.setup(camera_rig, camera, body) diff --git a/character/player/player.tscn b/character/player/player.tscn index cf1b4ef..2bb0035 100644 --- a/character/player/player.tscn +++ b/character/player/player.tscn @@ -36,6 +36,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.6, 0) [node name="Camera" type="Camera3D" parent="CameraRig" unique_id=52294557] transform = Transform3D(10.406274, 0, 0, 0, 10.406274, 0, 0, 0, 10.406274, 0, -0.6916404, -0.11179829) +near = 0.01 [node name="InteractionRay" type="RayCast3D" parent="CameraRig/Camera" unique_id=388601766] transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0) diff --git a/character/player/player_sync.tres b/character/player/player_sync.tres index 3fb97b9..0b3f709 100644 --- a/character/player/player_sync.tres +++ b/character/player/player_sync.tres @@ -10,9 +10,9 @@ properties/1/replication_mode = 1 properties/2/path = NodePath(".:health") properties/2/spawn = true properties/2/replication_mode = 1 -properties/3/path = NodePath("Body/AnimationPlayer:anim_state") +properties/3/path = NodePath("AnimationPlayer:anim_state") properties/3/spawn = true properties/3/replication_mode = 1 -properties/4/path = NodePath("Body/AnimationPlayer:anim_speed") +properties/4/path = NodePath("AnimationPlayer:anim_speed") properties/4/spawn = true properties/4/replication_mode = 1 diff --git a/ui/paint_panel.tscn b/ui/paint_panel.tscn index 637fd0e..9fac2dd 100644 --- a/ui/paint_panel.tscn +++ b/ui/paint_panel.tscn @@ -1,8 +1,8 @@ -[gd_scene load_steps=2 format=3] +[gd_scene format=3 uid="uid://bpjy7rb0tg6t6"] -[ext_resource type="Script" path="res://ui/paint_panel.gd" id="1_paint"] +[ext_resource type="Script" uid="uid://cjxxd2m1w00y5" path="res://ui/paint_panel.gd" id="1_paint"] -[node name="PaintPanel" type="Control"] +[node name="PaintPanel" type="Control" unique_id=891200937] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -11,7 +11,7 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_paint") -[node name="Dim" type="ColorRect" parent="."] +[node name="Dim" type="ColorRect" parent="." unique_id=2123131229] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -20,7 +20,7 @@ grow_horizontal = 2 grow_vertical = 2 color = Color(0.03, 0.06, 0.14, 0.72) -[node name="Center" type="CenterContainer" parent="."] +[node name="Center" type="CenterContainer" parent="." unique_id=209338815] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -29,35 +29,35 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="Panel" type="PanelContainer" parent="Center"] +[node name="Panel" type="PanelContainer" parent="Center" unique_id=1498072815] layout_mode = 2 -[node name="Margin" type="MarginContainer" parent="Center/Panel"] +[node name="Margin" type="MarginContainer" parent="Center/Panel" unique_id=1809312982] layout_mode = 2 theme_override_constants/margin_left = 18 theme_override_constants/margin_top = 14 theme_override_constants/margin_right = 18 theme_override_constants/margin_bottom = 14 -[node name="Box" type="VBoxContainer" parent="Center/Panel/Margin"] +[node name="Box" type="VBoxContainer" parent="Center/Panel/Margin" unique_id=2141345385] layout_mode = 2 theme_override_constants/separation = 12 -[node name="Title" type="Label" parent="Center/Panel/Margin/Box"] +[node name="Title" type="Label" parent="Center/Panel/Margin/Box" unique_id=1011886784] layout_mode = 2 theme_override_font_sizes/font_size = 22 text = "画脸" -[node name="Row" type="HBoxContainer" parent="Center/Panel/Margin/Box"] +[node name="Row" type="HBoxContainer" parent="Center/Panel/Margin/Box" unique_id=2134067517] layout_mode = 2 theme_override_constants/separation = 16 -[node name="CanvasStack" type="Control" parent="Center/Panel/Margin/Box/Row"] +[node name="CanvasStack" type="Control" parent="Center/Panel/Margin/Box/Row" unique_id=119694491] custom_minimum_size = Vector2(420, 420) layout_mode = 2 mouse_filter = 2 -[node name="SkinRect" type="ColorRect" parent="Center/Panel/Margin/Box/Row/CanvasStack"] +[node name="SkinRect" type="ColorRect" parent="Center/Panel/Margin/Box/Row/CanvasStack" unique_id=1261574717] unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 @@ -68,7 +68,7 @@ grow_vertical = 2 mouse_filter = 2 color = Color(0.537255, 0.701961, 1, 1) -[node name="PresetRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack"] +[node name="PresetRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack" unique_id=689779980] unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 @@ -79,7 +79,7 @@ grow_vertical = 2 mouse_filter = 2 expand_mode = 1 -[node name="StrokeRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack"] +[node name="StrokeRect" type="TextureRect" parent="Center/Panel/Margin/Box/Row/CanvasStack" unique_id=903984536] unique_name_in_owner = true layout_mode = 1 anchors_preset = 15 @@ -90,25 +90,24 @@ grow_vertical = 2 mouse_filter = 0 expand_mode = 1 -[node name="Tools" type="VBoxContainer" parent="Center/Panel/Margin/Box/Row"] +[node name="Tools" type="VBoxContainer" parent="Center/Panel/Margin/Box/Row" unique_id=1782799126] layout_mode = 2 theme_override_constants/separation = 10 -[node name="ColorButton" type="ColorPickerButton" parent="Center/Panel/Margin/Box/Row/Tools"] +[node name="ColorButton" type="ColorPickerButton" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=1604022405] unique_name_in_owner = true custom_minimum_size = Vector2(160, 36) layout_mode = 2 -color = Color(0, 0, 0, 1) -[node name="WidthRow" type="HBoxContainer" parent="Center/Panel/Margin/Box/Row/Tools"] +[node name="WidthRow" type="HBoxContainer" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=88372989] layout_mode = 2 theme_override_constants/separation = 8 -[node name="Label" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"] +[node name="Label" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow" unique_id=1544185599] layout_mode = 2 text = "粗细" -[node name="WidthSlider" type="HSlider" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"] +[node name="WidthSlider" type="HSlider" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow" unique_id=1487429877] unique_name_in_owner = true custom_minimum_size = Vector2(110, 0) layout_mode = 2 @@ -116,49 +115,49 @@ min_value = 2.0 max_value = 48.0 value = 12.0 -[node name="WidthLabel" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow"] +[node name="WidthLabel" type="Label" parent="Center/Panel/Margin/Box/Row/Tools/WidthRow" unique_id=1267285216] unique_name_in_owner = true custom_minimum_size = Vector2(52, 0) layout_mode = 2 text = "12 px" -[node name="EraserToggle" type="CheckButton" parent="Center/Panel/Margin/Box/Row/Tools"] +[node name="EraserToggle" type="CheckButton" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=503516949] unique_name_in_owner = true layout_mode = 2 text = "橡皮" -[node name="ClearButton" type="Button" parent="Center/Panel/Margin/Box/Row/Tools"] +[node name="ClearButton" type="Button" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=958943755] unique_name_in_owner = true layout_mode = 2 text = "清空" -[node name="Hint" type="Label" parent="Center/Panel/Margin/Box/Row/Tools"] -layout_mode = 2 -custom_minimum_size = Vector2(160, 0) +[node name="Hint" type="Label" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=1662983212] modulate = Color(1, 1, 1, 0.65) +custom_minimum_size = Vector2(160, 0) +layout_mode = 2 theme_override_font_sizes/font_size = 13 text = "在左边画布上直接画。 橡皮擦掉笔画,露出来的是底下的脸。" autowrap_mode = 3 -[node name="NameEdit" type="LineEdit" parent="Center/Panel/Margin/Box/Row/Tools"] +[node name="NameEdit" type="LineEdit" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=240695113] unique_name_in_owner = true custom_minimum_size = Vector2(160, 36) layout_mode = 2 placeholder_text = "表情名称(可留空)" -[node name="Bottom" type="HBoxContainer" parent="Center/Panel/Margin/Box"] +[node name="Bottom" type="HBoxContainer" parent="Center/Panel/Margin/Box" unique_id=401906608] layout_mode = 2 -alignment = 2 theme_override_constants/separation = 10 +alignment = 2 -[node name="CancelButton" type="Button" parent="Center/Panel/Margin/Box/Bottom"] +[node name="CancelButton" type="Button" parent="Center/Panel/Margin/Box/Bottom" unique_id=1172815596] unique_name_in_owner = true custom_minimum_size = Vector2(120, 40) layout_mode = 2 text = "取消" -[node name="DoneButton" type="Button" parent="Center/Panel/Margin/Box/Bottom"] +[node name="DoneButton" type="Button" parent="Center/Panel/Margin/Box/Bottom" unique_id=479306162] unique_name_in_owner = true custom_minimum_size = Vector2(120, 40) layout_mode = 2