How I Built “Super Miserable Adventurer” — A Cocos Creator WeChat Mini-Game Case Study
TL;DR: I shipped a 750×1334 portrait survival/crafting mini-game on Cocos Creator 3.8 for the WeChat platform. This is the post-mortem — the architecture that held up, the performance traps I walked into, and the overseas-i18n work that turned a Chinese-only build into something portable.
1. Why this game, why this engine
I wanted a game with a real progression loop — gather, craft, fight, dive dungeons — that still loads fast on a phone. WeChat's mini-game runtime is unforgiving about package size, so the engine choice was less "what's most powerful" and more "what fits the constraint." Cocos Creator 3.8 LTS won on three points: small runtime, real TypeScript support, and a build pipeline that targets WeChat directly.
I'm the one making the design calls, so the loop was built around a simple promise to the player: every session should move a number that matters. That single rule drove most of the UI decisions below.
2. The UI layer: a code-only widget library
The biggest architectural win was throwing out prefab-driven UI in favor of a small code-only widget library — think UINode, UILabel, UIButton, UIVStack/UIHStack, UIGrid. Reasons:
- Determinism. Layout is a function of data, not a hand-placed node graph. Refactoring a panel meant editing code, not hunting prefab references.
- Smaller builds. No prefab serialization overhead for dozens of near-identical cells.
- Reuse. The same grid component drives the home page, the map, and the inventory — one positioning algorithm, three surfaces.
The grid is a streaming layout: content height is computed from the row flow, the scroll view's viewport is fixed (not driven by a Widget, which fought scrollToTop), and each cell positions itself relative to contentHeight/2. That one detail killed a week of "scroll lands in the wrong place" bugs.
3. Performance budget I had to respect
Mini-game runtimes punish sloppiness. Three fixes moved the needle most:
- Skybox cleanup. An unused 3D backdrop was eating draw calls for nothing. Gone.
- Engine subpackage. Splitting the engine out of the main package dropped first-load size hard — critical under WeChat's strict size ceiling.
- PerfTier. A runtime quality switch that scales effects to the device, so low-end phones don't choke and flagships aren't capped artificially.
Rule I now live by: profile before you optimize, but optimize the package size before you profile the frame rate. Size blocks the install; frame rate only blocks the retention.
4. The overseas problem: i18n without a rewrite
The original build was Chinese-only, hardcoded strings everywhere. To make it portable I treated text as data:
- All visible strings routed through a single localization layer.
- A shared
textMetricshelper estimates label width from character class (CJK vs Latin) so layouts don't collapse when a short Chinese label becomes a long English sentence. - Structured fields over concatenated strings — e.g. an
isEquippedflag instead of baking "[Equipped]" into the name — so translations stay clean.
This is the part most post-mortems skip: localization is an architecture decision, not a find-and-replace pass. If you didn't build the seam in week one, you pay for it in a rewrite.
5. What I'd do differently
- Design the save/cloud layer earlier. Retention lives or dies on "I can pick this up tomorrow," and that needs a sync story, not a localStorage gamble.
- Ship one deep loop, not five shallow ones. The dungeon, the trade system, and the crafting tree each wanted to be the star. Picking one to go deep first would have shipped sooner.
- Measure fun, not just frames. I had a perf dashboard before I had a retention dashboard. Inverted priority.
6. Takeaways for other small teams
If you're building a mini-game or web game on a tight runtime: choose the engine by package-size ceiling first, build UI as code not prefabs, and treat i18n as a foundation pour, not a coat of paint. The game below the surface is the same game everywhere — only the words and the width of the text box change.