Velocity and acceleration
Implement Euler integration so objects accelerate smoothly and move at the same speed on any machine — frame-rate independence in practice.
- Implement Euler integration using position += velocity * dt and velocity += acceleration * dt
- Obtain delta time from pygame.time.Clock and use it to scale all motion
- Apply a constant force to an object over time
Movement in most beginner games looks like rect.x += speed. That works
until you run on a faster or slower machine: at 120 fps the player crosses the
map in half the time it takes at 60 fps. The fix is delta time — scale
every displacement by the number of seconds the last frame actually took.
Pyodide (the in-browser Python runner) does not support pygame's display
system. Read through this code carefully in the browser, then run it
locally: pip install pygame followed by python physics.py.
Euler integration
The simplest way to integrate physics is Euler integration:
velocity += acceleration * dt
position += velocity * dtApply this once per frame, where dt is the elapsed time in seconds. The
resulting motion is physically plausible for moderate time steps and cheap
enough for 2D games.
pygame.time.Clock.tick(fps) returns milliseconds since the last call.
Divide by 1000 to get seconds:
dt = clock.tick(60) / 1000.0 # seconds; capped at ~1/60Cap dt at something sensible (e.g. min(dt, 0.05)) so that resuming from
a breakpoint or tab-switch does not send the player flying across the map.
PhysicsBody class
import pygame
import sys
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Euler integration demo")
clock = pygame.time.Clock()
Vector2 = pygame.math.Vector2
# ── PhysicsBody ───────────────────────────────────────────────────────────────
class PhysicsBody:
def __init__(self, x, y):
self.position = Vector2(x, y)
self.velocity = Vector2(0, 0)
self.acceleration = Vector2(0, 0)
def apply_force(self, force):
"""Accumulate a force for this frame (mass = 1 for simplicity)."""
self.acceleration += force
def update(self, dt):
self.velocity += self.acceleration * dt
self.position += self.velocity * dt
self.acceleration = Vector2(0, 0) # reset each frame
def draw(self, surface):
pygame.draw.circle(surface, (100, 200, 120),
(int(self.position.x), int(self.position.y)), 14)
# ── Setup ─────────────────────────────────────────────────────────────────────
ball = PhysicsBody(320, 240)
WIND_FORCE = Vector2(40, 0) # 40 px/s² toward the right
running = True
while running:
dt = min(clock.tick(60) / 1000.0, 0.05)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Apply a constant wind force every frame
ball.apply_force(WIND_FORCE)
# Arrow keys: let the player counteract the wind
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]: ball.apply_force(Vector2(-120, 0))
if keys[pygame.K_RIGHT]: ball.apply_force(Vector2( 120, 0))
if keys[pygame.K_UP]: ball.apply_force(Vector2(0, -120))
if keys[pygame.K_DOWN]: ball.apply_force(Vector2(0, 120))
ball.update(dt)
# Wrap at screen edges
ball.position.x %= 640
ball.position.y %= 480
screen.fill((20, 20, 35))
ball.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()How it works
Force accumulation — apply_force() adds to acceleration rather than
setting it. Multiple forces (wind, gravity, player input) all contribute to
the same acceleration for that frame. After update() the accumulator is
zeroed so stale forces do not carry into the next frame.
Mass = 1 — Strictly, Newton's second law says acceleration = force / mass. For simple prototypes, treating mass as 1 makes force and acceleration
equivalent, which simplifies the code without changing the qualitative feel.
Add a self.mass field when you need lighter or heavier objects.
Velocity damping — Multiply velocity by a damping factor each frame
(self.velocity *= 0.98) to simulate friction or air resistance. Without it,
an object accelerated by a constant force accelerates forever.
Euler integration accumulates a small error each step. For most 2D arcade games the error is imperceptible. If you are building a physics-critical simulation (billiards, trebuchet), look into Verlet or Runge-Kutta integration instead.
Where to go next
Next: gravity and jumping — applying a constant downward force and the concept of jump impulses, coyote time, and jump buffering.
Vectors and math
A 2D vector is a direction and a magnitude. Master addition, scalar multiplication, normalisation, and the dot product — the four operations that underpin game physics.
Gravity and jumping
Model gravity as a constant downward acceleration, implement a jump impulse, and improve player feel with coyote time and jump buffering.