#!/usr/bin/env python3 """ 烘焙「地图模块」预制体(对齐 Unity Tile Palette 中的单个 Tile)。 输出: assets/resources/map-tiles/MapTile_*.prefab 在关卡编辑器右侧面板中作为笔刷模块使用。 """ from __future__ import annotations import json import random import string import uuid from pathlib import Path LAYER_DEFAULT = 1073741824 CELL = 56 TILE_SIZE = CELL * 0.9 # 与 Unity SILU 调色板一致 MAP_TILES = [ { "name": "MapTile_Baseblock", "display": "地面 Baseblock", "layer": "ground", "tileKey": "Baseblock", "texture": "textures/silu/Baseblock", "spriteUuid": "5625da25-9915-416f-be60-c6decb355672@f9941", "alpha": 255, }, { "name": "MapTile_JumpBlock", "display": "跳跃 JumpBlock", "layer": "ground", "tileKey": "JumpBlock", "texture": "textures/silu/JumpBlock", "spriteUuid": None, "alpha": 255, }, { "name": "MapTile_WallBlock", "display": "墙 WallBlock", "layer": "border", "tileKey": "WallBlock", "texture": "textures/silu/WallBlock", "spriteUuid": None, "alpha": 255, }, { "name": "MapTile_Decor23", "display": "装饰 素材23", "layer": "border", "tileKey": "Decor23", "texture": "textures/silu/Decor23", "spriteUuid": None, "alpha": 255, }, ] def uid() -> str: return str(uuid.uuid4()) def vec3(x: float, y: float, z: float = 0) -> dict: return {"__type__": "cc.Vec3", "x": x, "y": y, "z": z} def quat() -> dict: return {"__type__": "cc.Quat", "x": 0, "y": 0, "z": 0, "w": 1} def file_id() -> str: chars = string.ascii_letters + string.digits return "".join(random.choices(chars, k=22)) def read_sprite_uuid_from_meta(project: Path, texture_path: str) -> str | None: meta_path = project / "assets/resources" / f"{texture_path}.png.meta" if not meta_path.is_file(): return None try: meta = json.loads(meta_path.read_text(encoding="utf-8")) base = meta.get("uuid") if base: return f"{base}@f9941" except Exception: pass return None def build_tile_prefab(tile: dict) -> list: objs: list[dict] = [] root_id = 1 objs.append( { "__type__": "cc.Prefab", "_name": tile["name"], "_objFlags": 0, "_native": "", "data": {"__id__": root_id}, "optimizationPolicy": 0, "persistent": False, } ) objs.append( { "__type__": "cc.Node", "_name": tile["name"], "_objFlags": 0, "_parent": None, "_children": [], "_active": True, "_components": [], "_prefab": {"__id__": 2}, "_lpos": vec3(0, 0, 0), "_lrot": quat(), "_lscale": vec3(1, 1, 1), "_layer": LAYER_DEFAULT, "_euler": vec3(0, 0, 0), "_id": "", } ) objs.append( { "__type__": "cc.PrefabInfo", "root": {"__id__": root_id}, "asset": {"__id__": 0}, "fileId": file_id(), } ) return objs def write_meta(prefab_path: Path, prefab_uuid: str) -> None: meta = { "ver": "1.1.50", "importer": "prefab", "imported": True, "uuid": prefab_uuid, "files": [".json"], "subMetas": {}, "userData": {"syncNodeName": prefab_path.stem}, } prefab_path.with_suffix(".prefab.meta").write_text( json.dumps(meta, indent=2), encoding="utf-8" ) def main(): project = Path(__file__).resolve().parent.parent out = project / "assets/resources/map-tiles" out.mkdir(parents=True, exist_ok=True) catalog = [] for tile in MAP_TILES: sprite_uuid = tile.get("spriteUuid") or read_sprite_uuid_from_meta( project, tile["texture"] ) if not sprite_uuid: sprite_uuid = "5625da25-9915-416f-be60-c6decb355672@f9941" print(f" warn: {tile['name']} 使用 Baseblock 占位,请在编辑器导入 {tile['texture']}.png 后重烘焙") p = out / f"{tile['name']}.prefab" p.write_text(json.dumps(build_tile_prefab(tile), indent=2), encoding="utf-8") write_meta(p, uid()) catalog.append( { "name": tile["name"], "display": tile["display"], "layer": tile["layer"], "tileKey": tile["tileKey"], "texture": tile["texture"], "prefab": f"map-tiles/{tile['name']}", } ) print(f" baked {tile['name']}") (out / "_palette.json").write_text( json.dumps({"tiles": catalog}, indent=2, ensure_ascii=False), encoding="utf-8" ) print(f"Palette catalog -> {out / '_palette.json'}") if __name__ == "__main__": main()