Updated the readme.
This commit is contained in:
parent
27765e62bc
commit
fedb09be94
266
README.md
266
README.md
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
A Python library to make creating audio games easier.
|
A Python library to make creating audio games easier.
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
`libstormgames` provides a comprehensive set of tools for developing accessible games with audio-first design. It handles common game development tasks including:
|
`libstormgames` provides a comprehensive set of tools for developing accessible games with audio-first design. It handles common game development tasks including:
|
||||||
@ -14,6 +15,7 @@ A Python library to make creating audio games easier.
|
|||||||
- Menu systems and text display
|
- Menu systems and text display
|
||||||
- GUI initialization
|
- GUI initialization
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
@ -31,18 +33,23 @@ A Python library to make creating audio games easier.
|
|||||||
- Linux/Unix: `python-speechd` or `accessible-output2>=0.14`
|
- Linux/Unix: `python-speechd` or `accessible-output2>=0.14`
|
||||||
- Windows/macOS: `accessible-output2>=0.14`
|
- Windows/macOS: `accessible-output2>=0.14`
|
||||||
|
|
||||||
|
|
||||||
### Install from source
|
### Install from source
|
||||||
|
|
||||||
|
If you are on Linux, check your package manager first to see if the packages in requirements.txt are available.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.stormux.org/storm/libstormgames
|
git clone https://git.stormux.org/storm/libstormgames
|
||||||
cd libstormgames
|
cd libstormgames
|
||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
You can use libstormgames in two ways: the traditional function-based approach or the new class-based approach.
|
You can use libstormgames in two ways: the traditional function-based approach or the new class-based approach.
|
||||||
|
|
||||||
|
|
||||||
### Traditional Function-Based Approach
|
### Traditional Function-Based Approach
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -69,8 +76,8 @@ def main():
|
|||||||
|
|
||||||
# Define menu options
|
# Define menu options
|
||||||
while True:
|
while True:
|
||||||
choice = sg.game_menu(sounds, "play_game", "instructions", "credits", "donate", "exit_game")
|
choice = sg.game_menu(sounds)
|
||||||
if choice == "play_game":
|
if choice == "play":
|
||||||
play_game()
|
play_game()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -87,7 +94,7 @@ def main():
|
|||||||
# Create and initialize a game
|
# Create and initialize a game
|
||||||
game = sg.Game("My First Audio Game").initialize()
|
game = sg.Game("My First Audio Game").initialize()
|
||||||
|
|
||||||
# Welcome message (using fluent API)
|
# Welcome message
|
||||||
game.speak("Welcome to My First Audio Game!")
|
game.speak("Welcome to My First Audio Game!")
|
||||||
|
|
||||||
# Main game loop
|
# Main game loop
|
||||||
@ -100,14 +107,15 @@ def main():
|
|||||||
|
|
||||||
# Define menu options
|
# Define menu options
|
||||||
while True:
|
while True:
|
||||||
choice = sg.game_menu(game.sound.get_sounds(), "play_game", "instructions", "credits", "donate", "exit_game")
|
choice = sg.game_menu(game.sound.get_sounds())
|
||||||
if choice == "play_game":
|
if choice == "play":
|
||||||
play_game()
|
play_game()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Library Structure
|
## Library Structure
|
||||||
|
|
||||||
The library is organized into modules, each with a specific focus:
|
The library is organized into modules, each with a specific focus:
|
||||||
@ -122,6 +130,7 @@ The library is organized into modules, each with a specific focus:
|
|||||||
- **menu**: Menu systems
|
- **menu**: Menu systems
|
||||||
- **utils**: Utility functions and Game class
|
- **utils**: Utility functions and Game class
|
||||||
|
|
||||||
|
|
||||||
## Core Classes
|
## Core Classes
|
||||||
|
|
||||||
### Game
|
### Game
|
||||||
@ -133,19 +142,27 @@ The Game class provides a central way to manage all game systems:
|
|||||||
game = sg.Game("My Game").initialize()
|
game = sg.Game("My Game").initialize()
|
||||||
|
|
||||||
# Use fluent API for chaining commands
|
# Use fluent API for chaining commands
|
||||||
game.speak("Hello").play_bgm("music/theme.ogg")
|
game.speak("Hello").play_bgm("music/theme")
|
||||||
|
|
||||||
# Access components directly
|
# Access components directly
|
||||||
game.scoreboard.increase_score(10)
|
game.scoreboard.increase_score(10)
|
||||||
game.sound.play_random("explosion")
|
game.sound.play_random("explosion")
|
||||||
|
|
||||||
# Display text
|
# Display text
|
||||||
|
|
||||||
game.display_text(["Line 1", "Line 2"])
|
game.display_text(["Line 1", "Line 2"])
|
||||||
|
|
||||||
# Clean exit
|
# Clean exit
|
||||||
|
|
||||||
game.exit()
|
game.exit()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The initialization process sets up proper configuration paths:
|
||||||
|
- Creates game-specific directories if they don't exist
|
||||||
|
- Ensures all services have access to correct paths
|
||||||
|
- Connects all components for seamless operation
|
||||||
|
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
|
|
||||||
The library includes several service classes that replace global variables:
|
The library includes several service classes that replace global variables:
|
||||||
@ -181,6 +198,14 @@ config.read_local_config()
|
|||||||
difficulty = config.local_config.get("settings", "difficulty")
|
difficulty = config.local_config.get("settings", "difficulty")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Configuration files are automatically stored in standard system locations:
|
||||||
|
- Linux/Unix: `~/.config/storm-games/`
|
||||||
|
- Windows: `%APPDATA%\storm-games\`
|
||||||
|
- macOS: `~/Library/Application Support/storm-games/`
|
||||||
|
|
||||||
|
Each game has its own subdirectory based on the game name (lowercase with hyphens), for example, Wicked Quest becomes wicked-quest.
|
||||||
|
|
||||||
|
|
||||||
### Sound
|
### Sound
|
||||||
|
|
||||||
Manages sound loading and playback with positional audio support.
|
Manages sound loading and playback with positional audio support.
|
||||||
@ -213,6 +238,7 @@ sg.adjust_bgm_volume(-0.1) # Decrease background music volume
|
|||||||
sg.adjust_sfx_volume(0.1) # Increase sound effects volume
|
sg.adjust_sfx_volume(0.1) # Increase sound effects volume
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Speech
|
### Speech
|
||||||
|
|
||||||
Provides text-to-speech functionality using available speech providers.
|
Provides text-to-speech functionality using available speech providers.
|
||||||
@ -231,9 +257,10 @@ sg.speak("Hello, world!")
|
|||||||
sg.speak("This won't interrupt", interrupt=False)
|
sg.speak("This won't interrupt", interrupt=False)
|
||||||
|
|
||||||
# Clean up when done
|
# Clean up when done
|
||||||
speech.close()
|
exit_game() or as the class, game.exit_game()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Scoreboard
|
### Scoreboard
|
||||||
|
|
||||||
Tracks scores and manages high score tables.
|
Tracks scores and manages high score tables.
|
||||||
@ -261,6 +288,15 @@ for entry in high_scores:
|
|||||||
print(f"{entry['name']}: {entry['score']}")
|
print(f"{entry['name']}: {entry['score']}")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The Scoreboard system automatically manages high score data in the game's configuration directory:
|
||||||
|
- Linux/Unix: `~/.config/storm-games/game-name/config.ini`
|
||||||
|
- Windows: `%APPDATA%\storm-games\game-name\config.ini`
|
||||||
|
- macOS: `~/Library/Application Support/storm-games/game-name/config.ini`
|
||||||
|
|
||||||
|
Where `game-name` is the lowercase, hyphenated version of your game title. For example,
|
||||||
|
"My Awesome Game" would use the directory `my-awesome-game`.
|
||||||
|
|
||||||
|
|
||||||
## Key Functions
|
## Key Functions
|
||||||
|
|
||||||
### Game Initialization and Control
|
### Game Initialization and Control
|
||||||
@ -286,22 +322,167 @@ sg.exit_game()
|
|||||||
### Menu System
|
### Menu System
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Display a menu with options (functions should exist with these names)
|
# Basic menu example (returns option string)
|
||||||
choice = sg.game_menu(sounds, "play_game", "high_scores", "instructions", "credits", "exit_game")
|
choice = sg.game_menu(sounds)
|
||||||
|
if choice == "play":
|
||||||
|
# Start the game
|
||||||
|
play_game()
|
||||||
|
elif choice == "instructions":
|
||||||
|
sg.instructions()
|
||||||
|
# etc...
|
||||||
|
|
||||||
# Display built-in instructions
|
# Menu with play callback (callback is executed directly)
|
||||||
sg.instructions()
|
def play_callback():
|
||||||
|
sg.speak("Game started with callback!")
|
||||||
|
# Game code goes here
|
||||||
|
|
||||||
|
sg.game_menu(sounds, play_callback)
|
||||||
|
|
||||||
# Display built-in credits
|
# Menu with custom options
|
||||||
sg.credits()
|
choice = sg.game_menu(sounds, None, "practice_mode")
|
||||||
|
if choice == "practice_mode":
|
||||||
# Open donation page
|
# Handle practice mode
|
||||||
sg.donate()
|
practice_game()
|
||||||
|
elif choice == "difficulty":
|
||||||
# Interactive menu to learn available sounds
|
# Handle difficulty settings
|
||||||
sg.learn_sounds(sounds)
|
set_difficulty()
|
||||||
|
elif choice == "options":
|
||||||
|
# Handle options screen
|
||||||
|
show_options()
|
||||||
|
# etc...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Complete Menu Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import libstormgames as sg
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Initialize the game
|
||||||
|
sounds = sg.initialize_gui("Menu Example Game")
|
||||||
|
|
||||||
|
# Play menu music
|
||||||
|
sg.play_bgm("sounds/music_menu.ogg")
|
||||||
|
|
||||||
|
# Define play function with callback
|
||||||
|
def play_game():
|
||||||
|
sg.speak("Starting game!")
|
||||||
|
# Game code here
|
||||||
|
sg.speak("Game over!")
|
||||||
|
return "menu" # Return to menu after game ends
|
||||||
|
|
||||||
|
# Define custom menu option handlers
|
||||||
|
def practice_mode():
|
||||||
|
sg.speak("Starting practice mode!")
|
||||||
|
# Practice mode code here
|
||||||
|
return "menu"
|
||||||
|
|
||||||
|
def difficulty_settings():
|
||||||
|
# Show difficulty options
|
||||||
|
options = ["easy", "normal", "hard", "back"]
|
||||||
|
current = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
sg.speak(options[current])
|
||||||
|
|
||||||
|
event = pygame.event.wait()
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_UP and current > 0:
|
||||||
|
current -= 1
|
||||||
|
elif event.key == pygame.K_DOWN and current < len(options) - 1:
|
||||||
|
current += 1
|
||||||
|
elif event.key == pygame.K_RETURN:
|
||||||
|
if options[current] == "back":
|
||||||
|
return "menu"
|
||||||
|
sg.speak(f"Difficulty set to {options[current]}")
|
||||||
|
return "menu"
|
||||||
|
elif event.key == pygame.K_ESCAPE:
|
||||||
|
return "menu"
|
||||||
|
|
||||||
|
# Main menu loop
|
||||||
|
while True:
|
||||||
|
# Display menu with play callback and custom options
|
||||||
|
choice = sg.game_menu(sounds, play_game, "practice_mode", "difficulty")
|
||||||
|
|
||||||
|
# Handle menu choices
|
||||||
|
if choice == "practice_mode":
|
||||||
|
practice_mode()
|
||||||
|
elif choice == "difficulty":
|
||||||
|
difficulty_settings()
|
||||||
|
# Other options are handled automatically by game_menu
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Class-Based Menu Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import libstormgames as sg
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
class MyGame:
|
||||||
|
def __init__(self):
|
||||||
|
# Create and initialize game
|
||||||
|
self.game = sg.Game("Class-Based Menu Example").initialize()
|
||||||
|
self.sounds = self.game.sound.get_sounds()
|
||||||
|
|
||||||
|
def play_game(self):
|
||||||
|
self.game.speak("Starting game!")
|
||||||
|
# Game code here
|
||||||
|
self.game.speak("Game over!")
|
||||||
|
return "menu"
|
||||||
|
|
||||||
|
def practice_mode(self):
|
||||||
|
self.game.speak("Starting practice mode!")
|
||||||
|
# Practice mode code here
|
||||||
|
return "menu"
|
||||||
|
|
||||||
|
def settings(self):
|
||||||
|
# A nested menu example
|
||||||
|
submenu_options = ["graphics", "audio", "controls", "back"]
|
||||||
|
current = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.game.speak(submenu_options[current])
|
||||||
|
|
||||||
|
event = pygame.event.wait()
|
||||||
|
if event.type == pygame.KEYDOWN:
|
||||||
|
if event.key == pygame.K_UP and current > 0:
|
||||||
|
current -= 1
|
||||||
|
elif event.key == pygame.K_DOWN and current < len(submenu_options) - 1:
|
||||||
|
current += 1
|
||||||
|
elif event.key == pygame.K_RETURN:
|
||||||
|
if submenu_options[current] == "back":
|
||||||
|
return "menu"
|
||||||
|
self.game.speak(f"Selected {submenu_options[current]} settings")
|
||||||
|
return "menu"
|
||||||
|
elif event.key == pygame.K_ESCAPE:
|
||||||
|
return "menu"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Main menu loop
|
||||||
|
while True:
|
||||||
|
# Use playCallback parameter to directly call play_game when "play" is selected
|
||||||
|
choice = sg.game_menu(self.sounds, None, "practice_mode",
|
||||||
|
"settings")
|
||||||
|
|
||||||
|
# Handle other menu options
|
||||||
|
if choice == "practice_mode":
|
||||||
|
self.practice_mode()
|
||||||
|
elif choice == "settings":
|
||||||
|
self.settings()
|
||||||
|
|
||||||
|
# Run the game
|
||||||
|
if __name__ == "__main__":
|
||||||
|
game = MyGame()
|
||||||
|
game.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Text Display
|
### Text Display
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -319,6 +500,7 @@ sg.messagebox("Game Over! You scored 100 points.")
|
|||||||
name = sg.get_input("Enter your name:", "Player")
|
name = sg.get_input("Enter your name:", "Player")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Sound Effects
|
### Sound Effects
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -358,6 +540,7 @@ mid_value = sg.lerp(start=0, end=10, factor=0.5) # Returns 5.0
|
|||||||
smooth_value = sg.smooth_step(edge0=0, edge1=10, x=5) # Smooth transition
|
smooth_value = sg.smooth_step(edge0=0, edge1=10, x=5) # Smooth transition
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Advanced Examples
|
## Advanced Examples
|
||||||
|
|
||||||
### Using the Game Class (Modern Approach)
|
### Using the Game Class (Modern Approach)
|
||||||
@ -409,6 +592,7 @@ if __name__ == "__main__":
|
|||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Complex Sound Environment
|
### Complex Sound Environment
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -459,6 +643,7 @@ def create_sound_environment(player_x, player_y):
|
|||||||
sg.exit_game()
|
sg.exit_game()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Complete Game Structure with Class-Based Architecture
|
### Complete Game Structure with Class-Based Architecture
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -549,12 +734,10 @@ class MyGame:
|
|||||||
# Main menu loop
|
# Main menu loop
|
||||||
while True:
|
while True:
|
||||||
sounds = self.game.sound.get_sounds()
|
sounds = self.game.sound.get_sounds()
|
||||||
choice = sg.game_menu(sounds, "play_game", "settings",
|
choice = sg.game_menu(sounds, self.play_game, "settings",
|
||||||
"instructions", "credits", "donate", "exit_game")
|
"instructions", "credits", "donate", "exit_game")
|
||||||
|
|
||||||
if choice == "play_game":
|
if choice == "settings":
|
||||||
self.play_game()
|
|
||||||
elif choice == "settings":
|
|
||||||
self.settings()
|
self.settings()
|
||||||
elif choice == "instructions":
|
elif choice == "instructions":
|
||||||
sg.instructions()
|
sg.instructions()
|
||||||
@ -571,6 +754,7 @@ if __name__ == "__main__":
|
|||||||
game.run()
|
game.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Modern vs Traditional Approach**:
|
1. **Modern vs Traditional Approach**:
|
||||||
@ -586,18 +770,16 @@ if __name__ == "__main__":
|
|||||||
- Implement the Alt+key volume controls in your game
|
- Implement the Alt+key volume controls in your game
|
||||||
- Use volume services for better control
|
- Use volume services for better control
|
||||||
|
|
||||||
4. **Speech feedback**:
|
4. **Configuration**:
|
||||||
- Provide clear speech feedback for all actions
|
|
||||||
- Use the `interrupt` parameter to control speech priority
|
|
||||||
|
|
||||||
5. **Sound positioning**:
|
|
||||||
- Use positional audio to create an immersive environment
|
|
||||||
- Update object positions as the game state changes
|
|
||||||
|
|
||||||
6. **Configuration**:
|
|
||||||
- Save user preferences using the Config class
|
- Save user preferences using the Config class
|
||||||
- Load settings at startup
|
- Load settings at startup
|
||||||
|
|
||||||
|
5. **Path initialization**:
|
||||||
|
- Always initialize the framework with a proper game title
|
||||||
|
- Game title is used to determine configuration directory paths
|
||||||
|
- Services are interconnected, so proper initialization ensures correct operation
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### No Sound
|
### No Sound
|
||||||
@ -618,6 +800,26 @@ if __name__ == "__main__":
|
|||||||
- Ensure pygame is properly handling events
|
- Ensure pygame is properly handling events
|
||||||
- Check event loop for proper event handling
|
- Check event loop for proper event handling
|
||||||
|
|
||||||
|
### Scoreboard/Configuration Issues
|
||||||
|
|
||||||
|
- Check if path services are properly initialized with a game title
|
||||||
|
- Verify write permissions in the configuration directory
|
||||||
|
- For debugging, use the following code to view path configuration:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from libstormgames.services import PathService, ConfigService
|
||||||
|
|
||||||
|
# Print path information
|
||||||
|
path_service = PathService.get_instance()
|
||||||
|
print(f"Game Name: {path_service.gameName}")
|
||||||
|
print(f"Game Path: {path_service.gamePath}")
|
||||||
|
|
||||||
|
# Check config service connection
|
||||||
|
config_service = ConfigService.get_instance()
|
||||||
|
print(f"Config connected to path: {hasattr(config_service, 'pathService')}")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user