DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

How Fruit Ninja Works: Projectile Arcs and One Line of Geometry Slice Every Fruit

Fruit Ninja looks like it needs a sprite atlas and a physics engine. It doesn't. Strip it down and the whole game is two ideas stacked together: fruit are projectiles thrown up from below the screen, and a "slice" is the oldest trick in collision detection — the shortest distance from a point to a line segment. Everything else is decoration.

A fruit is just a projectile

There are no scripted paths. A fruit is a circle with a position and a velocity, spawned below the bottom edge and thrown upward with a strong negative vertical velocity plus a sideways nudge toward the middle. From there, one line of gravity per tick bends the launch into a parabola — rise, slow, fall — for free.

const GRAVITY = 900;                      // px/s^2, pulls everything down
function spawnOne(){
  const bomb = Math.random() < 0.12;      // ~1 in 8 is a bomb
  const type = bomb ? "bomb" : (Math.random() * FRUITS.length | 0);
  const r    = bomb ? 30 : FRUITS[type].r;
  const x  = rand(W * 0.15, W * 0.85);    // random column
  const y  = H + r + 10;                  // start just below the screen
  const vy = -rand(700, 860);             // thrown UP (negative y)
  const vx = clamp((W / 2 - x) * 0.6, -260, 260) + rand(-60, 60);
  fruits.push({ x, y, vx, vy, r, type, rot: 0, vrot: rand(-3, 3), sliced: false });
}
Enter fullscreen mode Exit fullscreen mode

Advancing that physics naively — by however long the last frame took — means the fruit fly higher on a slow machine than a fast one. The fix is a fixed-timestep accumulator: bank the real elapsed time and spend it in equal 1/120 s chunks, so the simulation is identical on a 60Hz or a 144Hz screen.

The blade is your recent pointer trail

There's no sword sprite either. The blade is an array of the last places your pointer visited, each stamped with a time; anything older than ~130ms is dropped. The important part is that each new point paired with the previous one is a line segment — the exact stroke your hand just swept.

The slice test — point-to-segment distance

This is the heart of the whole game. A fruit is a circle; the swipe is a segment; the fruit is cut when the segment passes within the fruit's radius of its centre. To find that shortest distance you project the centre onto the infinite line through the segment to get a parameter t, clamp t to [0,1] so you stay on the segment (not the endless line), then compare the closest point's distance to the radius. Compare squared distances and there isn't even a square root.

function segCircle(ax, ay, bx, by, cx, cy, r){
  const dx = bx - ax, dy = by - ay;
  const len2 = dx*dx + dy*dy;
  let t = len2 ? ((cx-ax)*dx + (cy-ay)*dy) / len2 : 0;  // project C onto A→B
  t = t < 0 ? 0 : t > 1 ? 1 : t;                        // clamp to the segment
  const px = ax + t*dx, py = ay + t*dy;                 // closest point on segment
  const ex = cx - px, ey = cy - py;
  return ex*ex + ey*ey <= r*r;                          // within the radius? → hit
}
Enter fullscreen mode Exit fullscreen mode

Because one segment is tested against every fruit each move, a single swipe drawn across three fruit registers three slices in the same tick — which is exactly how combos work. A short timer resets on every cut; while the blade keeps slicing, the streak climbs. Slicing spawns two half-discs thrown perpendicular to the cut plus a burst of juice particles, all obeying the same gravity. Touch a bomb, or let a fruit fall off the bottom untouched, and you lose one of three lives.

That's it: a parabola from one line of gravity, a segment from your last two pointer positions, and one distance test deciding every cut. No engine required.

Play the live build (and read all eight code blocks) here: https://dev48v.infy.uk/game/day57-fruit-ninja.html

Top comments (0)