Files
agame/.workbuddy/memory/2026-10-09.md
T

7.9 KiB
Raw 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) 早退,避免重复抓取时漏掉碰撞例外。