Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
||
"""用 Unity 源贴图 MD5 匹配恢复 __stage__,并完成 sanxing 命名统一。"""
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import json
|
||
import os
|
||
import re
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[1]
|
||
TEX = ROOT / "assets" / "resources" / "textures"
|
||
UNITY_TEX = Path("/Users/liuyufei/tfrh/竞赛/scratch-unity-base/Assets/Texture")
|
||
|
||
# 复用 unify 脚本的 RENAMES
|
||
import importlib.util
|
||
_spec = importlib.util.spec_from_file_location(
|
||
"unify", ROOT / "tools" / "unify-theme-texture-names.py"
|
||
)
|
||
_unify = importlib.util.module_from_spec(_spec)
|
||
_spec.loader.exec_module(_unify)
|
||
RENAMES = _unify.RENAMES
|
||
_norm_rel = _unify._norm_rel
|
||
build_rename_lookup = _unify.build_rename_lookup
|
||
move_pair = _unify.move_pair
|
||
apply_renames = _unify.apply_renames
|
||
cleanup_empty_dirs = _unify.cleanup_empty_dirs
|
||
move_legacy = _unify.move_legacy
|
||
SANXING_KEEP = _unify.SANXING_KEEP
|
||
THEME_SHIP = _unify.THEME_SHIP
|
||
build_path_replacements = _unify.build_path_replacements
|
||
patch_text_files = _unify.patch_text_files
|
||
patch_themes_database = _unify.patch_themes_database
|
||
patch_palettes = _unify.patch_palettes
|
||
|
||
UNITY_SOURCES: dict[str, list[Path]] = {
|
||
"silu": [UNITY_TEX / "silu"],
|
||
"numMan": [UNITY_TEX / "numMan"],
|
||
"redArmy": [UNITY_TEX / "redarmy"],
|
||
"chinese": [UNITY_TEX / "Panda", UNITY_TEX / "Chinese"],
|
||
"snow": [TEX / "snow"], # snow 仅 Cocos,从现有目录取
|
||
"sanxing": [TEX / "sanxing"],
|
||
}
|
||
|
||
|
||
def md5_file(path: Path) -> str:
|
||
h = hashlib.md5()
|
||
with path.open("rb") as f:
|
||
for chunk in iter(lambda: f.read(65536), b""):
|
||
h.update(chunk)
|
||
return h.hexdigest()
|
||
|
||
|
||
def build_unity_hash_index(theme: str) -> dict[str, str]:
|
||
"""md5 -> theme 内相对路径(不含 .png)"""
|
||
index: dict[str, str] = {}
|
||
for src_root in UNITY_SOURCES.get(theme, []):
|
||
if not src_root.exists():
|
||
continue
|
||
for png in src_root.rglob("*.png"):
|
||
rel = png.relative_to(src_root).as_posix().replace(".png", "")
|
||
# chinese: Panda/待机/机器人 -> skin/待机/机器人 便于 lookup
|
||
if theme == "chinese" and rel.startswith("Panda/"):
|
||
rel = "skin/" + rel[len("Panda/"):]
|
||
digest = md5_file(png)
|
||
index[digest] = rel
|
||
return index
|
||
|
||
|
||
def recover_stages_by_hash() -> int:
|
||
lookup = build_rename_lookup()
|
||
recovered = 0
|
||
for stage in sorted(TEX.rglob("__stage_*.png")):
|
||
theme_key = stage.relative_to(TEX).parts[0]
|
||
digest = md5_file(stage)
|
||
rel = build_unity_hash_index(theme_key).get(digest)
|
||
if not rel:
|
||
continue
|
||
new_rel = lookup.get((theme_key, Path(rel).name)) or lookup.get((theme_key, rel))
|
||
if not new_rel:
|
||
continue
|
||
dst = TEX / theme_key / f"{new_rel}.png"
|
||
if move_pair(stage, dst, skip_if_dst_exists=True):
|
||
recovered += 1
|
||
print(f" hash-recovered {theme_key}: {rel} -> {new_rel}.png")
|
||
return recovered
|
||
|
||
|
||
def copy_from_unity_if_missing(theme: str) -> int:
|
||
"""目标路径缺失时从 Unity 复制并重命名"""
|
||
lookup = build_rename_lookup()
|
||
count = 0
|
||
for src_root in UNITY_SOURCES.get(theme, []):
|
||
if not src_root.exists() or src_root == TEX / theme:
|
||
continue
|
||
for png in src_root.rglob("*.png"):
|
||
rel = png.relative_to(src_root).as_posix().replace(".png", "")
|
||
if theme == "chinese" and rel.startswith("Panda/"):
|
||
rel = "skin/" + rel[len("Panda/"):]
|
||
key_name = Path(rel).name
|
||
new_rel = lookup.get((theme, key_name)) or lookup.get((theme, rel))
|
||
if not new_rel:
|
||
continue
|
||
dst = TEX / theme / f"{new_rel}.png"
|
||
if dst.exists():
|
||
continue
|
||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||
shutil.copy2(png, dst)
|
||
count += 1
|
||
print(f" copied {theme}: {rel} -> {new_rel}.png")
|
||
return count
|
||
|
||
|
||
def remove_orphan_stages() -> None:
|
||
legacy_root = TEX / "_orphan_stages"
|
||
for stage in TEX.rglob("__stage_*.png"):
|
||
rel = stage.relative_to(TEX)
|
||
dest = legacy_root / rel
|
||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||
move_pair(stage, dest)
|
||
|
||
|
||
def main() -> None:
|
||
print("=== MD5 恢复 __stage__ ===")
|
||
n_hash = recover_stages_by_hash()
|
||
print(f" 恢复 {n_hash} 个")
|
||
|
||
print("=== 从 Unity 补全缺失文件 ===")
|
||
n_copy = 0
|
||
for theme in ["silu", "numMan", "redArmy", "chinese"]:
|
||
n_copy += copy_from_unity_if_missing(theme)
|
||
print(f" 复制 {n_copy} 个")
|
||
|
||
print("=== 常规重命名 ===")
|
||
n = apply_renames()
|
||
print(f" 重命名 {n} 个")
|
||
|
||
print("=== 清理 ===")
|
||
remove_orphan_stages()
|
||
for theme in ["silu", "snow", "numMan", "redArmy", "chinese"]:
|
||
cleanup_empty_dirs(TEX / theme)
|
||
keep = set(SANXING_KEEP) | set(THEME_SHIP.get(theme, ()))
|
||
move_legacy(theme, keep)
|
||
|
||
print("=== 更新引用 ===")
|
||
patch_text_files(build_path_replacements())
|
||
patch_themes_database()
|
||
patch_palettes()
|
||
print("完成。")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|