IMX6U-Game/tools/analyze_foreground.py

104 lines
3.4 KiB
Python

"""Print per-GUID analysis of the foreground Tilemap to help identify TileIds.
For each tile asset GUID, prints:
- count
- bounding box (min/max x, y in Unity cell coords)
- 5 sample positions
- top neighbor distribution: which GUID most often sits directly ABOVE this one
- bottom neighbor distribution: which GUID most often sits directly BELOW
Run:
python tools/analyze_foreground.py
"""
from __future__ import annotations
import os
import sys
from collections import Counter, defaultdict
from typing import Dict, Tuple
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import unity_to_level as u2l
def short(guid: str) -> str:
return guid[:8]
def main() -> int:
with open(u2l.SCENE_PATH, "r", encoding="utf-8") as f:
text = f.read()
docs = u2l.split_documents(text)
go_names = u2l.parse_gameobjects(docs)
tms = u2l.parse_tilemaps(docs, go_names)
fg = tms.get("foreground")
if not fg:
print("no foreground tilemap")
return 1
# Build a (x, y) -> guid map; drop duplicates conservatively.
cell_guid: Dict[Tuple[int, int], str] = {}
for x, y, idx in fg.cells:
if 0 <= idx < len(fg.asset_guids):
cell_guid[(x, y)] = fg.asset_guids[idx]
# Group cells by guid.
by_guid: Dict[str, list] = defaultdict(list)
for (x, y), guid in cell_guid.items():
by_guid[guid].append((x, y))
# Order by count desc.
ordered = sorted(by_guid.items(), key=lambda kv: -len(kv[1]))
print(f"foreground tile assets: {len(fg.asset_guids)}, total cells: {len(cell_guid)}")
print()
for guid, cells in ordered:
xs = [c[0] for c in cells]
ys = [c[1] for c in cells]
# Neighbors: in Unity Y is up, so "above" = y+1, "below" = y-1.
above_counter: Counter = Counter()
below_counter: Counter = Counter()
left_counter: Counter = Counter()
for x, y in cells:
up = cell_guid.get((x, y + 1))
dn = cell_guid.get((x, y - 1))
lf = cell_guid.get((x - 1, y))
above_counter[up] += 1 # None means empty/sky
below_counter[dn] += 1
left_counter[lf] += 1
# Sample positions: a few spread out cells.
sample = sorted(cells)[:: max(1, len(cells) // 5)][:5]
print(f"{short(guid)} ({guid})")
print(f" count : {len(cells)}")
print(f" bbox : x [{min(xs)} .. {max(xs)}] y [{min(ys)} .. {max(ys)}]")
print(f" samples: {sample}")
# top 3 of each direction
def fmt(c: Counter) -> str:
top = c.most_common(3)
return ", ".join(
f"{short(g) if g else '<empty>'}={n}" for g, n in top
)
print(f" above (Unity y+1): {fmt(above_counter)}")
print(f" below (Unity y-1): {fmt(below_counter)}")
print(f" left : {fmt(left_counter)}")
# If almost all rows are the same y, it's probably a horizontal-only tile.
y_hist = Counter(ys)
top_y = y_hist.most_common(3)
print(f" y histogram (top 3): {top_y}")
# Single-row or single-col flag
if len(set(ys)) <= 2:
print(f" >>> appears on only {len(set(ys))} distinct row(s) — likely a 'top' or row-specific tile")
if len(set(xs)) <= 2:
print(f" >>> appears on only {len(set(xs))} distinct column(s)")
print()
return 0
if __name__ == "__main__":
sys.exit(main())