Unity

Unity architecture for a multi-game puzzle framework

How we structured a single Unity codebase to run more than a dozen puzzle mechanics: package boundaries, the mechanic contract, a data-driven meta layer and the tracking that comes for free.

Written by CMZ Soft5 min read
Block Flow 3D puzzle board

When we decided that every new puzzle game at the studio would run on one codebase, the first question was not "which patterns" but "where are the walls". A framework that lets game code reach into everything is just a large game. The value comes from boundaries that hold when the tenth mechanic arrives and nobody remembers the first.

This is an engineering write-up of how the CMZ Puzzle Framework is structured in Unity. It assumes familiarity with assembly definitions, ScriptableObjects and the usual mobile SDKs.

Package boundaries

The project is split into assemblies with strictly one-directional dependencies:

  • Core — shared utilities, event bus, save system, localization, timers. Depends on nothing.
  • Puzzle.Contract — the interfaces a mechanic must implement. Depends on Core.
  • Puzzle.Mechanics.* — one assembly per mechanic (BoardSort, ConveyorSort, ArrowLogic, CrowdJam, BlockBlast …). Depend on Contract and Core only. They do not know the meta layer exists.
  • Meta — progression, rewards, streaks, events, leagues, collections. Depends on Core and Contract; never on a specific mechanic.
  • Monetization — ads mediation wrapper, IAP wrapper, placement configuration. Depends on Core.
  • Tracking — analytics client, event taxonomy, batching. Depends on Core.
  • Game — the thin per-title assembly: theme, art, level data, configuration. Depends on everything.

The rule that matters most: mechanics never reference Meta, Monetization or Tracking directly. They raise events through Contract; the framework subscribes. This is what lets a new mechanic ship without touching (or breaking) the systems around it.

The mechanic contract

The contract is intentionally small. A mechanic provides:

  • LoadLevel(LevelData) and Unload()
  • An input surface the framework routes touches into
  • Events: LevelStarted, MoveMade(MoveInfo), LevelWon(WinInfo), LevelFailed(FailInfo), HintRequested
  • A BoosterPort that accepts booster effects the mechanic supports (undo, shuffle, extra slot…) and declares which it does not

Everything the meta layer and tracking need — attempts, time, moves, booster use, fail reason — flows through those events. If a mechanic wants a booster that does not exist yet, it declares a new booster type in Contract, and the meta and shop systems handle it generically.

Keeping the contract narrow was harder than it sounds. Every mechanic tempts you to add "just one" method. We resisted by asking whether the meta layer would ever need to call it; if the answer was "only this mechanic cares", it stayed inside the mechanic assembly.

Configuration-driven meta

The meta systems are built as runtime services configured by ScriptableObjects and remote config, not as scenes or prefabs a game copies and edits. A title enables a win-streak system by adding a configuration asset that describes thresholds, rewards and reset rules. The service subscribes to LevelWon / LevelFailed from the contract and does the rest.

The same applies to time-limited events, leagues, reward tracks and collections. Because the configuration is data, most of it is also exposed through remote config, so the live-ops team can adjust an event's parameters — or switch it off — without an app release.

The UI for these systems lives in Meta as themed prefabs with slots the per-title Game assembly fills with art. That gives each title its own look while keeping the logic shared.

Tracking for free

The Tracking assembly subscribes to the contract events and emits a fixed taxonomy: level start/win/fail with attempt index, duration, moves and booster usage; economy transactions with source and sink; ad and IAP events with placement and product identifiers; session boundaries. Titles add events only for genuinely title-specific features.

This is the single decision that made our analytics stack possible. Because every game emits the same events with the same meaning, the dashboards are built once, and a new title is fully covered from its first build.

Events are batched on-device with offline buffering and flushed on a timer and at session end. The batching layer is written to survive being killed mid-flush, which is the normal case on mobile.

SDK isolation

Ads, IAP, attribution and analytics SDKs change often and break at inconvenient times. Each is wrapped behind an interface in its own assembly with a null implementation used in the editor and in tests. Mediation networks are configured in data, and placements are named constants shared with the Tracking taxonomy, so a placement's revenue and its effect on the session can always be joined.

The practical benefit: an SDK upgrade touches one assembly, compiles or fails in isolation, and can be validated on a device without rebuilding the mechanics.

Mobile performance budgets

The framework enforces a few budgets that every title inherits:

  • Startup to first interactive under a fixed target on a mid-range Android reference device, achieved by deferring SDK initialization and loading the first level scene additively.
  • Frame time headroom checked in CI on the reference device with the profiler, per mechanic.
  • Build size budget with Addressables for chapter content beyond the first, and texture compression presets per platform.
  • Allocation discipline in the gameplay loop: pooled objects, no per-frame allocations, pre-sized collections.

Budgets are boring until the day a new mechanic doubles frame time on a device none of the team owns. Having them in CI turns that into a failed build instead of a one-star review.

What we would do differently

Two things. First, we would have introduced the null SDK implementations from day one; retrofitting them cost more than writing them would have. Second, we under-invested in editor tooling for level data early, and designers paid for it with slower iteration. Level tooling is framework code, not per-game code.

Summary

Walls first, patterns second. A minimal mechanic contract, one-directional assembly dependencies, data-driven meta, tracking on the contract events and SDKs behind interfaces are what allowed fourteen puzzle games to ship on one codebase in under a year — and what lets the fifteenth start at 60–70% done.

If you are building or evaluating a Unity framework for a portfolio of games, our engineers are happy to compare notes. Get in touch.

See how we put this into practice

Our games are built on the ideas we write about.

Explore Our Games

Building a puzzle game?

We partner with publishers on prototypes and full productions.

Talk to us

Related games

Zen Garden: Flower Sort gameplay screenshot

Zen Garden: Flower Sort

Sort Puzzle

Group identical flowers into their vases and clear the board — a calm, beautiful take on the sort puzzle.

LiveAndroid
View game
Hole Crowd: Bus Jam gameplay screenshot

Hole Crowd: Bus Jam

Crowd Jam

Open escape paths so every stickman drops into the hole of its color and boards the right bus before your slots run out.

LiveAndroid
View game

Related articles

All insights
Icons of CMZ Soft's games on Google Play
Studio Updates·

Introducing CMZ Soft: a product-driven puzzle studio from Hanoi

Who we are, what we have shipped — sixteen titles on Google Play in under a year — and how we intend to work with publishers, investors and the people who join us.

2 min read

Mosaic of mobile puzzle game icons
Market Analysis·

The mobile puzzle landscape in 2026: what a small studio can still win

Puzzle remains one of the largest and most durable mobile genres, and one of the most competitive. Where the opportunities are for a focused studio: sub-genre gaps, production speed, hybrid economies and disciplined UA.

4 min read

Portfolio of prototype and live game icons
Analytics·

A go / kill framework for mobile game prototypes

Deciding which prototypes deserve more investment is the most expensive decision a studio makes. The decision framework we use: thresholds agreed upfront, a small set of metrics, and three outcomes — go, iterate, kill.

4 min read