Unity endless runner tutorial, procedural spawning mobile

Endless Runner Core Loop: Spawning, Scoring, and Difficulty Scaling in 200 Lines of Code

Small Game Mechanics & Prototyping

You’re about to drop a single, performance-safe script into a PlayMobile.online scene that builds the whole core loop: tile pooling and recycling, distance scoring, and a deterministic difficulty ramp that runs on phones without GC spikes.

Create an empty RunnerCore GameObject, add the provided script, and assign Player Transform, a UI label, a tile prefab, an obstacle prefab array, and optional pooled roots.

Working, paste-first C# snippet:

using UnityEngine;
using UnityEngine.UI;
public class RunnerCore : MonoBehaviour {
    public Transform player;
    public Text scoreLabel;
    public GameObject tilePrefab;
    public GameObject[] obstacles;
    public int poolSize=8;
    Transform[] pool;
    int head=0;
    float baseSpeed=6f; AnimationCurve speedCurve=AnimationCurve.Linear(0,1,60,2);
    int lastScore=0; float uiThrottle=0.1f;
    void Start(){
        pool=new Transform[poolSize];
        for(int i=0;i

This snippet avoids two big beginner mistakes: Instantiate/Destroy per tile that causes stutter and random per-frame spawning that breaks determinism.

If your player jitters or stops, check collider seams and too many colliders first—these often cause physics snagging, as forum reports showed. Next you’ll wire the scene for phones, tighten colliders, and profile on-device to confirm no GC spikes during a multi-minute run.

Drop-in Core Loop Script: Tile Spawning, Scoring, and Difficulty in One Pass

Get the whole tile pool, scoring, and speed ramp working from one compact script you paste into a GameObject. You will create a RunnerCore object and wire four inspector fields so the loop runs immediately.

  • Create a GameObject named RunnerCore and add RunnerCore.cs.
  • Under RunnerCore add two empty children: TilesRoot and ObstaclesRoot to keep pooled objects organized.
  • Assign references in this order: Player, UI Text, tile prefab, obstacle prefabs. Then run once before tuning probabilities.

What the single script does in one pass: it pre-warms a fixed pool of tiles and objects, repositions old segments behind the player, updates a distance score, and samples a speed curve to ramp difficulty. This keeps state in one inspector so you can trace spawn, recycle, score, and ramp logic without cross-script indirection.

Field Recommended Reason
Tile length (m) 10 Predictable recycle distance and camera fit
Tiles on screen 6–10 Start low to avoid pop-in and extra colliders
Recycle buffer 5 m Avoid visible repositioning
Obstacle spawn chance 10–30% Control difficulty without extra draw calls
Speed ramp Start 6 → Max 20; curve Deterministic difficulty scaling

Quick sanity checks you can run in under a minute: confirm active tile count remains constant, verify obstacle count does not grow, and profile to see 0B GC alloc per frame during steady play. Common beginner mistakes to avoid: allocating lists in Update, calling GetComponentsInChildren at runtime, or updating UI text every frame when score hasn’t changed.

Project Setup That Won’t Fight You on Mobile

Start with a lean project layout so your scene stays predictable on devices. A clear hierarchy reduces Find() calls, stops accidental duplicates after reloads, and makes pooling cleanup trivial when restarting a level.

  • Player
  • MainCamera
  • RunnerCore
  • Canvas (Screen Space – Overlay)
  • EventSystem
  • TilesRoot, ObstaclesRoot, optional VFXRoot

Movement baseline: keep forward motion constant and handle lane changes with a single MoveLane(int dir) method. Use kinematic-style snapping (MoveTowards or SmoothDamp) or a Rigidbody with interpolation. Do not mix Transform.Translate with a non-kinematic Rigidbody; that causes jitter.

Issue Quick fix
FixedUpdate vs Update mismatch Drive physics in FixedUpdate
Collider seams Align pivots and tile edges
Default drag/constraints Reset or tune Rigidbody settings

Pick target frame rate intentionally (60 for mid/high, 30 for low-end) and avoid cranking Fixed Timestep without measuring. These settings keep physics stable in your unity project and protect battery life while running.

Unity endless runner tutorial, procedural spawning mobile: Building Tiles as Reusable Chunks

Make each tile a predictable, testable chunk so your track never produces surprise gaps or physics snags. Define strict prefab rules up front and test them in-editor before playtesting on device.

Tile prefab rules

Every tile should be exactly N units long. Set the pivot at the tile start (z=0). The end must sit at z=N with no mystery offset.

Validate by dropping two instances end-to-end. Check for gaps, overlaps, and aligned lane markers.

Spawn points as child transforms

Create child transforms like SpawnPoints/LaneLeft_01, LaneMid_01, LaneRight_01. Iterate those transforms instead of calculating random XYZ positions at runtime.

  • Less per-frame math
  • Fewer overlaps
  • Easy visual review inside the prefab

Chunk strategy and optional tooling

Keep only ~5–10 segments alive and recycle them to reduce collider count and seam issues. This approach is a common forum best practice for stable physics.

If you need authored variation, consider Dreamteck Forever from the Asset Store for segment snapping and level streaming. Note: many seam problems come from mesh bounds or pivot misalignment, so align edges to bounds before using such tools.

Check Action Why
End-to-end placement Drop two tiles at z=0 and z=N Confirms no gap or overlap
Spawn point alignment Inspect child transforms in prefab Prevents runtime lane offsets
Collider count Limit active segments to 5–10 Reduces snagging and CPU cost

Object Pooling That Actually Stops Your FPS Drop

If your frame rate drops after a minute, the object lifecycle is the first place to inspect. Repeated Instantiate/Destroy builds garbage and forces GC runs on the main thread. That creates short but painful hitches on phones.

Prewarm pools in Start/Awake: instantiate tiles and obstacles once, disable them, and parent them under a pool root. This prevents scene reload duplicates and avoids costly creation during play.

Sizing and recycling

Use simple sizing rules: tiles pool = tiles on screen + 2 buffer. Obstacles pool = ceil(tiles on screen * spawn points per tile * max spawn probability).

When the player crosses a recycle threshold, move the oldest tile to spawnZ and reuse its obstacle slots. Reset transforms, colliders, visual state, and animation flags before enabling an object.

Item Rule Why
Tiles pool On-screen + 2 Keeps buffer to hide repositioning
Obstacles pool RoundUp(spawn slots * prob) Avoids runtime allocations
Prewarm timing Start/Awake Single allocation window

Common mistakes: letting pooled lists grow, or forgetting to reset colliders and state. Track ActiveTiles and ActiveObstacles in a dev UI so counts stay flat. That one check answers a lot of performance questions during testing.

Obstacles, Gates, and Reliable Collisions

Decide obstacle placement when a tile is recycled so collisions and triggers behave predictably. Set configuration once and let the tile run; this avoids per-frame random rolls and non-repeatable runs in an endless runner.

Deterministic obstacle placement

Assign a weighted chance per spawn point during tile setup. Each spawn point stores allowed types and a probability. This keeps difficulty stable and minimizes runtime work.

Trigger and collider setup

Set isTrigger or enable/disable colliders at spawn time, not inside unrelated callbacks like material-change handlers. That removes jitter from late physics state changes.

Pitfalls and mitigations

Many small box colliders create seams the physics engine can catch, and that causes the player to halt unexpectedly. Use a single simple collider per tile where possible and align seams to avoid micro-gaps.

Issue Fix Why
Collider stacks Merge or simplify colliders Reduces snagging and CPU cost
Late trigger toggles Configure at spawn Keeps physics deterministic
Complex gate types Use one collider per gate Easier contact resolution
  • On phones, fewer colliders save CPU and battery.
  • Preconfigure objects in the editor and verify lanes visually.

Scoring System: Distance, Multipliers, and UI Without Battery Drain

Measure progress with whole numbers and throttle updates to save frame time. Use the player’s Z (or a traveled-distance accumulator if you shift origin) to derive an integer score. Integers format cleanly on small screens and avoid jitter from floating decimals.

Distance and throttled updates

Sample position each frame but update the on-screen text only when the displayed integer changes or at a fixed interval (for example, 10 times per second). This reduces costly text rebuilds and limits work per frame, saving battery and CPU time.

Multipliers without extra systems

Expose a simple multiplier hook on score calculation (score = baseDistance * combo). Keep the hook deterministic: change combo only at discrete events like perfect gates or streak thresholds so results are repeatable for testing.

  • Use large touch targets for pause/retry buttons to aid usability.
  • Anchor HUD elements away from notches and respect safe-area Insets in the scene.
  • Avoid animating layout groups every frame; animate a single value if needed.
Issue Fix Why
Text rebuilt every frame Throttle updates Reduces GC and frame hitches
Overlapping Text components Consolidate into one label Prevents extra draw and layout work
Frequent font/material swaps Use static fonts and shared materials Avoids render state churn

Difficulty Scaling That Feels Fair (and Stays Deterministic)

Good pacing hands players a clear learning window before the challenge grows. That feeling drives replays and retention, a point GDC talks often summarize as the “core loop + difficulty ramp” approach to pacing. Keep increases predictable so you can balance and reproduce issues quickly.

Speed ramps vs. linear ramps

Think of speed as a reaction-time lever. Linear speed increases are simple but can steal reaction time too fast on small screens. An AnimationCurve-style ramp lets you give an early low-speed phase for learning, then a gradual skill phase that rewards mastery.

Spawn pressure without extra draw calls

Treat density as decision density, not unique geometry. Reuse pooled obstacle prefabs and toggle activation and placement to raise pressure. That increases challenge without new materials or extra draw calls, keeping performance steady.

Determinism and the feedback loop

Drive difficulty from distance milestones or a seeded process so runs are repeatable. Collect session feedback metrics—death distance histograms and fail reasons—and change one variable at a time. Small, measurable tweaks improve tuning and player experience.

Knob Effect Balancing tip
Speed Reduces reaction time Use curve with gentle start
Spawn pressure Increases decisions per second Reuse pooled types, vary placement
Pacing Retention driver Tune via session metrics

Mobile Performance Budget: Memory, Draw Calls, and Battery

Plan your performance budget around sustained smoothness, not short Editor demos. Aim for steady frame time after several minutes of play so thermal throttling and battery drain do not ruin the experience.

Below are focused, mobile-specific tactics you can apply to keep your game stable during long sessions. Use them together: rendering, memory, physics, and profiling interact on small devices.

Draw calls and rendering

Limit materials to one or two per tile and use texture atlases for repeated props. Favor GPU-instancing friendly meshes so repeating objects don’t multiply render state changes.

Memory and hidden allocations

Size pools to trade a bit of RAM for zero runtime allocations. Start conservative and increase pool sizes only if profiler traces show misses.

Avoid Update() allocations: no string concat per frame, no foreach on Unity collections, and no new arrays/lists. Confirm “GC Alloc” reads 0B during steady play.

Battery, thermals, and physics

Reduce expensive physics: fewer colliders, simpler rigidbodies, and stable FixedUpdate timing. Cap the frame rate so the device doesn’t hit sustained peak power and throttle CPU/GPU.

Profiling workflow

Reproduce a stutter after 2–5 minutes on a real device. Record a Profiler trace on-device, identify whether GC, CPU, physics, or rendering peaks cause the hitch, fix one bottleneck, and retest.

Budget Item Target Quick Check Action if exceeded
Materials per tile 1–2 Frame capture: draw calls Atlas textures / merge materials
Pool sizing On-screen + 2–4 buffer Active object count steady Increase pools, retest minutes-long run
GC Alloc 0B steady-state Profiler timeline Eliminate per-frame allocations
Frame cap 30–60 FPS (targeted) Device thermal/clock logs Lower cap or reduce physics load

Unity Docs You’ll Use While Implementing This

When debugging late at night, the official docs are the fastest way back to a working scene. Bookmark the exact pages below so you can resolve object life-cycle, collision, and performance issues without guesswork.

Instantiate/Destroy and pooling guidance

Read Object.Instantiate and Object.Destroy to understand allocation costs. Frequent creation and destruction will show up as GC spikes.

  • Key takeaway: use pooling to avoid per-frame allocations and reduce GC pressure.

Colliders, triggers, and rigidbody behavior

Check Collider.isTrigger, OnTriggerEnter, and OnCollisionEnter docs to learn when each event fires. Rigidbody presence and kinematic settings change which callback you get.

Profiler and GC tracking

Use the Profiler guide to record on-device traces, inspect GC Alloc, and isolate CPU spikes to scripts, physics, or rendering.

Problem Doc to open Quick fix
Hitching / GC Object.Instantiate / Destroy Prewarm pools
Missing triggers Collider & Rigidbody Check kinematic and layer matrix
CPU spike Profiler (on device) Trace scripts vs physics

Docs-first debugging list:

  • If triggers don’t fire, verify Rigidbody setup and collision layers.
  • If stutter appears, inspect GC Alloc and Instantiate counts in the profiler.
  • If the player snags, simplify colliders and review contact points.

Conclusion

Conclude with focused checks that confirm stability, determinism, and UI readiness for your endless runner project.

You now have a production-shaped core loop: spawn → recycle → score → ramp, built so active counts stay flat and allocations do not creep over time. Pooling prevents stutter and a small segment count avoids collider overload that causes random stops.

Do this, not that: recycle tiles instead of destroying them, configure triggers at spawn time, and simplify or realign collider seams to stop abrupt halts.

One-sitting next steps: add swipe input, add a restart flow and basic obstacle set, then profile a 5-minute run on device before adding more art. Finalize UI: make pause/retry buttons thumb-friendly, respect safe-area notches, and throttle HUD updates.

Iterate using data: collect death distance and fail reasons, tune one variable at each level, and keep the process deterministic so you can reproduce questions from playtest feedback.

Written as a hands-on Unity mobile tutorial by George Jones for PlayMobile.online, focused on the parts that break first: pooling, collisions, and sustained performance.

Leave a Reply

Your email address will not be published. Required fields are marked *