From 23b3e5792e128db5e6098537b7d3ba11708d3e1d Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 5 Mar 2025 14:21:48 -0500 Subject: [PATCH] Initial readme file added. Probably needs more work. --- README.md | 643 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 643 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..5211656 --- /dev/null +++ b/README.md @@ -0,0 +1,643 @@ +# 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.