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:
2026-06-16 15:30:58 +08:00
parent cba5105908
commit d393302388
6248 changed files with 17322729 additions and 11036 deletions

View File

@@ -0,0 +1,280 @@
#!/usr/bin/env python3
"""将地块贴图 .meta 设为 sprite-frame对齐 Unity pivot按透明边裁剪以贴满格子。"""
from __future__ import annotations
import argparse
import json
import re
import struct
import uuid
from pathlib import Path
TILE_GLOBS = [
"assets/resources/textures/*/Baseblock.png",
"assets/resources/textures/*/JumpBlock.png",
"assets/resources/textures/*/WallBlock.png",
"assets/resources/textures/*/Decor23.png",
"assets/resources/textures/*/kuai11.png",
"assets/resources/textures/*/素材切图-23.png",
"assets/resources/textures/*/素材切图2-23.png",
]
COCOS_TO_UNITY = {
"default": "Level",
"silu": "silu",
"snow": "snow",
"sanxing": "sanxing",
"chinese": "Chinese",
"numMan": "numMan",
"redarmy": "redArmy",
"redArmy": "redArmy",
}
def png_size(path: Path) -> tuple[int, int]:
with path.open("rb") as f:
f.read(16)
return struct.unpack(">II", f.read(8))
def alpha_bbox(png_path: Path) -> tuple[int, int, int, int] | None:
try:
from PIL import Image
except ImportError:
return None
try:
img = Image.open(png_path).convert("RGBA")
box = img.getbbox()
if not box:
return None
return box
except Exception:
return None
def read_unity_pivot(unity_tex: Path, theme_folder: str, tile_name: str) -> tuple[float, float]:
theme_dir = unity_tex if theme_folder == "Level" else unity_tex / theme_folder
meta_path = theme_dir / f"{tile_name}.png.meta"
if not meta_path.is_file() and tile_name == "Decor23":
meta_path = theme_dir / "素材切图-23.png.meta"
if not meta_path.is_file():
return 0.5, 0.92
text = meta_path.read_text(encoding="utf-8")
m = re.search(r"spritePivot:\s*\{x:\s*([^,]+),\s*y:\s*([^}]+)\}", text)
if not m:
return 0.5, 0.92
return float(m.group(1)), float(m.group(2))
def resolve_trim(
png_path: Path,
full_w: int,
full_h: int,
pivot_x: float,
pivot_y: float,
) -> dict:
"""按透明边裁剪,并换算 Unity pivot 到裁剪后 sprite。"""
bbox = alpha_bbox(png_path)
if not bbox:
return {
"trimType": "none",
"trimX": 0,
"trimY": 0,
"width": full_w,
"height": full_h,
"rawWidth": full_w,
"rawHeight": full_h,
"offsetX": 0,
"offsetY": 0,
"pivotX": pivot_x,
"pivotY": pivot_y,
}
left, top, right, bottom = bbox
trim_w = right - left
trim_h = bottom - top
if trim_w <= 0 or trim_h <= 0 or (trim_w >= full_w and trim_h >= full_h):
return {
"trimType": "none",
"trimX": 0,
"trimY": 0,
"width": full_w,
"height": full_h,
"rawWidth": full_w,
"rawHeight": full_h,
"offsetX": 0,
"offsetY": 0,
"pivotX": pivot_x,
"pivotY": pivot_y,
}
pivot_bottom = pivot_y * full_h
trim_bottom = full_h - bottom
pivot_bottom_trim = pivot_bottom - trim_bottom
new_pivot_y = max(0.0, min(1.0, pivot_bottom_trim / trim_h))
new_pivot_x = max(0.0, min(1.0, (pivot_x * full_w - left) / trim_w))
return {
"trimType": "custom",
"trimX": left,
"trimY": top,
"width": trim_w,
"height": trim_h,
"rawWidth": full_w,
"rawHeight": full_h,
"offsetX": 0,
"offsetY": 0,
"pivotX": round(new_pivot_x, 4),
"pivotY": round(new_pivot_y, 4),
}
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 patch_meta(meta_path: Path, png_path: Path, trim: dict) -> None:
ensure_image_meta(meta_path, meta_path.stem.replace(".png", ""))
meta = json.loads(meta_path.read_text(encoding="utf-8"))
base_uuid = meta["uuid"]
tex_uuid = f"{base_uuid}@6c48a"
sf_uuid = f"{base_uuid}@f9941"
meta["userData"] = {
"type": "sprite-frame",
"redirect": tex_uuid,
"hasAlpha": True,
"fixAlphaTransparencyArtifacts": False,
}
meta["subMetas"]["f9941"] = {
"ver": "1.0.12",
"importer": "sprite-frame",
"uuid": sf_uuid,
"imported": True,
"files": [".json"],
"subMetas": {},
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"minfilter": "linear",
"magfilter": "linear",
"premultiplyAlpha": False,
"generateMipmap": False,
"anisotropy": 1,
"trimType": trim["trimType"],
"trimThreshold": 1,
"rotated": False,
"offsetX": trim["offsetX"],
"offsetY": trim["offsetY"],
"trimX": trim["trimX"],
"trimY": trim["trimY"],
"width": trim["width"],
"height": trim["height"],
"rawWidth": trim["rawWidth"],
"rawHeight": trim["rawHeight"],
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"isUuid": True,
"imageUuidOrDatabaseUri": tex_uuid,
"atlasUuid": "",
"mipfilter": "none",
"packable": True,
"pixelsToUnit": 100,
"pivotX": trim["pivotX"],
"pivotY": trim["pivotY"],
"meshType": 0,
},
"displayName": meta_path.stem,
"id": "f9941",
"name": "spriteFrame",
}
meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
rel = meta_path.relative_to(meta_path.parents[4])
print(
f" patched {rel} -> draw {trim['width']}x{trim['height']} "
f"(raw {trim['rawWidth']}x{trim['rawHeight']}) pivot=({trim['pivotX']},{trim['pivotY']})"
)
def collect_tile_pngs(root: Path, themes: set[str] | None) -> list[Path]:
found: set[Path] = set()
for pattern in TILE_GLOBS:
for p in root.glob(pattern):
if not p.is_file():
continue
if themes:
theme = p.parent.name
if theme not in themes:
continue
found.add(p)
return sorted(found)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--unity-root", help="Unity 项目根目录,用于读取 spritePivot")
ap.add_argument("--themes", help="仅处理指定 Cocos 主题目录,逗号分隔,如 snow")
args = ap.parse_args()
root = Path(__file__).resolve().parent.parent
theme_filter = {t.strip() for t in args.themes.split(",") if t.strip()} if args.themes else None
unity_tex = Path(args.unity_root) / "Assets" / "Texture" if args.unity_root else None
pngs = collect_tile_pngs(root, theme_filter)
for png in pngs:
meta = png.with_suffix(".png.meta")
if not png.is_file():
print(f" skip missing {png.relative_to(root)}")
continue
w, h = png_size(png)
tile_name = png.stem
cocos_theme = png.parent.name
if unity_tex and unity_tex.is_dir():
unity_folder = COCOS_TO_UNITY.get(cocos_theme, cocos_theme)
pivot_x, pivot_y = read_unity_pivot(unity_tex, unity_folder, tile_name)
else:
pivot_x, pivot_y = 0.5, 0.92
trim = resolve_trim(png, w, h, pivot_x, pivot_y)
patch_meta(meta, png, trim)
print(f"Patched {len(pngs)} tile metas. 请在 Cocos Creator 中刷新 assets/resources/textures。")
if __name__ == "__main__":
main()