Phaser Asset Pipeline Automation: From AI Art to Game-Ready Sprites

Phaser Asset Pipeline Automation: From AI Art to Game-Ready Sprites

Honesty note: this is my actual asset pipeline for Merge Fish 2048 — one Python script (process_ai_assets.py) that turns AI-generated art into the exact assets the Phaser game loads. Every step below is from that script, trimmed to the parts you can reuse.

TL;DR

  1. A scriptable asset pipeline is what makes AI art usable in Phaser. Generated sprites need background removal, cropping, text, and size standardization — doing that by hand every time AI iterates is a full-time job.
  2. The gradient-threshold background removal is the core trick — a single hard cut at white fringes every sprite; a two-threshold gradient keeps anti-aliased edges and highlights intact.
  3. Bake sizes into the asset, not the scene. Resize/center in the pipeline to the exact dimensions your game uses (buttons 320×96, board 720×720, bubbles 48/72/96), so Phaser just loads and places.
  4. Re-runnability is the whole point. When AI regenerates a v2 asset, you re-run one script and get consistent output — the pipeline is what makes iteration cheap.

The pipeline (as it runs)

AI art (white bg, any size)
  → download (skip if cached)
  → remove_white_bg (two-threshold gradient)
  → trim_to_content (crop alpha bbox)
  → resize / center on canvas (exact target sizes)
  → add_button_text (shadow + round outline)
  → crop particles from cluster (small/medium/large)
  → save to public/assets/ui

Everything is deterministic and idempotent: re-running produces identical output and skips downloads.

Step-by-step

1. Download with cache. Each asset keyed by name; if not os.path.exists: urlretrieve. Re-runs don’t re-fetch, so downstream steps can be regenerated freely.

2. Gradient-threshold background removal (the core):

def remove_white_bg(img, threshold=235, edge_threshold=200):
    img = img.convert("RGBA")
    pixels = img.load()
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            r, g, b, a = pixels[x, y]
            min_val = min(r, g, b)
            if min_val >= threshold:
                pixels[x, y] = (r, g, b, 0)          # clearly white → transparent
            elif min_val >= edge_threshold:
                alpha = int((threshold - min_val) / (threshold - edge_threshold) * 255)
                pixels[x, y] = (r, g, b, alpha)       # near-white → gradient alpha
    return img

min(r,g,b) is the distance-from-white proxy: the closer a pixel is to white in all channels, the more transparent it becomes. Tune threshold/edge_threshold per asset type (title 240/210, buttons 240/210, bubbles 240/215).

3. Trim to contentimg.getbbox() crops transparent margins, removing AI’s unpredictable padding before any size math.

4. Resize or center-pad to exact sizes. Buttons resize((320,96), Image.LANCZOS); square assets (board 720×720, bubbles) center-paste onto a transparent canvas at the target — aspect preserved, no engine-side scaling.

5. Button text with shadow + outline:

draw.text((tx+2, ty+3), text, font=font, fill=(0,0,0,100))   # shadow
for dx in range(-ow, ow+1):
    for dy in range(-ow, ow+1):
        if dx*dx + dy*dy <= ow*ow:
            draw.text((tx+dx, ty+dy), text, font=font, fill=outline_color)  # round outline
draw.text((tx, ty), text, font=font, fill=text_color)       # main text

The circular sweep (dx²+dy² ≤ ow²) keeps the outline rounded, and a bold font (Impact/Arial Bold) keeps small UI text legible.

6. Crop particles from one cluster. Define three regions of the source image, trim + resize each to 48/72/96 — one AI generation gives you a consistent particle family.

Why this matters for Phaser specifically

Phaser loads assets by key at boot (preload). If your art arrives in arbitrary sizes and formats, you either scale at runtime (blur, layout drift) or hand-fix every asset. The pipeline removes both:

Pitfalls

  1. One-off fixes in an image editor — the next AI iteration erases them. Every transformation belongs in the script.
  2. Single-threshold background removal — holes in white fish bellies, fringes on edges. Use the gradient version.
  3. Scaling at runtimesetScale on a huge source is blur + memory. Bake it.
  4. Skipping the cache — re-downloading every run makes iteration slow and brittle.
  5. Ignoring alpha in text rendering — without the shadow+outline, button text vanishes on light backgrounds.

Bottom line

The Phaser asset pipeline is a solved problem once you script it: download → gradient-remove background → trim → size → text → particles, all deterministic and re-runnable. The gradient threshold is the step that separates game-ready art from AI-looking art, and re-runnability is what makes AI iteration actually cheap. This pipeline is one working piece of the full AI-assisted workflow and pairs with the AI asset generation guide for the generation side.