# gamethree **Repository Path**: mindyleelyyAI/gamethree ## Basic Information - **Project Name**: gamethree - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-11-05 - **Last Updated**: 2026-11-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 泡泡肉鸽 · Bubble Rogue(Godot 4.x) 把「泡泡龙」的瞄准、发射、三连消除、悬空坍塌手感完整搬进 Godot,并在外围长出一套肉鸽战斗系统: **泡泡即战斗** —— 场上泡泡阵是对面的兵力,每回合下压、触底扣血;你发射法术球打爆多少,就对 Boss 造成多少伤害。 - 引擎版本:**Godot 4.7.2 stable(GL Compatibility)** - 主场景:`res://scenes/Main.tscn` - 第一阶段:泡泡背包 + 三选一符文 + 5 层地下城 + 分裂王 + 三扇门轻量版 - 第二阶段:五行元素与反应、法术球附魔、自造棋盘(三种机关钉)、4 枚新符文、关卡元素泡泡阵 - 第三阶段:泡泡便利店(金币/永久购买)、护符与协同、Q 版泡泡精灵图鉴、`user://` 存档 - 第四阶段:主菜单 + 地下城地图选关、限时连击门小游戏、程序化音效与音量存档、Windows x86_64 导出 - 第五阶段(当前):Web 单线程导出(`wasm`)、内置 Noto Sans SC 中文字体、无头浏览器 9 项验收 - 全部数值在 `resources/` 下的 `.tres`,新增球 / 符文 / 机关钉 / 元素 / 反应 / **护符 / 商品 / 图鉴条目 / 连击门参数** 均 **零代码** --- ## 1. 怎么运行 ### 编辑器 用 Godot 4.7.x 打开本目录(`project.godot` 所在目录),按 **F5** 直接运行。 ### 命令行 ```bash # 首次导入(生成 .godot 类缓存,必须执行一次) Godot_v4.7.2-stable_win64_console.exe --headless --path "c:\Users\xdsy\.trae-cn\assistant\bubble-rogue" --import # 启动主场景(窗口) Godot_v4.7.2-stable_win64_console.exe --path "c:\Users\xdsy\.trae-cn\assistant\bubble-rogue" # 无头启动 90 帧后退出(验证无脚本报错) Godot_v4.7.2-stable_win64_console.exe --headless --path "c:\Users\xdsy\.trae-cn\assistant\bubble-rogue" --quit-after 90 ``` 本机 Godot 可执行文件: `C:\Users\xdsy\Downloads\Godot_v4.7.2-stable_win64.exe\Godot_v4.7.2-stable_win64_console.exe` ### 内置自检 / 截图参数 参数写在 `--` 之后(Godot 的用户参数区): ```bash # 130 项断言(第一 30 + 第二 30 + 第三 41 + 第四 29),全部跑完自动退出 ... --headless --path <工程目录> -- --selftest # 打印一局快照后退出 ... --headless --path <工程目录> -- --smoke # 自动发射 10 发(含放钉、真实元素反应),打印结果后退出 ... --headless --path <工程目录> -- --autoplay # 直接跳到通关抽球界面(截图用) ... --path <工程目录> -- --demo-reward # 构造元素反应现场并截图(火+水、雷+水) ... --path <工程目录> -- --demo-elements=<绝对路径>.png # 进入自造棋盘阶段并放置三种钉子后截图 ... --path <工程目录> -- --demo-build=<绝对路径>.png # 泡泡便利店界面截图(含金币余额、15 件商品、已购状态) ... --path <工程目录> -- --demo-shop=<绝对路径>.png # 泡泡精灵图鉴截图(含进度、稀有度完成度、未收集剪影、详情) ... --path <工程目录> -- --demo-gallery=<绝对路径>.png # 护符与协同展示(3 组协同同时生效)截图 ... --path <工程目录> -- --demo-amulet=<绝对路径>.png # 主菜单截图 ... --path <工程目录> -- --demo-menu=<绝对路径>.png # 地下城地图截图(5 层、解锁/通关/最高分) ... --path <工程目录> -- --demo-map=<绝对路径>.png # 连击门小游戏截图(计时条、连击、目标、可点击泡泡) ... --path <工程目录> -- --demo-combo=<绝对路径>.png # 完整流程无头校验:主菜单 → 地图 → 选关 → 通关 → 连击门 → 便利店 ... --headless --path <工程目录> -- --flow-check # Web 导出(单线程,产物到 build/web/) ... --headless --path <工程目录> --export-release "Web" build/web/index.html # 本地静态服务器(默认 8088,自动给 .wasm 配 application/wasm) python tools/serve_web.py 8088 # 无头浏览器验收(需先 npm install puppeteer-core pngjs --no-save) node tools/web_verify.js http://127.0.0.1:8088/ # 窗口模式截图,存 PNG 后退出 ... --path <工程目录> -- --screenshot=<绝对路径>.png ``` ### 操作 | 输入 | 行为 | |---|---| | 鼠标移动 | 瞄准(虚线实时预测,含 3 次墙面反弹) | | 鼠标左键 / 触屏 | 发射 | | 空格 | 按当前瞄准方向发射 | | R | 重开一整局 | | **主菜单**:开始冒险 / 继续进度 | 进入地图 / 直接进入已解锁最高层 | | **地图**:点击楼层卡片 | 进入该层(未解锁不可点,5 层含 Boss) | | **连击门**:点击高亮泡泡 | 连击 +1;点空或目标消散会断连并扣时间 | | **主菜单**:音量滑杆 / 静音按钮 | 实时调整并写入存档 | | **自造棋盘阶段**:左侧按钮 | 选择机关钉类型 | | **自造棋盘阶段**:左键空位 | 放置选中的钉子(自动吸附最近空位) | | **自造棋盘阶段**:左键已放钉子 / 右键 | 拆除钉子(非预设钉子返还数量) | | **自造棋盘阶段**:空格 / Enter / 按钮 | 开始战斗 | | **Web 版**:鼠标点击 / 触屏轻点 | 与桌面一致:按钮、瞄准、发射、连击门目标点击 | --- ## 2. 一局的流程 ``` 第 N 层开局(棋盘按敌阵性格生成,可能带属性泡泡) ↓ 自造棋盘阶段:在棋盘下半部空位安插机关钉(上限 = 关卡 max_pegs + 符文加成) ↓ 玩家有 3 次发射(可被符文加成) ←────────────┐ ↓ │ 发射法术球 → 吸附 → 同色 ≥3 BFS 消除 │ ↓ │ 消除属性泡泡 → 五行效果(环状/冻结/贯通/护盾/砸穿)│ ↓ │ 相邻异属性 → 反应表触发(蒸汽爆/连锁闪电/燃烧…) │ ↓ │ 命中/消除 → 对敌方造成伤害(含反应加成) │ ↓ │ 悬空检测 → 失连泡泡掉落(≥5 颗触发坍塌符文) │ ↓ │ 3 次发射用完 → 敌方回合:冻结/护盾拦截 或 下压一行 │ ↓ │ 下压后重力井拉动泡泡;触底?护盾吸收 或 扣 15 生命 │ ↓ └─ 未清空 敌方血量归零 → 抽球(可消耗重掷券)→ 选符文 → 三扇门 → **泡泡便利店**(花金币买永久内容) ↓ 便利店选择「前往下一层」→ 第 N+1 层;局内构筑(球/符文/钉子)本局有效,商店购买永久生效 ↓ 第 5 层击破分裂王 → 通关结算 / 生命归零 → 失败结算 → 结算金币入账、可选「逛便利店」 ``` --- ## 3. 工程结构 ``` bubble-rogue/ ├─ project.godot # 1280×720、canvas_items + keep 拉伸、GL Compatibility ├─ export_presets.cfg # 导出预设:Windows x86_64 + Web(单线程) ├─ assets/fonts/ # NotoSansSC-Regular.otf(子集中文字体,OFL)+ OFL.txt ├─ tools/ │ ├─ serve_web.py # 本地静态服务器(正确 .wasm MIME,无需 COOP/COEP) │ └─ web_verify.js # 无头浏览器验收(puppeteer-core + pngjs) ├─ scenes/ │ └─ Main.tscn # Main(Node2D) + BoardView(Node2D) + UILayer/HUD ├─ scripts/ │ ├─ game.gd # 主控制器:状态机、发射/吸附/消除/坍塌、元素链、钉子、战斗结算 │ ├─ board_view.gd # 表现层:泡泡纹理、元素环、钉子、放置空位、反应特效、屏震 │ ├─ hud.gd # 程序化 UI:顶栏、符文栏、敌情栏、背包条、自造棋盘面板、覆盖层 │ ├─ core/ │ │ └─ hex_board.gd # 六边形 12/11 网格:邻居、BFS、悬空、下压/回退、护盾泡 │ ├─ resources/ # 资源类型定义(class_name Resource) │ │ ├─ ball_def.gd # BallDef(含 element 附魔) │ │ ├─ rune_def.gd # RuneDef(12 种效果) │ │ ├─ amulet_def.gd # AmuletDef(护符 + 协同字段) │ │ ├─ shop_item_def.gd # ShopItemDef(BALL / AMULET / REROLL) │ │ ├─ collection_def.gd # CollectionDef(稀有度 / 掉落权重) │ │ ├─ enemy_pattern.gd # EnemyPattern(含 element_chance / element_pool) │ │ ├─ boss_def.gd # BossDef │ │ ├─ level_def.gd # LevelDef(含 elements_enabled / max_pegs / preset_pegs) │ │ ├─ peg_def.gd # PegDef(三种机关钉) │ │ ├─ element_def.gd # ElementDef(五行 + 消除 effect) │ │ ├─ reaction_rule.gd # ReactionRule(单条反应的字段约定) │ │ ├─ reaction_table.gd # ReactionTable(双向查找) │ │ └─ combo_gate_def.gd # ComboGateDef(连击门参数:时长/目标/奖励) │ └─ systems/ │ ├─ ball_system.gd # 球背包:加载/循环队列/同 id 吞噬升级 │ ├─ rune_system.gd # 符文:三选一池、被动数值聚合(含反应/钉子/元素加成) │ ├─ board_build_system.gd # 棋盘构建、下压补行、元素随机、机关钉落位与增删 │ ├─ resource_dir.gd # 资源目录扫描(兼容编辑器 .tres 与导出包 .tres.remap) │ ├─ level_system.gd # 关卡序列 │ ├─ element_system.gd # 元素/反应表查询、总开关 enabled │ ├─ save_system.gd # 局外存档:JSON 读写、字段清洗、损坏容错 │ ├─ amulet_system.gd # 护符:永久被动 + 数据驱动协同聚合 │ ├─ shop_system.gd # 便利店:商品加载、折扣、购买、重掷券 │ ├─ collection_system.gd # 图鉴:加权掉落、重复转金币、升星、进度 │ └─ audio_system.gd # 程序化合成 8 类音效 + 音量/静音持久化 ├─ resources/ │ ├─ balls/ ball_*.tres # 9 种法术球(7 基础 + 火球 + 雷球) │ ├─ runes/ rune_*.tres # 12 种符文(8 基础 + 4 元素/钉子向) │ ├─ amulets/ amulet_*.tres # 7 枚护符(含 4 组协同) │ ├─ shop/ shop_*.tres # 15 件商品(7 球解锁 + 7 护符 + 1 重掷券) │ ├─ collection/ sprite_*.tres # 10 条图鉴(普通4 / 稀有3 / 史诗2 / 传说1) │ ├─ patterns/ enemy_*.tres # 5 种敌阵(含 3 种带属性泡泡) │ ├─ bosses/ boss_*.tres # 分裂王(启用)+ 变色王、吞噬王(预留) │ ├─ levels/ level_0*.tres # 5 层地下城(含钉子上限与预设钉子) │ ├─ pegs/ peg_*.tres # 重力井 / 弹射柱 / 炸药钉 │ ├─ elements/ element_*.tres # 火 / 水 / 雷 / 木 / 土 │ ├─ reactions.tres # 元素反应表(5 条,全部带 effect) │ └─ combo_gate.tres # 连击门小游戏参数(15s / 12 连 / 480 分) ├─ build/ # 导出产物(windows/BubbleRogue.exe、web/index.html) └─ screenshots/ # main / reward / … / menu / map / combo / export_run / web_menu / web_map / web_play ``` --- ## 4. 各系统职责 | 系统 | 职责 | 关键接口 | |---|---|---| | **BallSystem** | 扫描 `resources/balls`,维护背包(`bag: Array[{def, level}]`)、发射循环、同名吞噬升级 | `load_defs / reset_starter / add_ball / current / advance / queue_preview` | | **RuneSystem** | 扫描 `resources/runes`,三选一(排除已拥有)、被动数值聚合 | `offer / add / bonus_shots / damage_multiplier / reaction_multiplier / peg_radius_bonus / peg_budget_bonus / element_damage_multiplier` | | **BoardBuildSystem** | 按 `LevelDef.pattern` 生成泡泡阵、元素概率、装甲、下压补行;机关钉落位/查询/拆除 | `build / fill_descended_row / apply_pegs / place / remove_at / peg_near / seed_preset_pegs` | | **ElementSystem** | 加载五行与 `reactions.tres`,提供反应查询、颜色、消除效果 id | `load_data / find_reaction / get_element / color_of / effect_of / enabled` | | **SaveSystem** | `user://` JSON 存档:金币、已购球/护符、图鉴、解锁关卡、最高分;缺失/损坏/缺字段容错 | `load_data / save_data / reset_data / add_coins / unlock_ball / unlock_amulet / add_collection_entry / record_score` | | **AmuletSystem** | 护符永久被动;通用遍历「护符 + 背包球」协同,返回生效协同列表 | `find_def / grant / sum_effect / damage_multiplier / reaction_multiplier / heal_on_clear / peg_budget / start_hp / active_synergies` | | **ShopSystem** | 扫描 `resources/shop`,计算折扣价、校验金币、购买落点(球/护符/券)、持久化 | `load_items / setup / price_of / owned / can_buy / buy / consume_reroll_ticket` | | **CollectionSystem** | 扫描 `resources/collection`,加权掉落、重复转金币、每 3 次升星、进度统计 | `load_defs / roll_drop / record_drop / roll_level_drop / progress / rarity_progress` | `game.gd` 只负责编排:元素效果与反应通过 `_element_queue` / `_reaction_queue` 统一结算,钉子通过 `_check_peg_ball()` 与 `_apply_gravity_wells()` 生效。 --- ## 5. 元素系统(第二阶段) ### 5.1 五行属性表 | 元素 | id | 颜色 | `effect` | 消除效果 | |---|---|---|---|---| | 火 | `fire` | `#ff7043` | `ring` | **消融相邻一环**:命中后把 6 个相邻格的泡泡各打一次 | | 水 | `water` | `#38b6ff` | `freeze` | **冻结推进并回退一行**:积攒 1 次「取消下压」,同时立刻把敌阵顶行移除(最多积攒 3 次) | | 雷 | `lightning` | `#ffe066` | `column` | **整列贯通**:沿当前列自顶到底全部命中,并画一道闪电 | | 木 | `wood` | `#31d99b` | `shield` | **生成己方护盾泡**:在相邻空位随机生成 1 颗护盾泡 | | 土 | `earth` | `#a97bff` | `smash` | **砸穿两层**:把同列下方第 1、2 行(`r+1`、`r+2`)的泡泡各砸一次 | **护盾泡规则(README 明确说明)** - 由木元素生成,`color = -1`、`element = wood`、`shield = true`。 - 不参与同色三连匹配,不计入悬空坍塌,也不会被重力井拉动。 - 敌方回合结算顺序:**冻结次数 → 护盾泡 → 正常下压**。有护盾泡时消耗 1 颗并整回合取消下压。 - 泡泡触底时若有护盾泡,消耗 1 颗吸收本次冲击,**不扣 15 生命**(但棋盘仍会重建)。 - 玩家命中护盾泡会击碎它,但不计分、不伤敌、不触发元素连锁。 ### 5.2 属性归属怎么来的(全部可配置) 1. **棋盘生成规则**:`EnemyPattern.element_chance`(0–1 概率)+ `element_pool`(元素 id 数组)。例如第 3 关 35% 火/雷/土,第 4 关 30% 水/木/土,Boss 关 22% 火/雷/水。下压补的新行沿用同一规则。 2. **法术球附魔**:`BallDef.element`。带属性的球落到空位后,落点泡泡会带上该元素(`_land()` 写入),之后再被打爆就会触发对应效果与反应。 - `resources/balls/ball_fire.tres`:火球(BOMB,落点 3×3 + 火属性)。 - `resources/balls/ball_lightning.tres`:雷球(PIERCE,穿透 + 雷属性)。 - 两种球都会进入通关抽球池,宝物门也有概率直接发放。 3. **旧关卡兼容开关**: - 全局:`ElementSystem.enabled = false` → 所有元素效果与反应都不结算。 - 单关:`LevelDef.elements_enabled = false` → 该关不生成、不结算元素(判定由 `_elements_active()` 统一处理)。 - 两个开关都关闭时,玩法完全回到第一阶段。 ### 5.3 反应表字段与新增反应 `resources/reactions.tres` 的 `rules` 是字典数组,**双向匹配**(A+B 与 B+A 等价)。 | 字段 | 含义 | |---|---| | `a` / `b` | 参与反应的两种元素 id | | `display_name` | 反应名(HUD toast 展示) | | `description` | 文案说明 | | `damage` | 对敌伤害;**负数表示回血** | | `radius` | 作用半径(六边形格数),不同 effect 的解释见下 | | `color_hex` | 反应颜色(粒子、环、闪电、闪白) | | `effect` | 反应表现/结算方式 id | 现有 5 条反应: | 组合 | 名称 | effect | 伤害 | 表现 | |---|---|---|---|---| | 火 + 水 | 蒸汽爆 | `steam_burst` | 8 | 全屏闪白 + 强屏震 + 大范围冲击环,半径内泡泡被冲散 | | 雷 + 水 | 连锁闪电 | `chain_lightning` | 6 | 两道锯齿闪电 + 中屏震,贯通双方所在整列 | | 木 + 火 | 燃烧蔓延 | `burn_spread` | 4 | 橙色扩散环 + 粒子,半径内烧穿 | | 火 + 土 | 熔岩喷发 | `lava` | 10 | 闪白 + 冲击环 + 粒子 | | 水 + 木 | 藤蔓生长 | `vines` | -3(回血 3) | 绿色环 + 粒子 | **新增一条反应(零代码)**:在 `rules` 里追加一条字典即可,`effect` 复用上面任意一个已实现值: ```ini rules = [{ "a": &"lightning", "b": &"earth", "display_name": "磁暴", "description": "雷 + 土引发磁暴,半径 2 内造成 7 点伤害。", "damage": 7, "radius": 2, "color_hex": "#fff59d", "effect": &"steam_burst" }] ``` **新增一种全新反应表现**:需要在 `game.gd` 的 `_apply_reaction()` 里加一个 `match` 分支(表现层),这属于「新增行为」而非「新增数据」。 反应伤害会乘以符文 `REACTION_BOOST` 的加成后计入 `_damage_enemy()`,因此会影响分数与 Boss 血量,不是纯视觉。 ### 5.4 元素链与防爆炸 - 元素效果命中泡泡后,会继续把新命中的属性泡泡放入队列,最多链 2 层(`MAX_CHAIN_DEPTH = 2`)。 - 反应按「相邻格对」去重,同一对泡泡在一发内只触发一次。 - 元素队列与反应队列由 `_drain_chains()` 统一清空,单发最多 140 次迭代,防止极端连锁卡死。 --- ## 6. 自造棋盘(BoardBuildSystem) ### 6.1 三种机关钉的 `.tres` 字段 公共字段(`PegDef`): | 字段 | 含义 | |---|---| | `id` | 唯一 id;`LevelDef.preset_pegs` 用它引用 | | `display_name` / `description` / `icon` | HUD 按钮与提示 | | `peg_type` | 0 = 重力井,1 = 弹射柱,2 = 炸药钉 | | `power` | 行为强度(重力井 = 每回合额外拉动数量,见下) | | `color_hex` | 钉子颜色与特效颜色 | | `radius` | 影响/爆炸半径(六边形格数) | | `chain_count` | 炸药钉连锁引爆的额外数量(其它类型忽略) | | `bounce_boost` | 弹射柱速度倍率(1.0 = 不加速) | 三种钉子的实际行为: | 类型 | 资源示例 | 行为 | |---|---|---| | **重力井** `0` | `peg_gravity_well.tres` | 敌方每次**真实下压**后,把 `radius` 内的泡泡向上/下拉向井所在行,最多移动 `2 + power` 颗;可能把泡泡拉近井心,也可能让敌方推进更快压向底线 | | **弹射柱** `1` | `peg_launcher.tres` | 法术球撞上后沿法线反弹并乘以 `bounce_boost`(上限 2.2 倍速度);同一发对同一根柱子只触发一次 | | **炸药钉** `2` | `peg_bomb.tres` | 法术球掠过即引爆:`radius` 内泡泡被炸开,并连锁引爆 `radius` 范围内的其它炸药钉,最多 `chain_count` 根;引爆后从棋盘移除 | **新增一种钉子类型 = 新增 `.tres`**:只要复用上述 3 种 `peg_type` 和字段,就完全零代码。若要新增「第四种行为」,才需要在 `game.gd` 里加一个 `match peg_type` 分支。 ### 6.2 玩家放置规则 - 每关开局若 `peg_budget > 0`,先进入 `build` 阶段;本工程 5 关全部启用(上限 2 / 2 / 3 / 3 / 4)。 - `peg_budget = LevelDef.max_pegs + RuneSystem.peg_budget_bonus() + 宝物门奖励`。 - 只能放在**棋盘下半部**(`row ≥ 6`)的**空格**;点击后自动吸附到 4.2 世界单位内最近的合法空位。 - 放置消耗 1 个预算;拆除返还 1 个(**关卡预设钉子不返还**)。 - 棋盘上会以 `+` 高亮所有当前可放置空位;已放钉子有独立造型与脉动。 - 按空格 / Enter 或点「开始战斗」结束放置,进入正常发射阶段。 - 预设钉子来自 `LevelDef.preset_pegs`,例如第 4 关自带炸药钉、第 5 关自带重力井,**不占用玩家预算**。 ### 6.3 `apply_pegs()` 钩子 `BoardBuildSystem.apply_pegs(board, peg_defs)` 会校验坐标是否在界内、是否为空位、是否与已有钉子重叠,通过后写入 `placements`。`placements` 是钉子真实生效的数据源: - `_check_peg_ball()` 在飞行子步进中检测弹射柱/炸药钉; - `_apply_gravity_wells()` 在敌方下压后执行; - `_detonate_peg()` 负责爆炸与连锁。 --- ## 7. 数据驱动:新增内容不改代码 > 所有资源目录都会被系统在启动时**自动扫描**(`DirAccess` + 文件名排序),新增 `.tres` 后无需注册,重启即生效。 ### 7.1 新增法术球 在 `resources/balls/` 新建 `.tres`: ```ini [gd_resource type="Resource" script_class="BallDef" load_steps=2 format=3] [ext_resource type="Script" path="res://scripts/resources/ball_def.gd" id="1_def"] [resource] script = ExtResource("1_def") id = &"my_ball" display_name = "冰锥球" description = "命中后沿飞行方向继续穿透,并把相邻泡泡冻结一层。" kind = 1 palette_index = 3 base_damage = 2 special_power = 3 max_level = 5 icon = "❄" element = &"water" # 可留空 &"";填了就变成附魔球 ``` | 字段 | 含义 | |---|---| | `id` | 唯一 id;**同名球在奖励处会被吞噬升级** | | `kind` | 0 NORMAL / 1 PIERCE / 2 BOMB / 3 SPLIT / 4 BOOMERANG / 5 WILD / 6 LIFESTEAL | | `palette_index` | 0–4,对应 `game.gd` 的 `PALETTE` 五种颜色 | | `base_damage` | Lv1 对敌伤害;`damage_at(level) = base_damage + (level-1)` | | `special_power` | 各 kind 的专属强度(穿透颗数 / 碎片下限等) | | `element` | 落点泡泡附带的五行 id;留空则无属性 | ### 7.2 新增符文 在 `resources/runes/` 新建 `.tres`: ```ini [resource] script = ExtResource("1_def") id = &"rune_my_rune" display_name = "重炮符文" description = "所有法术球伤害 +25%。" effect = 3 value = 0.25 rarity = 2 icon = "✦" ``` `effect` 枚举(`RuneDef.Effect`)与 `value` 语义: | 值 | 名称 | 效果 | value | |---|---|---|---| | 0 | FIRST_SHOT_CRIT | 每关首发必暴击 | 不使用 | | 1 | COLLAPSE_BLAST | 悬空坍塌 ≥5 颗追加爆炸 | 追加伤害 | | 2 | EXTRA_SHOT | 每回合额外发射 | 次数 | | 3 | DAMAGE_PCT | 全局伤害加成 | 0.15 = +15% | | 4 | HEAL_ON_CLEAR | 清空棋盘回血 | 生命值 | | 5 | ARMOR_PIERCE | 每击额外削甲层数 | 层数 | | 6 | BOSS_BONUS_SHOT | Boss 关额外发射 | 次数 | | 7 | START_SHIELD | 预留(触底减伤) | 减伤量 | | 8 | REACTION_BOOST | 元素反应伤害加成 | 1.0 = 翻倍 | | 9 | PEG_RADIUS | 机关钉影响/爆炸半径 | 格数 | | 10 | PEG_BUDGET | 每关额外可安放钉子数 | 颗数 | | 11 | ELEMENT_PCT | 带属性泡泡伤害加成 | 0.3 = +30% | ### 7.3 新增元素 / 反应 / 敌阵 / 关卡 - **元素**:`resources/elements/` 新增 `ElementDef`(`id / display_name / color_hex / icon / effect`)。 - `effect` 目前支持 `ring / freeze / column / shield / smash`;新增效果 id 需要在 `game.gd` 的 `_apply_element_effect()` 加分支。 - **反应**:见 5.3。 - **敌阵** `EnemyPattern`:`personality`、`start_rows`、`color_count`、`descend_every_turns`、`armor_rows`、`armor_chance`、`element_chance`、`element_pool`。 - **关卡** `LevelDef`:引用 `pattern` 与可选 `boss`;`elements_enabled`、`max_pegs`、`preset_pegs`。 - `preset_pegs` 格式:`[{"peg": &"peg_bomb", "row": 10, "col": 4}]`。 - 关卡按文件名排序组成序列,新增 `level_06.tres` 会自动成为第 6 层。 ### 7.4 新增护符 / 商品 / 图鉴条目(第三阶段) - **护符** `resources/amulets/`: ```ini [resource] script = ExtResource("1_def") id = &"amulet_my_amulet" display_name = "冰心吊坠" description = "反应伤害 +15%。" icon = "❄" rarity = 2 price = 38 effect = 2 # 2 = REACTION_PCT value = 0.15 synergy_ball = &"my_ball" # 背包中有该球才触发 synergy_effect = 2 synergy_bonus = 0.5 synergy_desc = "冰心共鸣:与冰锥球成对时反应伤害再 +50%。" ``` 字段与协同机制见 9.4;`effect` 枚举见 9.4 表。**新增协同 = 填 `synergy_*` 四个字段,不改代码**。 - **商品** `resources/shop/`:`kind`(0 BALL / 1 AMULET / 2 REROLL)、`ref_id`、`price`、`one_time`、`rarity`。新增商品见 9.3。 - **图鉴条目** `resources/collection/`: ```ini [resource] script = ExtResource("1_def") id = &"sprite_my_sprite" display_name = "冰晶泡泡" description = "喜欢贴在墙角的小家伙。" icon = "❄" rarity = 2 # 0 普通 / 1 稀有 / 2 史诗 / 3 传说 drop_weight = 5.0 source = "第 3 层及之后通关掉落" traits = "属性 · 水 / 体型 · 结晶" ``` --- ## 8. 与第一阶段的衔接 | 衔接点 | 实现 | |---|---| | 符文池 | 新增 4 枚 `.tres`:反应倍增(反应伤害 ×2)、引爆增幅(钉子半径 +1)、工兵锤(钉子预算 +1)、五行亲和(属性泡伤害 +30%) | | 三扇门 | 宝物门 60% 发随机球(含火球/雷球),40% 给下一关 `+1` 钉子预算;**连击门为限时小游戏(见 10.2)** | | 元素关卡 | 第 3 关(火/雷/土 35%)、第 4 关(水/木/土 30%)、第 5 关 Boss(火/雷/水 22%)均有属性泡泡阵 | | Boss | 分裂王机制不变;Boss 关棋盘带元素,但 Boss 血量未额外上调,**属性泡泡会略微降低难度**,已在已知限制中说明 | | 分数 / 血量 / Boss 伤害 | 反应伤害直接调用 `_damage_enemy()` 并追加 `score += 伤害 × 3`;属性泡泡伤害受 `ELEMENT_PCT` 影响;负伤害反应回血 | | 手感 | 所有第一阶段常量与发射/反弹/吸附/三消/坍塌代码一行未改;第二阶段只在命中、结算、敌方回合与表现层做加法 | --- ## 9. 第三阶段:局外成长(金币 / 便利店 / 护符 / 图鉴 / 存档) ### 9.1 两层循环怎么咬合 ``` 一层内的构筑(球 / 符文 / 机关钉) → 只在本局有效,下一局清零 ↓ 三扇门之后 泡泡便利店(金币消费) → 永久内容:解锁球、护符、重掷券库存 ↓ 下一层 / 下一局继续使用永久内容;图鉴、最高分、解锁关卡跨局累计 ``` - 每层通关的流程固定为:**抽球 → 选符文 → 三扇门 → 便利店 → 下一层**。 - 便利店「前往第 N 层」按钮同时承担「返回主界面」:关闭商店并推进关卡。 - 失败/通关结算界面的「逛便利店」按钮会以 `重新开始新一局` 的返回文案打开商店,先花金币再重开。 - 商店购买立即写盘;对局中获得的球/符文/钉子不写盘。 ### 9.2 金币与结算公式 金币是唯一局外货币,只在下列两处产生: **每层通关(`_settle_level_coins`)** ``` 本层金币 = round( (4 + floor(本层新增分数 / 100) + 本层新增反应次数) × 金币倍率 ) ``` - `4`:每层基础层数奖励 - `本层新增分数`:本层开始到清场之间的分数增量(不是全局分数) - `本层新增反应次数`:本层触发的元素反应次数 - `金币倍率`:`1 + 护符 COIN_PCT 总和`(幸运币 +10%、回收布袋 +20%) **每局结束(`_settle_run_coins`,失败或通关都结算)** ``` 结算金币 = round( (floor(剩余生命 / 10) + (通关 ? 15 : 0)) × 金币倍率 ) ``` **连击门(第四阶段改为限时小游戏)** ``` 成功金币 = round( (3 + 最高连击 × 2 + 本局累计反应 × 1) × 金币倍率 ) 失败金币 = 0 成功后 descend_delay += 2(下一关首次敌方推进延后 2 回合) ``` 仅成功发金币;失败/超时走同一结算面板并可继续到便利店,不会卡住。自检覆盖公式、成功/失败、延后语义。 ### 9.3 便利店(ShopSystem) 商品全部来自 `resources/shop/*.tres`,**新增商品只加 .tres**。商品字段: | 字段 | 含义 | |---|---| | `id` | 商品唯一 id | | `display_name / description / icon` | 卡片文案 | | `kind` | `BALL=0` 解锁球 / `AMULET=1` 买护符 / `REROLL=2` 重掷券 | | `ref_id` | 对应 `resources/balls` 或 `resources/amulets` 的 id | | `price` | 基础价格(`商人印章` 会打折,最低 1 金币) | | `one_time` | `true` 买断后显示「已购 · 永久生效」;`false` 可重复买(重掷券) | | `rarity` | 卡片配色 | 示例(`resources/shop/shop_ball_fire.tres`): ```ini [resource] id = &"shop_ball_fire" display_name = "法术球 · 火球" kind = 0 ref_id = &"fire" price = 32 one_time = true ``` 商品语义: - **法术球解锁**:写入 `unlocked_balls`,下一局 `start_run()` 会把它加入初始背包池(基础池仍是标准/穿透/吸血)。 - **护符**:写入 `amulets`,`AmuletSystem` 立即拥有并参与结算。 - **重掷券**:语义为「重掷抽球三选一」,不重掷关卡;一张券换一批候选,可叠加、跨局保留。抽球界面有「重掷三选一(券 N)」按钮。 - UI 有 15 张鼠标可点卡片、价格、余额、已购状态、图鉴入口、重置存档入口、返回按钮;金币不足会标红并禁用。 ### 9.4 护符(AmuletDef)与协同 护符是**永久被动**,与局内符文分开:符文每局清零,护符跨局保留。字段: | 字段 | 含义 | |---|---| | `id / display_name / description / icon / rarity / price` | 基础信息与商店标价 | | `effect` | 基础效果枚举 | | `value` | 基础数值 | | `synergy_ball` | 协同球 id(背包里有它才触发) | | `synergy_effect` | 协同加成作用到哪个效果 | | `synergy_bonus` | 协同数值 | | `synergy_desc` | 协同文案(商店/UI 展示) | `effect` 枚举:`0 DAMAGE_PCT`、`1 HEAL_ON_CLEAR`、`2 REACTION_PCT`、`3 ELEMENT_PCT`、`4 COIN_PCT`、`5 PEG_BUDGET`、`6 START_HP`、`7 SHOP_DISCOUNT`。 现有 7 枚护符(`resources/amulets/`): | 护符 | 基础效果 | 价格 | 协同球 | 协同效果 | |---|---|---|---|---| | 火心吊坠 | 反应伤害 +20% | 36 | 火球 `fire` | 反应伤害再 +80% | | 猎手徽记 | 全局伤害 +10% | 28 | 分裂球 `split` | 全局伤害再 +25% | | 回收布袋 | 金币 +20% | 24 | 吸血球 `lifesteal` | 清屏额外回血 +2 | | 泡罩护符 | 初始生命上限 +10 | 32 | — | — | | 钉匠钥匙 | 每关 +1 机关钉 | 30 | 雷球 `lightning` | 属性伤害 +20% | | 幸运币 | 金币 +10% | 16 | — | — | | 商人印章 | 商品 -15% | 55 | — | — | 协同**不是写死 if**:`AmuletSystem.sum_effect(effect, ball_system)` 遍历所有已拥有护符,只要 `synergy_ball` 在当前背包里,就把 `synergy_bonus` 叠加到 `synergy_effect`。新增一组协同只需复制一个 `.tres` 并填 4 个字段。底部状态栏会显示「护符:… ✦协同:A×B、C×D」。 已接入的结算点: - 伤害:`_damage_of_ball()` 乘 `AmuletSystem.damage_multiplier()` - 反应:`_reaction_multiplier()` 叠加护符反应加成 - 属性泡:`_hit_cell()` 叠加护符属性加成 - 清屏回血:`_level_clear()` - 机关钉预算:`load_level()` - 初始生命:`start_run()` - 金币:`_settle_level_coins / _settle_run_coins / 连击门` - 商店价格:`ShopSystem.price_of()` ### 9.5 图鉴(CollectionSystem) 条目来自 `resources/collection/*.tres`,**新增条目只加 .tres**。字段:`id / display_name / description / icon / rarity / drop_weight / source / traits`。 稀有度与权重: | 稀有度 | 枚举 | 默认权重 | 重复转金币 | |---|---|---|---| | 普通 | 0 | 18–20 | 3 | | 稀有 | 1 | 10 | 6 | | 史诗 | 2 | 5 | 12 | | 传说 | 3 | 2 | 25 | 规则: - 每层通关有 **70%** 概率掉落 1 条,按 `drop_weight` 加权抽取(`roll_drop` 可传 seed 做确定性自检)。 - **首次掉落 = 解锁**;重复掉落 = **转金币 + 收集次数 +1**。 - 每累计 **3 次重复** 升 1 星,最高 **3 星**(永久记录)。星级目前是收集成就,不提供战斗数值。 - 图鉴 UI 显示:总进度 `x/y`、每个稀有度的 `found/total`、网格卡片、未收集剪影(`?`)、稀有度配色、详情面板(属性、掉落来源、次数、星级、是否解锁)。 ### 9.6 存档(SaveSystem) - 位置:`user://bubble_rogue_save.json` - Windows 实际路径:`%APPDATA%\Godot\app_userdata\泡泡肉鸽 Bubble Rogue\bubble_rogue_save.json` - 结构: ```json { "version": 1, "coins": 0, "total_coins_earned": 0, "high_score": 0, "unlocked_levels": 1, "runs": 0, "wins": 0, "unlocked_balls": [], "amulets": [], "collection": {}, "collection_stars": {}, "tickets": 0, "level_records": { "1": {"cleared": false, "high_score": 0}, "...": {"cleared": false, "high_score": 0} }, "audio_volume": 0.8, "audio_muted": false } ``` - `collection` 是 `{条目id: 次数}`,`collection_stars` 是 `{条目id: 0-3}`。 - `level_records` 固定 5 层:`cleared` 是否通关、`high_score` 该层最高分。通关第 N 层会把 `unlocked_levels` 抬到 N+1。 - `audio_volume` 为 0.0–1.0(界面是 0–100%),`audio_muted` 为静音开关;两者由主菜单音频面板即时写入。 - 容错: - 文件不存在 → 默认存档,`corrupted=false` - JSON 解析失败 / 根节点不是字典 → 标记 `corrupted=true`,回落默认,**不崩溃** - 字段缺省或类型错误 → `_sanitize()` 补默认 / 夹取范围(负数金币归零,字符串数组强制成数组,集合强制成字典,`level_records` 缺省补齐 5 层,音量夹到 0–1) - 旧档(第三阶段只有 `version/coins/...` 没有新字段)→ 自动补 `level_records`、`audio_volume=0.8`、`audio_muted=false`,正常进入 - 重置入口:便利店左下角「重置存档」按钮,**需要点两次确认**(第一次变「再点一次确认重置」)。会清空金币、护符、图鉴、解锁关卡、每层记录、最高分、重掷券与音频设置,不可恢复。 - 购买 / 每层结算 / 每层记录 / 连击门 / 音量或静音变化 / 重开一局时都会写盘;自检使用独立临时文件,结束后还原真实存档路径。 ### 9.7 五阶段完成度总表 | 阶段 | 核心内容 | 状态 | |---|---|---| | 第一阶段 | 泡泡手感、9 种球、12 符文、5 层 + 分裂王、三扇门、HUD | ✅ 完成,30 项断言 | | 第二阶段 | 五行元素与 5 条反应、附魔球、3 种机关钉、自造棋盘、元素关卡 | ✅ 完成,30 项断言 | | 第三阶段 | 金币与结算、便利店 UI + 15 商品、7 护符 + 4 协同、10 条图鉴 + 图鉴 UI、JSON 存档 + 重置 | ✅ 完成,41 项断言 | | 第四阶段 | 主菜单 + 地下城地图选关(5 层/解锁/每层最高分)、限时连击门小游戏、程序化音效 + 音量存档、Windows 导出 | ✅ 完成,29 项断言 | | 第五阶段 | Web 单线程导出、内置中文字体、静态部署说明、无头浏览器 9 项验收 | ✅ 完成,浏览器 9/9 | | **合计** | — | **130 项 headless 自检全过;Web 验收 9/9** | 第四阶段清单: - [x] 主菜单「开始冒险」→ 地下城地图 → 点击已解锁楼层进入;「继续进度」直达已解锁最高层 - [x] 地图展示 5 层(含第 5 层 Boss)、已解锁/通关状态/每层最高分,锁定的楼层不可进入 - [x] 每层通关状态与最高分写入存档,`unlocked_levels` 真正驱动选关 - [x] 旧档缺字段自动补齐(`level_records` / `audio_volume` / `audio_muted`),损坏档不崩 - [x] 连击门升级为限时小游戏:15s、可点击高亮泡泡、连击/分数/计时条/目标/结算面板 - [x] 成功按 `3 + 最高连击×2 + 反应×1` 发金币,并让下一关首次推进延后 2 回合 - [x] 失败/超时有明确结算且可继续流程,不卡住 - [x] 8 类程序化合成音效(无外部素材),主菜单音量滑杆 + 静音开关写入存档 - [x] 游戏内音效接线:发射、消除、反应、Boss 受击、购买、过关、失败、连击 miss - [x] Windows x86_64 导出预设与产物,双击 exe 可进入主菜单 - [x] 导出兼容修复:`DirAccess` 同时识别 `.tres` 与导出后的 `.tres.remap`(否则导出包会加载 0 个资源) - [x] 自检 130 项全过(新增 29 项),完整流程 `--flow-check` 无脚本报错 - [x] 新增截图:主菜单、地图、连击门、导出运行画面 ### 9.8 阶段遗留与已知限制 第三阶段遗留中,第四阶段已解决: - ~~连击门是简化版~~ → 已升级为限时连击小游戏(含失败分支)。 - ~~`unlocked_levels` 没有选关 UI~~ → 已由地下城地图驱动。 - ~~无音频~~ → 已加入程序化音效与音量/静音持久化。 - ~~无导出配置~~ → 已配置并实际导出 Windows x86_64。 仍存在的限制: - 图鉴星级是收集成就,不提供战斗加成;没有图鉴奖励领取。 - 便利店一次展示全部 15 件商品,没有刷新/轮换/出售;商品超过 15 件需要加滚动。 - 存档单槽位,没有多档/云同步;重置是整档清零,没有回滚。 - 护符与商品资源未做本地化,文案为中文硬编码在 `.tres`。 - Web 版首帧较慢:`index.wasm` 约 39.5 MB、`index.pck` 约 2.3 MB,首次打开需等下载 + 编译(弱网/低端设备可能数秒);产物未开 gzip/brotli 预压缩。 - Web 版触屏只保证「按钮点击 / 瞄准 + 发射 / 连击门目标点击」;拖拽瞄准的精细度不如鼠标,小屏上 HUD 会等比缩放,没有单独的移动端布局或横竖屏切换提示。 - 中文字体是固定子集(Noto Sans SC,943 字):若新增文案出现子集外汉字,浏览器会回退到系统字体,可能缺字或字形不一致;新增文案后需重新生成子集。 - 浏览器无「单存档槽」以外的多档能力;`user://` 在 Web 上映射到 IndexedDB,清站点数据即等于删档。 - 连击门目标位置随机,没有手柄/键盘操作(只支持鼠标点击)。 --- ## 10. 第四/五阶段:打磨与 Web 版(选关 / 连击门 / 音频 / 导出) ### 10.1 主菜单与地下城地图 启动后进入主菜单: - **开始冒险** → 地下城地图 - **继续进度 · 第 N 层** → 直接进入 `unlocked_levels` 指向的已解锁最高层 - **泡泡图鉴** → 图鉴(返回时回到主菜单) - **音频设置**:音量滑杆 + 静音按钮,即时生效并写盘 地图(`hud.gd` 的 `show_map()`)展示 5 层卡片: | 楼层 | 表现 | |---|---| | 未解锁 | 灰色卡片、`未解锁 / 通关第 N-1 层后开放`,按钮禁用 | | 已解锁未通关 | 青色卡片、`已解锁 / 尚未挑战`(有分数则 `探索中 / 最高分 N`) | | 已通关 | 金色卡片、`已通关 / 最高分 N` | | 第 5 层 | 红色卡片、`Boss · 分裂王`,通关第 4 层后开放 | 规则: - 解锁由存档的 `unlocked_levels` 真正驱动:`SaveSystem.level_unlocked(n)` 判定,`start_run_at(index)` 再次校验,锁定层点击无效。 - 通关第 N 层时 `record_level_result(N, true, score)` 写入 `level_records[N]` 并把 `unlocked_levels` 抬到 N+1。 - 阵亡时写入 `record_level_result(N, false, score)`:只更新该层最高分,不改变通关状态。 - 地图与主菜单只是 HUD 覆盖层,战斗结算与核心手感仍在 `game.gd`,没有改动任何手感常量。 - 退出对局后可点「返回地图」重新选关;「继续进度」与从存档进入的语义一致。 ### 10.2 连击门小游戏(ComboGateDef) 参数全部在 `resources/combo_gate.tres`,改数值即可调难度/奖励,无需改代码: | 字段 | 当前值 | 含义 | |---|---|---| | `duration_sec` | 15 | 限时秒数 | | `target_combo` | 12 | 成功条件一:最高连击 | | `target_score` | 480 | 成功条件二:累计分数(二者任一达成即成功) | | `score_per_hit` | 35 | 每次命中基础分;每 4 连提升一档(×1/×2/×3…) | | `initial_targets` | 4 | 开场目标数 | | `max_targets` | 8 | 场上目标上限 | | `spawn_interval` | 0.75 | 补刷间隔(秒),断连压力会缩短 | | `target_lifetime` | 2.2 | 单个目标存活时间(秒) | | `miss_time_penalty` | 0.75 | 点空/漏掉时直接扣的剩余时间 | | `base_coins` / `coin_per_combo` / `coin_per_reaction` | 3 / 2 / 1 | 金币公式系数 | | `descend_delay` | 2 | 成功后下一关首次敌方推进延后回合 | 玩法: - 门内生成可点击的高亮泡泡;点击命中 → 连击 +1、分数按档位增加。 - 点中空地或目标消散 → 连击清零、`combo_pressure` 上升,立即扣除 `miss_time_penalty` 秒,并加快补刷。 - 达成 `target_combo` 或 `target_score` → 成功;时间归零 → 失败。 - 成功金币 `round((3 + 最高连击×2 + 本局反应×1) × 金币倍率)`,`descend_delay += 2`;失败不发金币。 - 两种结果都弹出结算面板(最高连击、得分、金币、延后语义/失败原因),点「继续 · 返回便利店」进入商品页,流程不会卡死。 - 自检覆盖启动/命中/断连/成功/失败/奖励公式/延后/继续流程。 ### 10.3 音频(AudioSystem) - **无外部素材**:启动时用 GDScript 合成 16-bit / 22050Hz PCM 波形(正弦、方波、锯齿 + 噪声包络)。 - 8 类音效:`shoot`(发射)、`pop`(消除)、`reaction`(元素反应)、`boss_hit`(Boss 受击)、`purchase`(购买)、`fail`(失败)、`win`(过关)、`miss`(连击断)。 - 播放采用 6 个 `AudioStreamPlayer` 轮转池,同名音效 0.06s 节流,避免一次连锁消除叠爆。 - 主菜单音频面板:`HSlider` 0–100% + 静音按钮;修改后立刻写入 `audio_volume` / `audio_muted` 并落盘,重进游戏自动恢复。 - 无头自检会实际调用 `play()`,并验证音量夹取、写盘与重载。 ### 10.4 导出(Windows x86_64) 预设 `export_presets.cfg`: - preset 名:`Windows Desktop` - 平台:`Windows Desktop`,架构 `x86_64` - 产物路径:`build/windows/BubbleRogue.exe` - `binary_format/embed_pck=true`(单 exe 携带资源,便于双击) - 控制台包装器开启,便于日志验证 命令(模板需先安装到 `%APPDATA%\Godot\export_templates\4.7.2.stable\`,即 Godot 导出版本目录;至少需要 `windows_release_x86_64.exe`): ```powershell # 导出(release) Godot_v4.7.2-stable_win64_console.exe --headless --path . --export-release "Windows Desktop" build/windows/BubbleRogue.exe # 双击等价验证:启动导出包并进入主菜单后截图 build\windows\BubbleRogue.exe -- --demo-menu=C:\...\screenshots\export_run.png # 导出包全流程(无头) build\windows\BubbleRogue.exe --headless -- --flow-check ``` - 产物:`build/windows/BubbleRogue.exe`(109 MB,`embed_pck` 单文件,无需额外 `pck`)。 - 双击 exe 直接进入主菜单;`--demo-menu=` 会写入指定 PNG 后退出。 - 导出包实测:`GAME_READY balls=9 runes=12 levels=5 elements=5 pegs=3 reactions=5 amulets=7 shop=15 collection=10 audio=8`; `--flow-check`(窗口模式实跑)输出 `combo_result=success combo=9 score=525 gain=23`、`shop state=shop visible=true coins=279 descend_delay=2`、`unlocked=4`; 控制台仅有显卡驱动回退 ANGLE 的警告,无 SCRIPT ERROR。 - **导出注意事项**:导出后 `resources/**/*.tres` 在 PCK 中变为 `*.tres.remap`,所有目录扫描统一走 `ResourceDir.tres_files()`(`scripts/systems/resource_dir.gd`),不要再用 `ends_with(".tres")`。 - Web 导出见 10.5;导出后校验命令:`node tools/web_verify.js http://127.0.0.1:8088/`。 ### 10.5 Web 导出与部署(单线程,无需 COOP/COEP) 预设(`export_presets.cfg` 的 `[preset.1]`): | 配置项 | 值 | 说明 | |---|---|---| | `name` / `platform` | `Web` / `Web` | 导出目标 | | `export_path` | `build/web/index.html` | 产物入口 | | `variant/thread_support` | `false` | **单线程**,不需要 `SharedArrayBuffer`,因此不需要 COOP/COEP 响应头 | | `variant/extensions_support` | `false` | 不启用 GDExtension | | `html/canvas_resize_policy` | `2` | 画布随窗口自适应 | | `html/focus_canvas_on_start` | `true` | 启动即聚焦,键盘/鼠标直接可用 | | `html/export_icon` | `true` | 导出 favicon | | `progressive_web_app/enabled` | `false` | 不生成 PWA 清单 | 模板:`web_nothreads_release.zip`(单线程模板,不是 `web_release.zip`),安装到 `%APPDATA%\Godot\export_templates\4.7.2.stable\`。 ```powershell # 导出 Godot_v4.7.2-stable_win64_console.exe --headless --path . --export-release "Web" build/web/index.html # 本地静态服务器(默认 8088,自带 .wasm / .pck / .js MIME 与 no-store) python tools/serve_web.py 8088 # 浏览器打开 http://127.0.0.1:8088/ # 无头浏览器验收(9 项断言 + 3 张截图) node tools/web_verify.js http://127.0.0.1:8088/ ``` 产物结构(`build/web/`,约 42 MB,整体已被 `.gitignore` 的 `build/` 排除,不入库): ``` build/web/ ├─ index.html 5.4 KB 页面入口(含 canvas、加载进度条、启动脚本) ├─ index.js 280 KB Godot Web 运行时胶水 ├─ index.wasm 39.5 MB GL Compatibility 运行时(单线程) ├─ index.pck 2.3 MB 资源包(场景/脚本/.tres/字体子集) ├─ index.audio.worklet.js 音频 worklet(无 threads 也可用) ├─ index.icon.png / index.apple-touch-icon.png ``` 生成命令(产物不入库,需本地重建): ```powershell Godot_v4.7.2-stable_win64_console.exe --headless --path . --export-release "Web" build/web/index.html ``` 部署要点: - **纯静态托管**:把 `build/web/` 整个目录丢到 Nginx / Apache / GitHub Pages / OSS / COS 即可,无需后端。 - **MIME**:`.wasm` 必须是 `application/wasm`(Nginx 默认 `application/octet-stream` 会导致 `WebAssembly.instantiateStreaming` 报错)。 已有配置一般包含 `types { application/wasm wasm; }`;没有则手动加。`.pck` 建议 `application/octet-stream`,`.js` 为 `application/javascript`。 - **响应头**:单线程构建 **不需要** `Cross-Origin-Opener-Policy: same-origin` / `Cross-Origin-Embedder-Policy: require-corp`;不需要 `Cross-Origin-Isolation`。 - **缓存**:`index.wasm` / `index.pck` 可长缓存加 hash 查询串;`index.html` / `index.js` 建议 `no-cache`,避免升级后仍用旧运行时(本地脚本统一 `no-store`)。 - **压缩**:Nginx 开 `gzip_types application/wasm application/javascript application/octet-stream;` 可把 39.5 MB 显著压小;未预压缩的 crt 产物交给服务器动态压缩即可。 - **HTTPS**:非 localhost 环境下浏览器要求 HTTPS 才允许部分能力(本作不使用指针锁定/全屏,HTTP 也能跑,但线上建议 HTTPS)。 - **字体**:`assets/fonts/NotoSansSC-Regular.otf`(391 KB 子集)会打进 pck;Web 端不再依赖系统字体,中文不会出现豆腐块。许可证 `assets/fonts/OFL.txt`(SIL OFL 1.1)。 - **输入**:`canvas_resize_policy=2` 自适应窗口;鼠标瞄准/发射与桌面一致;触屏轻点等价左键(按钮、瞄准发射、连击门目标均可点)。 - **验收结果**:无头 Chromium(Puppeteer,软件 WebGL)实测 9/9 通过——页面无 JS 报错、进入主菜单、画面非黑屏、进入地图、地图非黑屏、进入第 1 层、发射后产生消除/伤害、战斗画面非黑屏、运行期间无报错;截图见 `screenshots/web_menu.png`、`web_map.png`、`web_play.png`。 - **可玩范围**:主菜单 → 地图选关 → 战斗 → 三扇门 → 连击门 → 便利店 → 图鉴全部沿用桌面逻辑,未改任何数值/手感常量;Web 专属代码只有 `game.gd` 里 `OS.has_feature("web")` 守卫的只读状态镜像 `window.__BR_STATE`(供验收脚本读取,不影响玩法)。 --- ## 11. 第二阶段完成度 ### 已完成 - [x] 五行元素:火/水/雷/木/土,全部由 `ElementDef.effect` 驱动 - [x] 属性归属可配置:敌阵 `element_chance + element_pool`、法术球 `element` 附魔 - [x] 五种消除效果真实接线:环状 / 冻结+回退 / 整列 / 护盾泡 / 砸穿两层 - [x] 反应表 5 条全部补齐 `effect` 并接线,含蒸汽爆、连锁闪电、燃烧蔓延 - [x] 反应可视反馈:全屏闪白、屏震、扩散环、锯齿闪电、彩色粒子 - [x] `ElementSystem.enabled` + `LevelDef.elements_enabled` 双重开关,真正跳过结算 - [x] 法术球附魔:火球(炸弹型)、雷球(穿透型),进入抽球池与宝物门 - [x] 自造棋盘:三种钉子数据驱动、真实影响物理/下压/爆炸 - [x] 可放置空位可视化、放置/拆除、每关上限、关卡预设钉子 - [x] `BoardBuildSystem.apply_pegs()` 真正生效 - [x] 4 枚新符文(反应翻倍、钉子半径、钉子预算、五行亲和) - [x] 3 个关卡带属性泡泡阵,Boss 关也带元素 - [x] 宝物门接元素球与钉子预算 - [x] 修复第一阶段遗留的状态机 bug:`_finish_shot()` 在 `flying` 状态下直接 return,导致发射后永久卡在飞行状态(详见第 13 节) - [x] 自检 60 项全部通过(原 30 + 新增 30,第四阶段后总数为 130) - [x] headless 无报错、autoplay 实际发射跑通、4 张截图更新 ### 未完成 / 占位(第二阶段遗留,第三阶段已补齐部分) - 行内 UI 没有「元素总开关」按钮,`ElementSystem.enabled` 通过代码/调试参数切换;关卡级开关走 `.tres`。 - 变色王 / 吞噬王仍未实现,`_boss_turn()` 只实现 SPLIT。 - 钉子只能放在 `row ≥ 6` 的下半部,不能放在上半部或已有泡泡上。 - 元素反应没有「元素抗性 / 免疫」系统,所有敌阵对五行一视同仁。 - ~~连击门点击无效~~ → 第三阶段已改为简化可玩版(反应换金币 + 推进延后)。 - ~~商店、护符、重掷券、图鉴为占位~~ → 第三阶段已实现完整 UI 与存档。 - 无音频、无移动端导出配置。 ### 已知限制 - Boss 关带元素泡泡后,整体难度低于纯第一阶段版本(元素连锁会额外清场);若需要更硬的 Boss,可把 `boss_split_king.tres` 的 `hp` 上调,或把 `enemy_boss.tres` 的 `element_chance` 调到 0。 - 火球的落点是 3×3 炸弹,落点泡可能被自身爆炸炸掉,因此「火球附魔」在落点保留的稳定性不如雷球(雷球为穿透型,落点更稳)。 - 水元素的「回退一行」会直接删除顶行,不保留被删泡泡的分数;连续触发会快速缩短棋盘。 - 重力井每回合最多拉动 `2 + power` 颗,且只向井心所在行移动一格;它在帮助连锁的同时,也可能把泡泡拖向底线。 - 反应连锁最多 2 层、单发最多 140 次结算,极端密度的属性阵可能触发不到更深层。 - 炸弹球固定 3×3;奖励池随机 3 颗可能全是已拥有球(刻意的「重复不亏」设计)。 - 窗口固定 1280×720 设计分辨率,靠 `canvas_items` + `keep` 缩放。 --- ## 12. 核心手感:与原版一致性 以下逻辑一行未改(常量保持一致:`R=1`、`ROW_H=√3`、`GRID_TOP=0.8`、`DEATH_Y=27`、`SHOOTER=(12, 29.45)`、`SPEED=33`、`PREVIEW_STEP=0.42`): - 六边形 **12/11** 交替网格,行奇偶决定邻居规则; - 瞄准虚线实时预测,含 **最多 3 次墙面反弹**,末端显示落点圈; - 飞行子步进 + 墙面反弹 + 碰撞检测,发射后**吸附到最近的合法空位**; - 同色 **≥3 BFS** 消除; - 顶部连通性检测 → **失连悬空泡泡掉落**; - 下压时 `parity_offset` 翻转,原泡泡横向位置保持不变; - 分数(消除 15 / 坍塌 25 / 通关 100)、重开、触底判定。 第二阶段只在这些流程的「钩子点」追加元素队列、反应队列与钉子检测,没有改动任何手感常量或预测算法。 --- ## 13. 验证记录 ``` $ ... --headless --path . --import (无 SCRIPT ERROR) $ ... --headless --path . --quit-after 90 GAME_READY balls=9 runes=12 levels=5 elements=5 pegs=3 reactions=5 amulets=7 shop=15 collection=10 coins=... audio=8 (无脚本报错) $ ... --headless --path . -- --selftest SELFTEST_BEGIN ... 130 项 [OK] ... SELFTEST_RESULT pass=130 fail=0 $ ... --headless --path . -- --flow-check FLOW menu state=menu menu_visible=true FLOW map state=map cards=5 coins=... FLOW level state=... index=0 level=level_01 FLOW reward stage=ball offers=3 FLOW door stage=rune FLOW combo state=combo targets=4 duration=15.0 FLOW combo_result=success combo=9 score=525 gain=23 FLOW shop state=shop visible=true coins=... descend_delay=2 FLOW save record1={"cleared": true, "high_score": ...} unlocked=2 (全程无 SCRIPT ERROR) $ ... --headless --path . -- --autoplay AUTOPLAY_DONE state=playing level=2 hp=50 score=680 reactions=0 shots=10 pegs=1 (真实发射、放钉、结算均正常,无报错) $ ... --path . -- --demo-elements=.../screenshots/elements.png SCREENSHOT_SAVED ... err=0 reactions=29 $ ... --path . -- --demo-build=.../screenshots/build.png SCREENSHOT_SAVED ... err=0 pegs=3 $ ... --path . -- --demo-shop=.../screenshots/shop.png SCREENSHOT_SAVED ... err=0 items=15 $ ... --path . -- --demo-gallery=.../screenshots/gallery.png SCREENSHOT_SAVED ... err=0 found=5/10 $ ... --path . -- --demo-amulet=.../screenshots/amulet.png SCREENSHOT_SAVED ... err=0 synergies=3 $ ... --path . -- --demo-menu=.../screenshots/menu.png SCREENSHOT_SAVED ... err=0 state=menu $ ... --path . -- --demo-map=.../screenshots/map.png SCREENSHOT_SAVED ... err=0 unlocked=4 records=5 $ ... --path . -- --demo-combo=.../screenshots/combo.png SCREENSHOT_SAVED ... err=0 combo=5 targets=4 score=210 $ build/windows/BubbleRogue.exe -- --demo-menu=.../screenshots/export_run.png SCREENSHOT_SAVED .../export_run.png err=0 state=menu $ ... --headless --path . --export-release "Web" build/web/index.html (导出 0 错误;产物 index.html / index.js / index.wasm 39,514,754 B / index.pck 2,361,076 B) $ python tools/serve_web.py 8088 SERVING http://127.0.0.1:8088/ (root=build/web, .wasm -> application/wasm, 无 COOP/COEP) $ node tools/web_verify.js http://127.0.0.1:8088/ WEB_CHECK 1 页面加载无 JS 报错 PASS WEB_CHECK 2 进入主菜单 PASS WEB_CHECK 3 主菜单画面非黑屏 PASS WEB_CHECK 4 点击开始冒险进入地图 PASS WEB_CHECK 5 地图画面非黑屏 PASS WEB_CHECK 6 进入第 1 层 PASS WEB_CHECK 7 发射后产生消除/伤害 PASS (score 30, damage 2, shots 烧至 1) WEB_CHECK 8 战斗画面非黑屏 PASS WEB_CHECK 9 运行期间无 JS 报错 PASS WEB_VERIFY_RESULT pass=9 fail=0 (截图:screenshots/web_menu.png / web_map.png / web_play.png,均为中文正常渲染) $ ... --path . -- --shop-buy-check SHOP_BUY_CHECK ball=[已购买 法术球 · 火球(-32 金币)] amulet=[已购买 护符 · 幸运币(-16 金币)] ticket=[已购买 重掷券 · 抽球三选一(-12 金币)] coins=50 ... $ ... --headless --path . -- --print-save # 退出后重进,读取真实 user:// 存档 SAVE_RELOAD_CHECK path=.../bubble_rogue_save.json coins=50 balls=["fire"] amulets=["amulet_lucky_coin"] tickets=1 collection=1/10 unlocked=2 records=[1:C/1420,2:-/0,3:-/0,4:-/0,5:-/0] audio=0.80 muted=false ``` 自检覆盖:第一阶段 30 项(球类型、背包、三消、坍塌、装甲、炸弹/分裂/回旋/吸血、Boss、奖励门流程)+ 第二阶段 30 项(元素数据、反应接线、棋盘元素生成、五种消除、两种真实反应、反应/元素符文加成、总开关、雷球附魔、`apply_pegs`、弹射柱、炸药钉连锁、重力井)+ 第三阶段 41 项(存档缺失/损坏/缺字段/重置/写入重载一致、护符基础与 4 组协同、协同进伤害/回血/钉预算、商店购买/折扣/一次性/金币不足/重掷券、解锁球进初始池、图鉴首次/重复/升星/进度/稀有度、便利店状态机与离开推进、金币公式、最高分)+ 第四阶段 29 项(新档 5 层记录、旧档缺字段兼容、损坏档容错、每层通关/最高分写盘重载、主菜单/继续进度、地图 5 卡/锁定拒绝/选关、连击门参数/启动/命中/断连/成功/失败/公式/延后/继续流程、7 类音效就绪/播放/音量静音持久化/钳制、资源目录扫描兼容 `.tres.remap`)。第五阶段为 Web 无头浏览器验收 9 项(无 JS 报错、主菜单、主菜单非黑屏、地图、地图非黑屏、进入第 1 层、发射产生消除/伤害、战斗非黑屏、全程无报错),不占用 headless 自检额度。 **截图** - `screenshots/main.png`:战斗主界面(含元素泡泡与机关钉) - `screenshots/reward.png`:通关抽球三选一 - `screenshots/elements.png`:元素反应现场(蒸汽爆 / 连锁闪电,反应计数 29) - `screenshots/build.png`:自造棋盘放置界面(三钉面板、`+` 可放置空位、三颗已放钉子) - `screenshots/shop.png`:泡泡便利店(余额 120、15 件商品、已购状态、图鉴/重置/前往下一层) - `screenshots/gallery.png`:泡泡精灵图鉴(5/10、稀有度完成度、未收集剪影、详情面板) - `screenshots/amulet.png`:护符与协同(底部状态栏显示 3 组「护符×球」协同) - `screenshots/menu.png`:主菜单(开始冒险 / 继续进度 / 图鉴 / 音频设置) - `screenshots/map.png`:地下城地图(5 层、解锁/通关/最高分、锁定状态) - `screenshots/combo.png`:连击门小游戏(计时条、连击/分数/目标、可点击高亮泡泡) - `screenshots/export_run.png`:导出的 Windows exe 运行画面(双击等价启动,进入主菜单) - `screenshots/web_menu.png`:Web 版主菜单(Chromium 实机加载,中文正常) - `screenshots/web_map.png`:Web 版地下城地图(5 层卡片与解锁状态) - `screenshots/web_play.png`:Web 版战斗中(发射后得分 30、本回合伤害 2、棋盘正常渲染) ### 已修复的第一阶段 bug `_finish_shot()` 原先在 `state != "playing"` 时直接 `return`,而 `fire()` 会把状态设为 `flying`;因此真实发射后永远无法推进回合。第一阶段自检是手动把 `state` 设回 `playing` 再调 `_land()`,所以没有暴露。修复方式是允许 `flying` 状态进入结算并显式回到 `playing`,**未改动任何手感常量**。