Open any decent jumping game — a character taps the screen, launches upward, slows down, hangs for a heartbeat at the top, then falls back faster and faster until it lands. Nobody drew a thousand separate pictures of that arc. A program is deciding, 60 times every second, exactly where that character's pixel should sit — and it is doing it with nothing more than addition. This chapter is about that arithmetic: how a game engine turns a few numbers, updated frame after frame, into motion that feels like gravity, and how it decides when two moving shapes have touched.
The game loop: physics as arithmetic, repeated
Every real-time game, from a simple Scratch cat-and-mouse chase to a full Unity or Unreal title, runs the same three-step cycle over and over, as fast as the device can manage — usually 60 times per second:
let running = true;
function gameLoop() {
while (running) {
handleInput(); // did the player tap, press a key, tilt the phone?
updatePhysics(); // move every object one small step forward
render(); // draw the current positions to the screen
}
}
Each pass through that loop is called a frame. At 60 frames per second, each frame lasts about 16.7 milliseconds — far too fast for your eye to see the individual steps, so the sequence of still images reads as smooth motion, the same way a flip-book does. The part we care about here is updatePhysics(). Every object on screen that moves — a ball, a character, a bullet — carries a small set of numbers that this function updates every single frame. Understanding those numbers is the whole trick behind "realistic" game motion.
Three numbers per object: position, velocity, acceleration
Before writing any formula, think about what you actually need to know to describe a falling ball at any instant:
- Position — where the object is right now. In a 2D game, this is usually stored as pixel coordinates
xandy, measured from the top-left corner of the screen. This trips up a lot of beginners: in screen coordinates,yincreases downward, not upward like in the graphs you draw in a math notebook. So "moving up" meansyis decreasing, and "falling down" meansyis increasing. Keep this in your head for the rest of the chapter — it explains every negative sign you'll see below. - Velocity — how fast the position is currently changing, per frame. We'll call the vertical velocity
vy, measured in pixels-per-frame. A jumping character starts with a negativevy(moving up, toward smallery). - Acceleration (gravity) — how fast the velocity itself is changing, per frame. This is a constant pull, usually called
g, that is added tovyevery frame. Gravity always points downward on screen, sogis a positive number.
That's it. No calculus, no differential equations — just three numbers per object, updated with two lines of arithmetic every frame:
function updateBall(ball, gravity) {
ball.vy = ball.vy + gravity; // step 1: gravity changes the speed
ball.y = ball.y + ball.vy; // step 2: the NEW speed moves the ball
}
Notice the order: the engine updates the velocity first, then uses that already-updated velocity to move the object. This specific ordering has a name — semi-implicit Euler integration — and it is the method almost every 2D game engine uses (Unity's Rigidbody2D, Godot's physics, and hand-rolled physics in p5.js or Scratch's "change y by" blocks all follow this same pattern). Doing it in the other order — moving first, then updating speed — is mathematically valid too, but semi-implicit Euler behaves more stably when many objects interact (as you'll see later in higher-grade physics-engine work), so it's the convention worth learning correctly from the start.
Worked example: tracing a jump by hand
Let's trace an actual toss. A ball starts at y = 100 (100 pixels down from the top of the screen), launched upward with vy = -19 pixels/frame, under gravity g = +4 pixels/frame². Apply the two-line update once per frame, and watch what happens:
Frame vy = vy + g y = y + vy Ball's height on screen
0 (start) y = 100 near the bottom
1 -19+4 = -15 100-15 = 85 moving up
2 -15+4 = -11 85-11 = 74 moving up
3 -11+4 = -7 74-7 = 67 moving up
4 -7+4 = -3 67-3 = 64 moving up
5 -3+4 = 1 64+1 = 65 moving down
6 1+4 = 5 65+5 = 70 falling faster
Read that table like a story. For the first four frames, vy is negative, so y keeps shrinking — the ball is climbing, and gravity is chipping away at its upward speed by 4 every frame. At frame 4, vy = -3 is the smallest (weakest) upward speed the ball ever has while still going up; the ball is at its highest point on screen, y = 64. On frame 5, gravity finally wins: vy flips to +1, meaning the ball is now moving down, and y starts growing again. By frame 6 it's clearly falling, gaining downward speed exactly the same way it lost upward speed on the way up. This symmetry — losing speed going up at the same rate you gain it coming down — is exactly what you'd expect from constant gravity, and it fell straight out of one repeated two-line calculation. No jump animation was hand-drawn; the parabola emerged from arithmetic.
The graph below plots the same seven values. Because screen y grows downward, the chart is drawn so "up" on the page also means "up" on screen (smaller y) — you should see the familiar arc of a thrown ball, peaking at frame 4.
Misconception: "the engine solves the real falling-object formula"
A common assumption is that a game engine plugs numbers into the exact equation you may see later for constant acceleration, s = ut + ½at², and computes the precise height at any instant. It does not — at least not by default, and not for most gameplay physics. What you traced above, adding gravity to velocity and velocity to position once per frame, is an approximation called numerical integration, not the exact closed-form equation. The reason engines use the approximation instead of the exact formula is practical: real games constantly change what's happening to an object mid-flight — a wall gets hit, the player releases a boost, another object shoves this one — and an exact formula assumes nothing external interferes between the start and end time. A frame-by-frame update, on the other hand, can react to a new collision or a new input on the very next frame, because it never commits further ahead than one step. The tiny cost is that the simulated path is only an approximation of true gravity — usually close enough that no player notices, but not mathematically identical to it.
Collision detection: how the engine knows two things touched
Position and velocity explain how one object moves. But games are full of objects that need to notice each other — a character landing on a platform, a ball hitting a paddle, a bullet hitting an enemy. Checking this precisely, pixel by pixel, for every pair of objects, every single frame, would be far too slow even on a powerful phone. So engines simplify: instead of comparing exact sprite shapes, each object gets an invisible rectangle called its hitbox, and the engine checks whether the rectangles overlap. Because these rectangles are always aligned with the screen's horizontal and vertical axes (never rotated), this technique is called an Axis-Aligned Bounding Box check, or AABB for short.
Here is the intuition before the formula: two rectangles overlap only if they overlap both along the x-axis and along the y-axis at the same time. If one box is entirely to the left of the other, there's no collision no matter what their vertical positions are. If one box is entirely above the other, same story. Only when both directions overlap simultaneously do the shapes actually intersect. Each rectangle is described by four numbers: its top-left corner (x, y), its width, and its height. Its right edge is at x + width and its bottom edge is at y + height.
function isColliding(a, b) {
const xOverlap = a.x < b.x + b.width && a.x + a.width > b.x;
const yOverlap = a.y < b.y + b.height && a.y + a.height > b.y;
return xOverlap && yOverlap;
}
Read xOverlap carefully: a.x < b.x + b.width means "A's left edge is before B's right edge," and a.x + a.width > b.x means "A's right edge is after B's left edge." Both have to be true for A and B to share any horizontal space at all. The yOverlap line does the exact same check vertically. Only when both are true does the function report a collision.
Worked example: do these two boxes collide?
Box A (a player sprite) has x = 40, y = 60, width = 80, height = 100. So A spans horizontally from x = 40 to x = 120, and vertically from y = 60 to y = 160.
Box B (an enemy sprite) has x = 90, y = 100, width = 60, height = 60. So B spans horizontally from x = 90 to x = 150, and vertically from y = 100 to y = 160.
Check the x-axis first: is 40 < 150? Yes. Is 120 > 90? Yes. Both true, so the boxes overlap horizontally, from x = 90 (where B starts, since that's further right than A's start) to x = 120 (where A ends, since that's further left than B's end) — a width of 30 pixels.
Now check the y-axis: is 60 < 160? Yes. Is 160 > 100? Yes. Both true, so the boxes overlap vertically too, from y = 100 to y = 160 — a height of 60 pixels.
Since both axes overlap, isColliding(a, b) returns true. The actual shared rectangle — the region where both sprites are physically touching — runs from (90, 100) to (120, 160): a 30-by-60-pixel patch. A game would typically use this overlap to decide, say, how far to push the player back out of the enemy, or whether to trigger a "hit" event.
Misconception: "collision detection checks every pixel"
It's tempting to imagine the engine comparing every colored pixel of one sprite against every colored pixel of another, since that would give a perfectly precise answer — a curved sword just grazing a curved shield edge, exactly. In reality, almost no game does this by default, because it is far too expensive: two sprites of even 100x100 pixels would require up to 10,000 pixel comparisons, for every single pair of objects, every single frame. Instead, engines run collision detection in stages. The AABB check you just did is the fast first stage, called broad phase — it throws out the vast majority of object pairs that obviously aren't touching, using only four numbers and a handful of comparisons per pair. Only the small number of pairs that survive the AABB check (because their boxes do overlap) move on to a slower, more precise narrow phase check — such as circle-vs-circle distance, or exact polygon edges — if the game needs that extra precision at all. Most 2D games never bother with narrow phase; a good hitbox is close enough that players don't notice the difference.
What a "game engine" actually bundles together
Everything above — the game loop, the position/velocity/gravity update, AABB collision — is the physics engine, but it is only one part of what people mean by "game engine." A full engine like Unity (scripted in C#), Unreal Engine (C++), Godot (its own language, GDScript), or a lighter browser-based one like Phaser (JavaScript) bundles several systems together so a developer doesn't have to build each from zero:
- Renderer — draws sprites, models, and effects to the screen every frame, matching whatever
x, ythe physics system just computed. - Physics engine — the gravity/velocity/collision system covered in this chapter, usually extended with extra features like friction (which slows sliding objects down) and restitution (how "bouncy" a collision is).
- Input system — translates raw taps, key presses, or controller signals into events the game logic can react to.
- Audio system — plays sound effects and background music, often tied to physics events (a "thud" exactly when
isColliding()first returns true). - Scene graph and asset pipeline — keeps track of which objects currently exist in the level and manages loading images, sounds, and models efficiently.
If you've used Scratch's "change y by" and "if touching" blocks in an earlier class, you were already using a simplified version of exactly these two systems — position updates and collision checks — without necessarily seeing the arithmetic underneath. This chapter has been that arithmetic, made explicit. It's also the same underlying idea used in mobile battle-royale titles played widely in India, like BGMI and Free Fire, where bullet drop over distance and character fall damage are both driven by the same gravity-and-velocity update loop you just traced by hand — just running for many more objects, many more times per second. Indian studios building original games, such as nCore Games (maker of the mobile game FAU-G), rely on the same category of engine tooling described above rather than inventing physics from scratch for each title.
Frame-rate independence: why dt matters
There's a subtle bug hiding in the update rule y = y + vy as written so far: it silently assumes the game runs at a fixed, known frame rate. If your code was tuned assuming 60 frames per second, but a player's older phone can only manage 30 frames per second, the ball would appear to fall at half speed on that phone — not because gravity changed, but because the loop is simply calling updateBall() half as often, so vy gets added to y half as many times per real second.
The fix is to stop counting in "frames" and start counting in actual elapsed time. Engines measure the real time gap between frames — call it dt (delta time), usually a fraction of a second — and multiply every velocity and acceleration term by it:
let lastTime = 0;
function gameLoop(currentTime) {
const dt = (currentTime - lastTime) / 1000; // seconds since last frame
lastTime = currentTime;
ball.vy = ball.vy + gravity * dt;
ball.y = ball.y + ball.vy * dt;
requestAnimationFrame(gameLoop);
}
Now if a frame takes twice as long to arrive (because the device is slower), dt is twice as large, so the ball still moves the correct real-world distance in that frame — it just does it in fewer, bigger jumps rather than more, smaller ones. This is why the exact numbers in this chapter's hand-traced tables (fixed +4 per frame, no dt) are a simplified teaching version: they assume every frame takes exactly the same sliver of time, which is a fine assumption for learning the core idea, but a real shipped game always multiplies by dt so it behaves identically on a flagship phone and a budget one.
Practice: active recall
- A ball starts at
y = 180withvy = -13pixels/frame and gravityg = +3pixels/frame². Using the same two-line update rule from this chapter (updatevyfirst, theny), hand-trace six frames. At which frame is the ball at its highest point on screen (smallesty), and what is thatyvalue? - Box C has
x = 10, y = 10, width = 20, height = 20. Box D hasx = 25, y = 15, width = 10, height = 10. Using theisColliding()logic from this chapter, check the x-overlap condition and the y-overlap condition separately, then state whether the boxes collide. If they do, work out the exact overlap rectangle (its x-range, y-range, width, and height). - A classmate says: "My game's physics engine solves the exact equation for a falling object, so the motion it produces is mathematically perfect." Explain what is actually happening instead, and give one concrete reason engines are built this way rather than using the exact formula.
- Explain, in your own words, why multiplying velocity and gravity by
dt(delta time) makes a game behave the same way on a phone running at 30 fps and one running at 60 fps, when the version withoutdtwould not.
Summary
A game engine simulates motion by storing a position and a velocity for every moving object, then updating both, every frame, with two lines of arithmetic: velocity changes first under gravity or other acceleration, then position changes using that updated velocity (semi-implicit Euler integration). This is a deliberate approximation of real physics, not an exact formula, because it lets the simulation react instantly to new collisions or player input on the very next frame rather than committing to a fixed path in advance. Collisions between objects are detected cheaply using Axis-Aligned Bounding Boxes: two rectangles have collided only if they overlap on both the x-axis and the y-axis simultaneously, a check that costs a handful of comparisons instead of comparing every pixel. A full game engine wraps this physics system together with a renderer, input handling, audio, and asset management — and to behave consistently across devices of different speeds, it measures real elapsed time (dt) between frames rather than assuming a fixed frame rate.