pygame
This game runs as a desktop app
This platformer relies on pygame for real-time collision detection, gravity physics, dynamic camera scrolling, and array-based level loading. Run it locally to experience the smooth jumping mechanics and multiple levels.
How to run it locally
01Install pygame: pip install pygame
02Download platformer.py from the GitHub repo below
03Run it: python platformer.py
04Arrow keys or A/D to move, Space to jump, R to restart
platformer.py — logic highlights
GitHub ↗
"""
PROJECT 9: Platformer
======================
Concepts: physics (gravity, jumping), tilemaps, camera/scrolling,
enemy AI, collectibles, multiple levels, organized code structure
"""
import pygame
# ── Player Physics & Coyote Time ──────────────────────────────────────────────
class Player(pygame.sprite.Sprite):
def jump(self):
# Allow jumping if grounded OR if within the coyote time grace period
if self.on_ground or self.coyote > 0:
self.vel_y = JUMP_FORCE
self.on_ground = False
self.coyote = 0
def update(self, keys, tiles):
# Gravity
self.vel_y += GRAVITY
if self.vel_y > 20: self.vel_y = 20 # terminal velocity
# Move & collide vertically
was_on_ground = self.on_ground
self.on_ground = False
self.rect.y += int(self.vel_y)
for tile in pygame.sprite.spritecollide(self, tiles, False):
if self.vel_y > 0:
self.rect.bottom = tile.rect.top
self.on_ground = True
self.vel_y = 0
elif self.vel_y < 0:
self.rect.top = tile.rect.bottom
self.vel_y = 0
# Coyote time: keep a grace window after walking off an edge
if was_on_ground and not self.on_ground:
self.coyote = COYOTE_FRAMES
elif self.coyote > 0:
self.coyote -= 1
# ── Dynamic Camera Scrolling ──────────────────────────────────────────────────
class Camera:
def __init__(self, level_width, level_height):
self.offset_x = 0
self.offset_y = 0
self.lw = level_width
self.lh = level_height
def update(self, target):
# Center on player
self.offset_x = target.rect.centerx - WIDTH // 2
self.offset_y = target.rect.centery - HEIGHT // 2
# Clamp so we don't show outside the level boundaries
self.offset_x = max(0, min(self.offset_x, self.lw - WIDTH))
self.offset_y = max(0, min(self.offset_y, self.lh - HEIGHT))
def apply(self, rect):
return rect.move(-self.offset_x, -self.offset_y)
Concepts practiced
TilemapsString array parsing
PhysicsGravity & Velocity
CameraViewport offset
Game FeelCoyote time
State MgmtLevel transitions
CollectiblesSprite collisions
Reflection
I built a side-scrolling platformer with gravity physics, tilemap-based level loading, enemy AI, collectibles, and a dynamic camera that keeps the player centred while clamping at level boundaries. I learned how to implement a proper physics update loop — applying gravity each frame, clamping terminal velocity, and resolving vertical collisions by snapping the player's rect to the tile surface. The most satisfying bug to fix was the "jumping off a ledge" problem, where players would lose their jump ability a frame too early as they walked off an edge. I solved this by implementing coyote time — a short grace-period counter (coyote_frames) that starts when the player leaves the ground, giving them a few frames to still trigger a jump as if they were still standing on the platform.