Files
2026-10-09 16:36:17 +08:00

24 KiB
Raw Permalink Blame History

2026-10-09

简易准心(Crosshair)

  • 新增 ui/crosshair.gd:@tool extends Control,用 _draw() 画十字(gap/length/thickness/dot 可导出调参),mouse_filter = IGNORE,监听 NOTIFICATION_RESIZED 重绘。
  • 挂载约定:CanvasLayer > Crosshair(Control),Control 锚点为 Full Rect,中心即屏幕中心;推荐挂在 CameraRig 之下 —— 因为 player.gd _ready() 对非 authority 会 camera_rig.queue_free(),远端玩家准心随之一并释放,无需额外代码。
  • 项目 stretch 模式为 canvas_items + expand,CanvasLayer 会自动跟随缩放。

后续改动:只保留「点」

  • 最终形态:准心 = 屏幕中心一个点,无十字、无样式开关(enum Style 已移除)。
  • 导出参数:dot_color / dot_radius(默认 2.5) / outline / outline_color / outline_width。
  • _draw() 先画描边圆再画主体圆,保证浅背景可见。

player.gd 拆解方案(待执行)

  • 现状:character/player/player.gd 155 行,混了视角 / 移动跳跃 / 拾取 / 多人权限四类职责。
  • 方案:子节点组件化(与既有 PlayerAnimator 同模式)。根节点降为协调器(~40 行),新增 Look / Motor / Pickup 三个 Node 子节点,各自挂 player_look.gd / player_motor.gd / player_pickup.gd。
  • 三条硬约束:① move_and_slide() 留在根节点,Motor 只写 body.velocity;② 物理顺序由根节点显式编排(motor.step → move_and_slide → animator → pickup),不让子节点各写 _physics_process;③ 非 authority 时三个组件的输入/物理必须一并关闭。
  • 范式定位:组合优于继承 + 组件模式(Component Pattern);协调器部分带 Facade/Mediator 色彩;setup() 注入属手工依赖注入。不是 ECS(Godot 节点是「带行为的对象」,非纯数据组件)。
  • 状态:已落地(2026-10-09)。新增 player_look.gd / player_motor.gd / player_pickup.gd(均带 class_name:PlayerLook / PlayerMotor / PlayerPickup);player.gd 重写为协调器(~65 行);player.tscn 追加 Look / Motor / Pickup 三个 Node 子节点(挂 FaceRig 之后,ext_resource id 13_look / 14_motor / 15_pickup,未写 uid 由 Godot 补)。
  • 场景格式注意:本项目的 .tscn 节点带非标准 unique_id=(Godot 4.7 特性),unique_id 与 load_steps 均可省略;paint_panel.tscn 就完全没写,可作参照。
  • 已确认无外部脚本引用被搬走的属性(仅 world/main.tscn 通过场景 uid 引用玩家场景)。
  • 顺手去掉了原 _pickObject 里的两行 print() 调试输出。

拾取改为长按拖拽

  • player_pickup.gd:由「按一下切换」改为「按下抓起、松开放下」——is_action_pressed("carry") → _grab(),is_action_released("carry") → _drop()。
  • carry 动作确认绑定鼠标左键(button_index: 1);项目里另有未被任何脚本使用的 throw_carry(绑 Q 键),可作投掷功能的预留。
  • 加了 Input.mouse_mode != MOUSE_MODE_CAPTURED 早退,并在 player.gd 重新锁定鼠标那一支补 get_viewport().set_input_as_handled(),避免「点一下重新锁定鼠标」顺带抓起物体(两种分支执行顺序下都成立)。
  • physics_step() 与 has_object() 改用 is_instance_valid() —— 防止拖拽途中物体被 destroy 时取属性报错。
  • _drop() 保留线速度(松手有惯性 / 可甩出);想要「就地放下」需补 set_linear_velocity(Vector3.ZERO)。

按 Q 投掷

  • player_pickup.gd 接入 throw_carry(Q 键)→ _throw():取 -_hold_pos.global_transform.basis.z 作投掷方向(持握点挂在摄像机下且仅有平移,其 basis 即视线朝向,故无需再注入摄像机),set_linear_velocity(dir * throw_speed) 后置 _current = null。
  • 新增导出参数 throw_speed: float = 12.0。
  • 备选手感(注释里留了行):改用 apply_central_impulse 则重物飞得慢。

