Files
cocos/tools/export_unity_prefab_maps.py
2026-07-14 16:02:20 +08:00

162 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""从 Unity Level{N}.prefab 解析 TilemapGround / Border格子数据。
层级锁定:
- Baseblock / JumpBlock → ground地砖属性
- WallBlock / 墙饰 → border墙砖属性
不再用死板的 tileIndex==1 → JumpBlock。
"""
from __future__ import annotations
import re
from functools import lru_cache
from pathlib import Path
GROUND_TILES = frozenset({"Baseblock", "JumpBlock"})
BORDER_DECOR = frozenset(
{
"WallBlock",
"kuai11",
"Decor23",
"素材切图-23",
"素材切图2-23",
"小游戏素材红色_03",
}
)
def parse_tilemap_layer(text: str, layer_name: str) -> dict[str, int]:
"""返回格子 → 该 Tilemap 本地 m_TileIndex。"""
parts = re.split(r"(?=--- !u!1 &)", text)
for part in parts:
if f"m_Name: {layer_name}" not in part or "Tilemap:" not in part:
continue
tiles: dict[str, int] = {}
for m in re.finditer(
r"first: \{x: (-?\d+), y: (-?\d+), z: 0\}[\s\S]*?m_TileIndex: (\d+)",
part,
):
x, y, ti = int(m.group(1)), int(m.group(2)), int(m.group(3))
tiles[f"{x},{y}"] = ti
return tiles
return {}
def _tilemap_part(text: str, layer_name: str) -> str | None:
parts = re.split(r"(?=--- !u!1 &)", text)
for part in parts:
if f"m_Name: {layer_name}" not in part or "Tilemap:" not in part:
continue
return part
return None
def _tile_asset_guids(tilemap_part: str) -> list[str | None]:
"""按 m_TileAssetArray 顺序解析 guid空槽为 None"""
assets: list[str | None] = []
in_arr = False
for line in tilemap_part.splitlines():
if "m_TileAssetArray:" in line:
in_arr = True
continue
if in_arr and ("m_TileSpriteArray" in line or "m_AnimationFrameRate" in line):
break
if not in_arr:
continue
if "guid:" in line:
m = re.search(r"guid: ([a-f0-9]+)", line)
assets.append(m.group(1) if m else None)
elif "m_Data: {fileID: 0}" in line:
assets.append(None)
return assets
@lru_cache(maxsize=1)
def _guid_to_tile_name(unity_texture_root: str) -> dict[str, str]:
root = Path(unity_texture_root)
out: dict[str, str] = {}
if not root.is_dir():
return out
for meta in root.rglob("*.asset.meta"):
try:
text = meta.read_text(encoding="utf-8", errors="ignore")
except OSError:
continue
m = re.search(r"^guid: ([a-f0-9]+)", text, re.M)
if m:
out[m.group(1)] = meta.name.replace(".asset.meta", "")
return out
def tile_asset_names(text: str, layer_name: str, unity_texture_root: Path | None = None) -> list[str | None]:
part = _tilemap_part(text, layer_name)
if not part:
return []
guids = _tile_asset_guids(part)
if unity_texture_root is None:
# 默认prefab 所在工程 Assets/Texture
return [None] * len(guids)
mapping = _guid_to_tile_name(str(unity_texture_root))
return [mapping.get(g) if g else None for g in guids]
def normalize_ground_tile(name: str | None) -> str:
return "JumpBlock" if name == "JumpBlock" else "Baseblock"
def normalize_border_tile(name: str | None) -> str:
if not name or name in GROUND_TILES:
return "WallBlock"
if name in BORDER_DECOR:
return name
# 其它墙饰保留文件名,禁止地砖名漏到墙层
return name
def _resolve_name(assets: list[str | None], tile_index: int) -> str | None:
if 0 <= tile_index < len(assets):
return assets[tile_index]
return None
def parse_level_prefab(prefab_path: Path, unity_texture_root: Path | None = None) -> dict:
if not prefab_path.is_file():
return {}
text = prefab_path.read_text(encoding="utf-8")
g_raw = parse_tilemap_layer(text, "Ground")
b_raw = parse_tilemap_layer(text, "Border")
if not g_raw and not b_raw:
return {}
if unity_texture_root is None:
# Level91601.prefab → …/Assets/Prefabs/Level → …/Assets/Texture
unity_texture_root = prefab_path.resolve().parents[2] / "Texture"
g_assets = tile_asset_names(text, "Ground", unity_texture_root)
b_assets = tile_asset_names(text, "Border", unity_texture_root)
ground: dict[str, str] = {}
border: dict[str, str] = {}
# 按瓦片类型归层层级固定地砖→Ground墙/饰→Border
for key, ti in g_raw.items():
name = _resolve_name(g_assets, ti)
if name in GROUND_TILES:
ground[key] = normalize_ground_tile(name)
else:
border[key] = normalize_border_tile(name)
for key, ti in b_raw.items():
name = _resolve_name(b_assets, ti)
if name in GROUND_TILES:
ground[key] = normalize_ground_tile(name)
else:
border[key] = normalize_border_tile(name)
# 同格冲突:墙优先(不可走)
for key in list(ground.keys()):
if key in border:
del ground[key]
return {"ground": ground, "border": border}