Files
cocos/tools/export_all_levels.py
刘宇飞 d393302388 Complete Cocos Creator port with level bundles, themes, and tooling.
Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 15:30:58 +08:00

190 lines
5.9 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 全量导出关卡到单一 JSON 数据库(方便后期增删查改)。
数据来源:
- Assets/Scripts/Core/Levels*.cs → spawns / boundary / levelPath
- Assets/Prefabs/Level/LevelN.prefab → Ground / Border Tilemap
用法:
python3 tools/export_all_levels.py \\
--unity-root "/path/to/主站" \\
--output assets/level-data/levels-database.json
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
from export_unity_levels import (
LEVEL_RE,
BOUND_RE,
SPAWN_RE,
POS_RE,
PATH_RE,
PDIR_RE,
VDIR_RE,
kind_from_path,
parse_spawns,
build_border_cache,
)
from export_unity_prefab_maps import parse_level_prefab
from level_id import LEVEL_ID_BASE, normalize_level_id, prefab_resource_path
LEVEL_PATH_RE = re.compile(
r'levelPath\s*=\s*"?([^",\s]+Level\d+\.prefab)"?', re.I
)
def parse_level_block(chunk: str, lid: int) -> dict:
bound = BOUND_RE.search(chunk)
bx, by = (10, 10)
if bound:
bx, by = int(bound.group(1)), int(bound.group(2))
path_m = LEVEL_PATH_RE.search(chunk)
level_path = path_m.group(1) if path_m else f"Assets/Prefabs/Level/Level{lid}.prefab"
ext_id = normalize_level_id(lid)
return {
"levelID": ext_id,
"boundary": {"x": bx, "y": by},
"spawns": parse_spawns(chunk),
"unityPrefab": level_path.replace("\\", "/"),
"cocosPrefab": prefab_resource_path(ext_id),
}
def parse_levels_cs_file(path: Path) -> dict[int, dict]:
text = path.read_text(encoding="utf-8")
levels: dict[int, dict] = {}
matches = list(LEVEL_RE.finditer(text))
for i, m in enumerate(matches):
lid = int(m.group(1))
start = m.end()
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
levels[lid] = parse_level_block(text[start:end], lid)
return levels
def parse_all_levels_cs(core_dir: Path) -> dict[int, dict]:
merged: dict[int, dict] = {}
for cs in sorted(core_dir.glob("Levels*.cs")):
part = parse_levels_cs_file(cs)
for lid, L in part.items():
if lid in merged:
print(f"warn: duplicate level id {lid} in {cs.name}, keep first", file=sys.stderr)
continue
merged[lid] = L
return merged
def prefab_path_for_level(L: dict, prefab_dir: Path) -> Path | None:
rel = L.get("unityPrefab", "")
if rel:
name = Path(rel).name
p = prefab_dir / name
if p.is_file():
return p
p = prefab_dir / f"Level{L['levelID']}.prefab"
return p if p.is_file() else None
def merge_prefab_maps(L: dict, prefab_dir: Path) -> None:
p = prefab_path_for_level(L, prefab_dir)
if not p:
bx, by = L["boundary"]["x"], L["boundary"]["y"]
ring = build_border_cache({L["levelID"]: {**L, "borderKey": f"{bx},{by}"}})
L["border"] = ring[f"{bx},{by}"]
L["_mapSource"] = "boundary_ring"
return
maps = parse_level_prefab(p)
if maps.get("ground"):
L["ground"] = maps["ground"]
if maps.get("border"):
L["border"] = maps["border"]
L["_mapSource"] = "unity_prefab"
def strip_internal(L: dict) -> dict:
out = {
"levelID": L["levelID"],
"boundary": L["boundary"],
"spawns": L["spawns"],
}
if L.get("unityPrefab"):
out["unityPrefab"] = L["unityPrefab"]
if L.get("cocosPrefab"):
out["cocosPrefab"] = L["cocosPrefab"]
if L.get("ground"):
out["ground"] = L["ground"]
if L.get("border"):
out["border"] = L["border"]
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--unity-root", required=True, help="Unity 项目根目录(含 Assets")
ap.add_argument("--output", required=True, help="输出 levels-database.json")
ap.add_argument("--limit", type=int, default=0, help="仅导出前 N 个关卡(调试用)")
ap.add_argument("--skip-prefab-maps", action="store_true", help="不解析 Unity prefab 瓦片(快,地图由 Cocos 预制体承担)")
args = ap.parse_args()
unity = Path(args.unity_root)
core = unity / "Assets/Scripts/Core"
prefab_dir = unity / "Assets/Prefabs/Level"
if not core.is_dir():
print(f"Core dir not found: {core}", file=sys.stderr)
sys.exit(1)
print("Parsing Levels*.cs …")
levels = parse_all_levels_cs(core)
ids = sorted(levels.keys())
if args.limit > 0:
ids = ids[: args.limit]
levels = {k: levels[k] for k in ids}
print(f"Level definitions: {len(levels)}")
from_prefab = 0
from_ring = 0
if args.skip_prefab_maps:
print("Skipping prefab Tilemaps (--skip-prefab-maps)")
else:
print("Merging prefab Tilemaps …")
for i, lid in enumerate(ids):
if (i + 1) % 200 == 0:
print(f"{i + 1}/{len(ids)}")
merge_prefab_maps(levels[lid], prefab_dir)
if levels[lid].get("_mapSource") == "unity_prefab":
from_prefab += 1
else:
from_ring += 1
payload = {
"version": 1,
"generatedAt": datetime.now(timezone.utc).isoformat(),
"source": "Unity Levels*.cs + Assets/Prefabs/Level/*.prefab",
"stats": {
"total": len(levels),
"withPrefabTilemap": from_prefab,
"withBoundaryRing": from_ring,
},
"levelIdBase": LEVEL_ID_BASE,
"levels": {str(levels[lid]["levelID"]): strip_internal(levels[lid]) for lid in ids},
}
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"Wrote {out} ({out.stat().st_size // 1024} KB)")
print(f" prefab tilemap: {from_prefab}, fallback ring: {from_ring}")
if __name__ == "__main__":
main()