Indie Game Lab

How I Cut My Mini-Game Bundle Size 60%

By Haitao Pan · Game Developer & Studio Owner · 10 min read

TL;DR: Load time is retention. I shipped a 750×1334 portrait mini-game on Cocos Creator 3.8, and the single biggest perceived-speed win wasn't a faster engine — it was shipping less. Engine subpackaging, splitting assets behind the first screen, one brutal texture audit (the skydome went), dead-resource cleanup, and a runtime PerfTier that streams assets by device capability together cut the initial footprint by roughly 60%. Here's the teardown.

1. Why bundle size is a gameplay problem

On desktop, a few extra megabytes are invisible. On a phone over a flaky connection, the gap between "tap" and "playable" is the difference between a player who stays and a player who bounces. I treat initial load as part of the core loop — a slow first paint is just a bad first session. The goal wasn't a smaller number on a dashboard; it was a shorter wait before the first meaningful frame.

This is the same project I wrote up in my Cocos Creator mini-game post-mortem. The optimization pass came later, when the feature set was frozen and I could finally measure what was actually being shipped. The results below are directional — I'm not publishing exact byte counts, because your numbers will differ by asset, but the shape of the wins transfers.

2. The biggest lever: stop shipping the engine inline

The engine runtime is the largest single block in almost any Cocos build. The default packaging inlines it into your main bundle, so every player downloads the whole thing up front even if they only ever see the menu. The fix is to subpackage the engine so it loads as a separate, cacheable module rather than a monolith behind first paint.

In practice this meant flipping the build's separateEngine flag and re-running the build so the engine artifact is emitted as its own file. The first-screen script then pulls a much smaller shell first, and the engine module loads in parallel / on demand. The exact toggle name varies by target platform, but the principle is universal: the engine is infrastructure, not content — keep it out of the critical path.

Before: one monolith engine + content (blocks first paint) After: split first-screen engine (cached) First paint waits only on the small shell; the engine loads separately and stays cached for every later visit. Note: on platforms with an engine-plugin CDN, subpackaging can be replaced by referencing the shared engine — same idea, even smaller first download.
Moving the engine out of the critical path was the single largest cut — and it also improves repeat-visit load because the engine module is cacheable.

3. Split assets behind the first screen

Engine subpackaging helps the shell, but content assets (scenes, atlases, audio) were the next block. Most players never touch 80% of them in a single session. I moved everything past the home screen into on-demand subpackages that load when the relevant scene actually opens.

The mental model: the home/menu screen should be the only thing in the initial footprint. Every deeper scene — battle, dungeon, inventory — becomes a separately-fetched chunk. Cocos supports dynamic asset loading and subpackage manifests; the work is mostly discipline, not cleverness. Audit your scenes, draw a line at "what the player sees in the first 3 seconds," and push everything below that line out of the main bundle.

4. The texture audit nobody enjoys (and the skydome that had to go)

This is the part that actually hurt. I rendered a single-frame 3D skydome for ambience — a large texture that was on screen for maybe two seconds of a session, and only as a backdrop. It was dead weight on every load. I deleted it and replaced the ambience with a flat gradient (or a tiny baked backdrop). No player noticed. The bundle did.

The audit process that found it:

  1. List every texture over a threshold (say, larger than 256 KB) and ask "does this earn its place on first load?"
  2. Right-size — most UI textures were exported at 2× their display size. Downscaling to actual device resolution recovered a surprising amount.
  3. Compress — moving suitable textures to a compressed format (platform-dependent) cut memory and download size.
  4. De-duplicate — the auto-atlas tool had silently emitted near-identical sheets. Consolidating them removed overlap.

The lesson: a 3D scene's pretty-but-optional assets are the easiest fat to cut. Be ruthless about "ambience" — if a player can't name it, they won't miss it, but they will feel the faster load.

5. Dead resources: what the build keeps that you forgot

Engines are conservative — they bundle anything that might be referenced. Over a long project, that accumulates: an old prefab, a retired audio clip, a test scene. None of it shows up in your gameplay, all of it shows up in your bundle.

My cleanup pass:

This is the same "structured data, not hardcoded" discipline I applied to the i18n layer — when assets are referenced through a single index rather than scattered ad-hoc imports, it's possible to actually see what's unused.

6. PerfTier: don't make low-end phones download what they can't show

The deepest win was runtime, not build-time: a PerfTier system that detects device capability at launch and streams a different asset profile accordingly. Low-end devices get smaller textures and skip optional effects; high-end devices get the full set. Crucially, the light profile is the default first download, and heavier assets upgrade in after the game is already playable.

Launch detect tier PerfTier Low light set Mid light + extras High full, upgrades later First playable frame ships the LIGHT set only. Heavier tiers upgrade in after play starts. Low-end phones never download what they can't render.
PerfTier turns "one size fits all" into "small by default, heavy by upgrade" — the safest way to cut initial download without hurting high-end visuals.

The key design choice: the light profile is the default, not an opt-in. High-end devices opt up after the game is already on screen. That way the bundle size you measure is the size everyone experiences first.

7. What I'd do differently

  1. Measure before building features, not after. I only ran the audit once the feature set froze. Doing it earlier would have caught the skydome on day one.
  2. Bake PerfTier into the architecture from the start. Retrofitting asset tiers onto a build that assumed one uniform set was more work than designing for it. The engine comparison I wrote in Cocos vs Unity vs Godot matters here too — pick a pipeline that makes subpackaging cheap.
  3. Automate the dead-resource check. A CI step that fails the build on unreferenced large assets would have prevented the slow creep.

8. Takeaways

If your mini-game's first load feels sluggish, the fix is almost never "a faster engine" — it's "ship less." Pull the engine out of the critical path, split everything past the first screen into on-demand chunks, audit textures with zero mercy (the skydome goes), sweep dead resources, and add a PerfTier so low-end devices download only what they can render. Together those moves took my initial footprint down by roughly 60%, and the game felt faster on every device — not just the good ones.

9. Related reads

← Back to all field notes