pygame-core
gamedev-skills/awesome-gamedev-agent-skills
Build the core pygame game loop: init, events, delta-time movement, drawing, input, and sprite/group collision.
What is pygame-core?
pygame-core teaches the foundation of a pygame-ce game in Python: the main event/update/draw loop, frame-rate-independent movement via delta-time, Surface/Rect blitting, keyboard and mouse input handling, and Sprite/Group management with collision detection. Use this when starting a pygame project, fixing the game loop, handling input, or debugging sprite and collision logic.
- Set up pygame-ce window, clock, and the main game loop (events → update → draw → flip)
- Implement delta-time movement to make speed frame-rate independent using `clock.tick()` and `dt`
- Handle input via events (discrete actions like jump) and polled state (continuous movement)
- Organize game objects with `pygame.sprite.Sprite` subclasses and `Group` for batch updates and drawing
- Detect collisions between sprites and groups using `spritecollide()`, `groupcollide()`, and rect overlap
- Blit images and surfaces efficiently using `.convert()` and `.convert_alpha()` for fast rendering
How to install pygame-core
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill pygame-core- Python 3.7+
- Install pygame-ce: `pip install pygame-ce` (not legacy pygame)
How to use pygame-core
- 1.Initialize pygame with `pygame.init()` and create a display window using `pygame.display.set_mode()`
- 2.Create a `Clock` object and set up the main loop that runs at a target FPS (e.g., 60)
- 3.In each frame, drain the event queue with `pygame.event.get()`, update game state, clear the screen, draw all objects, and call `pygame.display.flip()`
- 4.Calculate delta-time as `dt = clock.tick(fps) / 1000` and scale all movement by `dt` to ensure frame-rate independence
- 5.Use `pygame.key.get_pressed()` for continuous input (held keys) and event-based input for discrete actions (key press, mouse click)
- 6.Subclass `pygame.sprite.Sprite` with `image` and `rect` attributes, add instances to a `Group`, then call `group.update(dt)` and `group.draw(screen)` each frame
- 7.Use `pygame.sprite.spritecollide()` or `groupcollide()` to detect collisions and respond (e.g., remove coins when collected)
Use cases
- Starting a new 2D game and setting up the main loop structure
- Fixing frame-rate-dependent movement that runs at different speeds on different machines
- Adding keyboard or mouse input to control a player character or trigger actions
- Organizing multiple game objects (enemies, projectiles, collectibles) with sprite groups
- Debugging collision detection between the player and obstacles or collectibles
- Game developers building 2D games in Python
- Students learning game loop architecture and real-time input handling
- Developers migrating from legacy pygame to the maintained pygame-ce fork
- Anyone debugging sprite rendering, collision, or input issues in an existing pygame project
pygame-core FAQ
Use pygame-ce (`pip install pygame-ce`). It is the actively maintained community fork and imports as `pygame`. Legacy pygame is no longer maintained; do not install both in the same environment.
You are likely moving objects by a fixed amount per frame instead of scaling by delta-time. Always compute `dt = clock.tick(fps) / 1000` and multiply movement by `dt` so speed is in pixels-per-second, not pixels-per-frame.
You are not pumping the event queue. Call `pygame.event.get()` (or `pygame.event.pump()`) every frame inside the main loop, even if you don't use the events. The OS needs to see the window responding to events.
Call `.convert()` (for opaque images) or `.convert_alpha()` (for transparent images) on loaded surfaces once at startup. Un-converted surfaces blit much slower. Store the converted surface and reuse it.
Use `pygame.key.get_pressed()` in the update loop for continuous input like movement. Use the event loop (`pygame.KEYDOWN` events) for discrete actions like jump or pause, since you only want to trigger them once per press.
Full instructions (SKILL.md)
Source of truth, from gamedev-skills/awesome-gamedev-agent-skills.
name: pygame-core description: > Structure a pygame (pygame-ce) game in Python: the init/event/update/draw loop, delta-time movement, Surface/Rect blitting, keyboard/mouse input, and Sprite/Group management with collision. Use when building or debugging a pygame game — when the user mentions pygame, pygame-ce, the game loop, blit, Surface, Rect, sprite groups, or clock.tick. Targets pygame-ce.
pygame Core
Build the foundation of a pygame game in Python: the main loop, delta-time
movement, drawing with Surface/Rect, input, and Sprite/Group management.
Targets pygame-ce 2.5.8 (the actively maintained community fork; same
import pygame).
When to use
- Use when starting a pygame game, fixing the loop, frame-rate-dependent speed, input handling, blitting, or sprite/group collision.
- Use when code does
import pygameand the project depends onpygame-ce(orpygame).
When not to use: Python language questions unrelated to pygame. 3D rendering
(pygame is 2D). For cross-engine save/load use save-systems; for rebindable input
architecture see input-systems.
Core workflow
- Install pygame-ce, not legacy pygame.
pip install pygame-ce— it's the maintained fork and imports aspygame. Don't install both in one environment. - Init and open a window.
pygame.init(),screen = pygame.display.set_mode((w, h)),clock = pygame.time.Clock(). - Run one loop: events → update → draw → flip. Pump the event queue every
frame (
for event in pygame.event.get()), update state, redraw, thenpygame.display.flip(). - Make it frame-rate independent. Get
dt = clock.tick(60) / 1000(seconds) and scale all motion bydt. Keep positions as floats; blit at integer rects. - Handle input two ways: event-based (
KEYDOWN/MOUSEBUTTONDOWN, for discrete actions) and polled (pygame.key.get_pressed(), for held movement). - Organise objects with
Sprite+Group. Subclasspygame.sprite.Spritewithimage/rect;group.update(dt)andgroup.draw(screen)handle the batch. Run it and watch the window before assuming it works.
Patterns
1. Minimal game loop (the skeleton)
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
running = True
while running:
dt = clock.tick(60) / 1000 # cap at 60 FPS; dt = seconds since last frame
for event in pygame.event.get(): # MUST drain the queue or the OS thinks it hung
if event.type == pygame.QUIT:
running = False
# update game state here, scaled by dt ...
screen.fill((18, 18, 28)) # clear each frame
# draw everything here ...
pygame.display.flip() # present the frame
pygame.quit()
2. Delta-time movement (frame-rate independent)
from pygame.math import Vector2
pos = Vector2(100, 100) # keep position as floats
speed = 220 # PIXELS PER SECOND, not per frame
# inside the loop, after computing dt:
keys = pygame.key.get_pressed()
direction = Vector2(
keys[pygame.K_RIGHT] - keys[pygame.K_LEFT],
keys[pygame.K_DOWN] - keys[pygame.K_UP],
)
if direction.length_squared() > 0:
direction = direction.normalize() # equal speed on diagonals
pos += direction * speed * dt # RIGHT: dt-scaled
screen.blit(player_img, (round(pos.x), round(pos.y))) # blit at integer pixels
3. Input: events vs polling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN: # discrete press: jump, menu, pause
if event.key == pygame.K_SPACE:
jump()
elif event.key == pygame.K_ESCAPE:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
shoot_at(event.pos) # event.pos = (x, y)
# Polled state (read once per frame) for continuous/held input:
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
move_left(dt)
4. A Sprite subclass + a Group
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
# convert() once at load makes blits much faster; _alpha keeps transparency.
self.image = pygame.image.load("player.png").convert_alpha()
self.rect = self.image.get_rect(center=(x, y))
self.pos = pygame.math.Vector2(self.rect.center)
self.speed = 240
def update(self, dt): # Group.update(dt) calls this per sprite
keys = pygame.key.get_pressed()
self.pos.x += (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * self.speed * dt
self.rect.center = (round(self.pos.x), round(self.pos.y))
all_sprites = pygame.sprite.Group()
all_sprites.add(Player(400, 300))
# in the loop:
all_sprites.update(dt) # calls each sprite's update(dt)
all_sprites.draw(screen) # blits each sprite at its rect
5. Collision detection
# Sprite vs group: e.g. player picking up coins (True = remove collided coins).
collected = pygame.sprite.spritecollide(player, coins, dokill=True)
score += len(collected)
# Group vs group: bullets vs enemies (kill both on hit).
hits = pygame.sprite.groupcollide(bullets, enemies, True, True)
# Plain rect overlap (no sprites needed):
if player.rect.colliderect(door_rect):
open_door()
Pitfalls
- Window freezes / "not responding" → you didn't pump the event queue. Call
pygame.event.get()(orpygame.event.pump()) every frame. - Speed differs on faster machines → you moved by a fixed amount per frame.
Scale by
dt = clock.tick(fps) / 1000and use pixels-per-second values. - Sub-pixel movement snaps/jitters →
rectcoordinates are integers; store the true position as aVector2of floats and assignrect.center = round(...)each frame. - Blits are slow / framerate drops → call
.convert()(opaque) or.convert_alpha()(transparent) on loaded images once; un-converted surfaces blit far slower. - Nothing appears → you forgot
pygame.display.flip()(orupdate()), or you drew beforescreen.fill(...)so it was cleared away. - Wrong draw order → pygame uses painter's order; later blits cover earlier ones. Draw background first, sprites last.
pip install pygamegot the old one → for the maintained fork usepip install pygame-ce; having both installed causes import conflicts.- Diagonal movement is faster → normalise the direction vector before scaling by speed.
References
- For
Groupvariants (GroupSingle,LayeredUpdatesfor z-order), pixel-perfect collision withmask, slicing a spritesheet, simple animation, sound/music, and text rendering, readreferences/sprites-and-collision.md.
Related skills
love2d-core— the same loop concepts in LÖVE/Lua.bevy-ecs— a heavier ECS engine when a project outgrows pygame.input-systems/save-systems— engine-agnostic input and persistence.platformer/roguelike— genre templates that pair with pygame.
Related skills
More from gamedev-skills/awesome-gamedev-agent-skills and the wider catalog.

roblox-datastores
Persist player data in Roblox with DataStoreService: load on join, save on leave, safe updates, and leaderboards.

roblox-luau
Script Roblox experiences in Luau: services, instances, events, and secure client/server communication.

roguelike
Build turn-based grid dungeons with procedural generation, permadeath, and field-of-view.

router
Routes game-dev requests to the right specialized skill based on engine and task.

rpg
Build RPGs with stats, leveling, inventory, quests, dialogue, and combat systems.

save-systems
Design crash-safe, versioned save/load for game state across sessions and patches.