Lesson 7 of 8

Physics-Based Games

Learn realistic physics to make your games feel amazing!

Learning Objectives

  • Understand gravity, velocity, and acceleration
  • Create realistic bouncing and collision effects
  • Build a platformer game with jumping mechanics
  • Add projectile physics for shooting games

Understanding Game Physics

Real-world physics makes games feel more natural and fun! Here are the key concepts:

Gravity

Pulls objects downward

velocity_y += gravity

Velocity

How fast and in what direction

x += velocity_x

Collision

When objects hit each other

velocity = -velocity * bounce

Friction

Objects slow down over time

velocity_x *= friction

Realistic Bouncing Ball

Create a ball that falls with gravity and bounces realistically:

import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Physics Ball!")
clock = pygame.time.Clock()

# Colors
BLACK = (0, 0, 0)
RED = (255, 100, 100)
WHITE = (255, 255, 255)

# Ball properties
ball_x = 400
ball_y = 100
ball_radius = 20
velocity_x = 5  # Horizontal speed
velocity_y = 0  # Vertical speed (starts at 0)
gravity = 0.5   # How fast things fall
bounce = 0.8    # Energy lost when bouncing
friction = 0.99 # Air resistance

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                # Reset ball position
                ball_x, ball_y = 400, 100
                velocity_x, velocity_y = 5, 0
    
    # Apply physics
    velocity_y += gravity  # Gravity pulls down
    velocity_x *= friction # Air slows things down
    velocity_y *= friction
    
    # Move the ball
    ball_x += velocity_x
    ball_y += velocity_y
    
    # Bounce off walls
    if ball_x <= ball_radius or ball_x >= 800 - ball_radius:
        velocity_x = -velocity_x * bounce
        ball_x = max(ball_radius, min(800 - ball_radius, ball_x))
    
    # Bounce off ground and ceiling
    if ball_y <= ball_radius or ball_y >= 600 - ball_radius:
        velocity_y = -velocity_y * bounce
        ball_y = max(ball_radius, min(600 - ball_radius, ball_y))
    
    # Draw everything
    screen.fill(BLACK)
    pygame.draw.circle(screen, RED, (int(ball_x), int(ball_y)), ball_radius)
    
    # Show physics info
    font = pygame.font.Font(None, 24)
    text = font.render(f"Velocity: ({velocity_x:.1f}, {velocity_y:.1f})", True, WHITE)
    screen.blit(text, (10, 10))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

Try this: Watch how the ball bounces lower each time due to energy loss! Press SPACE to reset.

Platformer Game with Jumping

Build a character that can jump on platforms with realistic physics:

import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Physics Platformer!")
clock = pygame.time.Clock()

# Colors
BLACK = (0, 0, 0)
BLUE = (100, 150, 255)
BROWN = (139, 69, 19)
WHITE = (255, 255, 255)

class Player:
    def __init__(self):
        self.x = 100
        self.y = 400
        self.width = 30
        self.height = 40
        self.velocity_x = 0
        self.velocity_y = 0
        self.on_ground = False
        self.speed = 5
        self.jump_power = -12
        self.gravity = 0.8
    
    def update(self, platforms):
        # Handle input
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.velocity_x = -self.speed
        elif keys[pygame.K_RIGHT]:
            self.velocity_x = self.speed
        else:
            self.velocity_x *= 0.8  # Friction when not moving
        
        if keys[pygame.K_SPACE] and self.on_ground:
            self.velocity_y = self.jump_power
            self.on_ground = False
        
        # Apply gravity
        self.velocity_y += self.gravity
        
        # Update position
        self.x += self.velocity_x
        self.y += self.velocity_y
        
        # Check platform collisions
        self.on_ground = False
        for platform in platforms:
            if self.check_collision(platform):
                # Landing on top of platform
                if self.velocity_y > 0 and self.y < platform['y']:
                    self.y = platform['y'] - self.height
                    self.velocity_y = 0
                    self.on_ground = True
        
        # Keep player on screen
        if self.x < 0:
            self.x = 0
        elif self.x > 800 - self.width:
            self.x = 800 - self.width
        
        # Ground collision
        if self.y > 600 - self.height:
            self.y = 600 - self.height
            self.velocity_y = 0
            self.on_ground = True
    
    def check_collision(self, platform):
        return (self.x < platform['x'] + platform['width'] and
                self.x + self.width > platform['x'] and
                self.y < platform['y'] + platform['height'] and
                self.y + self.height > platform['y'])
    
    def draw(self, screen):
        # Body
        pygame.draw.rect(screen, BLUE, (self.x, self.y, self.width, self.height))
        # Eyes
        pygame.draw.circle(screen, WHITE, (self.x + 8, self.y + 10), 3)
        pygame.draw.circle(screen, WHITE, (self.x + 22, self.y + 10), 3)

