""" 将多个独立 tile PNG 按 JSON 配置打包为 tile atlas,生成 C++ header。 用法: python tools/pack_tile_atlas.py config.json output.h JSON 格式: { "tile_size": 32, "columns": 4, "tiles": [ { "id": 0, "file": "empty.png" }, { "id": 1, "file": "ground_top.png" }, ... ] } - tile_size: 每个 tile 的像素尺寸(正方形) - columns: atlas 每行放几个 tile - tiles: 按 tile_id 排序的列表,id 从 0 开始连续 - file: 相对于 config.json 所在目录的 PNG 路径 - 未指定的 tile_id 自动填充透明 """ import json import os import sys import math from PIL import Image def pack(config_path: str, output_path: str): config_dir = os.path.dirname(os.path.abspath(config_path)) with open(config_path, "r", encoding="utf-8") as f: cfg = json.load(f) tile_size = cfg["tile_size"] columns = cfg["columns"] tiles = cfg["tiles"] max_id = max(t["id"] for t in tiles) tile_count = max_id + 1 rows = (tile_count + columns - 1) // columns atlas_w = columns * tile_size atlas_h = rows * tile_size atlas = Image.new("RGBA", (atlas_w, atlas_h), (0, 0, 0, 0)) tile_map = {t["id"]: t for t in tiles} for tid in range(tile_count): col = tid % columns row = tid // columns x = col * tile_size y = row * tile_size if tid in tile_map: png_path = os.path.join(config_dir, tile_map[tid]["file"]) img = Image.open(png_path).convert("RGBA") if img.size != (tile_size, tile_size): img = img.resize((tile_size, tile_size), Image.NEAREST) atlas.paste(img, (x, y)) var_name = os.path.splitext(os.path.basename(output_path))[0] pixels = list(atlas.getdata()) packed = [] for r, g, b, a in pixels: packed.append( ((r >> 3) << 11) | ((g >> 3) << 6) | ((b >> 3) << 1) | (1 if a else 0) ) with open(output_path, "w", encoding="utf-8") as f: f.write("// Auto-generated by tools/pack_tile_atlas.py\n") f.write(f"#pragma once\n#include \n#include \"Image.h\"\n\n") f.write(f"static const int32_t {var_name}_width = {atlas_w};\n") f.write(f"static const int32_t {var_name}_height = {atlas_h};\n") f.write(f"static const int32_t {var_name}_tile_size = {tile_size};\n") f.write(f"static const int32_t {var_name}_columns = {columns};\n\n") f.write(f"static const uint16_t {var_name}_pixels[] = {{\n") for i in range(0, len(packed), 16): chunk = packed[i : i + 16] line = ", ".join(f"0x{p:04X}" for p in chunk) f.write(f" {line},\n") f.write("};\n\n") f.write(f"static const RenderData::Image {var_name}_image({var_name}_pixels, {var_name}_width, {var_name}_height);\n") print(f"已生成: {output_path}") print(f" atlas: {atlas_w}x{atlas_h}, {tile_count} tiles ({columns}x{rows})") print(f" var: {var_name}") def main(): if len(sys.argv) < 3: print(f"用法: {sys.argv[0]} config.json output.h") sys.exit(1) pack(sys.argv[1], sys.argv[2]) if __name__ == "__main__": main()