[concept]Game Development with Pygame
Sound & Music
# theory
adding sound to your game
A game without sound feels dead. Pygame's mixer module handles both short sound effects (explosions, jumps, hits) and background music (looping tracks).
import pygame
pygame.init()
pygame.mixer.init() # already called by pygame.init(), but good to know it exists
There are two separate systems and they work differently:
- Sound effects: short clips, multiple can play at once
- Music: one track at a time, streams from disk (good for long files)
sound effects with pygame.mixer.Sound
Sound effects are loaded into memory. Use .wav or .ogg files (mp3 support is inconsistent).
# Load sounds (do this ONCE, outside the game loop)
jump_sound = pygame.mixer.Sound("assets/jump.wav")
hit_sound = pygame.mixer.Sound("assets/hit.wav")
coin_sound = pygame.mixer.Sound("assets/coin.ogg")
# Play a sound (inside the game loop, when something happens)
if player_jumped:
jump_sound.play()
if collision_detected:
hit_sound.play()
Key point: Load sounds once at startup. Don't load them inside the game loop or you'll destroy your frame rate.
Volume Control
# Set volume (0.0 = silent, 1.0 = full volume)
jump_sound.set_volume(0.5) # 50% volume
hit_sound.set_volume(0.3) # quieter
# Get current volume
current_vol = jump_sound.get_volume()
Stopping Sounds
hit_sound.stop() # stop this specific sound
pygame.mixer.stop() # stop ALL sounds
background music with pygame.mixer.music
Music streams from disk instead of loading into memory. Only one track plays at a time.
# Load and play background music
pygame.mixer.music.load("assets/background.ogg")
pygame.mixer.music.set_volume(0.4)
pygame.mixer.music.play(-1) # -1 = loop forever
# Pause / unpause
pygame.mixer.music.pause()
pygame.mixer.music.unpause()
# Stop completely
pygame.mixer.music.stop()
# Fade out over 2 seconds (smooth ending)
pygame.mixer.music.fadeout(2000)
The -1 argument is important. play(0) plays once, play(3) plays 3 extra times (4 total), play(-1) loops forever. For background music you almost always want -1.
channels: playing multiple sounds
Pygame has 8 channels by default. Each channel can play one sound at a time. When you call sound.play(), it grabs a free channel automatically.
# Need more simultaneous sounds?
pygame.mixer.set_num_channels(16) # now 16 channels
# Reserve a channel for important sounds (so it never gets stolen)
pygame.mixer.set_reserved(1)
important_channel = pygame.mixer.Channel(0) # channel 0 is reserved
important_channel.play(alert_sound)
If all channels are busy when you play a sound, the oldest sound gets cut off. Reserving a channel guarantees your critical sounds (like a game-over chime) always play.
common pattern: sound manager
Most games wrap their sounds in a simple manager:
class SoundManager:
def __init__(self):
self.sounds = {}
self.muted = False
def load(self, name, filepath):
self.sounds[name] = pygame.mixer.Sound(filepath)
def play(self, name, volume=1.0):
if not self.muted and name in self.sounds:
self.sounds[name].set_volume(volume)
self.sounds[name].play()
def toggle_mute(self):
self.muted = not self.muted
if self.muted:
pygame.mixer.stop()
# Usage:
sfx = SoundManager()
sfx.load("jump", "assets/jump.wav")
sfx.load("coin", "assets/coin.ogg")
# In game loop:
sfx.play("jump")
sfx.play("coin", volume=0.5)
Gerber tip: Keep your sound loading separate from your game logic. Load everything in an init function, then just call play() during the game. Clean separation.
file formats
| Format | Best For | Notes |
|---|---|---|
| .wav | Sound effects | Uncompressed, fast to load, large files |
| .ogg | Music + effects | Compressed, small files, good quality |
| .mp3 | Avoid | Licensing issues, inconsistent support |
Stick with .ogg for most things. Use .wav for tiny effects where loading speed matters.
# examples [2]
Load once at startup, play when events happen. Never load inside the game loop.
pygame needs a real window — copy this into a .py file and run it locally.
Music streams from disk. Use play(-1) for infinite loop, fadeout() for smooth transitions.
pygame needs a real window — copy this into a .py file and run it locally.
# challenges [3]