修复摄像机穿模

This commit is contained in:
2026-10-09 16:36:17 +08:00
parent e06818bc5a
commit 3dd9d2ec71
5 changed files with 82 additions and 33 deletions
+41
View File
@@ -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 只有四成)。 - 选 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)还飘。 - 没选等比方案(`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)。 - 代码注释已整理:`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`。
+8
View File
@@ -10,6 +10,8 @@ extends CharacterBody3D
## 本脚本不实现任何操作,只负责「谁先谁后」和「谁有权跑」。 ## 本脚本不实现任何操作,只负责「谁先谁后」和「谁有权跑」。
@onready var body: Node3D = $Body @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_rig: Node3D = $CameraRig
@onready var camera: Camera3D = $CameraRig/Camera @onready var camera: Camera3D = $CameraRig/Camera
## 准心(挂在 CameraRig/HUD 下)。远端玩家的 HUD 会随 camera_rig 一起被释放。 ## 准心(挂在 CameraRig/HUD 下)。远端玩家的 HUD 会随 camera_rig 一起被释放。
@@ -37,6 +39,12 @@ func _ready() -> void:
pickup.set_process_unhandled_input(false) pickup.set_process_unhandled_input(false)
return return
# 第一人称:藏掉自己的模型。
# 摄像机的近裁剪面被调小了(见 player.tscn 的 near = 0.01),已经盖不住自己的身体 ——
# 不藏就会从内部看到模型的内壁。远端玩家的模型是各自独立的实例,不受影响。
if model != null:
model.visible = false
# 依赖注入:组件不自己去 get_node,由这里接线。 # 依赖注入:组件不自己去 get_node,由这里接线。
# 好处是组件不认识玩家的场景结构,换场景 / 单测时注入别的节点即可。 # 好处是组件不认识玩家的场景结构,换场景 / 单测时注入别的节点即可。
look.setup(camera_rig, camera, body) look.setup(camera_rig, camera, body)
+1
View File
@@ -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] [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) 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] [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) transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0)
+2 -2
View File
@@ -10,9 +10,9 @@ properties/1/replication_mode = 1
properties/2/path = NodePath(".:health") properties/2/path = NodePath(".:health")
properties/2/spawn = true properties/2/spawn = true
properties/2/replication_mode = 1 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/spawn = true
properties/3/replication_mode = 1 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/spawn = true
properties/4/replication_mode = 1 properties/4/replication_mode = 1
+30 -31
View File
@@ -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 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -11,7 +11,7 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_paint") script = ExtResource("1_paint")
[node name="Dim" type="ColorRect" parent="."] [node name="Dim" type="ColorRect" parent="." unique_id=2123131229]
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -20,7 +20,7 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
color = Color(0.03, 0.06, 0.14, 0.72) 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 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -29,35 +29,35 @@ grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
[node name="Panel" type="PanelContainer" parent="Center"] [node name="Panel" type="PanelContainer" parent="Center" unique_id=1498072815]
layout_mode = 2 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 layout_mode = 2
theme_override_constants/margin_left = 18 theme_override_constants/margin_left = 18
theme_override_constants/margin_top = 14 theme_override_constants/margin_top = 14
theme_override_constants/margin_right = 18 theme_override_constants/margin_right = 18
theme_override_constants/margin_bottom = 14 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 layout_mode = 2
theme_override_constants/separation = 12 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 layout_mode = 2
theme_override_font_sizes/font_size = 22 theme_override_font_sizes/font_size = 22
text = "画脸" 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 layout_mode = 2
theme_override_constants/separation = 16 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) custom_minimum_size = Vector2(420, 420)
layout_mode = 2 layout_mode = 2
mouse_filter = 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 unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@@ -68,7 +68,7 @@ grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
color = Color(0.537255, 0.701961, 1, 1) 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 unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@@ -79,7 +79,7 @@ grow_vertical = 2
mouse_filter = 2 mouse_filter = 2
expand_mode = 1 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 unique_name_in_owner = true
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@@ -90,25 +90,24 @@ grow_vertical = 2
mouse_filter = 0 mouse_filter = 0
expand_mode = 1 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 layout_mode = 2
theme_override_constants/separation = 10 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 unique_name_in_owner = true
custom_minimum_size = Vector2(160, 36) custom_minimum_size = Vector2(160, 36)
layout_mode = 2 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 layout_mode = 2
theme_override_constants/separation = 8 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 layout_mode = 2
text = "粗细" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(110, 0) custom_minimum_size = Vector2(110, 0)
layout_mode = 2 layout_mode = 2
@@ -116,49 +115,49 @@ min_value = 2.0
max_value = 48.0 max_value = 48.0
value = 12.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 unique_name_in_owner = true
custom_minimum_size = Vector2(52, 0) custom_minimum_size = Vector2(52, 0)
layout_mode = 2 layout_mode = 2
text = "12 px" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "橡皮" 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 unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
text = "清空" text = "清空"
[node name="Hint" type="Label" parent="Center/Panel/Margin/Box/Row/Tools"] [node name="Hint" type="Label" parent="Center/Panel/Margin/Box/Row/Tools" unique_id=1662983212]
layout_mode = 2
custom_minimum_size = Vector2(160, 0)
modulate = Color(1, 1, 1, 0.65) modulate = Color(1, 1, 1, 0.65)
custom_minimum_size = Vector2(160, 0)
layout_mode = 2
theme_override_font_sizes/font_size = 13 theme_override_font_sizes/font_size = 13
text = "在左边画布上直接画。 text = "在左边画布上直接画。
橡皮擦掉笔画,露出来的是底下的脸。" 橡皮擦掉笔画,露出来的是底下的脸。"
autowrap_mode = 3 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 unique_name_in_owner = true
custom_minimum_size = Vector2(160, 36) custom_minimum_size = Vector2(160, 36)
layout_mode = 2 layout_mode = 2
placeholder_text = "表情名称(可留空)" 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 layout_mode = 2
alignment = 2
theme_override_constants/separation = 10 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 unique_name_in_owner = true
custom_minimum_size = Vector2(120, 40) custom_minimum_size = Vector2(120, 40)
layout_mode = 2 layout_mode = 2
text = "取消" 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 unique_name_in_owner = true
custom_minimum_size = Vector2(120, 40) custom_minimum_size = Vector2(120, 40)
layout_mode = 2 layout_mode = 2