Files
cocos_zhuzhan/tools/export_unity_prefab_maps.py

226 lines
7.0 KiB
Python
Raw Permalink 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格子数据。
贴图以 Tilemap 的 m_TileSpriteArray 为准(和 Unity 编辑器里看到的砖一致),
不按关卡主题改名、不换层、不把 JumpBlock 收成 Baseblock。
"""
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",
}
)
UNITY_FOLDER_TO_COCOS = {
"Chinese": "chinese",
"chinese": "chinese",
"RedArmy": "redArmy",
"redArmy": "redArmy",
"redarmy": "redArmy",
"numMan": "numMan",
"sanxing": "sanxing",
"snow": "snow",
"silu": "silu",
"SILU": "silu",
}
def tile_basename(name: str | None) -> str:
if not name:
return ""
return name.rsplit("/", 1)[-1]
def parse_tilemap_layer(text: str, layer_name: str) -> dict[str, int]:
"""返回格子 → 该 Tilemap 本地 m_TileIndex。"""
cells = parse_tilemap_cells(text, layer_name)
return {k: v[0] for k, v in cells.items()}
def parse_tilemap_cells(text: str, layer_name: str) -> dict[str, tuple[int, int]]:
"""格子 → (m_TileIndex, m_TileSpriteIndex)。"""
part = _tilemap_part(text, layer_name)
if not part:
return {}
tiles: dict[str, tuple[int, int]] = {}
for m in re.finditer(
r"first: \{x: (-?\d+), y: (-?\d+), z: 0\}[\s\S]*?m_TileIndex: (\d+)[\s\S]*?m_TileSpriteIndex: (\d+)",
part,
):
x, y = int(m.group(1)), int(m.group(2))
tiles[f"{x},{y}"] = (int(m.group(3)), int(m.group(4)))
return tiles
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 _guid_array(tilemap_part: str, header: str, stop_tokens: tuple[str, ...]) -> list[str | None]:
assets: list[str | None] = []
in_arr = False
for line in tilemap_part.splitlines():
if header in line:
in_arr = True
continue
if in_arr and any(tok in line for tok in stop_tokens):
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
def _tile_asset_guids(tilemap_part: str) -> list[str | None]:
"""按 m_TileAssetArray 顺序解析 guid空槽为 None"""
return _guid_array(
tilemap_part,
"m_TileAssetArray:",
("m_TileSpriteArray", "m_AnimationFrameRate"),
)
def _tile_sprite_guids(tilemap_part: str) -> list[str | None]:
return _guid_array(
tilemap_part,
"m_TileSpriteArray:",
("m_TileMatrixArray", "m_TileColorArray", "m_AnimationFrameRate"),
)
def _cocos_folder(unity_folder: str) -> str:
if unity_folder in UNITY_FOLDER_TO_COCOS:
return UNITY_FOLDER_TO_COCOS[unity_folder]
return unity_folder[:1].lower() + unity_folder[1:] if unity_folder else ""
@lru_cache(maxsize=1)
def _guid_to_tile_name(unity_texture_root: str) -> dict[str, str]:
"""guid → Cocos 贴图引用,如 numMan/Baseblock。"""
root = Path(unity_texture_root)
out: dict[str, str] = {}
if not root.is_dir():
return out
for meta in root.rglob("*.meta"):
name = meta.name
if not (name.endswith(".png.meta") or name.endswith(".asset.meta")):
continue
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 not m:
continue
stem = name.replace(".png.meta", "").replace(".asset.meta", "")
try:
rel = meta.relative_to(root)
except ValueError:
out[m.group(1)] = stem
continue
if len(rel.parts) >= 2:
out[m.group(1)] = f"{_cocos_folder(rel.parts[0])}/{stem}"
else:
out[m.group(1)] = stem
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:
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 tile_sprite_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_sprite_guids(part)
if unity_texture_root is None:
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 tile_basename(name) == "JumpBlock" else "Baseblock"
def normalize_border_tile(name: str | None) -> str:
base = tile_basename(name)
if not name or base in GROUND_TILES:
return "WallBlock"
if base 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:
"""按 Unity 原层导入:只保留有 Tile Asset 的格子,空槽不补砖。"""
if not prefab_path.is_file():
return {}
text = prefab_path.read_text(encoding="utf-8")
g_cells = parse_tilemap_cells(text, "Ground")
b_cells = parse_tilemap_cells(text, "Border")
if not g_cells and not b_cells:
return {}
if unity_texture_root is None:
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] = {}
for key, (ti, _si) in g_cells.items():
asset = _resolve_name(g_assets, ti)
if not asset:
continue
base = tile_basename(asset)
ground[key] = "JumpBlock" if base == "JumpBlock" else base if base in GROUND_TILES else "Baseblock"
for key, (ti, _si) in b_cells.items():
asset = _resolve_name(b_assets, ti)
if not asset:
# Unity 空槽不渲染,禁止补 WallBlock否则空隙里会多出砖
continue
base = tile_basename(asset)
if base in GROUND_TILES:
continue
border[key] = base if base else "WallBlock"
return {"ground": ground, "border": border}