# 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 ```python 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. ```python 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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. ```python # 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: ```python # 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 ```python 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 ```python # 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 ```python 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 ```python 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 ```python 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 ```python 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 ```python 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: ```python 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.