# Create platforms
platforms = [
    {'x': 200, 'y': 500, 'width': 150, 'height': 20},
    {'x': 400, 'y': 400, 'width': 150, 'height': 20},
    {'x': 600, 'y': 300, 'width': 150, 'height': 20},
    {'x': 300, 'y': 200, 'width': 200, 'height': 20},
]

player = Player()

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    
    # Update
    player.update(platforms)
    
    # Draw everything
    screen.fill(BLACK)
    
    # Draw platforms
    for platform in platforms:
        pygame.draw.rect(screen, BROWN, (platform['x'], platform['y'], platform['width'], platform['height']))
    
    # Draw ground
    pygame.draw.rect(screen, BROWN, (0, 580, 800, 20))
    
    # Draw player
    player.draw(screen)
    
    # Instructions
    font = pygame.font.Font(None, 24)
    text1 = font.render("Arrow Keys: Move", True, WHITE)
    text2 = font.render("Space: Jump", True, WHITE)
    screen.blit(text1, (10, 10))
    screen.blit(text2, (10, 35))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

Game Controls: Use arrow keys to move and SPACE to jump between platforms!

Projectile Physics Game

Create a cannon that shoots projectiles with realistic arc trajectories:

import pygame
import math
import sys

pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Projectile Physics!")
clock = pygame.time.Clock()

# Colors
BLACK = (0, 0, 0)
RED = (255, 100, 100)
YELLOW = (255, 255, 100)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)

class Projectile:
    def __init__(self, x, y, velocity_x, velocity_y):
        self.x = x
        self.y = y
        self.velocity_x = velocity_x
        self.velocity_y = velocity_y
        self.gravity = 0.3
        self.active = True
    
    def update(self):
        if self.active:
            # Apply gravity
            self.velocity_y += self.gravity
            
            # Update position
            self.x += self.velocity_x
            self.y += self.velocity_y
            
            # Remove if off screen
            if self.x < 0 or self.x > 800 or self.y > 600:
                self.active = False
    
    def draw(self, screen):
        if self.active:
            pygame.draw.circle(screen, YELLOW, (int(self.x), int(self.y)), 5)

# Cannon properties
cannon_x = 50
cannon_y = 550
cannon_angle = -45  # Degrees
cannon_length = 40
launch_power = 15

projectiles = []
charging = False

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_UP:
                cannon_angle = max(-90, cannon_angle - 5)
            elif event.key == pygame.K_DOWN:
                cannon_angle = min(0, cannon_angle + 5)
            elif event.key == pygame.K_SPACE:
                # Fire projectile
                angle_rad = math.radians(cannon_angle)
                vel_x = math.cos(angle_rad) * launch_power
                vel_y = math.sin(angle_rad) * launch_power
                
                projectiles.append(Projectile(cannon_x, cannon_y, vel_x, vel_y))
    
    # Update projectiles
    for projectile in projectiles[:]:
        projectile.update()
        if not projectile.active:
            projectiles.remove(projectile)
    
    # Draw everything
    screen.fill(BLACK)
    
    # Draw ground
    pygame.draw.rect(screen, GRAY, (0, 570, 800, 30))
    
    # Draw cannon
    end_x = cannon_x + math.cos(math.radians(cannon_angle)) * cannon_length
    end_y = cannon_y + math.sin(math.radians(cannon_angle)) * cannon_length
    pygame.draw.line(screen, RED, (cannon_x, cannon_y), (end_x, end_y), 8)
    pygame.draw.circle(screen, RED, (cannon_x, cannon_y), 15)
    
    # Draw projectiles
    for projectile in projectiles:
        projectile.draw(screen)
    
    # Instructions
    font = pygame.font.Font(None, 24)
    text1 = font.render("Up/Down: Aim cannon", True, WHITE)
    text2 = font.render("Space: Fire!", True, WHITE)
    text3 = font.render(f"Angle: {int(cannon_angle)}°", True, WHITE)
    screen.blit(text1, (10, 10))
    screen.blit(text2, (10, 35))
    screen.blit(text3, (10, 60))
    
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()

Physics Challenge: Try different angles to see how projectiles follow curved paths due to gravity!