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

281 lines
8.7 KiB
Python
Raw Permalink 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
"""将地块贴图 .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()