A busy moment in Falling Food Frenzy has perhaps sixty collectibles, twenty hazards and a hundred and sixty particles on screen. Drawing them is straightforward. Doing it every frame without allocating memory is where the actual engineering is.

Drawing a collectible

Each collectible is drawn in three passes.

  1. Glow — for tier-3 and above, a radial gradient behind the item at about 32% opacity. Gold for bonus items, red for risk placements.
  2. Glyph — the food character itself, drawn as text with an emoji-capable font stack, centred, with rotation and scale applied via the canvas transform.
  3. Label — only for multiplier tokens, which show their ×N value beneath.

Using emoji glyphs rather than images is an unusual choice with real advantages: zero download cost, resolution independence at any size, and no artwork that could infringe anyone's rights. The trade is that rendering varies between platforms — a burger looks different on Windows and on iOS.

For this style of game that variation is acceptable, because the shape and dominant colour are consistent enough that recognition transfers. It would not be acceptable in a game where exact appearance mattered.

Animation without state machines

Four animation states, all computed from elapsed time rather than stored frames:

  • Bob — y = baseY + sin(t × 2.4 + phase) × 5.
  • Spin — rotation accumulates at 2.2 radians per second.
  • Pulse — scale = 1 + sin(t × 5 + phase) × 0.12.
  • Drift — position integrates the item's own velocity.

Each item carries a random phase offset so a row of identical items does not animate in lockstep, which would look mechanical. That single field is the difference between a trail that looks alive and one that looks like a spreadsheet.

Culling

Drawing something outside the visible area costs the same as drawing something inside it, so the cheapest optimisation available is not drawing it.

Every renderer here skips entities more than 40 to 80 units outside the view, tested before any canvas state is touched. In a scrolling game that check is a simple comparison against the world offset, and it eliminates most of the draw calls in a long level.

Culling is separate from removal. An item scrolled past is culled from drawing immediately and removed from the array on the next sweep, which keeps the two concerns independent.

Object pooling

The most important performance technique in a JavaScript game, and the least visible.

Creating a new object for each collectible produces garbage that the collector eventually reclaims — in a pause, at an unpredictable moment, which the player experiences as a stutter. In a game where a stutter can cost a run, that is a real defect.

So entities come from a pool. A dead item is reset to a blank state and pushed back onto a free list; a spawn pops from the free list rather than allocating. The pool holds up to 400 objects, which covers the worst case on this site.

The general rule: anything created more than a few times per second should be pooled. Here that means collectibles, hazards, obstacles, particles and score pops.

Allocation-free collision

Collision tests run thousands of times per frame, so they must not allocate either.

That means no temporary objects, no array methods that create intermediates, and no destructuring into new variables inside the hot loop. The tests take plain numbers and return booleans:

  • Circle-circle — compare squared distance against squared combined radius. No square root, because comparing squares gives the same answer more cheaply.
  • Circle-box — clamp the circle centre to the box bounds and test the squared distance to that point.
  • Forgiving pickup — the same circle test plus a difficulty-scaled margin.

Avoiding the square root is a small saving per call and a meaningful one across thousands of calls per frame.

Sweeping dead entities

Removing items from the middle of an array is expensive, because everything after the removed element shifts down.

The pattern used here is a compaction sweep: walk the array once, copy live entities to a write index, release dead ones to the pool, then truncate. One pass, no intermediate arrays, and it runs once per frame rather than per removal.

Draw order

Canvas has no depth buffer, so draw order is the only layering mechanism. The order used throughout is:

  1. Background and parallax layers
  2. Ground and platforms
  3. Obstacles and hazards
  4. Collectibles
  5. Power-up pickups
  6. Game-specific overlays
  7. Power-up field effects
  8. The player
  9. Particles and score pops
  10. Vignette

Hazards sit below collectibles deliberately: if the two overlap, the collectible is on top and the player can see what they are reaching for. The player is drawn above everything in the world so they are never hidden by their own effects.

Try it yourself: Endless Food Collector

Six promotion tiers that raise both what food is worth and how much is chasing you.

Play Endless Food Collector

Key takeaways

  • Collectibles are drawn in three passes — glow, glyph, label — using emoji glyphs for zero download cost and resolution independence.
  • Animate from elapsed time with a per-item random phase, or identical items animate in lockstep and look mechanical.
  • Cull before touching any canvas state; not drawing something is the cheapest optimisation available.
  • Pool anything created more than a few times per second, or garbage collection pauses become stutters.
  • Keep collision tests allocation-free and compare squared distances rather than taking square roots.
  • Sweep dead entities in one compaction pass rather than splicing from the middle of an array.
  • Draw order is the only layering mechanism — hazards below collectibles, the player above the world.

References

  1. MDN Web Docs — Canvas API: drawing text and shapes — developer.mozilla.org

Frequently Asked Questions

Why use emoji instead of images for food items?

Zero download cost, resolution independence at any size, and no artwork that could infringe anyone's rights. The trade is that rendering varies between platforms, which is acceptable when shape and colour carry the recognition.

What is object pooling and why does it matter?

Reusing dead objects instead of allocating new ones. Without it, garbage collection pauses at unpredictable moments produce stutters — which in an arcade game can cost a run.

Why compare squared distances in collision tests?

Because comparing squares gives the same answer as comparing distances without the cost of a square root. Across thousands of calls per frame that saving is meaningful.

How are hundreds of items removed efficiently?

With a single compaction sweep: walk the array once copying live entities to a write index, release dead ones to the pool, then truncate. Splicing from the middle would shift everything after it.

Why are collectibles drawn on top of hazards?

So that when the two overlap, the player can see what they are reaching for. Canvas has no depth buffer, so draw order is the only layering mechanism available.