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>
This commit is contained in:
154
tools/recover-and-unify-textures.py
Normal file
154
tools/recover-and-unify-textures.py
Normal file
@@ -0,0 +1,154 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user