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.