diff --git a/.workbuddy/memory/2026-10-09.md b/.workbuddy/memory/2026-10-09.md
new file mode 100644
index 0000000..7bdbf97
--- /dev/null
+++ b/.workbuddy/memory/2026-10-09.md
@@ -0,0 +1,179 @@
+# 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,**未写 `
`**(避免 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`。
diff --git a/character/assets/alternate.png b/character/assets/alternate.png
new file mode 100644
index 0000000..9ce6571
Binary files /dev/null and b/character/assets/alternate.png differ
diff --git a/character/assets/alternate.png.import b/character/assets/alternate.png.import
new file mode 100644
index 0000000..f8f049f
--- /dev/null
+++ b/character/assets/alternate.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cks6g3h0xmddp"
+path="res://.godot/imported/alternate.png-7b4881a08ee8867fab4d239fa82e73c8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/alternate.png"
+dest_files=["res://.godot/imported/alternate.png-7b4881a08ee8867fab4d239fa82e73c8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/back_left.png b/character/assets/back_left.png
new file mode 100644
index 0000000..54797ae
Binary files /dev/null and b/character/assets/back_left.png differ
diff --git a/character/assets/back_left.png.import b/character/assets/back_left.png.import
new file mode 100644
index 0000000..b8b657f
--- /dev/null
+++ b/character/assets/back_left.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btejr3ppfng0k"
+path="res://.godot/imported/back_left.png-69929c1ec483ec82febf7b37f931d277.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/back_left.png"
+dest_files=["res://.godot/imported/back_left.png-69929c1ec483ec82febf7b37f931d277.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/back_left2.png b/character/assets/back_left2.png
new file mode 100644
index 0000000..77fcc3d
Binary files /dev/null and b/character/assets/back_left2.png differ
diff --git a/character/assets/back_left2.png.import b/character/assets/back_left2.png.import
new file mode 100644
index 0000000..334a4ec
--- /dev/null
+++ b/character/assets/back_left2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://nej52pksaaq4"
+path="res://.godot/imported/back_left2.png-0df67405ad915508079897f6fbbb6645.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/back_left2.png"
+dest_files=["res://.godot/imported/back_left2.png-0df67405ad915508079897f6fbbb6645.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/back_right.png b/character/assets/back_right.png
new file mode 100644
index 0000000..af1521f
Binary files /dev/null and b/character/assets/back_right.png differ
diff --git a/character/assets/back_right.png.import b/character/assets/back_right.png.import
new file mode 100644
index 0000000..a16b52a
--- /dev/null
+++ b/character/assets/back_right.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ny8xdx0ea368"
+path="res://.godot/imported/back_right.png-456075ee41841934a71473763e271f10.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/back_right.png"
+dest_files=["res://.godot/imported/back_right.png-456075ee41841934a71473763e271f10.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/back_right2.png b/character/assets/back_right2.png
new file mode 100644
index 0000000..088624a
Binary files /dev/null and b/character/assets/back_right2.png differ
diff --git a/character/assets/back_right2.png.import b/character/assets/back_right2.png.import
new file mode 100644
index 0000000..794e57e
--- /dev/null
+++ b/character/assets/back_right2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bed2xwppv813b"
+path="res://.godot/imported/back_right2.png-7c77600d7421f5cfaeecb09a0d14b910.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/back_right2.png"
+dest_files=["res://.godot/imported/back_right2.png-7c77600d7421f5cfaeecb09a0d14b910.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/beam.png b/character/assets/beam.png
new file mode 100644
index 0000000..505c120
Binary files /dev/null and b/character/assets/beam.png differ
diff --git a/character/assets/beam.png.import b/character/assets/beam.png.import
new file mode 100644
index 0000000..e7c96df
--- /dev/null
+++ b/character/assets/beam.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ch0e7cby3wrwr"
+path="res://.godot/imported/beam.png-dfcffbc614bc7739aa968a724610632e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/beam.png"
+dest_files=["res://.godot/imported/beam.png-dfcffbc614bc7739aa968a724610632e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/busy.gif b/character/assets/busy.gif
new file mode 100644
index 0000000..210226d
Binary files /dev/null and b/character/assets/busy.gif differ
diff --git a/character/assets/busy.png b/character/assets/busy.png
new file mode 100644
index 0000000..3a95b22
Binary files /dev/null and b/character/assets/busy.png differ
diff --git a/character/assets/busy.png.import b/character/assets/busy.png.import
new file mode 100644
index 0000000..3c30902
--- /dev/null
+++ b/character/assets/busy.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ct24uwuv6gdrc"
+path="res://.godot/imported/busy.png-841643be6ab2014e99b21b90832c8148.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/busy.png"
+dest_files=["res://.godot/imported/busy.png-841643be6ab2014e99b21b90832c8148.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/cursive_beam.png b/character/assets/cursive_beam.png
new file mode 100644
index 0000000..3bd7af2
Binary files /dev/null and b/character/assets/cursive_beam.png differ
diff --git a/character/assets/cursive_beam.png.import b/character/assets/cursive_beam.png.import
new file mode 100644
index 0000000..883dc96
--- /dev/null
+++ b/character/assets/cursive_beam.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dq813jmm6esc"
+path="res://.godot/imported/cursive_beam.png-5f69bc3983cfaa0e0ea7632f6044bd55.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/cursive_beam.png"
+dest_files=["res://.godot/imported/cursive_beam.png-5f69bc3983cfaa0e0ea7632f6044bd55.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/alternate.png b/character/assets/dark/alternate.png
new file mode 100644
index 0000000..b71bd06
Binary files /dev/null and b/character/assets/dark/alternate.png differ
diff --git a/character/assets/dark/alternate.png.import b/character/assets/dark/alternate.png.import
new file mode 100644
index 0000000..544dcaa
--- /dev/null
+++ b/character/assets/dark/alternate.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dg5tn3h1dh31a"
+path="res://.godot/imported/alternate.png-940b9395fb224a810ea3f2732bfb9566.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/alternate.png"
+dest_files=["res://.godot/imported/alternate.png-940b9395fb224a810ea3f2732bfb9566.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/back_left.png b/character/assets/dark/back_left.png
new file mode 100644
index 0000000..d1b5789
Binary files /dev/null and b/character/assets/dark/back_left.png differ
diff --git a/character/assets/dark/back_left.png.import b/character/assets/dark/back_left.png.import
new file mode 100644
index 0000000..fa6448a
--- /dev/null
+++ b/character/assets/dark/back_left.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ih72yymqb0l5"
+path="res://.godot/imported/back_left.png-1d73145b507337ad30a7666343e7a4b7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/back_left.png"
+dest_files=["res://.godot/imported/back_left.png-1d73145b507337ad30a7666343e7a4b7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/back_left2.png b/character/assets/dark/back_left2.png
new file mode 100644
index 0000000..c136b3d
Binary files /dev/null and b/character/assets/dark/back_left2.png differ
diff --git a/character/assets/dark/back_left2.png.import b/character/assets/dark/back_left2.png.import
new file mode 100644
index 0000000..3336b27
--- /dev/null
+++ b/character/assets/dark/back_left2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pia011h1rx7x"
+path="res://.godot/imported/back_left2.png-ff22760bb5a0121e0de3d3fd5858010a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/back_left2.png"
+dest_files=["res://.godot/imported/back_left2.png-ff22760bb5a0121e0de3d3fd5858010a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/back_right.png b/character/assets/dark/back_right.png
new file mode 100644
index 0000000..1059e6b
Binary files /dev/null and b/character/assets/dark/back_right.png differ
diff --git a/character/assets/dark/back_right.png.import b/character/assets/dark/back_right.png.import
new file mode 100644
index 0000000..8efa645
--- /dev/null
+++ b/character/assets/dark/back_right.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dkhprmvf7lip1"
+path="res://.godot/imported/back_right.png-f3a83b551d2b93fa6306faf17711b8a9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/back_right.png"
+dest_files=["res://.godot/imported/back_right.png-f3a83b551d2b93fa6306faf17711b8a9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/back_right2.png b/character/assets/dark/back_right2.png
new file mode 100644
index 0000000..e69da14
Binary files /dev/null and b/character/assets/dark/back_right2.png differ
diff --git a/character/assets/dark/back_right2.png.import b/character/assets/dark/back_right2.png.import
new file mode 100644
index 0000000..46c61e1
--- /dev/null
+++ b/character/assets/dark/back_right2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bgbq6r1vqogbi"
+path="res://.godot/imported/back_right2.png-9353b5ee06c8c42084e8bc19f64f9f30.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/back_right2.png"
+dest_files=["res://.godot/imported/back_right2.png-9353b5ee06c8c42084e8bc19f64f9f30.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/beam.png b/character/assets/dark/beam.png
new file mode 100644
index 0000000..8296e28
Binary files /dev/null and b/character/assets/dark/beam.png differ
diff --git a/character/assets/dark/beam.png.import b/character/assets/dark/beam.png.import
new file mode 100644
index 0000000..b5aa0ef
--- /dev/null
+++ b/character/assets/dark/beam.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmiwiv3a6pku8"
+path="res://.godot/imported/beam.png-88ba7bf7e63fa6cb8c471485e0432516.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/beam.png"
+dest_files=["res://.godot/imported/beam.png-88ba7bf7e63fa6cb8c471485e0432516.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/busy.gif b/character/assets/dark/busy.gif
new file mode 100644
index 0000000..0b5d519
Binary files /dev/null and b/character/assets/dark/busy.gif differ
diff --git a/character/assets/dark/busy.png b/character/assets/dark/busy.png
new file mode 100644
index 0000000..2090531
Binary files /dev/null and b/character/assets/dark/busy.png differ
diff --git a/character/assets/dark/busy.png.import b/character/assets/dark/busy.png.import
new file mode 100644
index 0000000..6055fb2
--- /dev/null
+++ b/character/assets/dark/busy.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2ggi5ym1rpvt"
+path="res://.godot/imported/busy.png-0cca612e123296b6c549e10f2bfd5b7f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/busy.png"
+dest_files=["res://.godot/imported/busy.png-0cca612e123296b6c549e10f2bfd5b7f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/cursive_beam.png b/character/assets/dark/cursive_beam.png
new file mode 100644
index 0000000..ffeab58
Binary files /dev/null and b/character/assets/dark/cursive_beam.png differ
diff --git a/character/assets/dark/cursive_beam.png.import b/character/assets/dark/cursive_beam.png.import
new file mode 100644
index 0000000..733bfd2
--- /dev/null
+++ b/character/assets/dark/cursive_beam.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crmex0uxvofyk"
+path="res://.godot/imported/cursive_beam.png-96b6484384e46e53dbdf1c0415ef8519.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/cursive_beam.png"
+dest_files=["res://.godot/imported/cursive_beam.png-96b6484384e46e53dbdf1c0415ef8519.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/dgn1.png b/character/assets/dark/dgn1.png
new file mode 100644
index 0000000..1e9906a
Binary files /dev/null and b/character/assets/dark/dgn1.png differ
diff --git a/character/assets/dark/dgn1.png.import b/character/assets/dark/dgn1.png.import
new file mode 100644
index 0000000..bec7407
--- /dev/null
+++ b/character/assets/dark/dgn1.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2b8jbf1oq1iy"
+path="res://.godot/imported/dgn1.png-a8a4f29c0c29122dd3661ccee97586af.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/dgn1.png"
+dest_files=["res://.godot/imported/dgn1.png-a8a4f29c0c29122dd3661ccee97586af.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/dgn2.png b/character/assets/dark/dgn2.png
new file mode 100644
index 0000000..42faa5a
Binary files /dev/null and b/character/assets/dark/dgn2.png differ
diff --git a/character/assets/dark/dgn2.png.import b/character/assets/dark/dgn2.png.import
new file mode 100644
index 0000000..da7f638
--- /dev/null
+++ b/character/assets/dark/dgn2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dd1j6b71undbe"
+path="res://.godot/imported/dgn2.png-8360811d95ff637372d1ef01fc0a4e9b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/dgn2.png"
+dest_files=["res://.godot/imported/dgn2.png-8360811d95ff637372d1ef01fc0a4e9b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/grab.png b/character/assets/dark/grab.png
new file mode 100644
index 0000000..fac5cc9
Binary files /dev/null and b/character/assets/dark/grab.png differ
diff --git a/character/assets/dark/grab.png.import b/character/assets/dark/grab.png.import
new file mode 100644
index 0000000..ebc85a3
--- /dev/null
+++ b/character/assets/dark/grab.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dp3c67abo2evj"
+path="res://.godot/imported/grab.png-3da645eb7c43654301d72d3c600cd46b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/grab.png"
+dest_files=["res://.godot/imported/grab.png-3da645eb7c43654301d72d3c600cd46b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/grab_hover.png b/character/assets/dark/grab_hover.png
new file mode 100644
index 0000000..48b137e
Binary files /dev/null and b/character/assets/dark/grab_hover.png differ
diff --git a/character/assets/dark/grab_hover.png.import b/character/assets/dark/grab_hover.png.import
new file mode 100644
index 0000000..b9b9e44
--- /dev/null
+++ b/character/assets/dark/grab_hover.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c75wrtgtxi1ds"
+path="res://.godot/imported/grab_hover.png-a83aaf4eac32d3ebf5089cddf6f92bfe.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/grab_hover.png"
+dest_files=["res://.godot/imported/grab_hover.png-a83aaf4eac32d3ebf5089cddf6f92bfe.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/handwriting.png b/character/assets/dark/handwriting.png
new file mode 100644
index 0000000..03042f8
Binary files /dev/null and b/character/assets/dark/handwriting.png differ
diff --git a/character/assets/dark/handwriting.png.import b/character/assets/dark/handwriting.png.import
new file mode 100644
index 0000000..5b21127
--- /dev/null
+++ b/character/assets/dark/handwriting.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://uvgenp8um0bj"
+path="res://.godot/imported/handwriting.png-f08d29c6908708457519222ccd8a210d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/handwriting.png"
+dest_files=["res://.godot/imported/handwriting.png-f08d29c6908708457519222ccd8a210d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/help.png b/character/assets/dark/help.png
new file mode 100644
index 0000000..c5d8e22
Binary files /dev/null and b/character/assets/dark/help.png differ
diff --git a/character/assets/dark/help.png.import b/character/assets/dark/help.png.import
new file mode 100644
index 0000000..cb38478
--- /dev/null
+++ b/character/assets/dark/help.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cnkc4k0hafq7k"
+path="res://.godot/imported/help.png-23156de2b71d4b87152f76c090039923.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/help.png"
+dest_files=["res://.godot/imported/help.png-23156de2b71d4b87152f76c090039923.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/horz.png b/character/assets/dark/horz.png
new file mode 100644
index 0000000..56aa2e5
Binary files /dev/null and b/character/assets/dark/horz.png differ
diff --git a/character/assets/dark/horz.png.import b/character/assets/dark/horz.png.import
new file mode 100644
index 0000000..65559b4
--- /dev/null
+++ b/character/assets/dark/horz.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dx7cw84v6krsu"
+path="res://.godot/imported/horz.png-4ddb720c40f1c9c7696de033b50cff77.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/horz.png"
+dest_files=["res://.godot/imported/horz.png-4ddb720c40f1c9c7696de033b50cff77.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/hourglass.gif b/character/assets/dark/hourglass.gif
new file mode 100644
index 0000000..135fc66
Binary files /dev/null and b/character/assets/dark/hourglass.gif differ
diff --git a/character/assets/dark/hourglass.png b/character/assets/dark/hourglass.png
new file mode 100644
index 0000000..5a72efa
Binary files /dev/null and b/character/assets/dark/hourglass.png differ
diff --git a/character/assets/dark/hourglass.png.import b/character/assets/dark/hourglass.png.import
new file mode 100644
index 0000000..2295bae
--- /dev/null
+++ b/character/assets/dark/hourglass.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://sqbcg6yr4wdh"
+path="res://.godot/imported/hourglass.png-fa578c0ecff92b4914366d3ab0332f9a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/hourglass.png"
+dest_files=["res://.godot/imported/hourglass.png-fa578c0ecff92b4914366d3ab0332f9a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/lens.png b/character/assets/dark/lens.png
new file mode 100644
index 0000000..4b56225
Binary files /dev/null and b/character/assets/dark/lens.png differ
diff --git a/character/assets/dark/lens.png.import b/character/assets/dark/lens.png.import
new file mode 100644
index 0000000..5c8b613
--- /dev/null
+++ b/character/assets/dark/lens.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bta7pv0o26xmx"
+path="res://.godot/imported/lens.png-53be0cdc1e781f90bd27063299564e14.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/lens.png"
+dest_files=["res://.godot/imported/lens.png-53be0cdc1e781f90bd27063299564e14.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/lens_maximize.png b/character/assets/dark/lens_maximize.png
new file mode 100644
index 0000000..2f6dd01
Binary files /dev/null and b/character/assets/dark/lens_maximize.png differ
diff --git a/character/assets/dark/lens_maximize.png.import b/character/assets/dark/lens_maximize.png.import
new file mode 100644
index 0000000..c586d97
--- /dev/null
+++ b/character/assets/dark/lens_maximize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://w4u5r8xnmf4d"
+path="res://.godot/imported/lens_maximize.png-a07e5e1efa50a73bd5df282b859e29c4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/lens_maximize.png"
+dest_files=["res://.godot/imported/lens_maximize.png-a07e5e1efa50a73bd5df282b859e29c4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/lens_minimize.png b/character/assets/dark/lens_minimize.png
new file mode 100644
index 0000000..b23d7e0
Binary files /dev/null and b/character/assets/dark/lens_minimize.png differ
diff --git a/character/assets/dark/lens_minimize.png.import b/character/assets/dark/lens_minimize.png.import
new file mode 100644
index 0000000..8c232e1
--- /dev/null
+++ b/character/assets/dark/lens_minimize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dx3oej4rlxcy4"
+path="res://.godot/imported/lens_minimize.png-f51a2e93ae22ab7edc403b3ef6f3ab3e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/lens_minimize.png"
+dest_files=["res://.godot/imported/lens_minimize.png-f51a2e93ae22ab7edc403b3ef6f3ab3e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/link.png b/character/assets/dark/link.png
new file mode 100644
index 0000000..f6b9f9e
Binary files /dev/null and b/character/assets/dark/link.png differ
diff --git a/character/assets/dark/link.png.import b/character/assets/dark/link.png.import
new file mode 100644
index 0000000..76ed8b7
--- /dev/null
+++ b/character/assets/dark/link.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b8w0ynd5qwgbe"
+path="res://.godot/imported/link.png-f1ff237f5ab0b296d25bfc99e0709bc7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/link.png"
+dest_files=["res://.godot/imported/link.png-f1ff237f5ab0b296d25bfc99e0709bc7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/link2.png b/character/assets/dark/link2.png
new file mode 100644
index 0000000..6d31b4a
Binary files /dev/null and b/character/assets/dark/link2.png differ
diff --git a/character/assets/dark/link2.png.import b/character/assets/dark/link2.png.import
new file mode 100644
index 0000000..f9c6c35
--- /dev/null
+++ b/character/assets/dark/link2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://df46yhoy0263o"
+path="res://.godot/imported/link2.png-e32ffa3020f712cffb6756ba0f9103c5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/link2.png"
+dest_files=["res://.godot/imported/link2.png-e32ffa3020f712cffb6756ba0f9103c5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/link3.png b/character/assets/dark/link3.png
new file mode 100644
index 0000000..6872d98
Binary files /dev/null and b/character/assets/dark/link3.png differ
diff --git a/character/assets/dark/link3.png.import b/character/assets/dark/link3.png.import
new file mode 100644
index 0000000..aa9f3bb
--- /dev/null
+++ b/character/assets/dark/link3.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://sra4ly7mwoxv"
+path="res://.godot/imported/link3.png-515fde49eb9c68ef20ad67962fa9d237.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/link3.png"
+dest_files=["res://.godot/imported/link3.png-515fde49eb9c68ef20ad67962fa9d237.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/maximize.png b/character/assets/dark/maximize.png
new file mode 100644
index 0000000..fbdc084
Binary files /dev/null and b/character/assets/dark/maximize.png differ
diff --git a/character/assets/dark/maximize.png.import b/character/assets/dark/maximize.png.import
new file mode 100644
index 0000000..1d8c2d8
--- /dev/null
+++ b/character/assets/dark/maximize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bgxinicbnvdjg"
+path="res://.godot/imported/maximize.png-0267cec3268311711ea9938430b7eac9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/maximize.png"
+dest_files=["res://.godot/imported/maximize.png-0267cec3268311711ea9938430b7eac9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/minimize.png b/character/assets/dark/minimize.png
new file mode 100644
index 0000000..ce77623
Binary files /dev/null and b/character/assets/dark/minimize.png differ
diff --git a/character/assets/dark/minimize.png.import b/character/assets/dark/minimize.png.import
new file mode 100644
index 0000000..0d2552c
--- /dev/null
+++ b/character/assets/dark/minimize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcl2w2uu6p6cv"
+path="res://.godot/imported/minimize.png-fccb7007132ad42461dc1c0b0b1c1370.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/minimize.png"
+dest_files=["res://.godot/imported/minimize.png-fccb7007132ad42461dc1c0b0b1c1370.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/move.png b/character/assets/dark/move.png
new file mode 100644
index 0000000..3533873
Binary files /dev/null and b/character/assets/dark/move.png differ
diff --git a/character/assets/dark/move.png.import b/character/assets/dark/move.png.import
new file mode 100644
index 0000000..8834153
--- /dev/null
+++ b/character/assets/dark/move.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://7t84w7f56rcm"
+path="res://.godot/imported/move.png-1ccda1944c5e01e5f4b81ab19c86010e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/move.png"
+dest_files=["res://.godot/imported/move.png-1ccda1944c5e01e5f4b81ab19c86010e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/person.png b/character/assets/dark/person.png
new file mode 100644
index 0000000..49641b0
Binary files /dev/null and b/character/assets/dark/person.png differ
diff --git a/character/assets/dark/person.png.import b/character/assets/dark/person.png.import
new file mode 100644
index 0000000..9b8acb4
--- /dev/null
+++ b/character/assets/dark/person.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cy0nrorxotn1h"
+path="res://.godot/imported/person.png-e552a793177c7d9d9808c96c7bf77ba9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/person.png"
+dest_files=["res://.godot/imported/person.png-e552a793177c7d9d9808c96c7bf77ba9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/pin.png b/character/assets/dark/pin.png
new file mode 100644
index 0000000..2987ec6
Binary files /dev/null and b/character/assets/dark/pin.png differ
diff --git a/character/assets/dark/pin.png.import b/character/assets/dark/pin.png.import
new file mode 100644
index 0000000..a0f07b9
--- /dev/null
+++ b/character/assets/dark/pin.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cnyu18nn7bq4x"
+path="res://.godot/imported/pin.png-a5b6bb7f70c9d9f622d5fce204bbd280.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/pin.png"
+dest_files=["res://.godot/imported/pin.png-a5b6bb7f70c9d9f622d5fce204bbd280.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/pointer.png b/character/assets/dark/pointer.png
new file mode 100644
index 0000000..7c44fee
Binary files /dev/null and b/character/assets/dark/pointer.png differ
diff --git a/character/assets/dark/pointer.png.import b/character/assets/dark/pointer.png.import
new file mode 100644
index 0000000..e0ef676
--- /dev/null
+++ b/character/assets/dark/pointer.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://lu4obv7kjbe3"
+path="res://.godot/imported/pointer.png-a1f7df44e59d089ea833443a3517dd22.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/pointer.png"
+dest_files=["res://.godot/imported/pointer.png-a1f7df44e59d089ea833443a3517dd22.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/precision.png b/character/assets/dark/precision.png
new file mode 100644
index 0000000..0afe391
Binary files /dev/null and b/character/assets/dark/precision.png differ
diff --git a/character/assets/dark/precision.png.import b/character/assets/dark/precision.png.import
new file mode 100644
index 0000000..e90471d
--- /dev/null
+++ b/character/assets/dark/precision.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bhl4rrqx21blh"
+path="res://.godot/imported/precision.png-1d866c5a9fe0aa63b872f853c81e2b7b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/precision.png"
+dest_files=["res://.godot/imported/precision.png-1d866c5a9fe0aa63b872f853c81e2b7b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/precision2.png b/character/assets/dark/precision2.png
new file mode 100644
index 0000000..f55559c
Binary files /dev/null and b/character/assets/dark/precision2.png differ
diff --git a/character/assets/dark/precision2.png.import b/character/assets/dark/precision2.png.import
new file mode 100644
index 0000000..a29ed1c
--- /dev/null
+++ b/character/assets/dark/precision2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cyeaxhpv8mq27"
+path="res://.godot/imported/precision2.png-b284e80bd330b83680c4d3c34d9b57e4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/precision2.png"
+dest_files=["res://.godot/imported/precision2.png-b284e80bd330b83680c4d3c34d9b57e4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/precision3.png b/character/assets/dark/precision3.png
new file mode 100644
index 0000000..ac06ac9
Binary files /dev/null and b/character/assets/dark/precision3.png differ
diff --git a/character/assets/dark/precision3.png.import b/character/assets/dark/precision3.png.import
new file mode 100644
index 0000000..f6ee6c6
--- /dev/null
+++ b/character/assets/dark/precision3.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://8u64pvlrts2s"
+path="res://.godot/imported/precision3.png-7c1b5d183be231c4fd7ca72d2a5d3004.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/precision3.png"
+dest_files=["res://.godot/imported/precision3.png-7c1b5d183be231c4fd7ca72d2a5d3004.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/precision4.png b/character/assets/dark/precision4.png
new file mode 100644
index 0000000..9f0f29d
Binary files /dev/null and b/character/assets/dark/precision4.png differ
diff --git a/character/assets/dark/precision4.png.import b/character/assets/dark/precision4.png.import
new file mode 100644
index 0000000..ce1c354
--- /dev/null
+++ b/character/assets/dark/precision4.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ba35mcigssejq"
+path="res://.godot/imported/precision4.png-231105b80a481323682024ec7a234524.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/precision4.png"
+dest_files=["res://.godot/imported/precision4.png-231105b80a481323682024ec7a234524.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/precision5.png b/character/assets/dark/precision5.png
new file mode 100644
index 0000000..48f9bbd
Binary files /dev/null and b/character/assets/dark/precision5.png differ
diff --git a/character/assets/dark/precision5.png.import b/character/assets/dark/precision5.png.import
new file mode 100644
index 0000000..708f063
--- /dev/null
+++ b/character/assets/dark/precision5.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bx3o5vtiuo1df"
+path="res://.godot/imported/precision5.png-31edcbd4edb266348dae9d7777da7af5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/precision5.png"
+dest_files=["res://.godot/imported/precision5.png-31edcbd4edb266348dae9d7777da7af5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/rotate.png b/character/assets/dark/rotate.png
new file mode 100644
index 0000000..f47a722
Binary files /dev/null and b/character/assets/dark/rotate.png differ
diff --git a/character/assets/dark/rotate.png.import b/character/assets/dark/rotate.png.import
new file mode 100644
index 0000000..812fe82
--- /dev/null
+++ b/character/assets/dark/rotate.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cttqfru6btswf"
+path="res://.godot/imported/rotate.png-827193c40aaf254b918a97f5d4ffb84e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/rotate.png"
+dest_files=["res://.godot/imported/rotate.png-827193c40aaf254b918a97f5d4ffb84e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/rotate2.png b/character/assets/dark/rotate2.png
new file mode 100644
index 0000000..3eeed16
Binary files /dev/null and b/character/assets/dark/rotate2.png differ
diff --git a/character/assets/dark/rotate2.png.import b/character/assets/dark/rotate2.png.import
new file mode 100644
index 0000000..389691a
--- /dev/null
+++ b/character/assets/dark/rotate2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d36bqg84e8gr0"
+path="res://.godot/imported/rotate2.png-286da5c6e8cf66936fe5b2f747e3ae24.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/rotate2.png"
+dest_files=["res://.godot/imported/rotate2.png-286da5c6e8cf66936fe5b2f747e3ae24.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/unavailable.png b/character/assets/dark/unavailable.png
new file mode 100644
index 0000000..27a71a6
Binary files /dev/null and b/character/assets/dark/unavailable.png differ
diff --git a/character/assets/dark/unavailable.png.import b/character/assets/dark/unavailable.png.import
new file mode 100644
index 0000000..063c7ce
--- /dev/null
+++ b/character/assets/dark/unavailable.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmbjg73138ny0"
+path="res://.godot/imported/unavailable.png-5754785eed6a7f1fbd9126a493f6910e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/unavailable.png"
+dest_files=["res://.godot/imported/unavailable.png-5754785eed6a7f1fbd9126a493f6910e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/vert.png b/character/assets/dark/vert.png
new file mode 100644
index 0000000..6923f19
Binary files /dev/null and b/character/assets/dark/vert.png differ
diff --git a/character/assets/dark/vert.png.import b/character/assets/dark/vert.png.import
new file mode 100644
index 0000000..23e919f
--- /dev/null
+++ b/character/assets/dark/vert.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cwsn6ieyc57io"
+path="res://.godot/imported/vert.png-0b5f6e54a5ab2ba0f72e7525da131b76.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/vert.png"
+dest_files=["res://.godot/imported/vert.png-0b5f6e54a5ab2ba0f72e7525da131b76.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dark/working.gif b/character/assets/dark/working.gif
new file mode 100644
index 0000000..5b2fb09
Binary files /dev/null and b/character/assets/dark/working.gif differ
diff --git a/character/assets/dark/working.png b/character/assets/dark/working.png
new file mode 100644
index 0000000..67b9c03
Binary files /dev/null and b/character/assets/dark/working.png differ
diff --git a/character/assets/dark/working.png.import b/character/assets/dark/working.png.import
new file mode 100644
index 0000000..6fd961d
--- /dev/null
+++ b/character/assets/dark/working.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dq2kofsok3ycv"
+path="res://.godot/imported/working.png-d6fb84696bf3635cac8c3d0ffa93642f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dark/working.png"
+dest_files=["res://.godot/imported/working.png-d6fb84696bf3635cac8c3d0ffa93642f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dgn1.png b/character/assets/dgn1.png
new file mode 100644
index 0000000..127be00
Binary files /dev/null and b/character/assets/dgn1.png differ
diff --git a/character/assets/dgn1.png.import b/character/assets/dgn1.png.import
new file mode 100644
index 0000000..cef5000
--- /dev/null
+++ b/character/assets/dgn1.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cn0yprqtkdgg7"
+path="res://.godot/imported/dgn1.png-acf14df5ce8d4fea69c692a5f580f887.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dgn1.png"
+dest_files=["res://.godot/imported/dgn1.png-acf14df5ce8d4fea69c692a5f580f887.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/dgn2.png b/character/assets/dgn2.png
new file mode 100644
index 0000000..398255a
Binary files /dev/null and b/character/assets/dgn2.png differ
diff --git a/character/assets/dgn2.png.import b/character/assets/dgn2.png.import
new file mode 100644
index 0000000..864a0d5
--- /dev/null
+++ b/character/assets/dgn2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b5tismi3v8afy"
+path="res://.godot/imported/dgn2.png-29a3d95fa22e382e06bd7cce8cbb2412.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/dgn2.png"
+dest_files=["res://.godot/imported/dgn2.png-29a3d95fa22e382e06bd7cce8cbb2412.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/grab.png b/character/assets/grab.png
new file mode 100644
index 0000000..579069e
Binary files /dev/null and b/character/assets/grab.png differ
diff --git a/character/assets/grab.png.import b/character/assets/grab.png.import
new file mode 100644
index 0000000..895fda7
--- /dev/null
+++ b/character/assets/grab.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c67qe66m2aft4"
+path="res://.godot/imported/grab.png-15ccc11b1ce7bc67c3d60db6d0ffc1ed.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/grab.png"
+dest_files=["res://.godot/imported/grab.png-15ccc11b1ce7bc67c3d60db6d0ffc1ed.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/grab_hover.png b/character/assets/grab_hover.png
new file mode 100644
index 0000000..d3bc19d
Binary files /dev/null and b/character/assets/grab_hover.png differ
diff --git a/character/assets/grab_hover.png.import b/character/assets/grab_hover.png.import
new file mode 100644
index 0000000..dd3fb6d
--- /dev/null
+++ b/character/assets/grab_hover.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkq6ya350cae8"
+path="res://.godot/imported/grab_hover.png-ae4fc79bd64160559a632cc7a661f9e3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/grab_hover.png"
+dest_files=["res://.godot/imported/grab_hover.png-ae4fc79bd64160559a632cc7a661f9e3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/handwriting.png b/character/assets/handwriting.png
new file mode 100644
index 0000000..80f5332
Binary files /dev/null and b/character/assets/handwriting.png differ
diff --git a/character/assets/handwriting.png.import b/character/assets/handwriting.png.import
new file mode 100644
index 0000000..2d4d1ed
--- /dev/null
+++ b/character/assets/handwriting.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bpyo70c6rwvos"
+path="res://.godot/imported/handwriting.png-7764e437bf825296e3402990d07c6cbc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/handwriting.png"
+dest_files=["res://.godot/imported/handwriting.png-7764e437bf825296e3402990d07c6cbc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/help.png b/character/assets/help.png
new file mode 100644
index 0000000..40bf6d2
Binary files /dev/null and b/character/assets/help.png differ
diff --git a/character/assets/help.png.import b/character/assets/help.png.import
new file mode 100644
index 0000000..2238ef1
--- /dev/null
+++ b/character/assets/help.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://4ec7i22lr1a4"
+path="res://.godot/imported/help.png-48e761ffa54397efbce36bfe5994d549.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/help.png"
+dest_files=["res://.godot/imported/help.png-48e761ffa54397efbce36bfe5994d549.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/horz.png b/character/assets/horz.png
new file mode 100644
index 0000000..ab70b9a
Binary files /dev/null and b/character/assets/horz.png differ
diff --git a/character/assets/horz.png.import b/character/assets/horz.png.import
new file mode 100644
index 0000000..0dbe5df
--- /dev/null
+++ b/character/assets/horz.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ble70s75fsih3"
+path="res://.godot/imported/horz.png-ebceaa5927bcd11a7d6c3d373a498a29.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/horz.png"
+dest_files=["res://.godot/imported/horz.png-ebceaa5927bcd11a7d6c3d373a498a29.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/hourglass.gif b/character/assets/hourglass.gif
new file mode 100644
index 0000000..ae18510
Binary files /dev/null and b/character/assets/hourglass.gif differ
diff --git a/character/assets/hourglass.png b/character/assets/hourglass.png
new file mode 100644
index 0000000..4242e2f
Binary files /dev/null and b/character/assets/hourglass.png differ
diff --git a/character/assets/hourglass.png.import b/character/assets/hourglass.png.import
new file mode 100644
index 0000000..f48e154
--- /dev/null
+++ b/character/assets/hourglass.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cmmvdhp4jkxov"
+path="res://.godot/imported/hourglass.png-2f848e5e319d3aca6016c878d9fec5fe.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/hourglass.png"
+dest_files=["res://.godot/imported/hourglass.png-2f848e5e319d3aca6016c878d9fec5fe.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/lens.png b/character/assets/lens.png
new file mode 100644
index 0000000..e45999d
Binary files /dev/null and b/character/assets/lens.png differ
diff --git a/character/assets/lens.png.import b/character/assets/lens.png.import
new file mode 100644
index 0000000..0bcbf4a
--- /dev/null
+++ b/character/assets/lens.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dfq1co82yc3xk"
+path="res://.godot/imported/lens.png-8e0352f0dfd40d760b08e5284f881686.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/lens.png"
+dest_files=["res://.godot/imported/lens.png-8e0352f0dfd40d760b08e5284f881686.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/lens_maximize.png b/character/assets/lens_maximize.png
new file mode 100644
index 0000000..3c26536
Binary files /dev/null and b/character/assets/lens_maximize.png differ
diff --git a/character/assets/lens_maximize.png.import b/character/assets/lens_maximize.png.import
new file mode 100644
index 0000000..b0f90d3
--- /dev/null
+++ b/character/assets/lens_maximize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://de6ba60eoms3v"
+path="res://.godot/imported/lens_maximize.png-d8388bcc188c0ce6b8c64c7b752ab6f8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/lens_maximize.png"
+dest_files=["res://.godot/imported/lens_maximize.png-d8388bcc188c0ce6b8c64c7b752ab6f8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/lens_minimize.png b/character/assets/lens_minimize.png
new file mode 100644
index 0000000..c161b6c
Binary files /dev/null and b/character/assets/lens_minimize.png differ
diff --git a/character/assets/lens_minimize.png.import b/character/assets/lens_minimize.png.import
new file mode 100644
index 0000000..25207c4
--- /dev/null
+++ b/character/assets/lens_minimize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d2io5tqnhp0yd"
+path="res://.godot/imported/lens_minimize.png-ad87b62e74768e14ed7633c100ac2f7c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/lens_minimize.png"
+dest_files=["res://.godot/imported/lens_minimize.png-ad87b62e74768e14ed7633c100ac2f7c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/link.png b/character/assets/link.png
new file mode 100644
index 0000000..b38b5bb
Binary files /dev/null and b/character/assets/link.png differ
diff --git a/character/assets/link.png.import b/character/assets/link.png.import
new file mode 100644
index 0000000..6b182ca
--- /dev/null
+++ b/character/assets/link.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dq4rsr0su7v37"
+path="res://.godot/imported/link.png-b5bfc33a002d39ff1566e9acb30e6fa8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/link.png"
+dest_files=["res://.godot/imported/link.png-b5bfc33a002d39ff1566e9acb30e6fa8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/link2.png b/character/assets/link2.png
new file mode 100644
index 0000000..ecb27d2
Binary files /dev/null and b/character/assets/link2.png differ
diff --git a/character/assets/link2.png.import b/character/assets/link2.png.import
new file mode 100644
index 0000000..0c07c49
--- /dev/null
+++ b/character/assets/link2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c7qeext6c6u4m"
+path="res://.godot/imported/link2.png-e1645441c4478a18f27a6c4bd6e9081a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/link2.png"
+dest_files=["res://.godot/imported/link2.png-e1645441c4478a18f27a6c4bd6e9081a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/link3.png b/character/assets/link3.png
new file mode 100644
index 0000000..6e76cc8
Binary files /dev/null and b/character/assets/link3.png differ
diff --git a/character/assets/link3.png.import b/character/assets/link3.png.import
new file mode 100644
index 0000000..4541b5e
--- /dev/null
+++ b/character/assets/link3.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://caf3nate4aj5"
+path="res://.godot/imported/link3.png-d5bd6642a5b321cd32335b5931537200.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/link3.png"
+dest_files=["res://.godot/imported/link3.png-d5bd6642a5b321cd32335b5931537200.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/maximize.png b/character/assets/maximize.png
new file mode 100644
index 0000000..bee5623
Binary files /dev/null and b/character/assets/maximize.png differ
diff --git a/character/assets/maximize.png.import b/character/assets/maximize.png.import
new file mode 100644
index 0000000..21643a4
--- /dev/null
+++ b/character/assets/maximize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qxcsts6i7p02"
+path="res://.godot/imported/maximize.png-e1bada99b53fd1f6fdd55915eab39a9a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/maximize.png"
+dest_files=["res://.godot/imported/maximize.png-e1bada99b53fd1f6fdd55915eab39a9a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/minimize.png b/character/assets/minimize.png
new file mode 100644
index 0000000..29db8ad
Binary files /dev/null and b/character/assets/minimize.png differ
diff --git a/character/assets/minimize.png.import b/character/assets/minimize.png.import
new file mode 100644
index 0000000..8a2a54d
--- /dev/null
+++ b/character/assets/minimize.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dbry7rscpb826"
+path="res://.godot/imported/minimize.png-d1fa172243303404f7062b2d4d107a2a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/minimize.png"
+dest_files=["res://.godot/imported/minimize.png-d1fa172243303404f7062b2d4d107a2a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/move.png b/character/assets/move.png
new file mode 100644
index 0000000..ab62b63
Binary files /dev/null and b/character/assets/move.png differ
diff --git a/character/assets/move.png.import b/character/assets/move.png.import
new file mode 100644
index 0000000..c23dbf0
--- /dev/null
+++ b/character/assets/move.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://nq6ao0hs0m8x"
+path="res://.godot/imported/move.png-ac20cd63ec3561066e9b4767066e86d3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/move.png"
+dest_files=["res://.godot/imported/move.png-ac20cd63ec3561066e9b4767066e86d3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/person.png b/character/assets/person.png
new file mode 100644
index 0000000..b18955b
Binary files /dev/null and b/character/assets/person.png differ
diff --git a/character/assets/person.png.import b/character/assets/person.png.import
new file mode 100644
index 0000000..a698ac6
--- /dev/null
+++ b/character/assets/person.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bn6l47k0e6y6p"
+path="res://.godot/imported/person.png-fb515867450f411bff446c1627ba7773.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/person.png"
+dest_files=["res://.godot/imported/person.png-fb515867450f411bff446c1627ba7773.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/pin.png b/character/assets/pin.png
new file mode 100644
index 0000000..2cb2710
Binary files /dev/null and b/character/assets/pin.png differ
diff --git a/character/assets/pin.png.import b/character/assets/pin.png.import
new file mode 100644
index 0000000..4db7a6c
--- /dev/null
+++ b/character/assets/pin.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3det1xj05in8"
+path="res://.godot/imported/pin.png-ad0bb6b74dac49dfb1f53f176bb244b6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/pin.png"
+dest_files=["res://.godot/imported/pin.png-ad0bb6b74dac49dfb1f53f176bb244b6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/pointer.png b/character/assets/pointer.png
new file mode 100644
index 0000000..a052e6f
Binary files /dev/null and b/character/assets/pointer.png differ
diff --git a/character/assets/pointer.png.import b/character/assets/pointer.png.import
new file mode 100644
index 0000000..c9b17be
--- /dev/null
+++ b/character/assets/pointer.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl71br308neni"
+path="res://.godot/imported/pointer.png-a4414502be6234af498130e40a05a3ea.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/pointer.png"
+dest_files=["res://.godot/imported/pointer.png-a4414502be6234af498130e40a05a3ea.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/precision.png b/character/assets/precision.png
new file mode 100644
index 0000000..1637f84
Binary files /dev/null and b/character/assets/precision.png differ
diff --git a/character/assets/precision.png.import b/character/assets/precision.png.import
new file mode 100644
index 0000000..d29c87c
--- /dev/null
+++ b/character/assets/precision.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://clmdagpljwj4i"
+path="res://.godot/imported/precision.png-8dffc57796b993b23a5a16be2b505632.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/precision.png"
+dest_files=["res://.godot/imported/precision.png-8dffc57796b993b23a5a16be2b505632.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/precision2.png b/character/assets/precision2.png
new file mode 100644
index 0000000..4057f42
Binary files /dev/null and b/character/assets/precision2.png differ
diff --git a/character/assets/precision2.png.import b/character/assets/precision2.png.import
new file mode 100644
index 0000000..ecbc489
--- /dev/null
+++ b/character/assets/precision2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dlb6akupy25nb"
+path="res://.godot/imported/precision2.png-83a1ce6358b2f5ebb7bff8427d459425.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/precision2.png"
+dest_files=["res://.godot/imported/precision2.png-83a1ce6358b2f5ebb7bff8427d459425.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/precision3.png b/character/assets/precision3.png
new file mode 100644
index 0000000..8092d3c
Binary files /dev/null and b/character/assets/precision3.png differ
diff --git a/character/assets/precision3.png.import b/character/assets/precision3.png.import
new file mode 100644
index 0000000..24afe6c
--- /dev/null
+++ b/character/assets/precision3.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmwvnop03l4dm"
+path="res://.godot/imported/precision3.png-8e302ff919f40a68410da7f1b4c4c70b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/precision3.png"
+dest_files=["res://.godot/imported/precision3.png-8e302ff919f40a68410da7f1b4c4c70b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/precision4.png b/character/assets/precision4.png
new file mode 100644
index 0000000..0a3bf4d
Binary files /dev/null and b/character/assets/precision4.png differ
diff --git a/character/assets/precision4.png.import b/character/assets/precision4.png.import
new file mode 100644
index 0000000..eae5dff
--- /dev/null
+++ b/character/assets/precision4.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kmfykgiejkba"
+path="res://.godot/imported/precision4.png-e70ef3753de474205171142896970d51.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/precision4.png"
+dest_files=["res://.godot/imported/precision4.png-e70ef3753de474205171142896970d51.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/precision5.png b/character/assets/precision5.png
new file mode 100644
index 0000000..cec65bd
Binary files /dev/null and b/character/assets/precision5.png differ
diff --git a/character/assets/precision5.png.import b/character/assets/precision5.png.import
new file mode 100644
index 0000000..db94a93
--- /dev/null
+++ b/character/assets/precision5.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://sq1vr3c2cjgt"
+path="res://.godot/imported/precision5.png-194cdfee72e5e8db845f2b14983c43bb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/precision5.png"
+dest_files=["res://.godot/imported/precision5.png-194cdfee72e5e8db845f2b14983c43bb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/rotate.png b/character/assets/rotate.png
new file mode 100644
index 0000000..8eeeb59
Binary files /dev/null and b/character/assets/rotate.png differ
diff --git a/character/assets/rotate.png.import b/character/assets/rotate.png.import
new file mode 100644
index 0000000..e55404f
--- /dev/null
+++ b/character/assets/rotate.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bat0ml4a4hkyy"
+path="res://.godot/imported/rotate.png-c09034a024edfba2171aebca352bd70b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/rotate.png"
+dest_files=["res://.godot/imported/rotate.png-c09034a024edfba2171aebca352bd70b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/rotate2.png b/character/assets/rotate2.png
new file mode 100644
index 0000000..8044d74
Binary files /dev/null and b/character/assets/rotate2.png differ
diff --git a/character/assets/rotate2.png.import b/character/assets/rotate2.png.import
new file mode 100644
index 0000000..b2a29d6
--- /dev/null
+++ b/character/assets/rotate2.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dc7raekowlqk1"
+path="res://.godot/imported/rotate2.png-9c0c1e4fa7a1b924429aa664d1f82b9f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/rotate2.png"
+dest_files=["res://.godot/imported/rotate2.png-9c0c1e4fa7a1b924429aa664d1f82b9f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/unavailable.png b/character/assets/unavailable.png
new file mode 100644
index 0000000..c67ea72
Binary files /dev/null and b/character/assets/unavailable.png differ
diff --git a/character/assets/unavailable.png.import b/character/assets/unavailable.png.import
new file mode 100644
index 0000000..d90d0fe
--- /dev/null
+++ b/character/assets/unavailable.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c61aoccov7qi4"
+path="res://.godot/imported/unavailable.png-ffa8531522436dc2e3073611bf521307.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/unavailable.png"
+dest_files=["res://.godot/imported/unavailable.png-ffa8531522436dc2e3073611bf521307.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/vert.png b/character/assets/vert.png
new file mode 100644
index 0000000..46f3513
Binary files /dev/null and b/character/assets/vert.png differ
diff --git a/character/assets/vert.png.import b/character/assets/vert.png.import
new file mode 100644
index 0000000..815aa7c
--- /dev/null
+++ b/character/assets/vert.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dom3kbjaty7yp"
+path="res://.godot/imported/vert.png-dab7aa728040976b9e595a16644f90c0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/vert.png"
+dest_files=["res://.godot/imported/vert.png-dab7aa728040976b9e595a16644f90c0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/assets/working.gif b/character/assets/working.gif
new file mode 100644
index 0000000..2b512de
Binary files /dev/null and b/character/assets/working.gif differ
diff --git a/character/assets/working.png b/character/assets/working.png
new file mode 100644
index 0000000..149bd71
Binary files /dev/null and b/character/assets/working.png differ
diff --git a/character/assets/working.png.import b/character/assets/working.png.import
new file mode 100644
index 0000000..de73a67
--- /dev/null
+++ b/character/assets/working.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b86upjuvgjwlf"
+path="res://.godot/imported/working.png-8cf765f650f5e906e979791374ba3c0b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://character/assets/working.png"
+dest_files=["res://.godot/imported/working.png-8cf765f650f5e906e979791374ba3c0b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/character/player/player.gd b/character/player/player.gd
index 478f624..a0c6a2b 100644
--- a/character/player/player.gd
+++ b/character/player/player.gd
@@ -1,45 +1,25 @@
extends CharacterBody3D
-
-## 跳跃输入缓冲(秒):按下跳跃后这段时间内若落地,自动补上一次起跳
-const JUMP_BUFFER_TIME: float = 0.1
-
-@export_group("视角")
-## 鼠标灵敏度(弧度/像素)
-@export var mouse_sensitivity: float = 0.0025
-## 最低俯角(向下看)
-@export var pitch_min_deg: float = -89.0
-## 最高仰角(向上看)
-@export var pitch_max_deg: float = 89.0
-
-@export_group("移动")
-## 常规移动速度(米/秒)
-@export var walk_speed: float = 5.0
-## 疾跑速度(米/秒)
-@export var sprint_speed: float = 8.5
-## 地面加速度(米/秒²)
-@export var acceleration: float = 45.0
-## 空中加速度(米/秒²)
-@export var air_acceleration: float = 12.0
-## 起跳初速度(米/秒)
-@export var jump_velocity: float = 5.0
+## 玩家协调器:只做三件事 —— 多人权限、依赖注入、按固定顺序编排各组件。
+##
+## 具体能力全在子节点组件里:
+## Look 视角 character/player/player_look.gd
+## Motor 移动跳跃 character/player/player_motor.gd
+## Pickup 拾取 character/player/player_pickup.gd
+## Animator 动画表现 character/player/player_animator.gd(挂在 AnimationPlayer 上)
+##
+## 本脚本不实现任何操作,只负责「谁先谁后」和「谁有权跑」。
@onready var body: Node3D = $Body
-## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
-## 分开是为了让 pitch 不掺进移动方向 —— 移动只用 rig 的 basis,低头看地也不会走歪。
+## 本地玩家自己的模型(鼠)。第一人称要藏掉它,见 _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
-## 动画状态机。控制器只把「运动结果」交给它,它也只管自己播动画。
-@onready var animator: PlayerAnimator = $Body/AnimationPlayer
-@onready var interaction_pos: Marker3D = $CameraRig/Camera/InteractionPos
-@onready var interaction_ray: RayCast3D = $CameraRig/Camera/InteractionRay
-
-var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
-var _yaw: float = 0.0
-var _pitch: float = 0.0
-var _jump_buffer: float = 0.0
-# 拾取
-var _pickObj = null
-var _pickPower = 10
+## 准心(挂在 CameraRig/HUD 下)。远端玩家的 HUD 会随 camera_rig 一起被释放。
+@onready var crosshair: Crosshair = $CameraRig/HUD/Crosshair
+@onready var animation_player: PlayerAnimator = $AnimationPlayer
+@onready var look: PlayerLook = $Look
+@onready var motor: PlayerMotor = $Motor
+@onready var pickup: PlayerPickup = $Pickup
func _ready() -> void:
@@ -47,109 +27,73 @@ func _ready() -> void:
# 拆掉相机 —— 否则每多一个玩家就多一台 Camera3D 抢渲染;
# 关掉输入与物理 —— 否则与权威端双重模拟,表现为抖动、漂移、穿地。
# 它的 position 与 Body.rotation 由 Sync 子节点同步;血条由 health 同步。
+ #
+ # 注意:输入已经分散到各组件里,所以这里必须把组件也一起关掉 ——
+ # 只关自己是不够的,否则远端玩家照样读本机输入。
if not is_multiplayer_authority():
camera_rig.queue_free()
set_physics_process(false)
set_process_unhandled_input(false)
+ look.set_process_unhandled_input(false)
+ motor.set_process_unhandled_input(false)
+ pickup.set_process_unhandled_input(false)
return
- _yaw = camera_rig.rotation.y
- _pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
- _apply_look()
+ # 第一人称:藏掉自己的模型。
+ # 摄像机的近裁剪面被调小了(见 player.tscn 的 near = 0.01),已经盖不住自己的身体 ——
+ # 不藏就会从内部看到模型的内壁。远端玩家的模型是各自独立的实例,不受影响。
+ if model != null:
+ model.visible = false
+
+ # 依赖注入:组件不自己去 get_node,由这里接线。
+ # 好处是组件不认识玩家的场景结构,换场景 / 单测时注入别的节点即可。
+ look.setup(camera_rig, camera, body)
+ motor.setup(self, camera_rig)
+ pickup.setup(
+ camera.get_node("InteractionRay") as RayCast3D,
+ camera.get_node("InteractionPos") as Marker3D,
+ self
+ )
+ # 准心接线:拾取组件一发现交互状态变化,就通知准心换外观(点 / 手 / 抓)。
+ pickup.interact_state_changed.connect(_on_pickup_interact_state_changed)
set_mouse_captured(true)
func _unhandled_input(event: InputEvent) -> void:
- if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
- _yaw -= event.relative.x * mouse_sensitivity
- _pitch = clampf(
- _pitch - event.relative.y * mouse_sensitivity,
- deg_to_rad(pitch_min_deg),
- deg_to_rad(pitch_max_deg)
- )
- _apply_look()
- elif event.is_action_pressed("jump"):
- _jump_buffer = JUMP_BUFFER_TIME
- elif event.is_action_pressed("ui_cancel"):
+ # 这里只留「跨组件的全局开关」,具体操作归各组件自己处理。
+ if event.is_action_pressed("ui_cancel"):
set_mouse_captured(Input.mouse_mode != Input.MOUSE_MODE_CAPTURED)
- elif event is InputEventMouseButton and event.pressed and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
+ elif event is InputEventMouseButton and event.pressed \
+ and Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
set_mouse_captured(true)
-
- if event is InputEventMouseButton and event.is_action_pressed("carry"):
- if _pickObj == null:
- _pickObject()
- else:
- _removePickObject()
+ # 吃掉这一下点击:它的意义只是「把鼠标锁回游戏」,
+ # 不该顺带触发拾取 —— 否则重新聚焦时会凭空抓起一个物体。
+ get_viewport().set_input_as_handled()
+
func set_mouse_captured(captured: bool) -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if captured else Input.MOUSE_MODE_VISIBLE
-## 把 _yaw / _pitch 落到节点上。鼠标一动就调一次(渲染帧),所以视角跟手、不等物理帧。
-## 身体跟着一起转 —— 第一人称里「你看哪,身体就朝哪」,别人看你也是这个朝向。
-func _apply_look() -> void:
- camera_rig.rotation.y = _yaw
- camera.rotation.x = _pitch
- body.rotation.y = _yaw
+## 把拾取组件的两个布尔量收敛成准心的三态。优先级:抓着 > 指着 > 没有。
+## 系统鼠标指针由准心自己同步(Crosshair.sync_os_cursor),这里不用管。
+func _on_pickup_interact_state_changed(hovering: bool, holding: bool) -> void:
+ var state := Crosshair.GrabState.NONE
+ if holding:
+ state = Crosshair.GrabState.HOLDING
+ elif hovering:
+ state = Crosshair.GrabState.HOVER
+ crosshair.set_grab_state(state)
func _physics_process(delta: float) -> void:
- # --- 重力 ---
- if is_on_floor():
- if velocity.y < 0.0:
- velocity.y = 0.0
- else:
- velocity.y -= _gravity * delta
-
- # --- 跳跃(读取输入缓冲)---
- if _jump_buffer > 0.0:
- _jump_buffer -= delta
- if is_on_floor():
- velocity.y = jump_velocity
- _jump_buffer = 0.0
-
- # --- 相对视线方向的水平输入 ---
- # 只用 rig 的 basis(只有 yaw),低头看地时前进方向不会跟着往下扎。
- var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
- var look_basis := camera_rig.global_transform.basis
- var wish := look_basis.x * input_dir.x - look_basis.z * input_dir.y
- wish.y = 0.0
- if wish.length_squared() > 0.000001:
- wish = wish.normalized()
-
- # 只有真正按了方向键才允许疾跑(避免原地"疾跑"触发跑动动画)
- var sprinting := Input.is_action_pressed("sprint") and input_dir.length_squared() > 0.000001
- var target_speed := sprint_speed if sprinting else walk_speed
- var accel := acceleration if is_on_floor() else air_acceleration
-
- var horizontal := Vector2(velocity.x, velocity.z)
- horizontal = horizontal.move_toward(Vector2(wish.x, wish.z) * target_speed, accel * delta)
- velocity.x = horizontal.x
- velocity.z = horizontal.y
-
+ # 顺序固定:算速度 → 同步蹲伏高度 → 推进 → 播动画 → 搬物体。
+ # 由根节点统一编排,各组件自己不许写 _physics_process ——
+ # 否则顺序取决于节点树顺序,改个节点位置就会出现「先滑再算速度」这类隐形 bug。
+ motor.step(delta)
+ # 蹲伏状态由 Motor 单点判定,再转交给 Look 去升降摄像机 ——
+ # 不让 Look 自己去读蹲键,免得两个组件各判一次、将来不同步。
+ look.set_crouching(motor.crouching)
move_and_slide()
-
- # 动画只读运动结果。控制器不认识任何片段名,动画器也不认识输入。
- animator.set_motion(
- velocity,
- is_on_floor(),
- sprinting,
- Input.is_action_pressed("crouch"),
- )
-
- # 物体拖动
- if _pickObj != null:
- var pickObjPos = _pickObj.global_position
- var pickPlacePos = interaction_pos.global_position
- _pickObj.set_linear_velocity((pickPlacePos - pickObjPos) * _pickPower)
-
-
-func _pickObject() -> void:
- var pickCollider = interaction_ray.get_collider()
- print(pickCollider)
- if pickCollider != null and pickCollider is RigidBody3D:
- print(pickCollider)
- _pickObj = pickCollider
-
-func _removePickObject() -> void:
- _pickObj = null
+ animation_player.set_motion(velocity, is_on_floor(), motor.sprinting, motor.crouching)
+ pickup.physics_step()
diff --git a/character/player/player.tscn b/character/player/player.tscn
index b8a7da4..2bb0035 100644
--- a/character/player/player.tscn
+++ b/character/player/player.tscn
@@ -1,24 +1,30 @@
[gd_scene format=3 uid="uid://bekuuw153mv03"]
[ext_resource type="Script" uid="uid://f5ptp3is4agf" path="res://character/player/player.gd" id="1_gtsfv"]
+[ext_resource type="Script" uid="uid://baid7qspg2mla" path="res://ui/crosshair.gd" id="2_vuhga"]
[ext_resource type="PackedScene" uid="uid://css1vk3u8gt7o" path="res://character/player/assets/mouse.glb" id="2_wu8e1"]
[ext_resource type="Script" uid="uid://roaqmma0vjon" path="res://character/player/player_animator.gd" id="3_g20gq"]
+[ext_resource type="Texture2D" uid="uid://bkq6ya350cae8" path="res://character/assets/grab_hover.png" id="3_i6sc0"]
[ext_resource type="SceneReplicationConfig" uid="uid://c4ubbxjfrrs47" path="res://character/player/player_sync.tres" id="3_wu8e1"]
[ext_resource type="AnimationLibrary" uid="uid://stsads1ivf5g" path="res://character/player/assets/animations/crouch.glb" id="4_g20gq"]
[ext_resource type="AnimationLibrary" uid="uid://ddnmtmswkjx6g" path="res://character/player/assets/animations/fall.glb" id="4_hr31g"]
+[ext_resource type="Texture2D" uid="uid://c67qe66m2aft4" path="res://character/assets/grab.png" id="4_ywhvj"]
[ext_resource type="AnimationLibrary" uid="uid://ddkl5cqdd5usx" path="res://character/player/assets/animations/idle.glb" id="5_lqp71"]
[ext_resource type="AnimationLibrary" uid="uid://esw60hwb2cty" path="res://character/player/assets/animations/jump.glb" id="6_vuhga"]
[ext_resource type="AnimationLibrary" uid="uid://rqhclfyiingo" path="res://character/player/assets/animations/run.glb" id="7_muf2e"]
[ext_resource type="AnimationLibrary" uid="uid://dy8a178vlbsof" path="res://character/player/assets/animations/sneak.glb" id="8_jo6ux"]
[ext_resource type="AnimationLibrary" uid="uid://db83440o2cyr7" path="res://character/player/assets/animations/walk.glb" id="9_fkbv7"]
[ext_resource type="Script" uid="uid://c6l2fl5jgvr1h" path="res://character/player/face/face_rig.gd" id="12_lqp71"]
+[ext_resource type="Script" uid="uid://bo1hob2whrlu0" path="res://character/player/player_look.gd" id="13_look"]
+[ext_resource type="Script" uid="uid://xta8h2snalsd" path="res://character/player/player_motor.gd" id="14_motor"]
+[ext_resource type="Script" uid="uid://b3ko4cfjf0vgu" path="res://character/player/player_pickup.gd" id="15_pickup"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_rkbax"]
radius = 0.54541016
height = 1.4063476
[node name="Player" type="CharacterBody3D" unique_id=869687860]
-transform = Transform3D(0.2, 0, 0, 0, 0.2, 0, 0, 0, 0.2, 0, 0, 0)
+transform = Transform3D(0.096096, 0, 0, 0, 0.096096, 0, 0, 0, 0.096096, 0, 0, 0)
script = ExtResource("1_gtsfv")
[node name="Collision" type="CollisionShape3D" parent="." unique_id=215132582]
@@ -29,22 +35,42 @@ shape = SubResource("CapsuleShape3D_rkbax")
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(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.6066488, -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]
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0)
-target_position = Vector3(0, 0.45, 0)
+target_position = Vector3(0, 0.25, 0)
[node name="InteractionPos" type="Marker3D" parent="CameraRig/Camera" unique_id=816323614]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.44393378)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.2425185)
+
+[node name="HUD" type="CanvasLayer" parent="CameraRig" unique_id=2008353247]
+
+[node name="Crosshair" type="Control" parent="CameraRig/HUD" unique_id=1606201608]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+mouse_filter = 2
+script = ExtResource("2_vuhga")
+hover_texture = ExtResource("3_i6sc0")
+hold_texture = ExtResource("4_ywhvj")
[node name="Body" type="Node3D" parent="." unique_id=394283421]
[node name="mouse" parent="Body" unique_id=1995806863 instance=ExtResource("2_wu8e1")]
transform = Transform3D(-0.18240134, 0, -1.5946032e-08, 0, 0.18240134, 0, 1.5946032e-08, 0, -0.18240134, 0, 0.8705118, 0)
-[node name="AnimationPlayer" type="AnimationPlayer" parent="Body" unique_id=1656879850]
-root_node = NodePath("../mouse")
+[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=1979575210]
+replication_config = ExtResource("3_wu8e1")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1656879850]
+root_node = NodePath("../Body/mouse")
libraries/crouch = ExtResource("4_g20gq")
libraries/fall = ExtResource("4_hr31g")
libraries/idle = ExtResource("5_lqp71")
@@ -54,8 +80,14 @@ libraries/sneak = ExtResource("8_jo6ux")
libraries/walk = ExtResource("9_fkbv7")
script = ExtResource("3_g20gq")
-[node name="Sync" type="MultiplayerSynchronizer" parent="." unique_id=1979575210]
-replication_config = ExtResource("3_wu8e1")
-
[node name="FaceRig" type="Node" parent="." unique_id=792293233]
script = ExtResource("12_lqp71")
+
+[node name="Look" type="Node" parent="." unique_id=1738452096]
+script = ExtResource("13_look")
+
+[node name="Motor" type="Node" parent="." unique_id=1043927815]
+script = ExtResource("14_motor")
+
+[node name="Pickup" type="Node" parent="." unique_id=1587230140]
+script = ExtResource("15_pickup")
diff --git a/character/player/player_animator.gd b/character/player/player_animator.gd
index f62b197..ae137bf 100644
--- a/character/player/player_animator.gd
+++ b/character/player/player_animator.gd
@@ -46,10 +46,17 @@ const ANIM_LOOPED: Array[int] = [
@export_group("片段基准速度")
## 各片段在多少米/秒下按 1.0 倍播放。这是片段自身的属性,不是控制器的手感参数:
## 控制器把跑速调到 6 m/s 时,这段动画自然播成 1.2 倍,脚步自动跟上,两边不用对齐。
-@export var walk_ref_speed: float = 5.0
-@export var run_ref_speed: float = 8.5
-@export var crouch_ref_speed: float = 2.5
-@export var sneak_ref_speed: float = 1.6
+##
+## 注意:这几个数和「模型缩放」严格绑定 —— 角色尺寸一变,同一个片段一个循环跨出的
+## 世界距离也按同比例变,所以基准速度必须跟着乘同一系数,否则脚步会明显打滑。
+## 当前值对应 0.15 m 身高(基准系数 k ≈ 0.0857,推导见 player_motor.gd 顶部的说明)。
+##
+## sneak_ref_speed 要和 player_motor.gd 的 crouch_speed 保持一致 ——
+## 蹲着走时播的就是 SNEAK 片段,两边不等就会打滑。(当前两者都是 0.2143)
+@export var walk_ref_speed: float = 0.4286
+@export var run_ref_speed: float = 0.7286
+@export var crouch_ref_speed: float = 0.2143
+@export var sneak_ref_speed: float = 0.2143
## 状态 → AnimationPlayer 里真正的片段键(形如 "walk/walk")。_ready() 里解析一次。
## 不直接拿 ANIM_CLIPS 的值去播,是因为多库时短名查不到 —— 见 _ready() 里的说明。
@@ -138,8 +145,9 @@ func _resolve_state(
velocity: Vector3, on_floor: bool, sprinting: bool, crouching: bool
) -> int:
if not on_floor:
- # 上升用 jump、越过顶点用 fall。jump 片段长 0.5s,
- # 恰好约等于 jump_velocity 5 / 重力 9.8 的上升时间。
+ # 上升用 jump、越过顶点用 fall。jump 片段长 0.5s,而上升时间是
+ # jump_velocity / 有效重力(1.5 / 4.9 ≈ 0.31s),片段只播得完约六成 ——
+ # 越过顶点立刻切 fall,观感上是连贯的。
return AnimState.JUMP if velocity.y > 0.0 else AnimState.FALL
var moving := Vector2(velocity.x, velocity.z).length() >= idle_threshold
if crouching:
diff --git a/character/player/player_look.gd b/character/player/player_look.gd
new file mode 100644
index 0000000..657fa2c
--- /dev/null
+++ b/character/player/player_look.gd
@@ -0,0 +1,93 @@
+class_name PlayerLook
+extends Node
+## 视角组件:把鼠标位移转成 CameraRig 的 yaw 与 Camera 的 pitch,
+## 并按蹲伏状态平滑升降摄像机。只管摄像机 —— 不碰速度、不碰动画。
+##
+## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 调 setup() 注入依赖。
+
+@export_group("视角")
+## 鼠标灵敏度(弧度/像素)
+@export var mouse_sensitivity: float = 0.0025
+## 最低俯角(向下看)
+@export var pitch_min_deg: float = -89.0
+## 最高仰角(向上看)
+@export var pitch_max_deg: float = 89.0
+
+@export_group("蹲伏")
+## 蹲下时摄像机高度 = 站立高度 × 这个比例
+@export_range(0.3, 1.0, 0.05) var crouch_height_ratio: float = 0.6
+## 蹲起过渡速度。这是「每秒收敛掉的比例」,不是米/秒 —— 越大越干脆。
+@export var crouch_transition_speed: float = 10.0
+
+## 眼睛分两层转:CameraRig 绕竖直轴转(yaw),Camera 原地抬头低头(pitch)。
+## 分开是为了让 pitch 不掺进移动方向 —— 移动只用 rig 的 basis,低头看地也不会走歪。
+var _yaw: float = 0.0
+var _pitch: float = 0.0
+
+var _rig: Node3D
+var _camera: Camera3D
+var _body: Node3D
+
+## 站立时 CameraRig 的本地高度(从场景读,不写死),以及当前要收敛到的高度。
+var _stand_height: float = 0.0
+var _target_height: float = 0.0
+
+
+func _ready() -> void:
+ # 依赖是 setup() 注入的 —— 没接线(远端玩家不会调 setup)就不该跑 _process。
+ set_process(false)
+
+
+## 由 Player 在 _ready() 注入依赖。组件不自己 get_node,才能脱离具体场景复用。
+func setup(rig: Node3D, camera: Camera3D, body: Node3D) -> void:
+ _rig = rig
+ _camera = camera
+ _body = body
+ _yaw = rig.rotation.y
+ _pitch = clampf(camera.rotation.x, deg_to_rad(pitch_min_deg), deg_to_rad(pitch_max_deg))
+ # 站立高度直接读场景里的 CameraRig —— 以后在编辑器里把它挪高挪低,这里自动跟上。
+ _stand_height = rig.position.y
+ _target_height = _stand_height
+ _apply()
+ set_process(true)
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
+ _yaw -= event.relative.x * mouse_sensitivity
+ _pitch = clampf(
+ _pitch - event.relative.y * mouse_sensitivity,
+ deg_to_rad(pitch_min_deg),
+ deg_to_rad(pitch_max_deg)
+ )
+ _apply()
+
+
+## 由 Player 每物理帧调用,把「是否蹲着」这个事实转成目标高度。
+## 只在目标真的变了才写入,避免每帧无谓赋值。
+func set_crouching(crouching: bool) -> void:
+ if _rig == null:
+ return
+ var target: float = _stand_height * crouch_height_ratio if crouching else _stand_height
+ if is_equal_approx(target, _target_height):
+ return
+ _target_height = target
+
+
+## 蹲起过渡放在渲染帧而不是物理帧 —— 物理帧只有 60Hz,看起来会一顿一顿。
+## 用指数收敛(1 - e^(-k·delta))而不是定量 lerp:过渡时间不受帧率影响。
+func _process(delta: float) -> void:
+ if _rig == null:
+ return
+ var t := 1.0 - exp(-crouch_transition_speed * delta)
+ _rig.position.y = lerpf(_rig.position.y, _target_height, t)
+
+
+## 把 _yaw / _pitch 落到节点上。鼠标一动就调一次(渲染帧),所以视角跟手、不等物理帧。
+## 身体跟着一起转 —— 第一人称里「你看哪,身体就朝哪」,别人看你也是这个朝向。
+func _apply() -> void:
+ if _rig == null:
+ return
+ _rig.rotation.y = _yaw
+ _camera.rotation.x = _pitch
+ _body.rotation.y = _yaw
diff --git a/character/player/player_look.gd.uid b/character/player/player_look.gd.uid
new file mode 100644
index 0000000..df661bd
--- /dev/null
+++ b/character/player/player_look.gd.uid
@@ -0,0 +1 @@
+uid://bo1hob2whrlu0
diff --git a/character/player/player_motor.gd b/character/player/player_motor.gd
new file mode 100644
index 0000000..d3f15e1
--- /dev/null
+++ b/character/player/player_motor.gd
@@ -0,0 +1,121 @@
+class_name PlayerMotor
+extends Node
+## 移动组件:重力、跳跃缓冲、水平加减速。
+##
+## 只算速度写回 body.velocity,**不调 move_and_slide()** —— 推进由 Player 统一做。
+## 这样「算速度 → 推进 → 播动画」的顺序永远固定,不受节点树顺序影响。
+
+## 跳跃输入缓冲(秒):按下跳跃后这段时间内若落地,自动补上一次起跳
+const JUMP_BUFFER_TIME: float = 0.1
+
+@export_group("移动")
+## 【设计基准】以人类为参照做等比缩放:人 1.75 m,游戏惯例 walk 5.0 / sprint 8.5 m/s。
+## 本角色身高 0.15 m,系数 k = 0.15 / 1.75 ≈ 0.0857,下面所有线性量都是原值 × k。
+## 这样「每秒走过几个自身体长」和人类一致,动作比例看起来才对。
+##
+## 换尺寸时的公式:线性量(速度/加速度)× k';跳跃那两个数(gravity_scale 与 jump_velocity)用下面的反解公式重算。
+##
+## 常规移动速度(米/秒)—— 约 2.9 身长/秒,和人类走路同一比例
+@export var walk_speed: float = 0.4286
+## 疾跑速度(米/秒)—— 约 4.9 身长/秒
+@export var sprint_speed: float = 0.7286
+## 蹲行速度(米/秒)—— 默认是走速的一半。蹲下时按住 Shift 也不会疾跑。
+## 注意:改这个值要同步改 player_animator.gd 的 sneak_ref_speed,否则蹲走时脚步会打滑。
+@export var crouch_speed: float = 0.2143
+## 地面加速度(米/秒²)—— 约 0.11 秒加到全速,起步干脆
+@export var acceleration: float = 3.8571
+## 空中加速度(米/秒²)
+@export var air_acceleration: float = 1.0286
+## 起跳初速度(米/秒)—— 配下面的 gravity_scale 0.5(g = 4.9)时,
+## 跳跃高度 ≈ 0.230 m(约 1.53 身高)、滞空 ≈ 0.61 s。
+## 这两个数是**配对**的:改重力必须按「反解公式」重算它,否则跳跃高度按 1/g 暴涨。
+@export var jump_velocity: float = 1.5
+
+@export_group("重力")
+## 重力缩放系数。0.5 = 重力减半(有效 g = 4.9 m/s²)。
+##
+## 为什么不用 1.0(物理真实的 9.8):重力加速度本身和物体大小无关,但我们的速度是按身材
+## 等比缩过的,于是落体相对过快。更关键的是跳跃 —— 由 h = v²/2g 和 T = 2v/g 可得
+## h = g·T²/8:**固定重力下滞空翻倍、高度要涨 4 倍**。所以「想跳得久又不想跳太高」
+## 只能降重力。取 0.5 时:跳高 0.230 m(1.53 身高)、滞空 0.61 s;
+## 若维持 1.0,同样 0.23 m 的高度只有 0.43 s 滞空,落地明显仓促。
+##
+## ⚠ 改这个值必须同步改 jump_velocity(跳跃高度 = v²/2g)。
+## 反解公式 —— 定目标高度 h(米)与滞空 T(秒):
+## g = 8h / T² → gravity_scale = g / 9.8
+## jump_velocity = 4h / T
+##
+## 只影响本角色。**不要**去改 ProjectSettings 的 physics/3d/default_gravity ——
+## 那是全局的,会连带把拖拽/投掷的刚体一起变飘。
+@export_range(0.0, 2.0, 0.01) var gravity_scale: float = 0.5
+
+## 本帧是否在疾跑 / 是否按住蹲键。
+## Player 读这两个值转交给 PlayerAnimator 切片段,crouching 还用来压低摄像机。
+var sprinting: bool = false
+var crouching: bool = false
+
+var _body: CharacterBody3D
+var _look_rig: Node3D
+## 项目设置里的默认重力(9.8)。实际用的是它 × gravity_scale。
+var _gravity: float = 9.8
+var _jump_buffer: float = 0.0
+
+
+## 由 Player 在 _ready() 注入依赖。look_rig 用来把输入转成「相对视线」的方向。
+func setup(body: CharacterBody3D, look_rig: Node3D) -> void:
+ _body = body
+ _look_rig = look_rig
+ _gravity = float(ProjectSettings.get_setting("physics/3d/default_gravity", 9.8))
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event.is_action_pressed("jump"):
+ _jump_buffer = JUMP_BUFFER_TIME
+
+
+## 每物理帧由 Player 调用一次,必须排在 move_and_slide() 之前。
+## 只写 _body.velocity,不做位移推进。
+func step(delta: float) -> void:
+ if _body == null or _look_rig == null:
+ return
+
+ # --- 重力 ---
+ if _body.is_on_floor():
+ if _body.velocity.y < 0.0:
+ _body.velocity.y = 0.0
+ else:
+ # gravity_scale 每帧现读,方便运行中拖着试手感(1.0 = 物理真实的 9.8)
+ _body.velocity.y -= _gravity * gravity_scale * delta
+
+ # --- 跳跃(读取输入缓冲)---
+ if _jump_buffer > 0.0:
+ _jump_buffer -= delta
+ if _body.is_on_floor():
+ _body.velocity.y = jump_velocity
+ _jump_buffer = 0.0
+
+ # --- 相对视线方向的水平输入 ---
+ # 只用 rig 的 basis(只有 yaw),低头看地时前进方向不会跟着往下扎。
+ var input_dir := Input.get_vector("move_left", "move_right", "move_back", "move_forward")
+ var look_basis := _look_rig.global_transform.basis
+ var wish := look_basis.x * input_dir.x - look_basis.z * input_dir.y
+ wish.y = 0.0
+ if wish.length_squared() > 0.000001:
+ wish = wish.normalized()
+
+ # 只有真正按了方向键才允许疾跑(避免原地"疾跑"触发跑动动画)
+ var wants_sprint := Input.is_action_pressed("sprint") and input_dir.length_squared() > 0.000001
+ crouching = Input.is_action_pressed("crouch")
+ # 蹲伏优先于疾跑:蹲着时按住 Shift 不算疾跑,速度取蹲行速度。
+ sprinting = wants_sprint and not crouching
+ var target_speed := walk_speed
+ if crouching:
+ target_speed = crouch_speed
+ elif sprinting:
+ target_speed = sprint_speed
+ var accel := acceleration if _body.is_on_floor() else air_acceleration
+
+ var horizontal := Vector2(_body.velocity.x, _body.velocity.z)
+ horizontal = horizontal.move_toward(Vector2(wish.x, wish.z) * target_speed, accel * delta)
+ _body.velocity.x = horizontal.x
+ _body.velocity.z = horizontal.y
diff --git a/character/player/player_motor.gd.uid b/character/player/player_motor.gd.uid
new file mode 100644
index 0000000..b8d7da5
--- /dev/null
+++ b/character/player/player_motor.gd.uid
@@ -0,0 +1 @@
+uid://xta8h2snalsd
diff --git a/character/player/player_pickup.gd b/character/player/player_pickup.gd
new file mode 100644
index 0000000..2c03c64
--- /dev/null
+++ b/character/player/player_pickup.gd
@@ -0,0 +1,142 @@
+class_name PlayerPickup
+extends Node
+## 拾取组件:**按住左键拖拽,松手放下,按 Q 朝准心方向甩出去**。
+## 按下左键时用射线抓起正前方的 RigidBody3D,按住期间持续吸在 InteractionPos 前;
+## 松开左键即原地放开,按 Q 则沿视线方向把手上的物体甩出去。
+## 只管「抓 / 拖 / 放 / 甩」,不碰移动与视角。
+##
+## 接法:Player 下挂一个普通 Node,脚本挂上去;由 Player._ready() 注入射线、持握点与玩家本体。
+
+## 拖拽力度:物体被拉向持握点的速度系数。越大跟手越紧,但过大容易来回抖。
+## 注意:它同时决定了拖拽时的稳态滞后 —— 滞后距离 = 移动速度 / pick_power。
+@export var pick_power: float = 10.0
+## 投掷初速度(米/秒):按 Q 甩出去时给物体的速度大小。
+@export var throw_speed: float = 12.0
+## 拖拽速度上限(米/秒)。防止物体离得远时被一股脑拽飞。
+@export var max_drag_speed: float = 30.0
+
+## 交互状态变化时发出。
+## hovering = 准心正指着可拖拽的刚体(还没抓);holding = 正抓着东西。
+## Player 接住这个信号去切准心外观。只在两者之一变化时发,避免每帧刷 UI。
+signal interact_state_changed(hovering: bool, holding: bool)
+
+var _current: RigidBody3D = null
+var _ray: RayCast3D
+var _hold_pos: Marker3D
+## 玩家本体。用来给被拖物体加碰撞例外,以及做速度前馈。
+var _body: CharacterBody3D
+## 上一次发出的状态,用来只在「变化那一刻」发信号。
+var _hovering: bool = false
+var _holding: bool = false
+
+
+## 由 Player 在 _ready() 注入依赖。body 是玩家本体(CharacterBody3D)。
+func setup(ray: RayCast3D, hold_pos: Marker3D, body: CharacterBody3D) -> void:
+ _ray = ray
+ _hold_pos = hold_pos
+ _body = body
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ # 只有鼠标锁进游戏(真正在玩)时才响应抓 / 拖 / 放 / 甩。
+ # 否则「点一下把鼠标锁回来」的那次点击会顺带抓起一个物体。
+ if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
+ return
+ # 长按拖拽:按下抓起,松开放下;拖拽途中按 Q 可以把手上的东西甩出去。
+ # 按住期间不需要任何输入处理 —— 物体靠 physics_step() 每物理帧持续吸住。
+ if event.is_action_pressed("carry"):
+ _grab()
+ elif event.is_action_released("carry"):
+ _drop()
+ elif event.is_action_pressed("throw_carry"):
+ _throw()
+
+
+## 每物理帧由 Player 调用一次,排在 move_and_slide() 之后。
+## 用速度把物体拽向持握点,而不是直接改 global_position ——
+## 这样它仍会和场景里其他刚体正常碰撞,不会被硬塞进墙里。
+func physics_step() -> void:
+ _update_interact_state()
+ # 用 is_instance_valid 而不是 `== null`:物体可能在拖拽途中被 destroy,
+ # 那种情况下引用不是 null 而是「已被释放」,直接取 global_position 会报错。
+ if not is_instance_valid(_current) or not is_instance_valid(_hold_pos):
+ return
+
+ var to_hold := _hold_pos.global_position - _current.global_position
+ var desired := to_hold * pick_power
+ # 速度前馈:把玩家自身的速度加进去。
+ #
+ # 只靠 to_hold * pick_power 的话,它是一个一阶跟随 —— 人匀速前进时物体必然
+ # 落后一个稳态距离,大小 = 移动速度 / pick_power。按 5 / 10 算就是 0.5 米,
+ # 比持握点离身体的 0.44 米还远,于是走着走着物体就退进了玩家碰撞体里,
+ # 被物理推出来、又被拽回去,两边打架 —— 看起来就是「一卡一卡」。
+ # 加上玩家速度后,匀速行走时滞后归零,物体稳稳待在身前。
+ if _body != null:
+ desired += _body.velocity
+ if desired.length() > max_drag_speed:
+ desired = desired.normalized() * max_drag_speed
+ _current.set_linear_velocity(desired)
+
+
+## 检测「指着可拖拽物 / 正抓着东西」这两个状态,**只在变化时**发信号。
+## 每帧都发会让 UI 无谓重绘,所以这里自己记着上一次的结果。
+func _update_interact_state() -> void:
+ if _ray == null:
+ return
+ # 主动刷新一次:RayCast3D 的结果通常滞后于本帧的 move_and_slide,
+ # 不强制更新的话,跑动中瞄到物体要慢一帧才亮。
+ _ray.force_raycast_update()
+ var hovering := _ray.get_collider() is RigidBody3D
+ var holding := is_instance_valid(_current)
+ if hovering == _hovering and holding == _holding:
+ return
+ _hovering = hovering
+ _holding = holding
+ interact_state_changed.emit(_hovering, _holding)
+
+
+func _grab() -> void:
+ if _ray == null or is_instance_valid(_current):
+ return
+ var collider := _ray.get_collider()
+ if collider is RigidBody3D:
+ _current = collider as RigidBody3D
+ # 让被拖的物体和玩家本体**互不碰撞**。
+ # 否则物体一旦挨到身体就会被物理推出,而拖拽又立刻把它拉回来 ——
+ # 两个解算器互相打架,表现就是持续的高频抖动。
+ # 只解除「物体 ↔ 玩家」这一对,物体和场景里其他东西照样正常碰撞。
+ if _body != null:
+ _current.add_collision_exception_with(_body)
+
+
+func _drop() -> void:
+ # 故意不清零线速度:松手时保留拖动惯性,甩出去的手感更自然。
+ # 想要「就地放下、不再滑出去」的话,这里补一句:
+ # if is_instance_valid(_current): _current.set_linear_velocity(Vector3.ZERO)
+ _forget(_current)
+ _current = null
+
+
+## 清掉抓取时留下的碰撞例外。物体可能已被销毁,所以先判有效性。
+func _forget(body: RigidBody3D) -> void:
+ if is_instance_valid(body) and _body != null:
+ body.remove_collision_exception_with(_body)
+
+
+## 按 Q 投掷:沿视线方向把物体甩出去,随后立刻松手。
+func _throw() -> void:
+ if not is_instance_valid(_current) or not is_instance_valid(_hold_pos):
+ return
+ # 持握点挂在摄像机下、且只有平移没有旋转,所以它的 basis 就是视线朝向,
+ # 其 -Z 即「准心正前方」。这样不必再给组件注入一台摄像机。
+ var dir := -_hold_pos.global_transform.basis.z
+ _current.set_linear_velocity(dir * throw_speed)
+ # 想让手感更「物理」(重物飞得慢、轻物飞得远)可以换成给冲量:
+ # _current.apply_central_impulse(dir * throw_speed)
+ _forget(_current)
+ _current = null
+
+
+## 当前是否抓着东西。调试用,也方便以后加「搬运时不能开枪」这类规则。
+func has_object() -> bool:
+ return is_instance_valid(_current)
diff --git a/character/player/player_pickup.gd.uid b/character/player/player_pickup.gd.uid
new file mode 100644
index 0000000..e277be8
--- /dev/null
+++ b/character/player/player_pickup.gd.uid
@@ -0,0 +1 @@
+uid://b3ko4cfjf0vgu
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/crosshair.gd b/ui/crosshair.gd
new file mode 100644
index 0000000..7bbd4aa
--- /dev/null
+++ b/ui/crosshair.gd
@@ -0,0 +1,163 @@
+@tool
+class_name Crosshair
+extends Control
+## 准心 / 指针:屏幕正中央。
+## 三种状态:
+## NONE 没指着可拖拽物 → 画一个点
+## HOVER 指着可拖拽物 → 显示 grab.png(张开的手,提示「可以抓」)
+## HOLDING 正抓着东西 → 显示 grab_hover.png(抓取态)
+##
+## 挂法:CanvasLayer > Crosshair(Control),Control 的锚点设为「Full Rect」
+## (anchor 全 0/1),这样 size 就等于视口尺寸,中心即屏幕中心。
+## 因为是 CanvasLayer 子节点,在 window/stretch/mode=canvas_items 下会自动跟随缩放。
+##
+## 状态由 Player 驱动:拾取组件的 interact_state_changed 信号 → set_grab_state()。
+
+## 指着可拖拽物体时的图标。没在 Inspector 里指定时自动加载。
+const HOVER_TEXTURE_PATH := "res://character/assets/grab_hover.png"
+## 正抓着东西时的图标。没在 Inspector 里指定时自动加载。
+const HOLD_TEXTURE_PATH := "res://character/assets/grab.png"
+
+## 准心的三种状态。
+enum GrabState {
+ NONE, ## 没指着可拖拽物体
+ HOVER, ## 指着可拖拽物体(还没抓)
+ HOLDING, ## 正抓着东西
+}
+
+@export_group("点")
+## 点的颜色
+@export var dot_color: Color = Color(1, 1, 1, 0.9):
+ set(v):
+ dot_color = v
+ queue_redraw()
+
+## 点的半径(像素)
+@export_range(0.5, 12.0, 0.5) var dot_radius: float = 2.5:
+ set(v):
+ dot_radius = v
+ queue_redraw()
+
+@export_group("手型")
+## 指着可拖拽物体时的图标。留空则自动加载 grab.png
+@export var hover_texture: Texture2D:
+ set(v):
+ hover_texture = v
+ queue_redraw()
+
+## 正抓着东西时的图标。留空则自动加载 grab_hover.png
+@export var hold_texture: Texture2D:
+ set(v):
+ hold_texture = v
+ queue_redraw()
+
+## 图标显示的边长(像素)。素材是 32×32,默认 32 就是 1:1 不缩放,像素最清晰。
+@export_range(8.0, 96.0, 1.0) var hand_size: float = 32.0:
+ set(v):
+ hand_size = v
+ queue_redraw()
+
+## 图标着色。素材自带颜色,默认 Color.WHITE 表示按原样显示(乘法调色)。
+@export var icon_modulate: Color = Color(1, 1, 1, 1):
+ set(v):
+ icon_modulate = v
+ queue_redraw()
+
+## 是否把同一张图同步给系统鼠标指针。
+## 游戏内鼠标是 MOUSE_MODE_CAPTURED 锁定隐藏的,这行只在鼠标可见时(Esc / 菜单)才有可见效果。
+@export var sync_os_cursor: bool = true
+
+@export_group("描边")
+## 是否给点画描边(浅色背景上也看得清)
+@export var outline: bool = true:
+ set(v):
+ outline = v
+ queue_redraw()
+
+## 描边颜色
+@export var outline_color: Color = Color(0, 0, 0, 0.6):
+ set(v):
+ outline_color = v
+ queue_redraw()
+
+## 描边宽度(像素)
+@export_range(0.5, 4.0, 0.5) var outline_width: float = 1.0:
+ set(v):
+ outline_width = v
+ queue_redraw()
+
+## 当前状态。
+var _state: int = GrabState.NONE
+
+
+func _ready() -> void:
+ # 准心不吃鼠标事件,否则会挡住后面的 UI / 拖拽
+ mouse_filter = Control.MOUSE_FILTER_IGNORE
+ if hover_texture == null and ResourceLoader.exists(HOVER_TEXTURE_PATH):
+ hover_texture = load(HOVER_TEXTURE_PATH)
+ if hold_texture == null and ResourceLoader.exists(HOLD_TEXTURE_PATH):
+ hold_texture = load(HOLD_TEXTURE_PATH)
+ queue_redraw()
+
+
+func _notification(what: int) -> void:
+ # 窗口尺寸变化时重画(stretch 模式下实时跟着变)
+ if what == NOTIFICATION_RESIZED:
+ queue_redraw()
+
+
+## 由 Player 调用,切换准心状态(GrabState)。
+func set_grab_state(state: int) -> void:
+ if state == _state:
+ return
+ _state = state
+ if sync_os_cursor and not Engine.is_editor_hint():
+ _sync_os_cursor()
+ queue_redraw()
+
+
+## 某个状态该用哪张图。公开出来是为了让系统指针复用同一份素材,不必两处各加载一遍。
+func texture_for_state(state: int) -> Texture2D:
+ match state:
+ GrabState.HOVER:
+ return hover_texture if hover_texture != null else hold_texture
+ GrabState.HOLDING:
+ return hold_texture if hold_texture != null else hover_texture
+ _:
+ return null
+
+
+func _draw() -> void:
+ var c := size * 0.5 # 控件中心 = 屏幕中心
+
+ var tex := texture_for_state(_state)
+ if tex != null:
+ _draw_icon(c, tex)
+ else:
+ _draw_dot(c)
+
+
+## 一个点(可选描边)
+func _draw_dot(c: Vector2) -> void:
+ if outline and outline_width > 0.0:
+ draw_circle(c, dot_radius + outline_width, outline_color)
+ draw_circle(c, dot_radius, dot_color)
+
+
+## 手型图标:居中、按原始宽高比缩放(非方形素材不会被拉变形)。
+func _draw_icon(c: Vector2, tex: Texture2D) -> void:
+ var tex_size := Vector2(tex.get_width(), tex.get_height())
+ var k := hand_size / maxf(tex_size.x, tex_size.y)
+ var draw_size := tex_size * k
+ draw_texture_rect(tex, Rect2(c - draw_size * 0.5, draw_size), false, icon_modulate)
+
+
+## 把当前状态的图标同步给系统鼠标指针。热点放在指尖附近,
+## 这样「点下去」的位置落在手指上,而不是图标左上角。
+func _sync_os_cursor() -> void:
+ var tex := texture_for_state(_state)
+ if tex == null:
+ Input.set_custom_mouse_cursor(null, Input.CURSOR_ARROW)
+ return
+ var hotspot := Vector2(tex.get_width() * 0.5, tex.get_height() * 0.12)
+ Input.set_custom_mouse_cursor(tex, Input.CURSOR_ARROW, hotspot)
diff --git a/ui/crosshair.gd.uid b/ui/crosshair.gd.uid
new file mode 100644
index 0000000..5824880
--- /dev/null
+++ b/ui/crosshair.gd.uid
@@ -0,0 +1 @@
+uid://baid7qspg2mla
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