Skip to main contentSkip to contact
Ocean View Games
Ocean View
Games
Blog header banner

Procedural Generation on Mobile: Balancing Complexity and Performance

David Edgecombe

David Edgecombe

·5 min read

Procedural generation is one of the most powerful tools in a game developer's toolkit. It creates near-infinite replayability from finite assets. But on mobile, where RAM is measured in gigabytes rather than tens of gigabytes and thermal throttling is a constant threat, procedural generation demands careful engineering.

When we built Empires Rise (a 4X turn-based strategy game targeting iOS and Android) we needed maps that felt hand-crafted but were generated fresh every session. This post covers the techniques we used and the tradeoffs we navigated.


Why Procedural Generation for a Strategy Game?

Turn-based strategy games live or die by map variety. If every playthrough feels the same, players churn. Pre-built maps are finite. Procedural generation offers:

  • Infinite replayability - no two sessions are identical
  • Reduced asset storage - algorithms replace dozens of hand-built map files
  • Dynamic difficulty - seed parameters can adjust map complexity to match player skill

But mobile imposes hard constraints. We could not run generation on a background thread for seconds at a time. Players expect near-instant loading. Our target was under 2 seconds from tap to playable map on a mid-range device.


The Generation Pipeline

Our pipeline has three stages, each building on the previous:

Stage 1: Terrain Layer (Modified Perlin Noise)

Standard Perlin Noise produces organic-looking height maps, but raw noise tends to generate repetitive, blobby terrain. We applied several modifications:

  • Octave layering - combining multiple noise frequencies to add both large-scale continents and small-scale terrain detail
  • Falloff masking - applying a radial gradient to guarantee water at map edges, creating natural island formations
  • Biome thresholds - mapping noise values to discrete terrain types (ocean, coast, plains, forest, mountain) rather than continuous gradients

The result is a terrain grid that looks natural but is structurally viable for gameplay. Every map has coastline, inland territory, and mountain chokepoints.

Stage 2: Feature Placement (Cellular Automata)

Raw terrain is a canvas. Features make it a map. We used cellular automata to distribute forests, rivers, and resource deposits:

  1. Scatter seed cells randomly across the terrain grid
  2. Run 3-4 iterations of growth rules (a forest cell expands if 3+ neighbours are also forest)
  3. Prune isolated cells that failed to cluster

This produces natural-looking clusters rather than the uniform scatter you get from pure random placement. Rivers use a different approach as we trace downhill paths from mountain cells to coast cells using gradient descent on the height map.

Stage 3: Gameplay Validation

Not every generated map is playable. We run a validation pass that checks:

  • Every player spawn point can reach every other spawn point (flood fill connectivity check)
  • Resource distribution is balanced within a tolerance band
  • No player starts with a significant geographic advantage

Maps that fail validation are silently re-rolled with a new seed. In practice, our generation parameters are tuned well enough that fewer than 5% of maps are rejected.

Key Takeaway: Procedural generation is not "generate and ship." Always validate that the output meets gameplay requirements before presenting it to the player.


The Mobile Optimisation Challenge

The generation pipeline above works well on desktop. On a phone with 3GB of RAM and a thermally constrained SoC, three problems emerge:

Problem 1: Memory Spikes During Generation

Generating the full map in memory means allocating large 2D arrays for terrain, features, and validation data simultaneously. On a 256x256 tile map, that is 65,000+ cells across multiple layers.

Solution: Chunk-based generation. We divided the map into 16x16 tile chunks and generated them sequentially. Only the current chunk's data lives in memory at once. Once a chunk is generated, its tile data is serialized to a lightweight format and the working arrays are released.

Problem 2: Main Thread Stalls

Cellular automata iterations and flood-fill validation are CPU-intensive. Running them synchronously freezes the UI.

Solution: Coroutine time-slicing. We wrapped the generation loop in a Unity Coroutine that yields every N iterations, returning control to the main thread to render a loading animation. The player sees a smooth progress bar rather than a frozen screen.

// Simplified time-slicing pattern
for (int i = 0; i < totalCells; i++) {
    ProcessCell(i);
    if (i % CELLS_PER_FRAME == 0) yield return null;
}

Problem 3: Tilemap Rendering Performance

Unity's Tilemap system is convenient but can choke on large maps. Setting 65,000 tiles in a single frame causes a visible stall.

Solution: Progressive chunk loading. We only instantiate tilemap chunks within the camera viewport plus a one-chunk buffer. As the player pans the camera, new chunks load and distant chunks unload. This keeps the active tile count under 4,000 at any time, regardless of total map size.

Key Takeaway: On mobile, the generation algorithm is only half the battle. How you load and render the result is equally important for perceived performance.


Tuning for Feel

The technical implementation is necessary but not sufficient. Procedural maps need to feel good to play on, not just look correct. We spent considerable time tuning:

  • Island size distribution - too many small islands makes naval traversal tedious; too few large landmasses reduces strategic variety
  • Resource density - too sparse and early game is frustrating; too dense and there is no incentive to expand
  • Chokepoint frequency - mountain passes and river crossings create natural defensive positions. Too few and combat becomes a formless blob; too many and expansion feels blocked

Each of these parameters is exposed in a configuration file, not hardcoded. This allowed our designers to iterate on feel without touching C# code.


Results

The final implementation generates a playable 256x256 map in under 1.5 seconds on a mid-range Android device (Snapdragon 665), well within our 2-second target. Memory usage during generation peaks at 18MB, compared to 45MB+ for the naive approach.

The chunk-based rendering keeps frame times under 16ms (60fps) during camera panning, with no hitches when crossing chunk boundaries.


Applying This to Your Project

If you are considering procedural generation for a mobile title, here are the key takeaways:

  1. Generate in chunks - never allocate the full map in memory at once
  2. Time-slice CPU work - yield to the main thread frequently to keep the UI responsive
  3. Load progressively - only render what the camera can see
  4. Validate gameplay viability - automated checks catch unplayable maps before the player does
  5. Expose tuning parameters - let designers iterate without code changes

These techniques are not limited to strategy games. They apply to any genre that uses procedural content, including roguelikes, survival games, or even procedurally generated educational levels.

Stop Searching. Start Building.

Ready to start your next project? Tell us about your game and we'll get back to you with a plan.

Start by telling us what kind of help you need.

Location

London, United Kingdom

Response Time

We typically respond within 24-48 hours

1
2
3
Step 1 of 3