# supermario **Repository Path**: graviton/supermario ## Basic Information - **Project Name**: supermario - **Description**: Super Mario Mixed - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-23 - **Last Updated**: 2026-10-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 混合马里奥(Super Mario Bros. Crossover)重制计划 把一个 2010 年的 Flash 同人游戏(`com.smbc.*`,ActionScript 3)重制成现代 2D 平台游戏, 并扩展出黑魂 / 假面骑士等新角色。 **现状**:4 个世界 · 16 关 · 5 场 BOSS · 9 个可选角色 · 3 档难度 · 62 个自动化测试。 ## 先看这份 - 📄 **[重制工具链调研](docs/重制工具链调研.md)** —— 资产实测数据、工具清单、引擎选型、角色扩展设计、版权提醒 - 📄 **[骑士原型设计](docs/骑士原型设计.md)** —— 2D 翻滚帧数据、重攻击、硬直、表现层 - 📄 **[难度模式设计](docs/难度模式设计.md)** —— EXERCISE / EASY / NORMAL 的差异与实现约束 - 📄 **[设计复盘](docs/设计复盘.md)** —— 44 轮里的十个关键决策、十个真实 bug、五个被否掉的想法;**想知道"为什么这么做"看这份** - 📄 **[关卡设计总表](docs/关卡设计.md)** —— 4 个世界 16 关的地形、敌人、精英、5 场 BOSS、编辑器 DSL、难度与测试表 ## 打包发给朋友 ```powershell pwsh tools/package.ps1 ``` 输出 `dist/混合马里奥-<日期>.zip`(约 70 MB),解压双击 `混合马里奥.exe` 就能玩: - **不需要装 Godot,也不需要装 .NET** —— Godot 的 Windows 导出会把 .NET 运行时一起塞进 `data_SuperMarioRemake_windows_x86_64/`(`coreclr.dll`、`hostfxr.dll`、`System.Private.CoreLib.dll` 都在里面), 也就是 self-contained 部署。 - 压缩包里还有一个 `怎么玩.txt`:键位、三个难度、四个世界的目标、以及"打不过可以逃课"的提示。 - 前置条件只有导出模板:`%APPDATA%\Godot\export_templates\4.7.2.stable.mono\` (`Godot_v4.7.2-stable_mono_export_templates.tpz` 里的 `windows_release_x86_64.exe` 等几个文件)。 导出的二进制**已经验证过**: - `混合马里奥.exe --headless -- --test=levels` → `[test] PASS levels: 33/33 load cleanly ...` - 用它自己截图(`--shot`)也能出图,说明渲染、资源、清单全部正常。 > 注意 CLI 参数要加 `--` 分隔:`混合马里奥.exe --headless -- --test=levels`。 > 前面的给引擎,后面的才进游戏。 ## 技术栈(已定) | 层 | 选择 | | --- | --- | | 游戏本体 | **Godot 4.7.2(.NET 版)+ C#** | | 资产管线 | Python 3.14 + uv(Pillow / lxml / numpy) | | 反编译 | JPEXS FFDec 26.4(`tools/ffdec/`) | C# 而不是 GDScript,是因为原版是 3.5 万行强类型 AS3:`Character` 的继承体系、常量和 状态机能近乎一对一平移过来。 ## 当前状态 | 阶段 | 状态 | | --- | --- | | SWF 解包 / AS3 反编译 | ✅ 287 个类,34,924 行 | | 美术导出 | ✅ 724 张位图 + 862 个矢量(hitbox 已剔除) | | 音频导出 | ✅ 116 个音效/音乐 | | 关卡数据 | ✅ 33 关 / 61 区域 → JSON | | 角色动画索引 | ✅ 1,296 个帧标签 → 424 个 clip(6 角色 × 3 形态) | | 图块目录 | ✅ 190 个命名 tile(按原版 tile 名索引) | | Godot 工程 | ✅ 512×480 / 60Hz / 最近邻过滤 | | **A. 1-1 可玩** | ✅ 移动/跳跃/奔跑、Goomba、踩敌、受伤死亡、重生(**敌人整波刷新**)、问号砖、金币、通关点 | | **C. 选人界面** | ✅ 9 角色可选(4 列网格)+ 难度选择,移动/跳跃/重力数值来自原版 AS3 | | **B. 新角色原型** | ✅ 灰烬人 AshenOne:翻滚(无敌帧)、**前捅**、硬直、体力、血量、顿帧 | | **B2. 双形态角色** | ✅ 假面骑士 Faiz:号码技能(103/106/279/143)+ 555 强化形态 | | **B3. 原创角色** | ✅ 猫蛋 MAO DAN:大葱剑 / 蟹钳 / 蒜沫风暴 / 昏睡 + 骑猫强化形态 | | **难度模式** | ✅ EXERCISE(无敌/无限体力/无限能量/坠坑救援)、EASY、NORMAL | | **D. 世界 1 关卡** | ✅ 1-1 → 1-4 完整流程(262 / 252 / 246 / 258 格),4 种地形主题、存档点、熔岩、自动换关 | | **E. 敌人扩充** | ✅ 新增 乌龟 / 食人花 / 煤球精灵 / **宝箱怪**,以及精英 **灰烬骑士**、**影面骑士**、**盾卫**、BOSS **薪王们的化身** | | **F. 逃课设计** | ✅ 精英脱战(leash)、1-1 砖架绕行、存档点回血(篝火式),打不过可以不打 | | **G. 选关与进度** | ✅ 选人界面第三块面板用方向键选关(`[` `]` 也行);最后一关与通关记录存在 `user://progress.cfg` | | **H. 世界 2** | ✅ 2-1 月下荒原、2-2 熔炉(**火焰喷口**)、2-3 天空回廊(全移动平台)、2-4 影之座(**噬影者**),1-4 终点直通 2-1 | | **I. 声音** | ✅ 15 个**合成**音效 + **3 段循环 BGM**(草原 / 地下 / BOSS,换关 0.5s 淡入淡出);`M` 全部静音、`N` 只关音乐。全部由 numpy 生成,无外部素材 | | **L. 标题画面** | ✅ LOGO + 角色列队 + 闪烁提示 + BGM,任何一个确认键进选人 | | **M. 世界 3 开局** | ✅ **3-1 冰原**:站在冰上会打滑(松手滑 ~75px),石台是唯一的“刹车点”;盾卫/影翼/影面骑士都站在冰上 | | **N. 3-2 碎冰洞** | ✅ **碎冰**:站上去先抖再塌(0.7s),12 格长桥必须一口气跑完;摔死会复原所有碎冰 | | **O. 3-3 冰海** | ✅ 地板是海,唯一的路是 9 块浮冰;静止冰岛留给盾卫当擂台 | | **P. 3-4 冰封王座** | ✅ **BOSS 巫妖王**:3 距离 3 招式、贴地波必须跳;过半血王座地面碎裂(复用碎冰) | | **Q. 通关画面** | ✅ 打完最后一关进 **THE END**:角色/难度/金币/死亡/用时 + 角色列队,`SPACE` 回选关 | | **R. 世界 4 开局** | ✅ **4-1 熔岩洞窟**:同屏上下两条路线(栈道安全 / 岩浆快),**选哪条都能过** | | **S. 4-2 熔岩渡口** | ✅ 整段岩浆河靠 13 块木筏过,喷口按相位喷发;岩石岛把河截成三段(掉一次只损失一段) | | **T. 4-3 矿车隧道** | ✅ **矿车**载具:自动前进、能跳、能打;车不会跳所以要在断口前跳车,掉下去只丢车不丢命 | | **U. 4-4 火焰要塞** | ✅ **BOSS 熔壳暴君**:喷火/缩壳滚/跳起震地,三招全考动作反应;场地左右都是岩浆 | | **V. 暂停菜单** | ✅ `ESC` 暂停:世界冻结、HUD 让位;继续 / **重开本关** / 回标题 / 退出 | | **W. 自动试跑** | ✅ `--autoplay` + `tools/run_autoplay.ps1`:机器人跑完 16 关,输出“最远到达格数”,当回归信号用 | | **X. 金币与续关** | ✅ **100 枚金币 = 1UP**(余数保留,HUD 常驻“还差多少”);标题显示上次进度,选关默认停在那关 | | **Y. HUD 与打击感** | ✅ HUD 拆成左上(角色+金币+1UP 进度条)/ 右上(命数+难度);击杀/受伤有**镜头轻震**并衰减 | | **Z. 死亡过场** | ✅ 死亡动画 1.5s→**1.0s**,之后**淡出 0.5s / 淡入 0.3s**;精英与 BOSS 受击 hitstop 加到 0.14s | | **AA. 弹簧** | ✅ 踩上去弹 **5.8 格**(普通跳 4.7),只在下落时触发;1-1 的弹簧能直接弹上砖架(新的逃课路线) | | **AB. 碎砖** | ✅ 顶一下就碎(`soft` 按格子标记),部分砖里藏金币;只标在装饰砖排上,不标在关键路径 | | **AC. 1-1 垂直密道** | ✅ 末段弹簧 → 砖台 → 金币 + **火焰花**:不上去也能通关,上去就带火焰进 1-2 | | **AD. 秘密的信号** | ✅ 2-1 高台加了碎砖暗格 + 蘑菇;1-2 的**金库入口水管**正上方挂 2 枚金币当提示 | | **AE. 倒计时** | ✅ 每关按 JSON 的 `time` 倒计时,剩 60s 变红 + 提示;时间到扣一条命并重置;EXERCISE 不计时 | | **AF. 直通水管** | ✅ 1-1 的 x=72 矮管把人直接送到 x=150,**完全跳过灰烬骑士**(单向,管口挂金币当提示) | | **AG. GAME OVER** | ✅ 命数耗尽不再“悄悄补命”:冻结 + 战绩画面 + `Enter 重来本关` / `Esc 回标题` | | **AH. HUD 容器化** | ✅ 还技术债:HUD 全部节点收进 `_hudRoot`,全屏界面只调 `SetHudVisible(false)`(之前三个界面各写一遍、连踩三次坑) | | **AI. 连杀** | ✅ 3 秒内连杀显示 `x3`…,第 3/6/10 杀各给一枚金币;受伤或超时就清零(只奖励、不加难度) | | **AJ. 终局两连战** | ✅ 4-4 暴君倒下后壳里钻出**真身**(更快、更短起手、火球更多、红热外观);门只在真身倒下后开 | | **AK. 通关卡** | ✅ THE END 中间站着**你用的角色**(2.6×),下面列出**真的击败过的 BOSS**;SPACE 再来一次 / ESC 回标题 | | **K. 变身道具** | ✅ `?` 块掉**蘑菇 / 火焰花**:小 → 大(一次免伤)→ **火焰**(`U` 扔火球);受伤逐级降,1-1 三块、1-2 两块 | | **J. 水管传送** | ✅ 站上水管口按 `S`/↓ 传送;1-2 出口右侧有一个**封死的金库**,16 枚金币 + 2 只宝箱怪,只能靠传送进去 | | 角色扩展 | ⬜ 更多角色(每个只需一张数值表 + 一套图 + 一个 AbilityProfile) | ## 假面骑士 Faiz(第 8 个角色) 设计取舍、号码依据、数值全部在 **[docs/假面骑士Faiz设计.md](docs/假面骑士Faiz设计.md)**, 这里只列结论。核心是**按原作手机号码发招**,而且**强化形态换一整套技能**: | 键 | 号码 | Faiz | Faiz Blaster(按 `Enter` 输 555 变身) | | --- | --- | --- | --- | | `J` | — | 拳套 | 光刃前捅(40px) | | `I` | 103 / 5214 | 单发光子弹 | 双肩炮粗光束(穿透全部) | | `O` | 106 / 103 | 三连射 | 扩散 6 连(±22°) | | `P` | 279 | 超高速 ×1.7 / 8s | ×1.9 / 10s / 前 2s 无敌 | | `;` | 143 | 深红电钻(斜向起飞 → **垂直落下**踩头 + φ 特效) | 光污染骑士踢(更大 + 落地冲击波) | | `Enter` | 555 | → 强化形态(耗 50 能量) | → 解除(免费) | **能量(Charge)而不是体力**:打中敌人才涨得快(近战 +6 / 弹丸 +3 / 自然 5 每秒), 所以必杀是"打出来的",不是站着等出来的。 ## 猫蛋(MAO DAN,第 9 个角色) 设定、取舍和数值在 **[docs/猫蛋设计.md](docs/猫蛋设计.md)**。她是原创角色 (外形参考《幽灵公主》的桑,但不借设定): | 键 | 猫蛋 | 强化形态(骑猫,`Enter` 召唤) | | --- | --- | --- | | `J` | **大葱剑**(慢、38px、伤害 4,前摇 14 帧) | **地瓜拳击**(快、36px、连击) | | `I` | **蟹钳夹击**(停在身前 60px、会夹合的蟹钳) | 地瓜·蟹钳(更大更远) | | `O` | **蒜沫风暴**(8 发整圈弹幕) | 圆圆·蒜沫(12 发) | | `P` | **机动**(8 秒加速 1.6×,25 能量) | 圆圆·机动(9 秒) | | `[` | **昏睡**(半径 150 / 3s,睡着的敌人**完全静止**、碰你不痛) | 圆圆·昏睡(半径 210 / 5s) | | `;` | **大葱剑·蓄力斩**(176×58,葱身会在挥砍时变长) / 空中变**剑刃风暴**(滞空+无敌+全向) | **圆圆吞噬**(168×76,空中也能张嘴) | | `Enter` | 召唤地瓜 + 圆圆 | 解除 | 和大葱剑的"重"对应:基础形态慢、范围大;强化形态反而更快(移速 ×1.15)但近战变短。 她的击杀印记是**猫爪**(`PhiMark.Paw`),不是 Faiz 的 φ——φ 是 Faiz 的专属符号。 ## 灰烬人 AshenOne > 原版《黑暗之魂》里的玩家角色就叫 Ashen One(灰烬人),所以角色名用它; > 代码里的内部 id 仍是 `knight`(贴图目录、`--character=knight`、`--test=knight-thrust` > 都用它,改 id 要连贴图路径一起动,没必要)。 下面正文有时简称"骑士",指的就是这个角色。 新增角色,原版 SWF 里没有,美术是 Pillow 程序生成的占位板甲(`tools/build_knight.py`)。 | 动作 | 数据 | | --- | --- | | 翻滚 `L` | 30 帧:启动 3 / **无敌 3–15 帧(13f = 217ms)** / 位移 22 帧(97px ≈ 3 格)/ 后摇 14 帧 | | **前捅** `J` | 9 帧前摇 + 5 帧判定 + 14 帧后摇;判定 **40×18**(旧版劈砍是 28×24);命中顿帧 5 帧 | | 体力 | 100 上限,翻滚 25、前捅 30,停手 0.5s 后 45/s 回复(NORMAL 下连滚 4 次) | | 生命 | 6(其他角色沿用原版一击即死) | | 受击 | 18 帧硬直 + 12 帧无敌,击退 22px | 规则(全部有对应测试): - 翻滚穿过敌人不掉血;但**无敌帧结束后仍在翻滚动画里就会挨打**——后摇是真实的。 - 翻滚撞墙照常播完后摇;滚出平台保留 60% 水平速度转为空中。 - 重攻击前摇 10 帧内可被翻滚取消,之后不可取消。 - 走路 140 / 跑 240 / 跳 600 / 重力 1250。 ## 世界 1:四关 + 精英 + BOSS 设计、数值和缺口都在 **[docs/关卡设计.md](docs/关卡设计.md)**,这里只列结论。 | 关 | 名字 | 主题 | 宽度 | 新东西 | | --- | --- | --- | --- | --- | | 1-1 | 蘑菇原野 | 草原 | 262 格 | 水管、食人花、中场精英**灰烬骑士**、存档点、**蘑菇 / 火焰花 `?` 块** | | 1-2 | 地下遗迹 | 地下 | 284 | 低天花板、柱阵、**岩浆**、煤球精灵群、两场精英、**隐藏金库(水管传送)** | | 1-3 | 云上花园 | 天空 | 246 | 全是蘑菇平台的跳跃关、**影面骑士**、两只**影翼**(飞行精英) | | 1-4 | 灰烬王城 | 城堡 | 258 | 熔岩桥、骑士卫队、**BOSS 薪王们的化身** + 封印门 | | 2-1 | 月下荒原 | 夜间 | 272 | **移动平台 / 升降台**、上下双路线、宽裂谷 | | 2-2 | 熔炉 | 熔岩 | 280 | **火焰喷口**、岩浆窄桥 + 盾卫、移动平台 | | 2-3 | 天空回廊 | 天空 | 252 | **11 块移动平台的空中关**、三段升降台、影面骑士、**影翼** | | 2-4 | 影之座 | 暗紫 | 180 | **BOSS 噬影者**(只在收招窗口可被伤害)、影厅不封门 | | 3-1 | 冰原 | 冰面 | 250 | **冰面打滑**(松手滑 ~75px)、石台是刹车点、冰上的精英战 | | 3-2 | 碎冰洞 | 冰面 | 240 | **碎冰**:站上去 0.7s 就塌,逼你一直跑(12 格长桥) | | 3-3 | 冰海 | 冰海 | 250 | **地板是海**:9 块浮冰(移动平台)+ 冰面物理,影翼盯着浮冰段 | | 3-4 | 冰封王座 | 冰宫 | 230 | **BOSS 巫妖王**:横扫 / 必须跳的贴地冰霜波 / 召唤亡灵;过半血王座地面碎裂 | | 4-1 | 熔岩洞窟 | 地下 | 240 | **上下双路线**:上层石栈道安全但贫、下层岩浆窄桥危险但肥;两路在洞厅汇合 | | 4-2 | 熔岩渡口 | 地下 | 250 | **岩浆河 + 木筏 + 喷口**:13 块移动平台掐时机过河,两块岩石岛把河截成三段 | | 4-3 | 矿车隧道 | 地下 | 260 | **矿车**(新载具):车不会跳,每次骑车的结局都是“在断口前跳下来”;掉下去车没了人还在 | | 4-4 | 火焰要塞 | 要塞 | 240 | **BOSS 熔壳暴君**:喷火 / 缩壳滚 / 跳起震地(三招全考动作反应),左右都是岩浆 | - **关卡比原版长**(原版 1-1 是 223 格):角色技能多了,同样的距离对他们更短。 - **难度是用表量出来的**:`uv run python tools/level_stats.py` 会打印每关的可站%、 最大空隙、敌人密度;这套表第一次跑就抓出 2-2/2-4 有两处**根本跳不过去**的岩浆, 以及 1-3 一个 6 格长跳。现在 `--test=levels` 也带同样的门禁。 - 地形、敌人位置都在 `tools/build_ours_levels.py` 里用 DSL 写,产物是 `game/assets/levels/ours/*.json`;`tools/preview_level.py` 能不开引擎就渲出整关 PNG。 - 关卡文件自己声明 `solid` / `hazard`,所以装饰图块永远不会变成墙。 ### 新敌人 | 名字 | HP | 招式 | 破解 | | --- | --- | --- | --- | | 煤球精灵 | 1 | 每 0.85s 跳一次,追着玩家跳 | 翻滚 / 一击 | | 乌龟 | 1 | 踩一下缩壳,再踢就滑出去撞死别的敌人 | 踩→踢 | | 食人花 | 1 | 从管口升起咬人;玩家站管口时不出来 | 站管口等它下去 | | **灰烬骑士**(精英) | 8 | 举剑 0.55s → 58×36 横扫 → 0.85s 硬直 | 翻滚穿过横扫,硬直反打 | | **影面骑士**(精英) | 6 | 下蹲 0.42s → 430px/s 冲刺踢 → 滑行 | 翻滚(它会冲过头) | | **薪王们的化身**(BOSS) | 40 | 砸地冲击波 / 火球扇;**二阶段**换成三连砸 + 8 发火焰环 + 冲锋 | 跳冲击波、走缝隙、翻滚冲锋 | | **宝箱怪** | 4 | 装成宝箱,靠近才张嘴扑上来;死后**按需给货**(火焰花 / 蘑菇 / 金币) | 可以整个跳过,也可以踩死 | | **盾卫**(精英) | 8 | 举塔盾逼近,近了盾撞;**正面伤害全挡** | 翻滚到背后打(挡一下后 0.35s 不能转身),必杀穿透,或者绕过去 | | **影翼**(精英·飞行) | 5 | 在自己那段空域盘旋,你从下面靠近就后仰再俯冲 | 俯冲后有 0.55s 硬直;**跑出它的空域它就飞回去** | | **巫妖王**(BOSS) | 46 | 横扫 / 贴地冰霜波 / 召唤亡灵(3 距离 3 招式),过半血后**王座地面碎裂** | 冰霜波**必须跳**;召唤时本体不动,是输出窗口 | | **熔壳暴君**(BOSS) | 50 | 喷火扇 / 缩壳滚 / 跳起震地(3 距离 3 招式,全部要求动作反应) | 火球钻空隙、壳贴地可跳过、落地冲击波必须跳 | **逃课**:精英只在 240px 内仇恨你,跑出范围它会走回原位(`leash` 测试守着这条); 1-1 的灰烬骑士头顶还有一条砖架可以从上面绕过去;存档点第一次碰到会**回满血** (篝火式),所以长关卡不会因为一次失误从头再来。 精英和 BOSS **踩不死**(踩上去扣自己的血),这是让“只会踩”的马里奥也必须绕路的设计。 ## 难度模式 在选人界面的第二块面板确认,或启动时 `--difficulty=`。 | | EXERCISE | EASY | NORMAL | | --- | --- | --- | --- | | 定位 | 练习 / 看地图 | 不想被劝退 | 设计基准 | | 受伤 | **完全忽略** | 正常 | 正常 | | 坠坑 | **救援**(回到最后站稳处) | 掉命 | 掉命 | | 体力 | **不消耗** | 140 上限(5 滚) | 100(4 滚) | | 翻滚无敌 | 13 帧(但打不到) | **17 帧** | 13 帧 | | 跳跃/移速 | ×1.00 | ×1.08–1.12(跳高 +19%) | ×1.00 | | 生命 | — | 骑士 8 | 骑士 6 | EASY **只加强角色、不改敌人**:改敌人会让玩家在 EASY 练出错误的时机感。 EXERCISE **保留敌人但关闭伤害**——"滚过 Goomba"正是要练的东西。 ## 角色数值(取自原版 AS3) | 角色 | 走 | 跑 | 跳跃 | 重力 | 受击框 | | --- | --- | --- | --- | --- | --- | | MARIO | 150 | 300 | 650 / 700(跑动) | 1400 | 24×32 | | LINK | 150 | 240 | 530 | 1000 | 30×32 | | SAMUS | 180 | 260 | 500 | 700 | 44×66 | | SIMON | 150 | 240 | 550 | 1000 | 32×60 | | BILL | 150 | 240 | 550 | 1000 | 68×70 | | MEGA MAN | 150 | 240 | 580 | 1100 | 40×48 | | ASHEN ONE 🆕 | 140 | 240 | 600 | 1250 | 24×32 | | FAIZ 🆕 | 170 | 285 | 620 | 1180 | 22×30 | | MAO DAN 🆕 | 150 | 265 | 600 | 1200 | 22×30 | > Faiz 强化形态把移速再乘 0.9(更重的光子血液装甲),所以跑图用基础形态、清场按 555。 跳跃力/重力直接来自 `Mario.as`、`Link.as` 等文件的初始化块(`jumpPwr`、`gravity`); 非 Mario 的跑速是按走速外推的,原版只有 Mario 有明确 `MAX_RUN_SPEED = 300`。 ## 键位 左边 WASD,右边 J K L ; 加 U I O P 一排,Space 用拇指。 | 动作 | 按键 | | --- | --- | | 移动 | `A` `D`(或 ← →) | | **进水管(传送)** | 站在水管口按 `S` / ↓ | | **扔火球**(火焰形态) | `U` | | **暂停** | `ESC`(继续 / 重开本关 / 回标题 / 退出) | | 跳跃 | `Space` / `K`(也可 `W` / ↑) · 手柄 `A` | | 奔跑(按住) | `Shift`(`L` 仍可,兼容老角色) | | **闪避 / 翻滚** | `L` | | 近战(骑士=前捅 / Faiz=拳套·光刃) | `J` | | **必杀**(Faiz 的 143) | `;` | | 道具 | `U` | | **技能槽 1 / 2 / 3 / 4** | `I` / `O` / `P` / `[`(Faiz:单发 / 连射 / 超高速;猫蛋:蟹钳 / 蒜沫 / 机动 / 昏睡) | | **形态切换**(Faiz 的 555) | `Enter` | | 暂停 | `Esc` | | **静音 / 开声音** | `M`(横幅会显示 SOUND OFF / SOUND ON) | | **音乐开 / 关** | `N`(横幅会显示 MUSIC ON / MUSIC OFF);11 首主题 BGM,见 [docs/关卡设计.md](docs/关卡设计.md) §2.596 | 选人界面是 **角色 → 难度 → 关卡** 三块面板,逐块确认: | 操作 | 作用 | | --- | --- | | `← → ↑ ↓` / `WASD` | 在**当前**面板里移动(角色 4 列网格 / 难度一行 / 关卡 8 列 × 2 行) | | `Space` | 确认这一块(打上 ✓ 并变绿),焦点跳到下一块;在已确认的块上再按一次就退回来重挑 | | `Enter` | 开始游戏(三块都 ✓ 时标题会变成「全部就绪 — 按 ENTER 开始」) | | 鼠标 | 移到选项上高亮,左键 = 选中并确认(等于 `Space`) | | `Esc` | 返回标题 | 角色格子高度和精灵会随行数自动缩小;`Q`/`E`、`[`/`]` 作为老捷径仍然能用。 `I O P [` 不绑定具体招式,而是绑定**槽位语义**(单发 / 连射 / 机动 / 第四技能): 以后加角色只需往槽位里填东西,玩家不用重新学键位。HUD 右下角会显示当前形态 每个键对应的号码,所以不用背招。猫蛋的昏睡在**槽位 4**(`[` / `RT`), 机动(加速)在槽位 3(`P` / `LT`),和 Faiz 一致。 ### 手柄(Xbox 键位) | 手柄 | 作用 | 键盘 | | --- | --- | --- | | A / X / B / Y | 跳 / 攻击 / 翻滚 / 必杀 | Space / J / L / ; | | LB / RB / LT / RT | 技能 1 / 2 / 3 / 4 | I / O / P / `[` | | L3(按下左摇杆) | 跑(按住) | Shift | | 左摇杆 · 十字键 | 移动 | WASD · 方向键 | | Start | 暂停(选人界面 = 开始) | ESC / Enter | | Back | 选人界面返回标题(游戏里 = 变身) | ESC / Enter | | M1 / M2 背键 | 道具 / 静音 | U / M | **热插拔**:手柄事件注册进同一套 InputMap 动作,Godot 4 的动作匹配只看按键、 不看设备,所以运行中插上直接用;键盘和手柄**同时有效**。 **提示用哪一套**:暂停菜单第 5 行「输入设备」可选 `自动 / 键盘 / 手柄`, 自动模式下按手柄就切到手柄提示、动键盘就切回。选择存在 `user://settings.cfg`。 截图调试可以用 `--input=pad` 强制手柄提示。 细节见 [docs/关卡设计.md](docs/关卡设计.md) §2.597。 所有按键集中在 [game/scripts/InputActions.cs](game/scripts/InputActions.cs),运行时建 InputMap。 接手柄时只需在那里补 `InputEventJoypadButton`,玩法代码读的是 action 名,不用改。 ## 跑起来 ```powershell # 1. 资产管线(Python 侧,可重复执行) uv sync uv run python tools/build_levels.py # 原版关卡(参考用) uv run python tools/build_assets.py uv run python tools/build_knight.py # 新角色 / 新敌人占位美术 uv run python tools/build_faiz.py uv run python tools/build_maodan.py uv run python tools/build_enemies.py uv run python tools/build_ours_levels.py # 世界 1 关卡 # 换过 PNG 之后要重新导入一次 & $godot --headless --path game --import # 2. 编译 + 运行(Godot) cd game dotnet build & "C:\MyProgram\Godot_v4.7.2\Godot_v4.7.2-stable_mono_win64_console.exe" --path . # 3. 自动化截图(验证渲染是否回归) & $godot --path game -- --shot=out.png --shot-frame=160 --demo ``` | 参数 | 作用 | | --- | --- | | `--shot=` | 第 N 个物理帧截图后退出(选人界面也支持;**用绝对路径**) | | `--shot-frame=N` | 指定 N(默认 4) | | `--demo` | 自动向右跑 + 周期跳跃/翻滚,用于验证物理/相机 | | `--character=` | 跳过选人,直接以指定角色开局。`ashen`(= `knight`,内部 id 仍叫 knight)、`faiz`、`maodan`、`mario` … | | `--difficulty=` | `exercise` / `easy` / `normal`(默认 normal) | | `--pose=<动作>` | 脚本化一个动作,配合 `--shot-frame` 出可复现截图:`roll` `attack` `thrust` `shot` `burst` `ult` `form` `axel` | | `--level=` | 从指定关卡开局:`1-1` … `1-4`,`test*` 是测试场(选人界面里也能按 `[` `]` 选) | | `--at=<格>` | 把玩家直接放到第 N 格(配合 `--shot` 检查某个场景) | | `--skip-select` | 跳过选人(默认 Mario) | | `--input=pad\|keyboard\|auto` | 强制提示用哪一套(截图/文档用,不写设置文件) | | `--ending` | 直接看通关画面(带示例数据,用于截图/调版式) | | `--autoplay` | 让机器人把 16 关各跑一遍,输出每关最远到达的格数(回归信号,配 `--fixed-fps 60` 加速) | | `--reborn` | 直接看 4-4 的第二形态(配 `--skip-select --level=4-4`,用于截图/调数值) | | `--screen=select` / `--screen=title` | 直接打开某个前台界面(截图/调版式用,不用按键) | | `--test=jump` | 断言跳跃顶点 ≈151px(原版数值) | | `--test=stomp` | 从 Goomba 上方落下,断言踩死、玩家存活、尸体被清理 | | `--test=block` | 顶问号砖,断言金币 +1 | | `--test=respawn` | 打死一只 Goomba 后让玩家阵亡,断言重生时整波敌人全部复原 | | `--test=exit` | 传送到终点,断言通关 | | `--test=roll-iframes` | 翻滚穿过 Goomba,断言不掉血且能穿过去 | | `--test=roll-recovery` | 断言 3–15 帧无敌成立、后摇里同一只 Goomba 能打中 | | `--test=roll-stamina` | 断言连续第 5 次翻滚被拒绝(体力只够 4 次) | | `--test=heavy-hit` | 断言近战击杀 Goomba 且产生顿帧 | | `--test=ashen-thrust` | 断言前捅够到 48px 外的 Goomba(旧 28px 判定够不到,改动的回归护栏)。旧名 `knight-thrust` 仍可用 | | `--test=faiz-shot` | 断言 103 只发 1 颗弹、能打中 150px 外 | | `--test=faiz-burst` | 断言 106 一次扳机发 3 发 | | `--test=faiz-ult` | 断言 143 **斜向起飞后垂直落下**(上升段与下落段 x 相同)、踩死敌人、出 φ 特效 | | `--test=faiz-form` | 断言 555 换成强化形态且血量上限不变 | | `--test=faiz-axel` | 断言 279 把走速提到 289px/s(170×1.7) | | `--test=maodan-claw` | 断言蟹钳停在身前 60px 处并夹死敌人(不是飞出去的弹) | | `--test=maodan-storm` | 断言蒜沫风暴一次放出 8 发整圈弹 | | `--test=maodan-sleep` | 断言昏睡让敌人**完全静止**(栗宝宝 + 管道食人花 + 影翼三只,60 帧后位置一动不动),且撞上去不掉血 | | `--test=maodan-ult` | 断言蓄力斩够到身前 80px;空中发动则滞空、无敌,并且吃掉**身后**的敌人(前向锥形永远做不到) | | `--test=maodan-form` | 断言召唤两只猫后换套技能、血量上限不变 | | `--test=maodan-kit` | 断言两套数值就是设计稿:昏睡半径 150/210、拳击 36×26、斩 176×58(空中转风暴)、吞噬 168×76(前向、可空中)、蟹钳走 claw 画法 | | `--test=maodan-devour` | 断言吞噬能够到身前 100px 的敌人,且**在空中张嘴时会悬停**(不掉下去) | | `--test=levels` | 断言 8 个关卡文件都能解析(1 起点 / 1 终点 / 无未知图块 / 熔岩不是实心) | | `--test=koopa-shell` | 断言踩→缩壳→踢飞→滑行的壳撞死路径上的栗宝宝 | | `--test=piranha` | 断言食人花会升起也会缩回 | | `--test=elite-knight` | 断言灰烬骑士会举剑、横扫,并被突刺打死 | | `--test=elite-rider` | 断言影面骑士会下蹲、冲刺,并被 6 点伤害打死 | | `--test=boss` | 断言薪王们的化身会放招,掉 40 点血后开门 | | `--test=boss-rage` | 断言二阶段换成三连砸 + 8 发火焰环 | | `--test=lava` | 断言碰到熔岩即死 | | `--test=checkpoint` | 断言存档点回血,且死后从存档点重生 | | `--test=progress` | 断言碰 1-1 终点后自动载入 1-2 | | `--test=mimic` | 断言宝箱怪被靠近触发、4 下打死并掉金币 | | `--test=leash` | 断言精英会追、玩家跑远后走回原位并清仇恨 | | `--test=shield-block` | 断言盾卫正面伤害归零、背面掉血 | | `--test=platform-ride` | 断言横向平台载人、升降台抬人 | | `--test=geyser` | 断言火焰喷口会喷火并烧伤玩家 | | `--test=boss-shade` | 断言噬影者平时免伤、收招窗口内掉血 | | `--test=audio` | 断言 15 个音效 + 3 段 BGM 全部加载、BGM 是循环、切歌生效 | | `--test=warp` | 断言踩水管口按 ↓ 会传送到别处、并能再传回来 | | `--test=powerup` | 断言 `?` 块掉蘑菇、吃到变大、挨打先缩小不掉血 | | `--test=fire` | 断言火焰形态能扔火球、受伤逐级降(火焰→大→小→掉血) | | `--test=elite-wing` | 断言影翼会盘旋、俯冲,并且 5 下可以打死 | | `--test=ice` | 断言冰面会打滑(松手后还前进 30–150px) | | `--test=crumble` | 断言碎冰先撑住你、再塌掉让你掉下去 | | `--test=boss-lich` | 断言巫妖王过半血进二阶段、地面碎裂、且能被磨死 | | `--test=ending` | 断言打完最后一关会进 THE END 画面 | | `--test=cart` | 断言矿车会载人前进、掉进断口后车没了但人还在 | | `--test=boss-tyrant` | 断言熔壳暴君会跳、会放火/冲击波、并且能被磨死 | | `--test=pause` | 断言暂停时世界冻结、重开本关回到起点 | | `--test=oneup` | 断言 100 枚金币给一条命、余数保留 | | `--test=shake` | 断言击杀触发镜头震动、并且会衰减回 0 | | `--test=fade` | 断言死亡淡出、重生淡入、并且扣命 | | `--test=spring` | 断言弹簧把人弹起 6 格(比普通跳高) | | `--test=brick` | 断言碎砖顶一下就碎、并掉出藏着的金币 | | `--test=timer` | 断言倒计时会走完,走完扣一条命 | | `--test=bypass` | 断言 1-1 的直通水管把人送到灰烬骑士另一侧 | | `--test=gameover` | 断言命数耗尽弹 GAME OVER、重来会给回 3 条命 | | `--test=warp-return` | 断言 1-2 金库回程把人送回走廊、且落点不在墙里 | | `--test=coin-touch` | 断言站在金币下方就能吃到 | | `--test=unstick` | 断言卡进墙里的人物会被自动抬出来 | | `--test=press-guard` | 断言“按着空格进入选人界面”不会被同一次按键确认(模拟真实按键) | | `--test=combo` | 断言三连杀显示 x3、里程碑给币、超时清零 | | `--test=reborn` | 断言 4-4 暴君倒下后召出真身 | | `--test=difficulty --difficulty=exercise` | 断言无敌、坠坑救援、体力不消耗 | | `--test=difficulty --difficulty=easy` | 断言跳跃顶点升到 ≈180px | 测试输出 `[test] PASS/FAIL`,退出码 0/1,方便以后接 CI。跑全部: ```powershell pwsh tools/run_tests.ps1 # 80 个场景 + 一次几何 lint ``` ## 截图 —— 你能玩到什么 按「开始 → 世界 → BOSS → 机制 → 界面」排,不按开发顺序。 ### 开始 | | | | --- | --- | | ![标题:9 个角色列队,还会告诉你上次](docs/images/milestone-33-title.png) | ![选人:角色 / 难度 / 关卡三列,](docs/images/milestone-6-select.png) | | **标题**:9 个角色列队,还会告诉你上次打到哪一关 | **选人**:角色 / 难度 / 关卡三列,WSAD 选、`j` 确认 | | ![EXERCISE 练习模式:无敌、无](docs/images/milestone-4-exercise.png) | ![1-1 的中场精英灰烬骑士:举剑就是](docs/images/milestone-9-1-1.png) | | **EXERCISE** 练习模式:无敌、无限体力、坠坑救援、不计时 | 1-1 的中场精英**灰烬骑士**:举剑就是读招,翻滚能穿过去 | ### 四个世界 | | | | --- | --- | | ![1-2 地下遗迹:低天花板 + 柱阵](docs/images/milestone-9-1-2.png) | ![1-3 云上花园:蘑菇平台,掉下去就](docs/images/milestone-9-1-3.png) | | 1-2 地下遗迹:低天花板 + 柱阵 + 煤球精灵 | 1-3 云上花园:蘑菇平台,掉下去就没了 | | ![1-4 灰烬王城:薪王们的化身,血条在正](docs/images/milestone-9-1-4-boss.png) | ![2-1 月下荒原:夜间、上下双路线、](docs/images/milestone-13-2-1.png) | | 1-4 灰烬王城:**薪王们的化身**,血条在正上方 | 2-1 月下荒原:夜间、上下双路线、升降台 | | ![2-2 熔炉:岩浆是地面,喷口是节拍](docs/images/milestone-14-2-2.png) | ![2-3 天空回廊:全是移动平台,看时](docs/images/milestone-15-2-3.png) | | 2-2 熔炉:岩浆是地面,喷口是节拍器 | 2-3 天空回廊:全是移动平台,看时机不看手速 | | ![2-4 影之座:噬影者,只在收招窗口](docs/images/milestone-16-2-4.png) | ![3-1 冰原:松手还会滑 30–15](docs/images/milestone-23-ice.png) | | 2-4 影之座:**噬影者**,只在收招窗口掉血 | 3-1 冰原:松手还会滑 30–150px | | ![3-2 碎冰洞:碎冰先撑住你,0.7](docs/images/milestone-24-crumble.png) | ![3-3 冰海:浮冰会沉,落水即死](docs/images/milestone-25-sea.png) | | 3-2 碎冰洞:碎冰先撑住你,0.7s 后塌 | 3-3 冰海:浮冰会沉,落水即死 | | ![3-4 冰封王座:巫妖王,过半血后王](docs/images/milestone-26-lich.png) | ![4-1 熔岩洞窟:上下双路线,岩浆在](docs/images/milestone-28-cave.png) | | 3-4 冰封王座:**巫妖王**,过半血后王座地面碎裂 | 4-1 熔岩洞窟:上下双路线,岩浆在下面等 | | ![4-2 熔岩渡口:木筏 + 喷口,走](docs/images/milestone-29-river.png) | ![4-3 矿车隧道:矿车自动前进,车不](docs/images/milestone-30-cart.png) | | 4-2 熔岩渡口:木筏 + 喷口,走错一步就下水 | 4-3 矿车隧道:**矿车**自动前进,车不会跳所以要在断口前跳车 | | ![4-4 火焰要塞:熔壳暴君,左右都是](docs/images/milestone-31-tyrant.png) | ![打完第一形态壳里钻出真身:更快、起手](docs/images/milestone-42-reborn.png) | | 4-4 火焰要塞:**熔壳暴君**,左右都是岩浆 | 打完第一形态壳里钻出**真身**:更快、起手更短、火球更多 | ### 机制 | | | | --- | --- | | ![顶 ? 块掉蘑菇:变大 = 一次免伤](docs/images/milestone-18-mushroom.png) | ![火焰花:U 扔火球](docs/images/milestone-19-fire.png) | | 顶 `?` 块掉**蘑菇**:变大 = 一次免伤 | **火焰花**:`U` 扔火球 | | ![盾卫:塔盾挡住整个正面,只能绕背](docs/images/milestone-11-shield.png) | ![水管传送:站上管口按 ↓](docs/images/milestone-17-pipe.png) | | **盾卫**:塔盾挡住整个正面,只能绕背 | **水管传送**:站上管口按 ↓ | | ![传送进去的隐藏金库:金币 + 宝箱怪](docs/images/milestone-17-vault.png) | ![弹簧:弹起 5.8 格,只在下落时触](docs/images/milestone-35-spring.png) | | 传送进去的**隐藏金库**:金币 + 宝箱怪 | **弹簧**:弹起 5.8 格,只在下落时触发 | | ![1-1 末段的垂直密道:弹上去拿火焰](docs/images/milestone-36-secret.png) | ![秘密要有信号:金库管口上方挂两枚金币](docs/images/milestone-37-hint.png) | | 1-1 末段的**垂直密道**:弹上去拿火焰花 | 秘密要有信号:金库管口上方挂两枚金币 | ### 界面 | | | | --- | --- | | ![ESC 暂停:继续 / 重开本关 /](docs/images/milestone-40-hudroot.png) | ![GAME OVER:战绩 + 重来 ](docs/images/milestone-39-over.png) | | `ESC` **暂停**:继续 / 重开本关 / 回标题 / 退出 | **GAME OVER**:战绩 + 重来 / 回标题 | | ![连杀:3 秒内连杀显示 x3,里程碑](docs/images/milestone-41-combo.png) | ![THE END:你的角色 + 战绩 ](docs/images/milestone-43-end.png) | | **连杀**:3 秒内连杀显示 `x3`,里程碑给金币 | **THE END**:你的角色 + 战绩 + 击败名单 | | ![选人界面 16 关](docs/images/milestone-44-select-grid.png) | | | 修好后的选人界面:16 关排成 8×2,全部可见 | | > 截图必须**带窗口**跑(不要 `--headless`):headless 下 `RenderingServer.FramePostDraw` > 不触发,`--shot` 会一直等下去。 ## 资产管线 ``` inputs/*.swf │ FFDec -export sprite/image/sound/shape ▼ work/nested_main.swf ──(去掉 HRect 碰撞盒)──► work/nested_nohit2.swf │ FFDec -format sprite:png ▼ work/sprite_png2/DefineSprite__/.png │ tools/anim_index.py 帧序号 ↔ 帧标签 │ tools/build_assets.py 按 clip 归组、生成 manifest ▼ game/assets/{sprites,tiles,levels}/ + manifest.json ▲ │ tools/build_knight.py 骑士占位美术(原版没有这个角色) │ tools/build_faiz.py Faiz 占位美术(2 形态 × 10 clip) │ tools/build_maodan.py 猫蛋占位美术(2 形态 × 20 clip) │ tools/build_enemies.py 新敌人占位美术(煤球/灰烬骑士/影面骑士/薪王们的化身/宝箱怪/盾卫/噬影者) │ tools/build_audio.py 合成音效(不依赖原版素材) │ tools/build_music.py 合成循环 BGM └─ assets/sprites/generated/ + manifest.{generated,faiz,maodan,enemies}.json ``` 生成的 manifest **在运行时叠加**到 `manifest.json` 上([Manifest.cs](game/scripts/Manifest.cs)): 每个新角色一个文件,所以重跑 `build_assets.py` 不会删掉新角色, 重跑 `build_knight.py` 也不会删掉 Faiz。 关键结论:**帧标签就是动画名**。原版用 `convLab()` 把 `walk-1` 拼成 `walk-1_1` (`_1` = 小体型,`_2` = 大体型,`_3` = 火力形态),所以 1,296 个标签天然就是 「角色 × 形态 × 动画」,不需要人工标注。 ## 已知缺口 - 水管网络现在有两处(1-2 的金库往返对、1-1 的单向直通管),但还很少; 倒计时已经做了(第 36 轮)。 - 音乐有 11 段无缝循环(`title` / `select` / `meadow` / `underground` / `castle` / `night` / `sky` / `ice` / `boss` / `final` / `ending`,见 [docs/关卡设计.md](docs/关卡设计.md) §2.596), 切曲有 0.5s 交叉淡入,BOSS 出现时切战斗曲;但**没有**随战斗强度分层的动态音轨。 - BOSS 已经是多招式 + 二阶段(薪王们的化身会连砸/冲锋,巫妖王会碎地,暴君会滚/跳), 但**没有破防/连段系统**:打起来是"读招—还手"的循环,不是动作游戏的连招。 - 灰烬人是**原型**:占位美术、剑没有拖尾/轨迹,没有格挡/弹反、 没有变强循环(魂/篝火)。**计划中**:击败 1-4 的 BOSS 后本局可变身成它的形态 「薪王们的化身」——只写了设计,没实现,见 [docs/骑士原型设计.md](docs/骑士原型设计.md) 第 11 节。 - Faiz 是**原型**:占位美术;没做的原作技能有 Faiz Edge 能量环束缚(需要"定身"状态)、 Faiz Shot 点穴、Axel 的多连深红电钻、摩托、飞行。 - **小怪**都是一击死(伤害数字不影响手感,区分招式的是范围/穿透/无敌帧); 精英 5–8 HP、BOSS 40–50 HP,伤害在那里才真正起作用。 - `--demo` / `--at` 这些开发用参数还在(截图和机器人复用), 但已经不是唯一的验证手段了——`--test=*` 有 80 个场景。 - 其他角色没有闪避/攻击动作——`AbilityProfile` 里 `CanRoll/CanHeavyAttack/CanShoot` 是关的, 加动作只需补 clip + 打开开关。 - 金币现在是**实体**(关卡里 `coin` 图元 + 碎砖里藏的金币),不再依赖原版 "砖块格内嵌金币"的机制;那个导入期的限制已经不适用了。 - tile 名可带后缀(`groundMushroomOrangeMid&&charVert&&`),目前只取基名,还没处理翻转/位移修饰。 - 碰撞用「tile 所属 AS3 类」推断(`com.smbc.ground.*` 实心,`Scenery` 装饰), 个别 tile(管道内部、栏杆)还需按原版 `Ground.as` 的判定细化。 - 音频已接入:15 个音效(`tools/build_audio.py`)+ 11 段无缝 looping BGM (`tools/build_bgm.py` 从本地曲库里剪,输出 OGG,运行时给 `AudioStreamOggVorbis` 设 `Loop`;缺 OGG 时退回 `AudioStreamWav` + `LoopMode = Forward`)。 - 帧率与网格:渲染 ~120fps、物理固定 60Hz,所有玩法逻辑必须写在 `_PhysicsProcess` 里。 ## 工具 | 脚本 | 作用 | | --- | --- | | `tools/swf_probe.py` | SWF 头 / tag 直方图 / 资产清单 / 内嵌 SWF 定位 | | `tools/level_probe.py` | 抽取 LEVELDATA XML,统计关卡、区域、tile 名 | | `tools/anim_index.py` | 帧序号 → 帧标签(遍历 DefineSprite 时间轴) | | `tools/build_assets.py` | 生成 `game/assets/` + manifest | | `tools/build_knight.py` | 骑士占位美术 + `manifest.generated.json`(新角色,不来自 SWF) | | `tools/build_faiz.py` | Faiz 占位美术(两套形态)+ `manifest.faiz.json` | | `tools/build_levels.py` | LEVELDATA XML → 每关一个 JSON(原版关卡,留作参考) | | `tools/build_ours_levels.py` | **世界 1 关卡源文件**:DSL → `levels/ours/*.json` + `index.json` | | `tools/preview_level.py` | 把关卡 JSON 渲成整关 PNG(不开引擎就能看地形) | | `tools/build_enemies.py` | 新敌人占位美术 + `manifest.enemies.json` | | `tools/build_audio.py` | 合成 14 个音效 WAV(方波/噪声 + 包络,numpy) | | `tools/build_music.py` | 合成 3 段循环 BGM(小音序器:琶音 + 贝斯 + 鼓) | | `tools/level_stats.py` | 关卡难度对照表(可站% / 最大空隙 / 敌人密度),并给出宽空隙的位置 | | `tools/build_bgm.py` | 从 D: 上的音源切出 11 首主题 BGM 循环(OGG,-19 LUFS,58–84s) | | `tools/level_walls.py` | **几何 lint**:找出比分左右两侧都高出 4.7 格(跳跃上限)的墙——那种墙会让关卡走不通 | | `tools/run_autoplay.ps1` | 机器人跑 16 关,输出每关最远到达格数(`--fixed-fps 60` 加速) | | `tools/run_tests.ps1` | 一次跑完全部 `--test=` 场景 | | `tools/sprite_dump.py` | 调试:打印某个 symbol 的嵌套结构 | | `tools/tile_report.py` | 校验关卡用到的 tile 是否都已导出 | > 注意:`ffdec.bat` 在中文/长路径下会异常退出,请用 `java -jar tools\ffdec\ffdec.jar ...`。 > `DefineBinaryData` 用 `-export binaryData`(不是 `binary`)。