# agame 项目长期笔记 Godot 4.7.2(Forward+,Jolt Physics,D3D12)。编辑器位于 `D:\_yvmou\Godot\Godot_v4.7.2-stable_win64.exe\Godot_v4.7.2-stable_win64.exe`(另有 mono 版)。 ## 代码约定 - **GDScript 警告被当作错误**(至少 `inference_on_variant`)。不要写成 `var x := some_variant_returning_call()`;要显式写类型,例如 `var s: Script = node.get_script() as Script`。改完脚本务必用 `--check-only --script res://xxx.gd` 自查。 - 缩进用 tab,注释用中文(项目既有风格)。 ## 无头验证的正确姿势(踩过坑) - `--check-only --script res://x.gd`:GDScript 语法校验,有效。 - `godot --headless --editor --path --quit`:验证编辑器插件能否加载,有效。 - **不要**用 `extends SceneTree` 的 `--script` 脚本去测 `_ready()` 相关行为: 这种模式下节点不会真正进入场景树(`is_inside_tree()` 为 false),`_ready` 不执行, 会得出完全错误的结论。正确做法是造一个临时场景,用位置参数当主场景跑: `godot --headless --path res://tools/_tmp_probe.tscn`。 - 即使是 `--headless`(dummy 渲染后端),**shader 编译错误仍会被报告** (`SHADER ERROR: ...`),所以可以用无头运行来校验 shader 语法。 ## 玩家 = LimboAI LimboHSM(2026-10-15 起) - LimboAI v1.8.1 是 **GDExtension 版**(addons/limboai 只有 bin/,无 plugin.cfg), **不需要启用 editor plugin**;ClassDB 里没有 HSMPlayer —— `LimboHSM`/`LimboState` 本身就是 Node。 - 玩家状态机在 `character/player/hsm/`:根 `player_hsm.gd`(Grounded⇄Airborne)+ Grounded{Idle,Walk,Sprint,Crouch,Sneak} + Airborne{Jump,Fall},全是场景节点、脚本挂状态上。 - 分工:**HSM 管「是什么状态」**(写 motor.target_speed/crouching/sprinting + 调 animator.play_state),**motor 只管物理**(重力/加速度/跳跃缓冲 + try_jump), animator 只管「怎么播」(play_state + set_gait 刷倍率)。player.gd 管线第一位 `hsm.update(delta)`(MANUAL 模式,仅权威端 initialize+set_active;远端靠 Sync 复制)。 - 走/站立转换按**输入**判定(非旧版的速度阈值)——顶墙时播 walk 不播 idle,是预期行为。 - 用法要点:子机 `_enter` 里可 `set_initial_state()` 动态挑入口子状态;环境判定 (起跳/踏空/落地)放**叶子**状态的 _update,别放父机;落地判定要带 `vy<=0` 门槛。 - 踩坑:新 class_name 后无头 `--check-only` 报「Could not find type」= 全局类缓存没刷新, 让编辑器 scan 一次即解;MCP node_set_property 写 enum int 失败 → 手改 tscn + 场景 force_reload。 ## toon 卡通渲染管线 - `shader/cel-shader-base.gdshader`(`USE_*` 宏开关各模块)+ `shader/includes/*.gdshaderinc`, 上游是 godot4-cel-shader 项目。 - `shader/toon.tres` 是材质模板;`shader/outline.gdshader` 作为 `next_pass` 描边。 - `shader/toon_apply.gd`(@tool Node3D)给每个 MeshInstance3D 复制一份独立材质, 因为**每个模型有各自的贴图**,所以必须独立(不能共享一份材质)。 - 已在项目设置里用了 **global uniform**:`diffuse_curve`、`specular_smoothness`、 `fresnel_smoothness`、`outline_width`、`outline_color` —— 这几项调参本来就是全局实时生效。 - 贴图尺寸极不统一(289 张 PNG / 24 种尺寸,1024²、512²、4096²、32² 混杂), 因此**不适合** Texture2DArray + per-instance uniform 方案(显存会爆且要额外打包流水线)。 - 编辑器插件 `addons/toon_refresh/`:改 toon.tres 时自动重刷 toon_apply 材质。 ## 坐标系陷阱 + 上游着色器的边界(重要) Godot 4 的 `fragment()` 里 `NORMAL` / `TANGENT` / `BINORMAL` 都处于**视图空间**, 而 `NORMAL_MAP` 要的是**切线空间**值、由引擎用 TANGENT/BINORMAL 还原。 把切线空间值直接赋给 `NORMAL` 会让整个场景的法线变成一个跟着相机转的常量 —— 表现为平视全黑、俯瞰才亮。 但**不要**因此去改上游文件:`cel-shader-base.gdshader` 与 `includes/normal-map.gdshaderinc` 来自 , 本项目的策略是**逐字跟随上游**。上游的法线贴图路径 `NORMAL = normal.vector;` 确实有上述坐标空间错误,但上游所有变体都是 `#define USE_NORMAL_MAP 0`, 全仓库从未启用过这条路径(README 也没提),所以是"能编译但结果错"的死代码。 本项目保持 `USE_NORMAL_MAP 0`,与上游一致;`toon.tres` 里残留的 `shader_parameter/normal_scale` 在宏关闭时会被忽略,无副作用。 **结论:要动上游 shader 之前,先用 `E:/Godot/godot4-cel-shader-main/` 里的干净副本 diff。** 正确做法(若将来真要开法线贴图):只写 `NORMAL_MAP = 原始切线空间值` + `NORMAL_MAP_DEPTH`,让引擎合成;不要自己写 `NORMAL`,也不要预先做 `*2-1` 重映射 (引擎自己做,蓝通道由引擎重建)。