Initial readme file added. Probably needs more work.
This commit is contained in:
parent
8e274731c5
commit
23b3e5792e
643
README.md
Normal file
643
README.md
Normal file
@ -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.
|
Loading…
x
Reference in New Issue
Block a user