PygStormGames Library
A Python framework for creating accessible audio games with Pyglet.
Overview
PygStormGames is a specialized game framework built on top of Pyglet, designed specifically for creating audio-focused games with accessibility in mind. The library provides a complete set of tools for building games with rich audio feedback, text-to-speech integration, menu systems, and more.
Core Features
- Accessible Interface: Built-in text-to-speech support and screen reader compatibility
- Audio Management: Spatial audio, sound effects, and background music
- Menu System: Fully accessible menu with navigation sounds
- Configuration: Local and global settings management
- Scoreboard: High score tracking and persistence
- Text Display: Accessible text display with navigation
Installation
PygStormGames requires Python 3.6+ and depends on the following libraries:
- pyglet
- pyperclip
- wx (wxPython)
- xdg
- speechd or accessible_output2 (for text-to-speech)
Getting Started
Basic Game Structure
from pygstormgames import pygstormgames
class MyGame:
def __init__(self):
# Initialize the game with a title
self.game = pygstormgames("My Audio Game")
# Set up game-specific variables and components
self.score = 0
self.gameActive = False
# Set up the main game loop
pyglet.clock.schedule_interval(self.update, 1/60.0)
def update(self, dt):
# Main game update loop
if self.gameActive:
# Update game state
pass
def start(self):
# Show main menu
selection = self.game.menu.game_menu()
if selection == "play":
# Start the game
self.gameActive = True
self.game.run()
if __name__ == "__main__":
game = MyGame()
game.start()
Main Components
pygstormgames Class
The main class that coordinates all game systems. Initialize with the game title.
game = pygstormgames("My Game Title")
Methods
run()
: Start the game loopwait(validKeys=None, timeout=None)
: Wait for key press(es) with optional timeoutwait_for_completion(condition, validKeys=None, timeout=None)
: Wait for a condition to be met, keys to be pressed, or timeoutpause_game()
: Pause all game systemsexit_game()
: Clean up and exit the game
Config
Handles loading and saving of both local and global game configurations.
# Get a config value (section, key, default value, global or local)
volume = game.config.get_float("audio", "master_volume", 0.8)
# Save a config value
game.config.set_value("audio", "master_volume", 0.9)
Methods
get_value(section, key, default=None, globalConfig=False)
: Get string valueset_value(section, key, value, globalConfig=False)
: Set valueget_int(section, key, default=0, globalConfig=False)
: Get integer valueget_float(section, key, default=0.0, globalConfig=False)
: Get float valueget_bool(section, key, default=False, globalConfig=False)
: Get boolean value
Display
Handles text display, navigation, and information presentation.
# Display text with speech output
game.display.display_text(textLines, game.speech)
# Show a message box
game.display.messagebox("Game over! Your score: 1000")
# Get text input from the user
name = game.display.get_input("Enter your name:", "Player")
Methods
display_text(text, speech)
: Display and navigate text with speech outputinstructions(speech)
: Display game instructions from filecredits(speech)
: Display game credits from filemessagebox(text)
: Display a simple message box with textget_input(prompt="Enter text:", defaultText="")
: Display a dialog box for text inputdonate(speech)
: Open the donation webpage
Menu
Handles main menu and submenu functionality for games.
# Show a menu and get selection
options = ["start", "options", "exit"]
selection = game.menu.show_menu(options, "Main Menu")
# Show the default game menu
selection = game.menu.game_menu()
Methods
show_menu(options, title=None, with_music=False)
: Display a menu and return selected optiongame_menu()
: Show main game menulearn_sounds()
: Interactive menu for learning game sounds
Sound
Handles all audio functionality including background music, sound effects, and volume control.
# Play a sound effect
game.sound.play_sound("explosion")
# Play background music
game.sound.play_bgm("sounds/music_level1.ogg")
# Adjust volume
game.sound.adjust_master_volume(0.1) # Increase by 10%
Methods
play_sound(soundName, volume=1.0)
: Play a sound effectplay_bgm(music_file)
: Play background musicpause_bgm()
: Pause background musicresume_bgm()
: Resume background musicplay_random(base_name, pause=False, interrupt=False)
: Play random variation of a soundplay_positional(soundName, source_pos, listener_pos, mode='2d')
: Play sound with positional audioupdate_positional(player, source_pos, listener_pos, mode='2d')
: Update position of a playing soundcut_scene(soundName)
: Play a sound as a cut sceneadjust_master_volume(change)
: Adjust master volumeadjust_bgm_volume(change)
: Adjust background music volumeadjust_sfx_volume(change)
: Adjust sound effects volumeget_volumes()
: Get current volume levelspause()
: Pause all audioresume()
: Resume all audiostop_all_sounds()
: Stop all playing sounds
Speech
Provides text-to-speech functionality with screen text display support.
# Speak text
game.speech.speak("Welcome to my game!")
# Speak with no interruption
game.speech.speak("This will wait for previous speech to finish", interrupt=False)
Methods
speak(text, interrupt=True)
: Speak text and display it on screen
Scoreboard
Handles high score tracking with player names and score management.
# Increase the score
game.scoreboard.increase_score(100)
# Check and add high score if qualified
if game.scoreboard.check_high_score():
game.scoreboard.add_high_score()
# Get high scores
highScores = game.scoreboard.get_high_scores()
Methods
get_score()
: Get current scoreget_high_scores()
: Get list of high scoresdecrease_score(points=1)
: Decrease the current scoreincrease_score(points=1)
: Increase the current scorecheck_high_score()
: Check if current score qualifies as a high scoreadd_high_score()
: Add current score to high scores if it qualifies
Key Event Handling
PygStormGames provides a simple way to handle keyboard input:
# Wait for any key
key, modifiers = game.wait()
# Wait for specific keys
validKeys = [pyglet.window.key.SPACE, pyglet.window.key.RETURN]
key, modifiers = game.wait(validKeys)
# Wait with timeout (5 seconds)
key, modifiers = game.wait(timeout=5.0)
# Check for specific key
if key == pyglet.window.key.SPACE:
# Space was pressed
pass
Directory Structure
PygStormGames expects the following directory structure:
your_game/
â __init__.py
â main.py
â sounds/ # Sound effects and music
â â music_menu.ogg
â â game-intro.ogg
â â ...
â files/ # Game text files
â instructions.txt
â credits.txt
Required Sound Files
PygStormGames looks for these sound files, though they're optional:
game-intro.ogg
: Played when the game startsmusic_menu.ogg
: Played during menu navigationmenu-move.ogg
: Played when navigating menu itemsmenu-select.ogg
: Played when selecting a menu item
Advanced Usage Examples
Creating a Custom Menu
def settings_menu(self):
options = ["sound volume", "music volume", "speech rate", "back"]
while True:
selection = self.game.menu.show_menu(options, "Settings")
if selection == "sound volume":
self.adjust_sound_volume()
elif selection == "music volume":
self.adjust_music_volume()
elif selection == "speech rate":
self.adjust_speech_rate()
elif selection == "back":
return
Playing Positional Audio
# For 2D games (x-position only)
def update_enemy_sounds(self):
for enemy in self.enemies:
# Player at x=0, enemy at some x position
self.game.sound.play_positional(
"enemy-sound",
enemy.x, # Source position
self.player.x, # Listener position
mode='2d'
)
# For 3D games
def update_enemy_sounds_3d(self):
for enemy in self.enemies:
# 3D positions (x, y, z)
source_pos = (enemy.x, enemy.y, enemy.z)
listener_pos = (self.player.x, self.player.y, self.player.z)
self.game.sound.play_positional(
"enemy-sound",
source_pos,
listener_pos,
mode='3d'
)
Creating a Countdown Timer
def start_countdown(self):
for i in range(3, 0, -1):
self.game.speech.speak(str(i))
self.game.sound.play_sound(f"countdown-{i}")
# Wait 1 second
self.game.wait(timeout=1.0)
self.game.speech.speak("Go!")
self.game.sound.play_sound("start")
self.gameActive = True
Saving and Loading Game Progress
def save_game(self):
# Save player progress
self.game.config.set_value("save", "level", self.currentLevel)
self.game.config.set_value("save", "score", self.score)
self.game.config.set_value("save", "health", self.playerHealth)
self.game.speech.speak("Game saved.")
def load_game(self):
# Load player progress
self.currentLevel = self.game.config.get_int("save", "level", 1)
self.score = self.game.config.get_int("save", "score", 0)
self.playerHealth = self.game.config.get_int("save", "health", 100)
self.game.speech.speak(f"Game loaded. Level {self.currentLevel}")
Common Patterns
Game Loop with Pause Support
def update(self, dt):
# Skip if game is paused
if self.game._paused:
return
# Update game state
self.update_player(dt)
self.update_enemies(dt)
self.check_collisions()
self.update_score()
Handling Game Over
def game_over(self):
# Stop gameplay sounds
self.game.sound.stop_all_sounds()
# Play game over sound
self.game.sound.play_sound("game-over")
# Display final score
finalScore = self.game.scoreboard.get_score()
self.game.speech.speak(f"Game over! Your score: {finalScore}")
# Check for high score
if self.game.scoreboard.check_high_score():
self.game.scoreboard.add_high_score()
# Wait for key press
self.game.display.messagebox("Game over!")
# Return to main menu
self.game.menu.game_menu()
Creating a Timed Event System
class TimedEvent:
def __init__(self, time, callback):
self.triggerTime = time
self.callback = callback
self.triggered = False
class EventSystem:
def __init__(self, game):
self.game = game
self.events = []
self.gameTime = 0
def add_event(self, delay, callback):
triggerTime = self.gameTime + delay
self.events.append(TimedEvent(triggerTime, callback))
def update(self, dt):
self.gameTime += dt
for event in self.events:
if not event.triggered and self.gameTime >= event.triggerTime:
event.callback()
event.triggered = True
# Remove triggered events
self.events = [e for e in self.events if not e.triggered]
# Usage
events = EventSystem(game)
events.add_event(5.0, lambda: game.speech.speak("Warning! Enemy approaching!"))
Tips and Best Practices
-
Audio Cues: Always provide clear audio feedback for important actions and events.
-
Speech Rate: Keep speech concise and prioritize critical information.
-
Sound Balance: Maintain a good balance between speech, sound effects, and music.
-
Consistent Controls: Keep controls consistent and provide easy ways to learn them.
-
Performance: Be mindful of audio channel usage, especially with spatial audio.
-
Documentation: Include clear instructions for controls and gameplay.
-
File Structure: Follow the expected file structure for sounds and text files.
Troubleshooting
Common Issues
-
No speech output
- Ensure speechd or accessible_output2 is installed
-
Sound files not playing
- Verify file paths and formats (ogg or wav)
- Check if sound files exist in the correct directory
- Check volume settings in the game
-
Menus not responding
- Ensure pyglet event loop is running
- Check if key handlers are properly registered
Debugging Tips
- Use
print
statements to track game state - Enable more verbose logging in the speech module
- Test with minimal sound files to isolate issues
Complete Example Game
Here's a simple example of a complete game that demonstrates the key features of PygStormGames:
import pyglet
from pygstormgames import pygstormgames
import random
import math
class SimpleGame:
def __init__(self):
# Initialize game
self.game = pygstormgames("Sound Hunter")
# Game variables
self.playerX = 0
self.playerY = 0
self.targetX = 0
self.targetY = 0
self.score = 0
self.timeLeft = 60
self.gameActive = False
# Set up game loop
pyglet.clock.schedule_interval(self.update, 1/60.0)
def start_game(self):
# Reset game state
self.score = 0
self.timeLeft = 60
self.gameActive = True
self.game.scoreboard.currentScore = 0
# Place target
self.place_new_target()
# Start background music
self.game.sound.play_bgm("sounds/game_music.ogg")
# Announce game start
self.game.speech.speak("Game started. Use arrow keys to find the target.")
def place_new_target(self):
# Place target randomly within range
distance = random.uniform(5, 15)
angle = random.uniform(0, 2 * math.pi)
self.targetX = math.cos(angle) * distance
self.targetY = math.sin(angle) * distance
# Play new target sound
self.game.sound.play_sound("new-target")
def update(self, dt):
if not self.gameActive or self.game._paused:
return
# Update timer
self.timeLeft -= dt
if self.timeLeft <= 0:
self.game_over()
return
# Play positional target sound every second
if int(self.timeLeft) != int(self.timeLeft - dt):
self.game.sound.play_positional(
"target-ping",
self.targetX,
self.playerX,
mode='2d'
)
# Calculate distance to target
distance = math.sqrt((self.playerX - self.targetX)**2 +
(self.playerY - self.targetY)**2)
# Check for target collection
if distance < 1.0:
self.collect_target()
def on_key_press(self, symbol, modifiers):
if not self.gameActive:
return
# Move player
if symbol == pyglet.window.key.UP:
self.playerY += 1
elif symbol == pyglet.window.key.DOWN:
self.playerY -= 1
elif symbol == pyglet.window.key.LEFT:
self.playerX -= 1
elif symbol == pyglet.window.key.RIGHT:
self.playerX += 1
# Announce position occasionally
if random.random() < 0.1:
self.announce_position()
def announce_position(self):
# Calculate distance and direction to target
dx = self.targetX - self.playerX
dy = self.targetY - self.playerY
distance = math.sqrt(dx*dx + dy*dy)
# Create directional hint
if abs(dx) > abs(dy):
direction = "east" if dx > 0 else "west"
else:
direction = "north" if dy > 0 else "south"
# Announce distance category
if distance < 2.0:
proximity = "very close"
elif distance < 5.0:
proximity = "close"
elif distance < 10.0:
proximity = "medium distance"
else:
proximity = "far away"
self.game.speech.speak(f"Target is {proximity} to the {direction}")
def collect_target(self):
# Play collection sound
self.game.sound.play_sound("target-collect")
# Update score
self.score += 1
self.game.scoreboard.increase_score(100)
# Announce collection
self.game.speech.speak(f"Target collected! Score: {self.score}")
# Place new target
self.place_new_target()
# Add time bonus
self.timeLeft += 5
def game_over(self):
self.gameActive = False
self.game.sound.stop_all_sounds()
self.game.sound.play_sound("game-over")
finalScore = self.game.scoreboard.get_score()
self.game.speech.speak(f"Game over! Final score: {finalScore}")
# Check for high score
self.game.scoreboard.add_high_score()
# Return to menu after delay
self.game.wait(timeout=3.0)
self.start()
def start(self):
# Register key handlers
self.game.display.window.push_handlers(self.on_key_press)
# Show main menu
while True:
selection = self.game.menu.game_menu()
if selection == "play":
self.start_game()
break
elif selection == "exit":
self.game.exit_game()
break
# Start game loop
self.game.run()
if __name__ == "__main__":
game = SimpleGame()
game.start()
This example demonstrates using menus, speech, positional audio, and scoreboards in a simple audio-based game where the player needs to find targets using audio cues.