Files
cocos/tools/import_unity_textures.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

357 lines
11 KiB
Python
Raw 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 Assets/Texture 导入 PNG 到 Cocos assets/resources/textures/
用法:
python3 tools/import_unity_textures.py \\
--unity-root "/path/to/主站" \\
[--themes silu,snow,default]
"""
from __future__ import annotations
import argparse
import json
import shutil
import uuid
from pathlib import Path
# 与 GameController UIStyleNames 对应Unity 目录名 → Cocos 子目录)
THEME_MAP = {
"default": ".", # Assets/Texture 根目录单层 png
"silu": "silu",
"chinese": "Chinese",
"redArmy": "redarmy",
"numMan": "numMan",
"snow": "snow",
"sanxing": "sanxing",
}
# 角色 + 砖块核心文件名(各主题有则拷贝,无则跳过)
CORE_NAMES = {
"Baseblock.png",
"JumpBlock.png",
"WallBlock.png",
"player_F.png",
"player_B.png",
"ship_F.png",
"ship_B.png",
"Ship_F.png",
"Prop.png",
"nProp.png",
"kuai11.png",
"素材切图-23.png",
"素材切图2-23.png",
"Prop_kuai1.png",
"Prop_kuai2.png",
"Prop_kuai.png",
"nProp_kuai1.png",
"nProp_kuai2.png",
"nProp_kuai.png",
"Prop_Dumpling.png",
"Prop_Mooncake.png",
"Prop_Ricedumpling.png",
"Prop_Zhongzi.png",
"Prop_star.png",
"nProp_star.png",
"nProp_Dumpling.png",
"nProp_Mooncake.png",
"nProp_Ricedumpling.png",
"nProp_Zhongzi.png",
}
# 丝路:对齐 sanxing/snow 命名skin/待机正面、*Ship_F、Prop_kuai1
SILU_CANONICAL_COPIES: list[tuple[str, str]] = [
("ship_F.png", "siluShip_F.png"),
("ship_B.png", "siluShip_B.png"),
("Prop.png", "Prop_kuai1.png"),
("nProp.png", "nProp_kuai1.png"),
]
def ensure_directory_meta(meta_path: Path) -> None:
if meta_path.is_file():
return
meta_path.write_text(
json.dumps(
{
"ver": "1.2.0",
"importer": "directory",
"imported": True,
"uuid": str(uuid.uuid4()),
"files": [],
"subMetas": {},
"userData": {},
},
indent=2,
),
encoding="utf-8",
)
def ensure_image_meta(meta_path: Path, display_name: str) -> None:
if meta_path.is_file():
return
base_uuid = str(uuid.uuid4())
meta_path.write_text(
json.dumps(
{
"ver": "1.0.27",
"importer": "image",
"imported": False,
"uuid": base_uuid,
"files": [".json", ".png"],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": f"{base_uuid}@6c48a",
"displayName": display_name,
"id": "6c48a",
"name": "texture",
"userData": {
"imageUuidOrDatabaseUri": base_uuid,
"isUuid": True,
"visible": False,
},
"ver": "1.0.22",
"imported": False,
"files": [".json"],
"subMetas": {},
}
},
"userData": {"type": "texture", "redirect": f"{base_uuid}@6c48a"},
},
indent=2,
),
encoding="utf-8",
)
def ensure_png_meta(png_path: Path) -> None:
ensure_image_meta(png_path.with_suffix(".png.meta"), png_path.stem)
def first_existing_file(*candidates: Path) -> Path | None:
for path in candidates:
if path.is_file():
return path
return None
def copy_skin_tree(src_skin: Path, dst_skin: Path) -> None:
"""将 player/skin 整树复制到主题根 skin对齐 sanxing 目录结构)。"""
if not src_skin.is_dir():
return
dst_skin.mkdir(parents=True, exist_ok=True)
ensure_directory_meta(dst_skin.parent / f"{dst_skin.name}.meta")
for src in sorted(src_skin.rglob("*")):
rel = src.relative_to(src_skin)
dst = dst_skin / rel
if src.is_dir():
dst.mkdir(parents=True, exist_ok=True)
ensure_directory_meta(dst.parent / f"{dst.name}.meta")
continue
if src.suffix.lower() != ".png":
continue
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
ensure_png_meta(dst)
def normalize_silu_assets(theme_dir: Path) -> None:
"""拷贝丝路实体贴图为标准文件名(保留 Unity 原名,额外写规范别名)。"""
skin_root = theme_dir / "skin"
player_skin = theme_dir / "player" / "skin"
if player_skin.is_dir():
copy_skin_tree(player_skin, skin_root)
skin_front = skin_root / "待机正面"
skin_back = skin_root / "待机背面"
skin_front.mkdir(parents=True, exist_ok=True)
skin_back.mkdir(parents=True, exist_ok=True)
ensure_directory_meta(skin_root.parent / f"{skin_root.name}.meta")
ensure_directory_meta(skin_front.parent / f"{skin_front.name}.meta")
ensure_directory_meta(skin_back.parent / f"{skin_back.name}.meta")
front_src = first_existing_file(
skin_front / "1.png",
player_skin / "待机正面" / "1.png",
theme_dir / "player" / "idel-正" / "1.png",
theme_dir / "player_F.png",
)
if front_src and front_src != skin_front / "1.png":
shutil.copy2(front_src, skin_front / "1.png")
if (skin_front / "1.png").is_file():
ensure_png_meta(skin_front / "1.png")
back_src = first_existing_file(
skin_back / "1.png",
player_skin / "待机背面" / "1.png",
theme_dir / "player" / "idel-反" / "1.png",
theme_dir / "player_B.png",
)
if back_src and back_src != skin_back / "1.png":
shutil.copy2(back_src, skin_back / "1.png")
if (skin_back / "1.png").is_file():
ensure_png_meta(skin_back / "1.png")
for src_name, dst_name in SILU_CANONICAL_COPIES:
src = theme_dir / src_name
dst = theme_dir / dst_name
if src.is_file():
shutil.copy2(src, dst)
ensure_png_meta(dst)
def normalize_nprop_filenames(theme_dir: Path) -> None:
"""Unity numMan 等主题 nProp 文件名可能带尾空格,统一为无空格文件名。"""
for png in theme_dir.rglob("nProp*.png"):
stem = png.stem
if stem != stem.rstrip():
fixed = png.with_name(f"{stem.rstrip()}.png")
if fixed != png:
if fixed.is_file():
png.unlink()
else:
png.rename(fixed)
meta = png.with_suffix(".png.meta")
fixed_meta = fixed.with_suffix(".png.meta")
if meta.is_file() and not fixed_meta.is_file():
meta.rename(fixed_meta)
# HUD 按钮UIMain 右侧,与 themes-database.json hud 字段对应)
HUD_GLOBS = (
"anniu_*.png",
"redarmy*.png",
"素材切图-*.png",
"素材切图2-*.png",
"1倍速.png",
"2倍速.png",
"4倍速.png",
"声音.png",
"声音关闭.png",
"导航图标.png",
"重置.png",
"放大图标.png",
"缩小图标.png",
)
def copy_hud_assets(unity_tex: Path, out_root: Path, style_key: str, rel: str) -> int:
"""拷贝 Unity UI 按钮贴图(原先 rglob 会跳过 anniu_/倍速/声音)"""
src = unity_tex if rel == "." else unity_tex / rel
if not src.is_dir():
return 0
dst = out_root / style_key
dst.mkdir(parents=True, exist_ok=True)
count = 0
seen: set[str] = set()
for pattern in HUD_GLOBS:
for png in src.glob(pattern):
if not png.is_file():
continue
key = str(png.resolve())
if key in seen:
continue
seen.add(key)
target = dst / png.name
shutil.copy2(png, target)
count += 1
return count
def copy_theme(unity_tex: Path, out_root: Path, style_key: str, rel: str) -> int:
src = unity_tex if rel == "." else unity_tex / rel
dst = out_root / style_key
dst.mkdir(parents=True, exist_ok=True)
count = 0
if rel == ".":
for png in src.glob("*.png"):
shutil.copy2(png, dst / png.name)
if png.name == "Ship_F.png":
shutil.copy2(png, dst / "ship_F.png")
count += 1
return count
if not src.is_dir():
print(f" skip missing theme dir: {src}")
return 0
for png in src.rglob("*.png"):
rel_path = png.relative_to(src)
# 跳过 HUD 专用碎图(由 copy_hud_assets 扁平拷贝到主题根目录)
if png.name.startswith("anniu_") or "倍速" in png.name or png.name.startswith("声音"):
continue
if png.name.startswith("素材切图") or png.name.startswith("redarmy"):
continue
target = dst / rel_path
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(png, target)
count += 1
# silu 装饰砖块别名(烘焙脚本用 Decor23
decor = dst / "素材切图-23.png"
if decor.is_file():
shutil.copy2(decor, dst / "Decor23.png")
normalize_nprop_filenames(dst)
if style_key == "silu":
normalize_silu_assets(dst)
return count
def copy_prop(unity_tex: Path, out_root: Path) -> int:
src = unity_tex / "Prop"
if not src.is_dir():
return 0
dst = out_root / "prop"
dst.mkdir(parents=True, exist_ok=True)
n = 0
for png in src.glob("*.png"):
name = png.name.replace("载具 正面", "vehicle_F").replace("载具 背面", "vehicle_B")
shutil.copy2(png, dst / name)
n += 1
return n
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--unity-root", required=True)
ap.add_argument("--out", default="assets/resources/textures")
ap.add_argument("--themes", default=",".join(THEME_MAP.keys()))
args = ap.parse_args()
project = Path(__file__).resolve().parent.parent
unity_tex = Path(args.unity_root) / "Assets" / "Texture"
out_root = project / args.out
if not unity_tex.is_dir():
raise SystemExit(f"Unity Texture 不存在: {unity_tex}")
themes = [t.strip() for t in args.themes.split(",") if t.strip()]
total = 0
for key in themes:
rel = THEME_MAP.get(key)
if rel is None:
print(f"unknown theme: {key}")
continue
n = copy_theme(unity_tex, out_root, key, rel)
hn = copy_hud_assets(unity_tex, out_root, key, rel)
print(f" {key}: {n} png (+ {hn} hud)")
total += n + hn
pn = copy_prop(unity_tex, out_root)
print(f" prop: {pn} png")
total += pn
print(f"Imported {total} files -> {out_root}")
print("请在 Cocos Creator 中刷新 assets/resources/textures然后运行:")
print(" python3 tools/fix_tile_texture_metas.py")
if __name__ == "__main__":
main()