Newton's laws give us the rules. Forces — gravity, wind, friction, drag — give our objects a reason to move.
At the end of Chapter 1, we had a ball accelerating toward the mouse. It looked like something was pulling it. But we never said what that something was. We just picked an acceleration vector and ran with it.
In the real world, acceleration doesn't just appear. It comes from forces: gravity pulling you down, wind pushing you sideways, friction slowing your shoes on the ground, a spring yanking a door shut.
By the end of this chapter, you'll be able to throw multiple forces at an object — gravity, wind, friction, drag — and watch them combine into realistic motion. The same two-line motion algorithm from Chapter 1 still applies. The only thing that changes is where the acceleration comes from.
| Chapter 1 | Chapter 2 |
|---|---|
| We invent acceleration | Forces produce acceleration |
| One acceleration per frame | Many forces accumulate |
| All objects respond the same | Mass makes objects respond differently |
Isaac Newton gave us three laws of motion. Together, they explain why things move (or don't).
Before Newton, people followed Aristotle's idea: things stop unless something keeps pushing them. Newton flipped it: things keep going unless something stops them. A hockey puck on frictionless ice would slide forever. It's forces (friction, air resistance) that slow things down.
In code, this means: if no forces act on an object, its velocity stays constant. We don't need to "keep pushing" it each frame.
Push against a wall. The wall pushes back on you with equal force. Push a friend on roller skates — they slide backward, but you might too (depending on mass and friction). The forces are equal; the outcomes depend on each object's properties.
In our simulations, we won't always model both sides of a force pair. When we apply "wind" to a ball, we won't bother modeling the force the ball exerts back on the air. We're inspired by physics, not slaves to it.
Newton's second law is the star of this chapter. It's usually written as:
But for us programmers, the more useful form is:
Acceleration is directly proportional to force and inversely proportional to mass. Push harder, you accelerate more. Be heavier, you accelerate less. This single equation turns any force vector into an acceleration vector.
For simplicity, let's start with mass = 1. Then a = F / 1 = F. The acceleration is the force. This is a perfectly fine starting point, and it's what we'll use until we introduce mass in a few chapters.
pseudocode function applyForce(force): acceleration = force # mass = 1, so a = F
Here's a problem. Say we want gravity and wind to act on our ball. If applyForce() just sets acceleration to the force, the second call overwrites the first:
pseudocode applyForce(wind) # acceleration = wind applyForce(gravity) # acceleration = gravity (wind is lost!)
The fix is force accumulation: we add each force to acceleration instead of replacing it.
The net force is the sum of all forces. Divide by mass and you get acceleration.
pseudocode function applyForce(force): acceleration = acceleration + force # accumulate!
update()). Acceleration has no memory — it's recalculated from scratch every frame based on whatever forces are present right now. Velocity remembers. Acceleration doesn't.pseudocode function update(): velocity = velocity + acceleration location = location + velocity acceleration = (0, 0) # clear for next frame
Now we can throw as many forces as we want — 1, 5, 100 — and they'll all combine naturally. Gravity pulling down? Wind pushing right? Friction slowing you? They all add up into one net acceleration.
Let's apply two of the simplest forces: gravity (a constant downward pull) and wind (a horizontal push).
Gravity is easy to model. Near Earth's surface, every object experiences the same gravitational acceleration: about 9.8 m/s2 downward. In pixel-land, we'll use a much smaller number:
Wind is equally simple — a constant horizontal force:
Each frame, we apply both forces, then update:
pseudocode each frame: mover.applyForce(gravity) mover.applyForce(wind) mover.update() mover.display() mover.checkEdges()
Edge-checking is also simple: if the ball hits the bottom, reverse its y-velocity (bounce). If it hits a side, reverse its x-velocity. Multiply by a damping factor (like 0.9) for energy loss on each bounce.
Time to bring mass into the picture. Newton's second law is F = ma, which means a = F/m. A heavier object accelerates less from the same force.
pseudocode function applyForce(force): f = force / mass # a = F / m acceleration = acceleration + f
Now imagine two balls — one with mass 1 and another with mass 10 — both experiencing the same gravity force. The light ball accelerates 10 times faster than the heavy one. Add in a wind force and watch how differently they move.
| Property | Light ball (m=1) | Heavy ball (m=5) |
|---|---|---|
| Gravity force | (0, 0.1) | (0, 0.1) |
| Acceleration from gravity | (0, 0.1) | (0, 0.02) |
| Wind force | (0.05, 0) | (0.05, 0) |
| Acceleration from wind | (0.05, 0) | (0.01, 0) |
gravity = (0, 0.1 * mass).So far our forces have been push-forward forces. But some forces resist motion. Friction is the most common one: it acts in the opposite direction of an object's velocity and slows it down.
The formula for kinetic friction is:
Let's break this apart:
| Symbol | Meaning | In our sim |
|---|---|---|
| μ | Coefficient of friction (how rough the surface is) | A constant, e.g. 0.01 |
| N | Normal force (how hard the surface pushes back) | We'll simplify to 1 |
| v̂ | Unit velocity vector (direction of motion) | velocity.normalize() |
| − | Opposite direction | Multiply by -1 |
pseudocode function computeFriction(mover): friction = mover.velocity.copy() friction.normalize() # direction of motion friction.mult(-1) # reverse it friction.mult(mu) # scale by coefficient return friction
When an object moves through a fluid (air, water), it experiences drag — a resistive force that increases with speed. The faster you move, the harder the fluid pushes back.
That's the full equation. We'll simplify it for our simulation by rolling the density, area, and drag coefficient into one constant c:
The key difference from friction: drag is proportional to the square of velocity. Going twice as fast means four times the drag. This is why terminal velocity exists — at some speed, drag equals gravity and acceleration stops.
pseudocode function computeDrag(mover, c): speed = mover.velocity.mag() dragMag = c * speed * speed # proportional to v^2 drag = mover.velocity.copy() drag.normalize() drag.mult(-1) drag.mult(dragMag) return drag
Everything comes together here. Below, multiple balls with different masses fall under gravity, experience wind (click and hold to apply wind), and encounter friction along the ground. Watch how mass changes everything.
Click/tap and hold to apply a rightward wind force. Balls have different masses (size = mass). Friction acts along the bottom. Watch heavier balls resist wind more.
Forces are the bridge between "things that move" and "things that move believably." Here's what we built:
| Concept | Formula | Key Insight |
|---|---|---|
| Newton's 2nd Law | a = F / m | Force divided by mass gives acceleration |
| Force Accumulation | a = ΣF / m | Sum all forces, then divide by mass |
| Gravity | (0, g) | Constant downward force |
| Friction | −μ N v̂ | Opposes motion, constant magnitude |
| Drag | −c |v|2 v̂ | Opposes motion, grows with speed squared |
applyForce(f) adds f/mass to acceleration. update() adds acceleration to velocity, velocity to location, then clears acceleration. This is the engine for everything in the rest of the book.What we covered:
• Newton's three laws
• F = ma and its rearrangement
• Force accumulation
• Gravity and wind
• Mass and its effect
• Friction (constant resistance)
• Drag (velocity-dependent resistance)
What comes next:
Chapter 3: Oscillation. We'll add angles, trigonometry, and periodic motion to our toolkit. Think pendulums, springs, and wave patterns — forces that pull objects back toward equilibrium.