15 KiB
15 KiB
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.gd155 行,混了视角 / 移动跳跃 / 拾取 / 多人权限四类职责。 - 方案:子节点组件化(与既有
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 指 + 手掌 + 拇指的圆角矩形),双层 —— 底层深色#1a1a1astroke-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_power10(新滞后 3.0/10 = 0.3 m < 0.444 m 持握距离,且已有速度前馈,安全)、throw_speed12(被扔的物体本身没缩,投掷距离不该变)、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%(多出耳朵/尾巴)。
- 模型:glb 顶点原始高
- 缩放系数
k = 0.15 / 0.187313 = 0.800799。 player.tscn:Player.scale0.12 → 0.096096;Camera.scale8.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_power10、throw_speed12(被扔物体是世界尺寸,未缩)、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_speed0.1371 → 0.2143,与crouch_speed对齐 —— 蹲着走播的就是 SNEAK 片段,两边不等会打滑(原来会以 1.56× 播放,逼近 1.6 的 clamp 上限)。注释里写明了这条配对关系。- 世界高度:站立
1.6 × 0.096096 = 0.1538 m→ 蹲下0.0923 m。 - 未做(可以后续补):蹲下时没有缩小碰撞胶囊 —— 所以现在还钻不过低矮缝隙。要做需要额外处理「头顶有障碍时不许起身」的检测。