Appearance
资源转换管线 — 练习
练习 1:分析 generate_wasm_assets.py 的扫描逻辑
目标:理解 Python 脚本如何从 C 源代码中提取资源依赖信息。
任务:
- 阅读
generate_wasm_assets.py的主函数,描述其工作流程 - 找到用于匹配
INCGFX和INCBIN宏的正则表达式,解释其匹配模式 - 统计一个典型目录(如
graphics/pokemon/)下的资源文件数量,估算这些资源转换后的 C 数组总大小
参考答案
1. 工作流程:
2. 正则表达式分析:
python
# 典型的匹配模式
INCGFX_PATTERN = r'INCGFX\s*\(\s*"([^"]+)"\s*\)'
INCBIN_PATTERN = r'INCBIN\s*\(\s*"([^"]+)"\s*\)'INCGFX\s*\(— 匹配 "INCGFX" 后跟可选空白和左括号\s*"([^"]+)"\s*— 匹配引号内的文件路径(捕获组)\)— 匹配右括号
捕获组 ([^"]+) 提取资源文件的相对路径。
3. 资源估算:
以 graphics/pokemon/ 为例:
| 资源类型 | 数量 | 单个大小 | 总大小估算 |
|---|---|---|---|
| 精灵正面图 (.4bpp) | ~400 | 1-4 KB | ~800 KB |
| 精灵背面图 (.4bpp) | ~400 | 1-2 KB | ~400 KB |
| 精灵调色板 (.pal) | ~400 | 32 字节 | ~12 KB |
| 图标 (.4bpp) | ~400 | 256 字节 | ~100 KB |
| 合计 | ~2000 | ~1.3 MB |
转换为 C 数组后,每个字节表示为 0xHH, 形式(约 6 字节/原始字节),源码膨胀约 6 倍。但编译后的 .wasm.o 中数据以二进制存储,体积与原始数据接近。
练习 2:理解地图数据的转换过程
目标:掌握 wasm_asm_data.py 如何将 GBA 汇编地图数据转换为 C 数组。
任务:
- 找到项目中一个地图数据的汇编源文件(如
data/maps/PetalburgCity/下的.s文件) - 描述该文件中的数据结构(
.byte、.halfword、.word指令的含义) - 说明 wasm_asm_data.py 如何处理
.align对齐指令
参考答案
1. 地图汇编文件示例:
asm
.include "constants/map_groups.inc"
.include "constants/flags.inc"
.global gMapHeader_PetalburgCity
gMapHeader_PetalburgCity:
.byte 30, 20 /* 地图宽度, 高度 (图块数) */
.byte MAP_GROUP_PETALBURG, MAP_NUM_PETALBURG_CITY /* 地图组, 编号 */
.byte MAP_LAYOUT_PETALBURG_CITY /* 布局 ID */
.byte 0 /* 填充 */
.halfword FLAG_VISITED_PETALBURG /* 访问标志 */
.byte WEATHER_SUNNY /* 天气类型 */
.global gMapLayout_PetalburgCity
gMapLayout_PetalburgCity:
.byte 30, 20 /* 宽度, 高度 */
.word 0x0600E800 /* 图块映射表地址 (VRAM 偏移) */
.word gMetatiles_PetalburgCity /* 元图块数据指针 */
.word gMetatileAttr_PetalburgCity /* 元图块属性指针 */2. 数据指令含义:
| 汇编指令 | 大小 | 说明 |
|---|---|---|
.byte value | 1 字节 (8位) | 小数值、枚举常量、标志位 |
.halfword value | 2 字节 (16位) | 标志位组合、坐标、计数器 |
.word value | 4 字节 (32位) | 内存地址、指针、大数值 |
.ascii "text" | N 字节 | 字符串数据(无终止符) |
.space N | N 字节 | 零填充/对齐 |
3. .align 对齐处理:
.align n 要求下一条数据对齐到 2^n 字节边界。在转换为 C 数组时,wasm_asm_data.py 需要:
python
def handle_align(current_offset, alignment):
"""计算并插入填充字节"""
boundary = 1 << alignment # 2^n
padding = (boundary - (current_offset % boundary)) % boundary
return b'\x00' * padding在 C 数组输出中体现为:
c
// .align 2 后插入 2 字节对齐填充
const u8 mapData[] = {
0x1E, 0x14, // 宽度, 高度
0x00, 0x00, // .align 2 填充
0x08, 0xE8, 0x00, 0x06 // 4 字节地址 (对齐后)
};对齐的正确处理对于游戏代码中的指针访问至关重要——GBA 的 16 位和 32 位内存访问必须在偶数/4 字节对齐地址上进行。
练习 3:设计自定义资源的转换流程
目标:能够为新类型的资源设计完整的转换管线。
任务:
- 假设你要添加一个新的精灵动画数据(JSON 格式),设计从 JSON 到 WASM 可用数据的完整转换流程
- 说明转换后的数据格式应满足哪些约束(对齐、字节序、与游戏代码的接口)
- 编写伪代码描述转换脚本的核心逻辑
参考答案
1. 完整转换流程设计:
2. 数据约束:
| 约束 | 说明 |
|---|---|
| 字节序 | GBA 使用小端序(Little-Endian),与 WASM 默认一致,无需转换 |
| 对齐 | 结构体必须自然对齐(u16 对齐到 2 字节,u32 对齐到 4 字节) |
| 指针大小 | WASM32 中指针为 4 字节,与 GBA ARM 一致 |
| 字符串编码 | 游戏使用自定义字符编码,非标准 ASCII/Unicode |
| 数组长度 | 数组长度需硬编码或通过终止标记表示 |
3. 转换脚本伪代码:
python
import json
import struct
def convert_sprite_animation(json_path, output_path):
"""将 JSON 动画定义转换为 C 数组"""
# 1. 读取并解析 JSON
with open(json_path) as f:
anim_data = json.load(f)
# 2. 验证格式
validate_animation_schema(anim_data)
# 3. 构建 C 头文件
lines = []
lines.append("// 自动生成 - 请勿手动修改")
lines.append(f"// 源文件: {json_path}")
lines.append("#include 'global.h'")
lines.append("")
# 4. 为每个动画序列生成结构体数组
for anim in anim_data['animations']:
name = anim['name']
frames = anim['frames']
lines.append(f"const struct SpriteFrameImage {name}_images[] = {{")
for frame in frames:
tile_num = frame['tile']
x_offset = frame.get('x_offset', 0)
y_offset = frame.get('y_offset', 0)
# 注意: GBA 结构体字段顺序必须与 C 定义一致
lines.append(f" {{{tile_num}, {x_offset}, {y_offset}}},")
lines.append("};")
lines.append("")
# 5. 生成动画序列表
lines.append(f"const union AnimCmd {name}_anim[] = {{")
for i, frame in enumerate(frames):
duration = frame['duration']
lines.append(f" ANIMCMD_FRAME({frame['tile']}, {duration}),")
lines.append(" ANIMCMD_END,")
lines.append("};")
lines.append("")
# 6. 写入输出文件
with open(output_path, 'w') as f:
f.write('\n'.join(lines))关键设计决策:
- 输出 C 源文件而非原始二进制,便于编译器优化和符号管理
- 使用
const限定符,数据放入 WASM 的只读数据段 - 结构体布局必须与
include/sprite.h中的定义完全匹配 ANIMCMD_FRAME和ANIMCMD_END宏来自项目的头文件,保持与现有代码一致
拓展挑战
资源压缩对比:选择一组图形资源,分别以未压缩、LZ77 压缩、Run-Length 压缩格式存储,对比 WASM 模块体积和运行时解压性能。分析在 WASM 环境下哪种策略最优。
增量资源更新:当前每次构建都会重新处理所有资源。设计一个增量构建方案,通过哈希值检测变化的资源文件,只重新转换修改过的部分。
自定义 INCGFX 格式:选择一种新的图形格式(如 WebP 或 SVG),编写 Python 转换器将其转为 GBA 的 4bpp/8bpp Tile 格式。需要处理颜色量化、调色板生成和 Tile 切割。
资源热加载:设计一种在开发阶段无需重新编译整个 WASM 模块就能更新图形资源的方案。提示:考虑将资源数据放在 WASM 内存中的独立区域,通过 JavaScript 动态加载。
地图数据验证器:编写一个验证工具,检查转换后的地图数据是否符合 GBA 的限制(如地图尺寸限制、Tile 数量限制、调色板数量限制)。这在 GBA 硬件模拟 中有相关约束说明。
资源使用分析:统计项目中所有
INCGFX和INCBIN引用的资源,生成一份报告,包含:资源类型分布、总大小、按模块分类的大小占比。这有助于识别优化目标。