可拖拽时准心变手型

  • 新增素材 character/assets/hand_cursor.svg(新建了 character/assets/ 目录):简笔手(4 指 + 手掌 + 拇指的圆角矩形),双层 —— 底层深色 #1a1a1a stroke-width 4 当描边,上层白色 stroke-width 1.5 覆盖内部接缝。32×32,未写 <title>(避免 ThorVG 解析风险)。
  • ui/crosshair.gd:加 class_name Crosshair;新增 hand_texture(自动加载上面的 svg) / hand_size(=26)、set_grabbable(bool);_draw() 在 _grabbable && hand_texture != null 时走 _draw_hand(),否则走 _draw_dot()。
  • player_pickup.gd:新增 signal hover_changed(grabbable: bool),physics_step() 里先调 _update_hover()——用 _ray.force_raycast_update() 强制刷新(否则结果滞后于本帧 move_and_slide,跑动中瞄到物体慢一帧),只在状态变化时发信号。
  • player.gd:@onready var crosshair: Crosshair = $CameraRig/HUD/Crosshair;_ready() 权威分支里 pickup.hover_changed.connect(_on_pickup_hover_changed);handler 里既切准心,也 Input.set_custom_mouse_cursor() 换系统指针(含指尖热点),鼠标未捕获时该行无副作用。
  • 注意:用户侧已把 AnimationPlayer 从 Body/AnimationPlayer 提到 Player 根节点($AnimationPlayer),并把 animator 变量改名为 animation_player;相机 scale 改为 5。

换用真实光标素材(grab / grab_hover)

  • 素材来源:C:\Users\yvmou\Downloads\cursors\light\(light 主题 = 深色手型,32×32 RGBA)。
  • 已复制到 character/assets/grab.png(指着可拖拽物)与 character/assets/grab_hover.png(正抓着),Godot 已生成 .import。
  • 删除了临时的 character/assets/hand_cursor.svg 及其 .import(已被真实素材取代)。
  • ui/crosshair.gd 改为三态:enum GrabState { NONE, HOVER, HOLDING };导出 hover_texture / hold_texture / hand_size(=32,1:1 不缩放) / icon_modulate / sync_os_cursor;公开 texture_for_state(state) 供系统指针复用素材;_sync_os_cursor() 内部完成 Input.set_custom_mouse_cursor(Engine.is_editor_hint() 下跳过),热点在图标高度 12% 处 —— 系统指针逻辑已从 player.gd 移入准心。
  • player_pickup.gd:信号由 hover_changed(bool) 改为 interact_state_changed(hovering, holding),_update_hover() 改名 _update_interact_state() 并同时跟踪 _holding。
  • player.gd:handler 改名 _on_pickup_interact_state_changed(false),只负责把两个布尔量收敛成 Crosshair.GrabState(优先级 抓着 > 指着 > 没有)。
  • 待用户确认:像素手在非整数倍分辨率缩放下发糊的话,需把 Crosshair 的 texture_filter 设为 Nearest。

拖拽时物体一卡一卡(已修)

  • 根因(几何 + 控制律):physics_step() 用 v = to_hold * pick_power 是一阶跟随,人匀速前进时物体必有稳态滞后,滞后距离 = 移动速度 / pick_power。当时为 walk_speed 5 ÷ pick_power 10 = 0.5 m,而持握点离身体仅 0.44 m(InteractionPos local z = -0.4439,Player 缩放 0.2 × Camera 缩放 5 = 净 1.0,故世界距离就是 0.444 m)→ 物体退进玩家胶囊体 → 物理推出 / 拖拽拉回互相打架 → 高频抖动。
  • 修复 1(前馈):desired = to_hold * pick_power + _body.velocity —— 匀速行走时滞后归零。这是根治。
  • 修复 2(碰撞例外):_grab() 里 _current.add_collision_exception_with(_body),_drop() / _throw() 经 _forget() 用 remove_collision_exception_with 解除。只解除「物体 ↔ 玩家」这一对,物体与场景其他碰撞体照常交互。
  • 另加 @export var max_drag_speed: float = 30.0 速度上限,物体离得远时不会被一股脑拽飞。
  • 接口变更:PlayerPickup.setup(ray, hold_pos, body) 多了第三个参数(玩家本体),player.gd 里传 self。_grab() 加了 is_instance_valid(_current) 早退,避免重复抓取时漏掉碰撞例外。

