Files
agame/.workbuddy-ai/memory/MEMORY.md
T
2026-10-02 10:44:58 +08:00

6.0 KiB
Raw Blame History

项目长期记忆

工程约定

  • 玩家控制器只认「运动结果」,不认识任何动画片段名;表现层脚本 player_animator.gd 直接挂在 Body/AnimationPlayer 上(animation_player 留空即自绑自身,所以两者是同一个节点)。 新增动作状态改 ANIM_CLIPS,不要往 player.gd 里塞片段名。
  • 组件的可调参数放在组件自己身上(如 Zipline 的速度 / 刹车 / 吸附半径),控制器只读不定义。
  • 头顶 UI(血条、交互提示)是 player.tscn 里的 Label3D 节点,不在代码里动态创建。
  • 测试用 McpTestSuite(addons/godot_ai/testing),脚本放 res://tests/。
  • 界面放 res://ui/。项目没有 Theme 资源,风格一律用节点上的 theme_override_* 内联写; 给容器(如 VBoxContainer)设 theme_override_styles/* 会向下继承给子按钮,不用逐个设。
  • 中文字体不用额外配:Godot 默认主题会回退到系统字体,实测中文正常渲染。
  • Net 是 autoload,它的「联机测试」面板在 _ready() 里就 visible = true(CanvasLayer layer=20), 会盖在任何场景正中间。做开始菜单时要么让菜单的「联机」按钮接管它,要么在菜单期间把它藏起来。

动画(AnimationPlayer)

  • 多 AnimationLibrary 下,片段必须用「库名/片段名」的限定键。实测(4.7.2): has_animation("idle") 为假,has_animation("idle/idle") 才为真,get_animation 同理。 本项目每个 glb 导入成一个库、库名与片段同名,所以真键形如 walk/walk。 短名播不出来且不报错,是典型的静默失效 —— PlayerAnimator._ready() 里扫库解析一次。
  • 导入的 GLB 片段 loop_mode 默认是 NONE,编辑器里改不了,只能运行时补。
  • 带 class_name 的脚本挂在 AnimationPlayer 上没问题,但不能写 self is AnimationPlayer: class_name X extends Node 之后 self 的静态类型就是 X,那句是编译期错误。 要拿「自己就是播放器」这个事实得绕一圈:遍历 get_parent().get_children() 找 is AnimationPlayer 的兄弟 —— 自己就在里面。见 PlayerAnimator._ready()。
  • 状态集 IDLE/WALK/CROUCH/RUN/FALL/SNEAK/JUMP。签名 set_motion(velocity, on_floor, sprinting, crouching): 第 4 个参数是「按住蹲键」这个输入事实(crouch 动作 = Ctrl),蹲着不动 → CROUCH、蹲着走 → SNEAK, 这个区分在表现层做,控制器不需要知道两者差别。优先级:空中 > 蹲 > 移动。

模型:mouse.glb

  • body_mesh 有两个 surface:s0 "lambert1"(身体,UV 几乎占满 0~1,贴 mouse_mouse_lambert1_BaseColor.png)、s1 "face"(面部,独立 UV 孤岛 u[0.142,0.356] v[0.038,0.238],贴 mouse_表情测试.png)。 面部是独立材质槽 → 换脸不用动 Blender,set_surface_override_material(1, mat) 即可。
  • mouse_表情测试.png 是 1024² RGBA 但 alpha 全 255(实心蓝 #89B3FF 底 + 黑线脸), 不是透明覆盖层,只能当底图,不能当笔画层。
  • 面部孤岛只占 1024² 的 21%×20%,实际脸分辨率约 219×205 —— 要用 shader 把孤岛重映射到 0~1 再用方形小贴图,有效分辨率能翻一倍多。

Godot MCP 踩坑

  • 数值标量经 MCP 会被字符串化:node_set_property 传 0.15 能写进去,但回显是 "0.15"; 而 batch_execute 里包 {"command": "set_property", "params": {...}} 直接报 INTERNAL_ERROR … handler returned malformed result(和旧版本文档里写的正好相反,以实测为准)。 改被编辑器占用的 .tscn / .tres 时别绕这个弯 —— 直接写文本更省事:.tscn 可写, .tres 报 EBUSY 就改用 filesystem_manage(op="write_text")。
  • node_get_properties 读不到脚本的 @export 属性(会列进 unknown_fields),但 node_set_property 能写。 改完场景文件要 scene_open(force_reload=true) 让编辑器重读盘,否则节点上还挂着旧脚本实例。
  • @tool 脚本现造的节点不会写进 .tscn:add_child 后不给 owner, PackedScene::pack() 就会跳过它。缆绳网格因此可以纯由锚点推导,不存在「文件和锚点对不上」的脏数据。
  • 新 class_name 要跑一次 filesystem_manage(op="scan") 才进全局类表, 否则别的脚本把它当类型引用会解析不到。
  • game_manage(op="suspend") 期间输入事件不送达,next_frame 也拦不住物理帧; 想抓「运行中途某一瞬间」的画面不可靠,只能靠日志 / 属性断言验证。
  • 测试跑在编辑器进程里,@tool 节点必须真的 add_child 进编辑中场景才会跑 _ready(); 用完在 teardown() 摘掉,节点名以 _McpTest 开头可被测试运行器兜底清理。
  • 非 @tool 脚本在编辑器里是「placeholder instance」:load(path).can_instantiate() 返回 false, .new() 报 Nonexistent function 'new' in base 'GDScript',set_script 后调方法报 Attempt to call a method on a placeholder instance(player.gd 也一样,不是脚本本身的问题)。 想在 test_run 里验非 @tool 脚本,就临时生成一个「去掉 class_name、加上 @tool」的副本喂给测试,跑完删掉。
  • test_run 的 GDScript 编译缓存是按路径的:删掉再重建同名脚本,跑到的可能还是旧版本 (现象:逻辑明明改了,测试却按旧逻辑失败)。要验新代码就换个文件名,或者重启编辑器。
  • 编辑器进程的 InputMap 只有内建动作(ui_accept 有,interact / jump 没有)—— 项目动作要等游戏进程启动时才 load 进去。所以断言 InputMap.has_action(...) 的测试在编辑器里必然假红, 先调一次 InputMap.load_from_project_settings()。
  • 改输入动作走 input_map_manage(op="ensure_binding", params={action, event_type="key", keycode="Ctrl"}), 幂等且直接落 project.godot,比手改文件稳。