gamedev-skills/pygame-core
> 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.
npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill 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+ (the actively maintained community fork; same
import pygame).
input handling, blitting, or sprite/group collision.
import pygame and the project depends on pygame-ce(or pygame).
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.
pip install pygame-ce — it's themaintained fork and imports as pygame. Don't install both in one environment.
pygame.init(), `screen =pygame.display.set_mode((w, h)), clock = pygame.time.Clock()`.
frame (for event in pygame.event.get()), update state, redraw, then
pygame.display.flip().
dt = clock.tick(60) / 1000 (seconds)and scale all motion by dt. Keep positions as floats; blit at integer rects.
KEYDOWN/MOUSEBUTTONDOWN, for discreteactions) and polled (pygame.key.get_pressed(), for held movement).
Sprite + Group. Subclass pygame.sprite.Spritewith image/rect; group.update(dt) and group.draw(screen) handle the
batch. Run it and watch the window before assuming it works.
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()
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
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)
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
# 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()
pygame.event.get() (or pygame.event.pump()) every frame.
Scale by dt = clock.tick(fps) / 1000 and use pixels-per-second values.
rect coordinates are integers; store thetrue position as a Vector2 of floats and assign rect.center = round(...) each
frame.
.convert() (opaque) or.convert_alpha() (transparent) on loaded images once; un-converted surfaces blit
far slower.
pygame.display.flip() (or update()), or youdrew before screen.fill(...) so it was cleared away.
Draw background first, sprites last.
pip install pygame got the old one → for the maintained fork usepip install pygame-ce; having both installed causes import conflicts.
speed.
Group variants (GroupSingle, LayeredUpdates for z-order), pixel-perfectcollision with mask, slicing a spritesheet, simple animation, sound/music, and
text rendering, read references/sprites-and-collision.md.
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.Take gamedev-skills/pygame-core from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.