玩家缩放层级(查明,供后续改尺寸参考)

  • character/player/player.tscn 的缩放链:Player ×0.2 → 子节点 Collision(胶囊 r0.545/h1.406,本地 y0.703) / CameraRig(眼高 y1.6) / Body/mouse(×0.1824) 累计 ×0.2。
  • Camera ×5 是补偿值,用来把相机子树抵消回世界缩放 1.0(0.2×5=1.0),因此 InteractionRay 世界长度 0.5 m、InteractionPos 世界距离 0.444 m。
  • 推论:改 Player.scale 必须同步 Camera.scale = 1 / Player.scale,否则交互距离/持握距离会跟着缩。另外 PlayerMotor 的 walk/sprint/jump/accl 是世界单位,不会自动缩放,改尺寸时需手动同倍调整。
  • 世界尺寸现值:碰撞胶囊半径 0.109 / 高 0.281,眼高 0.32 m。

整体人物缩到 0.6 倍(已实施)

  • player.tscn:Player 缩放 0.2 → 0.12;Camera 缩放 5 → 8.333333(= 1/0.12,保持相机子树净缩放 1.0,交互距离/持握距离不变)。Camera 的位置偏移与 CameraRig 的 y=1.6 都自动随父级缩放,未改。
  • player_motor.gd:walk 5.0→3.0、sprint 8.5→5.1、acceleration 45→27、air_acceleration 12→7.2,均为 ×0.6;jump_velocity 5.0→3.87(×√0.6,因跳跃高度 = v²/2g,直接乘 0.6 会跳得偏矮)。
  • player_animator.gd:基准速度也必须 ×0.6 —— walk 5.0→3.0、run 8.5→5.1、crouch 2.5→1.5、sneak 1.6→0.96。原因:模型缩到 0.6 倍后,同一片段一个循环跨出的世界距离也缩到 0.6 倍,不改基准速度脚步会明显打滑。同时修正了 _resolve_state() 里那句「jump 片段 0.5s ≈ 5/9.8 上升时间」的过时注释。
  • 未改动:pick_power 10(新滞后 3.0/10 = 0.3 m < 0.444 m 持握距离,且已有速度前馈,安全)、throw_speed 12(被扔的物体本身没缩,投掷距离不该变)、mouse_sensitivity(纯角度)。
  • 缩放后世界尺寸:胶囊半径 0.0654 / 高 0.169,眼高 0.192 m;跳跃绝对高度 1.276 → 0.766 m(相对身高观感不变)。

再缩到「视觉高度 0.15 m」(已实施)

  • 口径确认(重要):用户选的是视觉模型高度,不是碰撞胶囊。两者不等:
    • 模型:glb 顶点原始高 8.557729(min y -4.7746 / max y 3.7832)× mouse 节点缩放 0.18240134 = 本地 1.560941;世界 = ×0.12 = 0.187313 m(当时)
    • 胶囊:本地 1.4063476 × 0.12 = 0.1687617 m
    • 模型底部在本地 y ≈ -0.0007(脚踩原点,与胶囊底部对齐),故测量可信。模型比胶囊高 11%(多出耳朵/尾巴)。
  • 缩放系数 k = 0.15 / 0.187313 = 0.800799。
  • player.tscn:Player.scale 0.12 → 0.096096;Camera.scale 8.333333 → 10.406274(= 1/0.096096)。
  • player_motor.gd:walk 3.0→2.4024、sprint 5.1→4.0841、accel 27→21.6216、air_accel 7.2→5.7658、jump 3.87→3.4632(×√k)。
  • player_animator.gd:walk_ref 3.0→2.4024、run_ref 5.1→4.0841、crouch_ref 1.5→1.2012、sneak_ref 0.96→0.7688。
  • 注释已改成「按同一系数同步缩放」的通用表述,不再写死 0.6,避免下次再失效。
  • 结果:胶囊世界 半径 0.05241 / 高 0.13514,眼高 0.08729 m,跳跃绝对高度 ≈ 0.612 m;拖拽滞后 = 2.4024/10 = 0.24 m < 0.444 m 持握距离,安全。
  • 可复用公式:Player.scale = 目标世界高 / (模型本地总高),Camera.scale = 1 / Player.scale,速度与动画基准速度均 ×k(jump 用 ×√k)。

