同步主站 Cocos 当前工程:关卡资源、运行脚本与打包工具。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-12 14:40:34 +08:00
parent ae75fce80b
commit fac515a669
5609 changed files with 27744634 additions and 5653 deletions

View File

@@ -1,10 +1,8 @@
#!/usr/bin/env python3
"""从 Unity Level{N}.prefab 解析 TilemapGround / Border格子数据。
层级锁定:
- Baseblock / JumpBlock → ground地砖属性
- WallBlock / 墙饰 → border墙砖属性
不再用死板的 tileIndex==1 → JumpBlock。
贴图以 Tilemap 的 m_TileSpriteArray 为准(和 Unity 编辑器里看到的砖一致),
不按关卡主题改名、不换层、不把 JumpBlock 收成 Baseblock。
"""
from __future__ import annotations
@@ -24,22 +22,45 @@ BORDER_DECOR = frozenset(
}
)
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。"""
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 {}
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:
@@ -51,15 +72,14 @@ def _tilemap_part(text: str, layer_name: str) -> str | None:
return None
def _tile_asset_guids(tilemap_part: str) -> list[str | None]:
"""按 m_TileAssetArray 顺序解析 guid空槽为 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 "m_TileAssetArray:" in line:
if header in line:
in_arr = True
continue
if in_arr and ("m_TileSpriteArray" in line or "m_AnimationFrameRate" in line):
if in_arr and any(tok in line for tok in stop_tokens):
break
if not in_arr:
continue
@@ -71,20 +91,57 @@ def _tile_asset_guids(tilemap_part: str) -> list[str | 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("*.asset.meta"):
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 m:
out[m.group(1)] = meta.name.replace(".asset.meta", "")
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
@@ -94,22 +151,32 @@ def tile_asset_names(text: str, layer_name: str, unity_texture_root: Path | None
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 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 name == "JumpBlock" else "Baseblock"
return "JumpBlock" if tile_basename(name) == "JumpBlock" else "Baseblock"
def normalize_border_tile(name: str | None) -> str:
if not name or name in GROUND_TILES:
base = tile_basename(name)
if not name or base in GROUND_TILES:
return "WallBlock"
if name in BORDER_DECOR:
if base in BORDER_DECOR:
return name
# 其它墙饰保留文件名,禁止地砖名漏到墙层
return name
@@ -120,16 +187,16 @@ def _resolve_name(assets: list[str | None], tile_index: int) -> str | 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_raw = parse_tilemap_layer(text, "Ground")
b_raw = parse_tilemap_layer(text, "Border")
if not g_raw and not b_raw:
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:
# 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)
@@ -138,24 +205,21 @@ def parse_level_prefab(prefab_path: Path, unity_texture_root: Path | None = None
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, _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 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]
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}