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 loop
  • wait(validKeys=None, timeout=None): Wait for key press(es) with optional timeout
  • wait_for_completion(condition, validKeys=None, timeout=None): Wait for a condition to be met, keys to be pressed, or timeout
  • pause_game(): Pause all game systems
  • exit_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 value
  • set_value(section, key, value, globalConfig=False): Set value
  • get_int(section, key, default=0, globalConfig=False): Get integer value
  • get_float(section, key, default=0.0, globalConfig=False): Get float value
  • get_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 output
  • instructions(speech): Display game instructions from file
  • credits(speech): Display game credits from file
  • messagebox(text): Display a simple message box with text
  • get_input(prompt="Enter text:", defaultText=""): Display a dialog box for text input
  • donate(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 option
  • game_menu(): Show main game menu
  • learn_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 effect
  • play_bgm(music_file): Play background music
  • pause_bgm(): Pause background music
  • resume_bgm(): Resume background music
  • play_random(base_name, pause=False, interrupt=False): Play random variation of a sound
  • play_positional(soundName, source_pos, listener_pos, mode='2d'): Play sound with positional audio
  • update_positional(player, source_pos, listener_pos, mode='2d'): Update position of a playing sound
  • cut_scene(soundName): Play a sound as a cut scene
  • adjust_master_volume(change): Adjust master volume
  • adjust_bgm_volume(change): Adjust background music volume
  • adjust_sfx_volume(change): Adjust sound effects volume
  • get_volumes(): Get current volume levels
  • pause(): Pause all audio
  • resume(): Resume all audio
  • stop_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 score
  • get_high_scores(): Get list of high scores
  • decrease_score(points=1): Decrease the current score
  • increase_score(points=1): Increase the current score
  • check_high_score(): Check if current score qualifies as a high score
  • add_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 starts
  • music_menu.ogg: Played during menu navigation
  • menu-move.ogg: Played when navigating menu items
  • menu-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

  1. Audio Cues: Always provide clear audio feedback for important actions and events.

  2. Speech Rate: Keep speech concise and prioritize critical information.

  3. Sound Balance: Maintain a good balance between speech, sound effects, and music.

  4. Consistent Controls: Keep controls consistent and provide easy ways to learn them.

  5. Performance: Be mindful of audio channel usage, especially with spatial audio.

  6. Documentation: Include clear instructions for controls and gameplay.

  7. File Structure: Follow the expected file structure for sounds and text files.

Troubleshooting

Common Issues

  1. No speech output

    • Ensure speechd or accessible_output2 is installed
  2. 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
  3. 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.

Description
Conversion of libstormgames to pyglet.
Readme GPL-3.0 128 KiB
Languages
Python 100%