按 0.15 m 身高重新设计移动参数(已实施)

  • 用户明确:「不用管之前,按 0.15 的身高重新设计」→ 采取以人类为参照的等比设计,不再依赖旧基准。
  • 设计基准:人 1.75 m,游戏惯例 walk 5.0 / sprint 8.5 / accel 45 / air_accel 12 / jump 5.0(跳跃高度 1.28 m ≈ 0.73 身高)。系数 k = 0.15 / 1.75 ≈ 0.0857143。
  • player_motor.gd:walk 2.4024→0.4286、sprint 4.0841→0.7286、accel 21.6216→3.8571、air_accel 5.7658→1.0286、jump 3.4632→1.4639(×√k)。
  • player_animator.gd:walk_ref 2.4024→0.4286、run_ref 4.0841→0.7286、crouch_ref 1.2012→0.2143、sneak_ref 0.7688→0.1371。
  • 校验:两边都是「走路 = 2.86 身长/秒」「跳跃 = 0.73 身高」,比例一致。加速度给出约 0.11 秒加到全速;跳跃高度 0.109 m。
  • 拖拽滞后 = 0.4286/10 = 0.043 m ≪ 0.444 m 持握距离,安全。
  • 未改动(有意保持):交互距离/持握距离(射线 0.5 m、持握 0.444 m —— 若按 k 缩到 0.038 m 会够不到任何东西)、pick_power 10、throw_speed 12(被扔物体是世界尺寸,未缩)、mouse_sensitivity(纯角度)。
  • 世界尺度查不到可靠线索:world/main2.tscn 有 295 个 Transform3D,但混入了嵌套实例的局部变换,位置跨度(X33/Z57)不可信;198 个实例缩放为 1.0,资产疑似米制。若实际关卡跨度大、用户觉得慢,把 walk/sprint 等线性量乘同一倍数即可。

蹲伏真正生效(已实施)

  • 背景:蹲下改速度、改摄像机高度这两件事从来没实现过(原 player.gd 也只把 crouching 传给动画器切 CROUCH/SNEAK 片段),不是回归。
  • player_motor.gd:新增 @export var crouch_speed: float = 0.2143(= walk 的一半 = 2.5×k)。step() 里改为蹲伏优先于疾跑:sprinting = wants_sprint and not crouching,速度按 crouch_speed > sprint_speed 优先级取。
  • player_look.gd:新增蹲伏组 crouch_height_ratio(=0.6) / crouch_transition_speed(=10);setup() 从场景读 _stand_height = rig.position.y(不写死 1.6);新增 set_crouching(bool) 设目标高度;_process() 用 1 - exp(-k·delta) 指数收敛平滑升降(渲染帧,物理帧会一顿一顿);_ready() 里 set_process(false),setup() 才开 —— 远端玩家不调 setup 就不会跑。
  • player.gd:_physics_process 里 motor.step(delta) 之后加 look.set_crouching(motor.crouching) —— 蹲伏状态由 Motor 单点判定,不让 Look 自己读蹲键,避免两处各判一次不同步。
  • player_animator.gd:sneak_ref_speed 0.1371 → 0.2143,与 crouch_speed 对齐 —— 蹲着走播的就是 SNEAK 片段,两边不等会打滑(原来会以 1.56× 播放,逼近 1.6 的 clamp 上限)。注释里写明了这条配对关系。
  • 世界高度:站立 1.6 × 0.096096 = 0.1538 m → 蹲下 0.0923 m。
  • 未做(可以后续补):蹲下时没有缩小碰撞胶囊 —— 所以现在还钻不过低矮缝隙。要做需要额外处理「头顶有障碍时不许起身」的检测。

