From 93222895a57583e08f02fc06324e7b8c87ba39a8 Mon Sep 17 00:00:00 2001 From: yvmou Date: Tue, 22 Sep 2026 16:59:16 +0800 Subject: [PATCH] Create 2026-09-22.md --- .workbuddy/memory/2026-09-22.md | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .workbuddy/memory/2026-09-22.md diff --git a/.workbuddy/memory/2026-09-22.md b/.workbuddy/memory/2026-09-22.md new file mode 100644 index 0000000..0f3231e --- /dev/null +++ b/.workbuddy/memory/2026-09-22.md @@ -0,0 +1,60 @@ +# 2026-09-22 + +## 给 Godot 项目 agame 接上局域网联机(ENet 基础版) + +全流程通过 `godot-ai` MCP 操作编辑器完成,`E:\GD_Projects\agame` 项目。 + +### 落地结果 + +- 新增 `autoload/net.gd`(Autoload 名 `Net`):传输层 + 开局/加入/断开 + 玩家生成回收 + 主菜单/HUD(代码建 UI,无 .tscn)。 +- 新增 `world/main.gd`:把 `player.tscn` 注册给 `MultiplayerSpawner.spawn_function`,`spawn_function` 里 `set_multiplayer_authority(peer_id, true)` + `add_to_group("net_player")`。 +- 新增 `player/player_sync.tres`(`SceneReplicationConfig`),同步字段:`.:position`、`Body:rotation`,`replication_mode = 1`(ALWAYS)。 +- `player/player.tscn`:根下加 `Sync`(`MultiplayerSynchronizer`,`root_path` 保持默认 `..`),绑定上面那个 .tres。 +- `world/main.tscn`:删掉硬编码的 Player 实例,改挂 `MultiplayerSpawner`(`spawn_path = ..`)。 +- `player/player.gd`:加 `_is_local`;远端玩家 `camera_rig.queue_free()` + 关物理 + 关 unhandled input。 +- 新增 `tests/test_net_replication.gd`(`McpTestSuite`),3 个用例 12 条断言全绿,钉住同步配置不被静默写坏。 + +### 关键结论 / 踩坑 + +- **节点 authority 不继承**:必须 `set_multiplayer_authority(id, true)`,否则子节点 `MultiplayerSynchronizer` 仍是服务器 authority,同步不发。 +- **远端玩家必须拆相机并关物理**,否则多台 Camera3D 抢渲染、双重模拟导致抖动。 +- 去中心化 authority 模型跑通(host=1 本地操控 / client 远端回放,client 侧镜像)+ ENet 中继正常。 +- 命令行调试入口:`Net._apply_cmdline()` 支持 `-- --host` / `-- --join `。 +- ~~项目已存在的无关报错:`res://addons/ziva_agent/ziva_agent.gdextension` 文件缺失,每次启动都刷 3 行 ERROR。~~ **已修复**: + 插件目录已删除,但 `.godot/extension_list.cfg` 里残留了一行 `res://addons/ziva_agent/ziva_agent.gdextension`。 + `filesystem_manage(op="scan")` 不会清掉它(Godot 只在运行中观测到 .gdextension 增删时才重写该文件), + 直接删掉那一行即可。已用 headless 启动验证:3 行 ERROR 消失。 +- AppID 仍是 480(Spacewar 测试,Steam 客户端未装 → `status:1`),换 Steam P2P 时只需改 `Net.host_game/join_game`。 + +### 输出文档 + +- 新增 `docs/联机框架使用指南.md`:面向零联机基础的人,含概念(peer/authority/复制 vs RPC)、文件地图、 + 三种跑法、四个「新功能怎么同步」配方、Steam P2P 迁移(直连 SteamID 版 + 大厅版)、调试与已知限制。 +- **Steam API 签名已用探针脚本让 GDScript 分析器逐个校验**(本项目装的是 camelCase 风格 GodotSteam): + - `Steam.createLobby(type, max_members)` **返回 void**,结果走 `lobby_created(connect_result, lobby_id)` 信号 + - `Steam.joinLobby(lobby_id)` → `lobby_joined(lobby_id, permissions, locked, response)` 信号 + - `Steam.requestLobbyList()` → `lobby_match_list(lobbies: Array)` 信号;成功码 = 1 + - `Steam.getSteamID()`;`setLobbyData/getLobbyData/getLobbyOwner/getNumLobbyMembers/getLobbyMemberByIndex` + - `SteamMultiplayerPeer`:`create_host(port=0)`、`create_client(steam_id, port=0)`、`host_with_lobby(lobby_id)`、 + `connect_to_lobby(lobby_id)`、`add_peer(steam_id, port=0)`、`set_no_delay/set_no_nagle/set_debug_level` +- 校验手法(可复用):写临时 .gd 调用候选 API,`script_create` 返回的 diagnostics 会指出「方法不存在」「返回 void 不能赋值」等,比查文档更快更准。 +- 新增 `docs/联机实战教程-由浅入深.md`:九关递进教程(血量同步 → 颜色认领 → 头顶名牌 → 挥手 RPC → 两段式攻击 → + 子弹 Spawner → 服务器权威血量 → 远端插值 → 玩家列表)。按键约定 H/G/K/J。 +- 教程里引入的两个工程改动(后续实现时要跟着做):`player.gd` 加 `class_name PlayerController`、 + `main.gd` 加 `class_name GameWorld`、`bullet.gd` 加 `class_name Bullet`、`bullets.gd` 加 `class_name BulletPool`, + 这样跨脚本调用才有编译期检查。 +- **写教程时抓到一个真 bug**:`parent.broadcast_hit(...)` 是本地调用,伤害传不到对方机器,必须 + `world.broadcast_hit.rpc(...)`。GDScript 对漏 `.rpc()` 不报任何错,这是最阴的一类联机 bug。 +- **第二个发现**:`@rpc("authority")` 的检查是「发起方 peer id == 被调节点 authority」,所以服务器不能 + 调用别人玩家节点上的 authority 模式 RPC。广播函数必须放在 authority 为 1 的节点上(世界根节点 Main 天然满足)。 +- 教程全部代码用「拼成一个文件做整体编译」的方式验证过:`diagnostics: []` 零错误。 + GDScript 对动态成员访问极其宽容(`Node` 上调脚本方法、`Node3D.new()` 设脚本属性都不报错), + 所以「编译通过」不等于「语义正确」,逻辑对错还得靠人读。 + + +### godot-ai MCP 使用注意(重要) + +- **autoload 的全局标识符在「正在运行的编辑器会话」里不可见**:新加的 autoload,脚本里直接写 `Net.xxx` 会报 `Identifier not found: Net`。改用 `get_node_or_null("/root/Net")`。 +- Godot 在 Windows 上**不往重定向的 stdout 写日志**(`_console.exe` 更糟,它自己开控制台)。抓输出必须用引擎参数 `--log-file `。 +- `game_manage.get_ui_elements` / `get_scene_tree` 看不到 `/root` 下的 autoload 节点,只认当前场景树。 +- 验证资源是否真被解析:写 `res://tests/test_*.gd`(继承 `McpTestSuite`,方法名 `test_*`,用 `assert_true/assert_eq/assert_gt`),再 `test_run`。