SimulatorsGPT Blog

dice Sep 18, 2025 5 min read

Casino-Style Dice in 2D: Making Rolls Feel Real (Without WebGL)

How we built natural dice rolls—throws, bounces, and stops—using a tiny 2D canvas engine and pooled sound.

TL;DR

You don’t need WebGL for convincing dice. With a single requestAnimationFrame loop, a tiny 2D physics model, and pooled WebAudio, rolls feel real, stay reproducible, and run smoothly on mobile.

Why 2D, not 3D?

Three reasons: performance, determinism, and size. We target 60fps on desktop / 30fps+ on phones, deterministic replays via seeded randomness, and a small bundle. A carefully tuned 2D model gives us 90% of the “casino feel” with a fraction of the cost.

From gesture to throw

A roll begins with a flick gesture:

  • Initial linear velocity: direction and magnitude from the drag vector.
  • Initial angular velocity: a randomized spin tied to flick speed (not just a constant).
  • Staggered starts: when rolling 5–10 dice, we offset launch times by a few milliseconds so they don’t overlap perfectly.

That’s enough to make each roll personal while preserving determinism under a fixed seed.

Collisions, friction, and that last wobble

We use a 2D rigid body approximation with:

  • Wall collisions: low elasticity so dice don’t ping-pong forever.
  • Die–die collisions: slightly lower restitution than walls; impulse-based response.
  • Friction: dynamic → static; when linear and angular speeds fall below thresholds for ~0.5s, we snap to a stable face.

The “casino wobble” comes from asymmetric damping: linear speed decays faster than angular, then flips as the die starts to skid. It looks right and converges quickly.

Sound sells the illusion

All sounds come from a pooled WebAudio bus:

  • Click: small rim contacts; short, higher-pitched.
  • Thunk: heavy impacts; pitched down and slightly louder.
  • Settle: a soft, low click as the last micro-bounce disappears.

We cap concurrent voices (e.g., 6) and randomize pitch ±7% to avoid repetition. On mobile, audio starts only after the first user interaction—then it works consistently.

Reproducibility with seeds

Every roll is driven by a seeded PRNG. The same seed + same gesture produce the same collision sequence and the same final face(s). That’s why our Share links can replay both process and outcome across devices.

Performance guardrails

  • One RAF per page; clamp Δt ≤ 50 ms so missed frames don’t explode the simulation.
  • Canvas-first rendering (no DOM animation for dice); hit-tests and HUD stay minimal.
  • Auto-degrade: if fps < 45 for a second, we downshift shadows/particles.

Test-yourself checklist

  • Ten dice, hard flick → multiple wall hits, varied spins, no “phase lock.”
  • Light flick → short slide, believable micro-bounces.
  • Same seed, three repeats → identical end faces and order.
  • Phone test → 30fps+ and zero console errors.

Try next:

Roll some Dice with 6–10 pieces and compare stats.

See how seeding works.

Contrast with Wheel vs. Coin vs. Dice.