Adds level prefabs, theme assets, audio, extensions, and deployment scripts for the Unity WebGL migration. Co-authored-by: Cursor <cursoragent@cursor.com>
454 lines
18 KiB
Python
454 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""将各主题 textures 文件名统一为与 sanxing 相同的命名规范。"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
TEX = ROOT / "assets" / "resources" / "textures"
|
|
|
|
RENAMES: list[tuple[str, str, str]] = []
|
|
|
|
|
|
def add(theme: str, old: str, new: str) -> None:
|
|
old = old.replace("\\", "/")
|
|
new = new.replace("\\", "/")
|
|
if not old.endswith(".png"):
|
|
old += ".png"
|
|
if not new.endswith(".png"):
|
|
new += ".png"
|
|
RENAMES.append((theme, old, new))
|
|
|
|
|
|
# ── silu ──
|
|
for src, dst in [
|
|
("素材切图_画板 1", "bg"),
|
|
("素材切图-03", "anniu_03"),
|
|
("素材切图-05", "anniu_06"),
|
|
("1倍速", "anniu_08"),
|
|
("2倍速", "anniu_10"),
|
|
("4倍速", "anniu_12"),
|
|
("素材切图-09", "anniu_17"),
|
|
("素材切图-10", "anniu_19"),
|
|
("声音", "anniu_22"),
|
|
("声音关闭", "anniu_21"),
|
|
("素材切图-23", "kuai11"),
|
|
("Decor23", "kuai11"),
|
|
]:
|
|
add("silu", src, dst)
|
|
for i, src in enumerate(["机器人", "机器人2", "机器人3", "机器人4"], start=1):
|
|
add("silu", f"skin/跳背面/{src}", f"skin/跳背面/{i}")
|
|
|
|
# ── snow ──
|
|
add("snow", "Prop_kuai2", "Prop_kuai1")
|
|
add("snow", "nProp_kuai2", "nProp_kuai1")
|
|
|
|
# ── numMan ──
|
|
for i in range(1, 5):
|
|
add("numMan", f"skin/待机/待机{i}", f"skin/待机正面/{i}")
|
|
for i in range(1, 3):
|
|
add("numMan", f"skin/待机背面/待机{i}", f"skin/待机背面/{i}")
|
|
for i in range(1, 5):
|
|
add("numMan", f"skin/走/走{i}", f"skin/走/{i}")
|
|
add("numMan", f"skin/走背面/走背面{i}", f"skin/走背面/{i}")
|
|
add("numMan", "skin/跳/跳1", "skin/跳/1")
|
|
add("numMan", "skin/跳/跳2", "skin/跳/2")
|
|
add("numMan", "skin/跳/跳4", "skin/跳/3")
|
|
add("numMan", "skin/跳/跳5", "skin/跳/4")
|
|
add("numMan", "skin/跳背面/跳背面1", "skin/跳背面/1")
|
|
add("numMan", "skin/跳背面/跳背面2", "skin/跳背面/2")
|
|
add("numMan", "skin/跳背面/跳背面4", "skin/跳背面/3")
|
|
add("numMan", "skin/跳背面/跳背面5", "skin/跳背面/4")
|
|
add("numMan", "Prop_kuai", "Prop_kuai1")
|
|
add("numMan", "nProp_kuai", "nProp_kuai1")
|
|
|
|
# ── redArmy ──
|
|
for src, dst in [
|
|
("小游戏素材红色_03", "kuai11"),
|
|
("redarmy02", "anniu_03"),
|
|
("redarmy09", "anniu_06"),
|
|
("redarmy11", "anniu_08"),
|
|
("redarmy13", "anniu_10"),
|
|
("redarmy15", "anniu_12"),
|
|
("redarmy20", "anniu_17"),
|
|
("redarmy29", "anniu_19"),
|
|
("redarmy35", "anniu_22"),
|
|
("redarmy36", "anniu_21"),
|
|
("Prop_star", "Prop_kuai1"),
|
|
("nProp_star", "nProp_kuai1"),
|
|
("redarmyship_F", "redArmyShip_F"),
|
|
("redarmyship_B", "redArmyShip_B"),
|
|
]:
|
|
add("redArmy", src, dst)
|
|
for i in range(1, 3):
|
|
add("redArmy", f"skin/待机/小红军待机{i}", f"skin/待机正面/{i}")
|
|
add("redArmy", f"skin/待机背面/小红军待机{i}", f"skin/待机背面/{i}")
|
|
for i in range(1, 4):
|
|
add("redArmy", f"skin/走/小红军走{i}", f"skin/走/{i}")
|
|
add("redArmy", f"skin/走背面/小红军走{i}", f"skin/走背面/{i}")
|
|
add("redArmy", f"skin/跳/小红军跳{i}", f"skin/跳/{i}")
|
|
add("redArmy", f"skin/跳背面/小红军跳背面{i}", f"skin/跳背面/{i}")
|
|
|
|
# ── chinese ──
|
|
for src, dst in [
|
|
("素材切图2_画板 1", "bg"),
|
|
("素材切图2-23", "kuai11"),
|
|
("素材切图2-02", "anniu_03"),
|
|
("素材切图2-03", "anniu_06"),
|
|
("素材切图2-06", "anniu_08"),
|
|
("素材切图2-07", "anniu_10"),
|
|
("素材切图2-08", "anniu_12"),
|
|
("素材切图2-09", "anniu_17"),
|
|
("素材切图2-10", "anniu_19"),
|
|
("素材切图2-11", "anniu_22"),
|
|
("素材切图2-12", "anniu_21"),
|
|
("Prop_Dumpling", "Prop_kuai1"),
|
|
("nProp_Dumpling", "nProp_kuai1"),
|
|
("yun_F", "chineseShip_F"),
|
|
("yun_B", "chineseShip_B"),
|
|
]:
|
|
add("chinese", src, dst)
|
|
add("chinese", "skin/待机/机器人", "skin/待机正面/1")
|
|
add("chinese", "skin/待机/机器人-1", "skin/待机正面/2")
|
|
add("chinese", "skin/待机背面/机器人", "skin/待机背面/1")
|
|
add("chinese", "skin/待机背面/机器人-1", "skin/待机背面/2")
|
|
add("chinese", "skin/走/机器人", "skin/走/1")
|
|
add("chinese", "skin/走/机器人-1", "skin/走/2")
|
|
add("chinese", "skin/走/机器人-2", "skin/走/3")
|
|
add("chinese", "skin/走背面/机器人", "skin/走背面/1")
|
|
add("chinese", "skin/走背面/机器人-1", "skin/走背面/2")
|
|
add("chinese", "skin/走背面/机器人-2", "skin/走背面/3")
|
|
add("chinese", "skin/跳/机器人", "skin/跳/1")
|
|
add("chinese", "skin/跳/机器人-1", "skin/跳/2")
|
|
add("chinese", "skin/跳/机器人-2", "skin/跳/3")
|
|
add("chinese", "skin/跳背面/机器人", "skin/跳背面/1")
|
|
add("chinese", "skin/跳背面/机器人-1", "skin/跳背面/2")
|
|
add("chinese", "skin/跳背面/机器人-2", "skin/跳背面/3")
|
|
|
|
|
|
def _norm_rel(p: str) -> str:
|
|
p = p.replace("\\", "/")
|
|
return p[:-4] if p.endswith(".png") else p
|
|
|
|
|
|
def build_rename_lookup() -> dict[tuple[str, str], str]:
|
|
lookup: dict[tuple[str, str], str] = {}
|
|
for theme, old, new in RENAMES:
|
|
old_n = _norm_rel(old)
|
|
new_n = _norm_rel(new)
|
|
lookup[(theme, Path(old_n).name)] = new_n
|
|
lookup[(theme, old_n)] = new_n
|
|
return lookup
|
|
|
|
|
|
def move_pair(src: Path, dst: Path, *, skip_if_dst_exists: bool = False) -> bool:
|
|
if not src.exists():
|
|
return False
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
if dst.exists():
|
|
if skip_if_dst_exists:
|
|
legacy = dst.parent / "_legacy" / "duplicates" / src.name
|
|
legacy.parent.mkdir(parents=True, exist_ok=True)
|
|
return move_pair(src, legacy)
|
|
return False
|
|
os.rename(src, dst)
|
|
meta_src = Path(str(src) + ".meta")
|
|
meta_dst = Path(str(dst) + ".meta")
|
|
if meta_src.exists():
|
|
if meta_dst.exists():
|
|
meta_dst.unlink()
|
|
os.rename(meta_src, meta_dst)
|
|
return True
|
|
|
|
|
|
def read_meta_display_name(meta_path: Path) -> str | None:
|
|
if not meta_path.exists():
|
|
return None
|
|
try:
|
|
data = json.loads(meta_path.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError:
|
|
return None
|
|
for sub in (data.get("subMetas") or {}).values():
|
|
name = sub.get("displayName")
|
|
if name:
|
|
return str(name)
|
|
return None
|
|
|
|
|
|
def recover_stages_from_meta() -> int:
|
|
lookup = build_rename_lookup()
|
|
recovered = 0
|
|
for stage in sorted(TEX.rglob("__stage_*.png")):
|
|
theme_key = stage.relative_to(TEX).parts[0]
|
|
display = read_meta_display_name(Path(str(stage) + ".meta"))
|
|
if not display:
|
|
continue
|
|
new_rel = lookup.get((theme_key, display)) or lookup.get((theme_key, _norm_rel(display)))
|
|
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" recovered {theme_key}/{display} -> {new_rel}.png")
|
|
return recovered
|
|
|
|
|
|
def apply_renames() -> int:
|
|
count = 0
|
|
for theme, old_rel, new_rel in RENAMES:
|
|
src = TEX / theme / old_rel
|
|
dst = TEX / theme / new_rel
|
|
if not src.exists():
|
|
continue
|
|
if dst.exists():
|
|
move_pair(src, src.parent / "_legacy" / "duplicates" / src.name)
|
|
continue
|
|
if move_pair(src, dst):
|
|
count += 1
|
|
return count
|
|
|
|
|
|
def cleanup_empty_dirs(theme_dir: Path) -> None:
|
|
for dirpath, dirnames, filenames in os.walk(theme_dir, topdown=False):
|
|
p = Path(dirpath)
|
|
if p == theme_dir:
|
|
continue
|
|
has_png = any(f.endswith(".png") for f in filenames)
|
|
if not has_png and not dirnames:
|
|
for f in list(p.iterdir()):
|
|
f.unlink()
|
|
meta = Path(str(p) + ".meta")
|
|
if meta.exists():
|
|
meta.unlink()
|
|
p.rmdir()
|
|
|
|
|
|
SANXING_KEEP = {
|
|
"bg", "Baseblock", "JumpBlock", "WallBlock", "kuai11",
|
|
"Prop_kuai1", "nProp_kuai1",
|
|
"anniu_03", "anniu_06", "anniu_08", "anniu_10", "anniu_12",
|
|
"anniu_17", "anniu_19", "anniu_21", "anniu_22",
|
|
}
|
|
|
|
THEME_SHIP = {
|
|
"silu": ("siluShip_F", "siluShip_B"),
|
|
"snow": ("snowShip_F", "snowShip_B"),
|
|
"numMan": ("numManShip_F", "numManShip_B"),
|
|
"redArmy": ("redArmyShip_F", "redArmyShip_B"),
|
|
"chinese": ("chineseShip_F", "chineseShip_B"),
|
|
"sanxing": ("sanxingShip_F", "sanxingShip_B"),
|
|
}
|
|
|
|
|
|
def move_legacy(theme: str, keep_names: set[str]) -> None:
|
|
theme_dir = TEX / theme
|
|
legacy = theme_dir / "_legacy"
|
|
for path in sorted(theme_dir.rglob("*.png")):
|
|
rel = path.relative_to(theme_dir).as_posix()
|
|
if rel.startswith("_legacy/") or rel.startswith("__stage_"):
|
|
continue
|
|
base = rel.replace(".png", "")
|
|
if base in keep_names:
|
|
continue
|
|
if base.startswith("skin/"):
|
|
parts = base.split("/")
|
|
if len(parts) == 3 and parts[1] in {
|
|
"待机正面", "待机背面", "走", "走背面", "跳", "跳背面",
|
|
} and re.fullmatch(r"\d+", parts[2]):
|
|
continue
|
|
dest = legacy / rel
|
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
move_pair(path, dest)
|
|
|
|
|
|
def build_path_replacements() -> dict[str, str]:
|
|
pairs = [
|
|
("textures/silu/素材切图_画板 1", "textures/silu/bg"),
|
|
("textures/silu/素材切图-23", "textures/silu/kuai11"),
|
|
("textures/silu/Decor23", "textures/silu/kuai11"),
|
|
("textures/silu/素材切图-03", "textures/silu/anniu_03"),
|
|
("textures/silu/素材切图-05", "textures/silu/anniu_06"),
|
|
("textures/silu/1倍速", "textures/silu/anniu_08"),
|
|
("textures/silu/2倍速", "textures/silu/anniu_10"),
|
|
("textures/silu/4倍速", "textures/silu/anniu_12"),
|
|
("textures/silu/素材切图-09", "textures/silu/anniu_17"),
|
|
("textures/silu/素材切图-10", "textures/silu/anniu_19"),
|
|
("textures/silu/声音关闭", "textures/silu/anniu_21"),
|
|
("textures/silu/声音", "textures/silu/anniu_22"),
|
|
("textures/snow/Prop_kuai2", "textures/snow/Prop_kuai1"),
|
|
("textures/snow/nProp_kuai2", "textures/snow/nProp_kuai1"),
|
|
("textures/numMan/Prop_kuai", "textures/numMan/Prop_kuai1"),
|
|
("textures/numMan/nProp_kuai", "textures/numMan/nProp_kuai1"),
|
|
("textures/numMan/skin/待机/待机1", "textures/numMan/skin/待机正面/1"),
|
|
("textures/numMan/skin/待机背面/待机1", "textures/numMan/skin/待机背面/1"),
|
|
("textures/redArmy/小游戏素材红色_03", "textures/redArmy/kuai11"),
|
|
("textures/redArmy/Prop_star", "textures/redArmy/Prop_kuai1"),
|
|
("textures/redArmy/nProp_star", "textures/redArmy/nProp_kuai1"),
|
|
("textures/redArmy/redarmyship_F", "textures/redArmy/redArmyShip_F"),
|
|
("textures/redArmy/redarmyship_B", "textures/redArmy/redArmyShip_B"),
|
|
("textures/redArmy/redarmy02", "textures/redArmy/anniu_03"),
|
|
("textures/redArmy/redarmy09", "textures/redArmy/anniu_06"),
|
|
("textures/redArmy/redarmy11", "textures/redArmy/anniu_08"),
|
|
("textures/redArmy/redarmy13", "textures/redArmy/anniu_10"),
|
|
("textures/redArmy/redarmy15", "textures/redArmy/anniu_12"),
|
|
("textures/redArmy/redarmy20", "textures/redArmy/anniu_17"),
|
|
("textures/redArmy/redarmy29", "textures/redArmy/anniu_19"),
|
|
("textures/redArmy/redarmy35", "textures/redArmy/anniu_22"),
|
|
("textures/redArmy/redarmy36", "textures/redArmy/anniu_21"),
|
|
("textures/redArmy/skin/待机/小红军待机1", "textures/redArmy/skin/待机正面/1"),
|
|
("textures/redArmy/skin/待机背面/小红军待机1", "textures/redArmy/skin/待机背面/1"),
|
|
("textures/chinese/素材切图2_画板 1", "textures/chinese/bg"),
|
|
("textures/chinese/素材切图2-23", "textures/chinese/kuai11"),
|
|
("textures/chinese/素材切图2-02", "textures/chinese/anniu_03"),
|
|
("textures/chinese/素材切图2-03", "textures/chinese/anniu_06"),
|
|
("textures/chinese/素材切图2-06", "textures/chinese/anniu_08"),
|
|
("textures/chinese/素材切图2-07", "textures/chinese/anniu_10"),
|
|
("textures/chinese/素材切图2-08", "textures/chinese/anniu_12"),
|
|
("textures/chinese/素材切图2-09", "textures/chinese/anniu_17"),
|
|
("textures/chinese/素材切图2-10", "textures/chinese/anniu_19"),
|
|
("textures/chinese/素材切图2-11", "textures/chinese/anniu_22"),
|
|
("textures/chinese/素材切图2-12", "textures/chinese/anniu_21"),
|
|
("textures/chinese/Prop_Dumpling", "textures/chinese/Prop_kuai1"),
|
|
("textures/chinese/nProp_Dumpling", "textures/chinese/nProp_kuai1"),
|
|
("textures/chinese/yun_F", "textures/chinese/chineseShip_F"),
|
|
("textures/chinese/yun_B", "textures/chinese/chineseShip_B"),
|
|
("textures/chinese/Panda/待机/机器人", "textures/chinese/skin/待机正面/1"),
|
|
("textures/chinese/Panda/待机背面/机器人", "textures/chinese/skin/待机背面/1"),
|
|
("textures/chinese/Panda/走", "textures/chinese/skin/走"),
|
|
("textures/chinese/Panda/跳", "textures/chinese/skin/跳"),
|
|
("textures/chinese/Panda/走背面", "textures/chinese/skin/走背面"),
|
|
("textures/chinese/Panda/跳背面", "textures/chinese/skin/跳背面"),
|
|
("textures/chinese/Panda/待机", "textures/chinese/skin/待机正面"),
|
|
('borderDecorKey": "素材切图-23', 'borderDecorKey": "kuai11'),
|
|
('borderDecorKey": "素材切图2-23', 'borderDecorKey": "kuai11'),
|
|
('borderDecorKey": "小游戏素材红色_03', 'borderDecorKey": "kuai11'),
|
|
]
|
|
return dict(pairs)
|
|
|
|
|
|
def patch_text_files(reps: dict[str, str]) -> int:
|
|
count = 0
|
|
for base in [ROOT / "assets", ROOT / "extensions"]:
|
|
if not base.exists():
|
|
continue
|
|
for path in base.rglob("*"):
|
|
if path.suffix not in {".ts", ".json", ".js", ".jsx"}:
|
|
continue
|
|
if "node_modules" in path.parts or "library" in path.parts:
|
|
continue
|
|
text = path.read_text(encoding="utf-8")
|
|
orig = text
|
|
for old, new in sorted(reps.items(), key=lambda x: -len(x[0])):
|
|
text = text.replace(old, new)
|
|
if text != orig:
|
|
path.write_text(text, encoding="utf-8")
|
|
count += 1
|
|
print(f" patched {path.relative_to(ROOT)}")
|
|
return count
|
|
|
|
|
|
def patch_themes_database() -> None:
|
|
db_path = ROOT / "assets" / "resources" / "theme" / "themes-database.json"
|
|
data = json.loads(db_path.read_text(encoding="utf-8"))
|
|
themes = data["themes"]
|
|
|
|
def hud(theme: str) -> dict:
|
|
return {
|
|
"navigation": f"textures/{theme}/anniu_03",
|
|
"revert": f"textures/{theme}/anniu_06",
|
|
"speed1": f"textures/{theme}/anniu_08",
|
|
"speed2": f"textures/{theme}/anniu_10",
|
|
"speed4": f"textures/{theme}/anniu_12",
|
|
"zoomIn": f"textures/{theme}/anniu_17",
|
|
"zoomOut": f"textures/{theme}/anniu_19",
|
|
"audioOn": f"textures/{theme}/anniu_22",
|
|
"audioOff": f"textures/{theme}/anniu_21",
|
|
}
|
|
|
|
folder_map = {
|
|
"silu": "silu", "sanxing": "sanxing", "snow": "snow",
|
|
"chinese": "chinese", "numMan": "numMan", "redarmy": "redArmy",
|
|
}
|
|
|
|
for key, folder in folder_map.items():
|
|
if key not in themes:
|
|
continue
|
|
t = themes[key]
|
|
ship_f, ship_b = THEME_SHIP[folder]
|
|
t["background"] = f"textures/{folder}/bg"
|
|
t["borderDecorKey"] = "kuai11"
|
|
t["entities"] = {
|
|
"playerFront": f"textures/{folder}/skin/待机正面/1",
|
|
"playerBack": f"textures/{folder}/skin/待机背面/1",
|
|
"vehicleFront": f"textures/{folder}/{ship_f}",
|
|
"vehicleBack": f"textures/{folder}/{ship_b}",
|
|
"prop": f"textures/{folder}/Prop_kuai1",
|
|
"propGround": f"textures/{folder}/nProp_kuai1",
|
|
}
|
|
t["tiles"] = {
|
|
"Baseblock": f"textures/{folder}/Baseblock",
|
|
"JumpBlock": f"textures/{folder}/JumpBlock",
|
|
"WallBlock": f"textures/{folder}/WallBlock",
|
|
"borderDecor": f"textures/{folder}/kuai11",
|
|
}
|
|
existing_hud = t.get("hud", {})
|
|
t["hud"] = {**hud(folder), **{k: v for k, v in existing_hud.items() if k not in hud(folder)}}
|
|
|
|
db_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
print(f" updated {db_path.relative_to(ROOT)}")
|
|
|
|
|
|
def patch_palettes() -> None:
|
|
idx = ROOT / "assets" / "resources" / "map-tiles" / "palettes" / "_index.json"
|
|
if idx.exists():
|
|
data = json.loads(idx.read_text(encoding="utf-8"))
|
|
for pal in data.get("palettes", []):
|
|
theme = pal.get("theme") or pal.get("id", "")
|
|
folder = {"redarmy": "redArmy"}.get(theme, theme)
|
|
for tile in pal.get("tiles", []):
|
|
tex = tile.get("texture", "")
|
|
if any(x in tex for x in ("素材", "Decor", "小游戏", "kuai11")):
|
|
tile["texture"] = f"textures/{folder}/kuai11"
|
|
idx.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
for name, folder in [("silu", "silu"), ("chinese", "chinese"), ("redarmy", "redArmy")]:
|
|
p = ROOT / "assets" / "resources" / "map-tiles" / "palettes" / f"{name}.json"
|
|
if not p.exists():
|
|
continue
|
|
pal = json.loads(p.read_text(encoding="utf-8"))
|
|
for tile in pal.get("tiles", []):
|
|
tex = tile.get("texture", "")
|
|
if any(x in tex for x in ("素材", "Decor", "小游戏", "kuai11")):
|
|
tile["texture"] = f"textures/{folder}/kuai11"
|
|
p.write_text(json.dumps(pal, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
|
|
def main() -> None:
|
|
print("=== 恢复 __stage__ 贴图 ===")
|
|
n_rec = recover_stages_from_meta()
|
|
print(f" 恢复 {n_rec} 个文件")
|
|
|
|
print("=== 重命名贴图 ===")
|
|
n = apply_renames()
|
|
print(f" 完成 {n} 项重命名")
|
|
|
|
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("=== 更新配置与代码引用 ===")
|
|
reps = build_path_replacements()
|
|
n_files = patch_text_files(reps)
|
|
patch_themes_database()
|
|
patch_palettes()
|
|
print(f" 共更新 {n_files} 个文本文件")
|
|
print("完成。")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|