Quick answer: Use SPAWN_EVENT = pygame.event.custom_type() for the timer ID, schedule with pygame.time.set_timer(SPAWN_EVENT, 1000), and drain via pygame.event.get every frame.
An enemy spawner uses pygame.time.set_timer(pygame.USEREVENT, 2000) to spawn every 2 seconds. No enemies appear. Adding a print on every event — only standard input events show. The USEREVENT isn’t being delivered.
USEREVENT Collisions
pygame.USEREVENT is a constant (typically 24). If two subsystems both use USEREVENT directly, their events collide and become hard to distinguish. The custom_type API returns the next unused ID:
import pygame
pygame.init()
SPAWN_EVENT = pygame.event.custom_type() # unique ID
WAVE_EVENT = pygame.event.custom_type() # different unique ID
pygame.time.set_timer(SPAWN_EVENT, 2000) # every 2 seconds
Now SPAWN_EVENT and WAVE_EVENT can’t collide. Each timer drives a distinct event type.
Drain the Queue
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == SPAWN_EVENT:
spawn_enemy()
elif event.type == WAVE_EVENT:
start_next_wave()
# update, draw...
pygame.display.flip()
clock.tick(60)
pygame.event.get must run every frame. If you have a code path that exits the inner for loop but skips it on some condition, those frames miss events.
Stopping the Timer
pygame.time.set_timer(SPAWN_EVENT, 0) # 0 ms = cancel
Resetting to 0 stops the timer. Resetting to a new positive value reschedules at the new interval.
loops Parameter
Pygame 2.0+ added loops:
pygame.time.set_timer(SPAWN_EVENT, 2000, loops=5) # fire 5 times then stop
Default is 0 (forever). Useful for finite-duration mechanics like a powerup that ticks down.
Verifying
Add a print at the top of each event branch. Run the game; SPAWN_EVENT prints should appear every 2 seconds. If not, the timer isn’t scheduled (typo in set_timer call) or the event loop is broken (skipped frames).
“custom_type for IDs, set_timer to schedule, event.get to drain. Three pieces; all required.”
Define all custom event types at module top — never sprinkle pygame.USEREVENT + 5 patterns through the code, they collide.