关于「重力是否该随角色缩小」(已分析,待用户定夺)

  • 物理结论:不该变。重力加速度是行星属性,与物体大小无关。
  • 但设计上是真问题:我们的速度按 k=0.0857 等比缩了,重力却没缩,导致落体相对过快。
    • 落「自身身高」用时:人类 (1.75 m, g=9.8) = 0.598 s;本角色 (0.15 m, g=9.8) = 0.175 s,快 3.4 倍。
    • 若 g = 9.8 × k = 0.84:本角色也是 0.598 s,与人类完全一致(缩小版人类)。
  • 顺带暴露的问题:jump 片段长 0.5 s。现在 g=9.8、jump_velocity=1.4639 → 上升仅 0.149 s,只播得完片段的 30%。若 g=0.84 且 jump_velocity = walk × 1.0 = 0.4286,上升 = 0.51 s,正好等于片段长度 —— 回到原始设计(原来 jump 5.0 / g 9.8 = 0.51 s)里那种吻合。
  • 实现方式(已落地为旋钮):PlayerMotor 新增 @export_range(0.0, 2.0, 0.01) var gravity_scale: float = 1.0,step() 里 _body.velocity.y -= _gravity * gravity_scale * delta(每帧现读,方便运行中拖 Inspector 试手感)。默认 1.0 = 行为完全不变。绝不要改 ProjectSettings physics/3d/default_gravity —— 那是全局的,会让场景里所有 RigidBody 一起变飘,包括拖拽/投掷的物体。
  • 三个候选:9.8(物理真实,落体快、跳跃动画对不上)/ 2.87(×√k,折中)/ 0.84(×k,缩小版人类,跳跃动画吻合,但比月球 1.62 还飘)。
  • ⚠ 配对规律:重力 × s 时 jump_velocity 也要 × s,否则跳跃高度按 1/s 暴涨。现在是 g=9.8 + v=1.4639 → 高 0.109 m;若 g=0.84 而 v 不变,跳跃高会变成 1.28 m(8.5 个身高);正确配对是 g=0.84 + v=0.4286。这条已写进 gravity_scale 的代码注释。
  • 用户尚未选定最终值。

跳跃「高度过低、落地太快」的调法(已给方案,待用户选)

  • 问题:现状 g=9.8 + jump_velocity=1.4639 → 跳高 0.109 m(0.73 身高)、滞空仅 0.30 s,跳一下几乎立刻落地。

  • 核心关系:h = v²/2g、T = 2v/g → 合起来 h = g·T²/8。 → 固定重力下,滞空翻倍 = 高度涨 4 倍。所以「既要跳得久、又不跳太高」只能靠降重力,光提 jump_velocity 做不到。

  • 反解公式(定目标高度 h 与滞空 T):g = 8h/T²(→ gravity_scale = g/9.8),jump_velocity = 4h/T。

  • 三个候选(角色 0.15 m,1 身高 = 0.15 m):

    方案 gravity_scale jump_velocity 跳高 身高倍数 滞空 落地时长
    现状 1.0 1.4639 0.109 m 0.73 0.30 s —
    A 只提跳跃 1.0 2.10 0.225 m 1.50 0.43 s 不变
    B 只降重力 0.5 1.4639 0.219 m 1.46 0.60 s ×1.41
    C 组合(推荐) 0.5 1.50 0.230 m 1.53 0.61 s ×1.41

    → A 和 B 高度几乎一样,但 B 的滞空长 40% —— 这正是「降重力」的价值。

  • 副作用:g=4.9 时从 1 m 高落下要 0.639 s(原 0.452 s)。仍远好于等比 g=0.84(比月球还飘)。

  • jump 动画片段 0.5 s:现状只播得完 30%,C 方案升到 61%。要让上升时间完全等于 0.5 s 需 T=1.0 s → g 低到 1.84(接近月球),不建议。

  • 可选改进(未实施):把 jump_velocity 换成「跳跃高度 jump_height(米)」—— 设计上更直观,且改重力时高度自动保持,不会出现「改重力后跳高暴涨」的坑。

跳跃参数定案:方案 C(已实施)

  • 用户问「推荐哪个方案」→ 定 C:gravity_scale = 0.5(有效 g = 4.9)、jump_velocity = 1.5。
  • 结果:跳高 0.230 m(1.53 身高)、滞空 0.612 s、上升 0.306 s;从 1 m 高落下 0.639 s(原 0.452 s,×1.41)。
  • 选 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。