Hopefully improved the sound handling for looping and for Y positioning. Should make it easier to have things falling or approaching from the Y axis.
This commit is contained in:
parent
3b01662d98
commit
d513927d52
552
README.md
552
README.md
@ -7,7 +7,7 @@ A Python library to make creating audio games easier.
|
|||||||
|
|
||||||
`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:
|
||||||
|
|
||||||
- Sound and music playback with positional audio
|
- Sound and music playback with positional audio and 3D spatialization
|
||||||
- Text-to-speech integration
|
- Text-to-speech integration
|
||||||
- Configuration management
|
- Configuration management
|
||||||
- Score tracking and high score tables
|
- Score tracking and high score tables
|
||||||
@ -28,6 +28,7 @@ A Python library to make creating audio games easier.
|
|||||||
- numpy>=1.19.0
|
- numpy>=1.19.0
|
||||||
- wxpython
|
- wxpython
|
||||||
|
|
||||||
|
|
||||||
#### Speech Providers (one required)
|
#### Speech Providers (one required)
|
||||||
|
|
||||||
- Linux/Unix: `python-speechd` or `accessible-output2>=0.14`
|
- Linux/Unix: `python-speechd` or `accessible-output2>=0.14`
|
||||||
@ -76,14 +77,16 @@ def main():
|
|||||||
|
|
||||||
# Define menu options
|
# Define menu options
|
||||||
while True:
|
while True:
|
||||||
choice = sg.game_menu(sounds)
|
choice = sg.game_menu(sounds, play_game)
|
||||||
if choice == "play":
|
|
||||||
play_game()
|
# The game_menu function already includes standard options like
|
||||||
|
# high scores, instructions, credits, and exit
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Modern Class-Based Approach
|
### Modern Class-Based Approach
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@ -107,9 +110,10 @@ def main():
|
|||||||
|
|
||||||
# Define menu options
|
# Define menu options
|
||||||
while True:
|
while True:
|
||||||
choice = sg.game_menu(game.sound.get_sounds())
|
choice = sg.game_menu(game.sound.sounds, play_game)
|
||||||
if choice == "play":
|
|
||||||
play_game()
|
# The game_menu function already includes standard options like
|
||||||
|
# high scores, instructions, credits, and exit
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@ -122,7 +126,7 @@ The library is organized into modules, each with a specific focus:
|
|||||||
|
|
||||||
- **config**: Configuration management
|
- **config**: Configuration management
|
||||||
- **services**: Core services that replace global variables
|
- **services**: Core services that replace global variables
|
||||||
- **sound**: Sound and music playback
|
- **sound**: Sound and music playback with 3D positional audio
|
||||||
- **speech**: Text-to-speech functionality
|
- **speech**: Text-to-speech functionality
|
||||||
- **scoreboard**: High score tracking
|
- **scoreboard**: High score tracking
|
||||||
- **input**: Input handling and dialogs
|
- **input**: Input handling and dialogs
|
||||||
@ -149,11 +153,9 @@ 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()
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -205,444 +207,85 @@ Configuration files are automatically stored in standard system locations:
|
|||||||
|
|
||||||
Each game has its own subdirectory based on the game name (lowercase with hyphens), for example, Wicked Quest becomes wicked-quest.
|
Each game has its own subdirectory based on the game name (lowercase with hyphens), for example, Wicked Quest becomes wicked-quest.
|
||||||
|
|
||||||
|
|
||||||
### Sound
|
|
||||||
|
|
||||||
Manages sound loading and playback with positional audio support.
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Create a sound manager
|
# Create a sound manager
|
||||||
sound_system = sg.Sound("sounds/")
|
sound_system = sg.Sound("sounds/")
|
||||||
|
|
||||||
# Get the dictionary of loaded sounds
|
# Get the dictionary of loaded sounds
|
||||||
sounds = sound_system.get_sounds()
|
sounds = sound_system.sounds
|
||||||
|
|
||||||
# Play a sound
|
# Play a sound
|
||||||
sg.play_sound(sounds["explosion"])
|
channel = sound_system.play_sound("explosion")
|
||||||
|
|
||||||
# Play a sound with positional audio (player at x=5, object at x=10)
|
# Play a looping sound
|
||||||
channel = sg.obj_play(sounds, "footsteps", 5, 10)
|
channel = sound_system.play_sound("ambient", loop=True)
|
||||||
|
|
||||||
|
# Play a sound with 3D positional audio (horizontal and vertical positioning)
|
||||||
|
channel = sound_system.obj_play("footsteps",
|
||||||
|
playerPos=5, objPos=10,
|
||||||
|
playerY=0, objY=5,
|
||||||
|
loop=True)
|
||||||
|
|
||||||
# Update sound position as player or object moves
|
# Update sound position as player or object moves
|
||||||
channel = sg.obj_update(channel, 6, 10) # Player moved to x=6
|
channel = sound_system.obj_update(channel, 6, 10, 0, 5) # Player moved to x=6
|
||||||
|
|
||||||
# Stop the sound
|
# Stop the sound
|
||||||
|
channel = sound_system.obj_stop(channel)
|
||||||
|
|
||||||
|
# Update all active looping sounds with a single call
|
||||||
|
updated_count = sound_system.update_all_active_loops(player_x=6, player_y=0)
|
||||||
|
|
||||||
|
# Global function equivalents
|
||||||
|
channel = sg.obj_play(sounds, "footsteps", 5, 10, 0, 5, loop=True)
|
||||||
|
channel = sg.obj_update(channel, 6, 10, 0, 5)
|
||||||
channel = sg.obj_stop(channel)
|
channel = sg.obj_stop(channel)
|
||||||
|
|
||||||
# Play background music
|
# Play background music
|
||||||
sg.play_bgm("sounds/background.ogg")
|
sg.play_bgm("sounds/background.ogg", loop=True)
|
||||||
|
|
||||||
# Adjust volume
|
# Adjust volume
|
||||||
sg.adjust_master_volume(0.1) # Increase master volume
|
sg.adjust_master_volume(0.1) # Increase master volume
|
||||||
sg.adjust_bgm_volume(-0.1) # Decrease background music volume
|
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
|
||||||
```
|
|
||||||
|
|
||||||
|
# Create a sound manager
|
||||||
|
sound_system = sg.Sound("sounds/")
|
||||||
|
|
||||||
### Speech
|
# Get the dictionary of loaded sounds
|
||||||
|
sounds = sound_system.sounds
|
||||||
|
|
||||||
Provides text-to-speech functionality using available speech providers.
|
# Play a sound
|
||||||
|
channel = sound_system.play_sound("explosion")
|
||||||
|
|
||||||
```python
|
# Play a looping sound
|
||||||
# Create a speech manager (usually you'll use the global instance)
|
channel = sound_system.play_sound("ambient", loop=True)
|
||||||
speech = sg.Speech()
|
|
||||||
|
|
||||||
# Speak text
|
# Play a sound with 3D positional audio (horizontal and vertical positioning)
|
||||||
speech.speak("Hello, world!")
|
channel = sound_system.obj_play("footsteps",
|
||||||
|
playerPos=5, objPos=10,
|
||||||
|
playerY=0, objY=5,
|
||||||
|
loop=True)
|
||||||
|
|
||||||
# Or use the global function for convenience
|
# Update sound position as player or object moves
|
||||||
sg.speak("Hello, world!")
|
channel = sound_system.obj_update(channel, 6, 10, 0, 5) # Player moved to x=6
|
||||||
|
|
||||||
# Speak without interrupting previous speech
|
# Stop the sound
|
||||||
sg.speak("This won't interrupt", interrupt=False)
|
channel = sound_system.obj_stop(channel)
|
||||||
|
|
||||||
# Clean up when done
|
# Update all active looping sounds with a single call
|
||||||
exit_game() or as the class, game.exit_game()
|
updated_count = sound_system.update_all_active_loops(player_x=6, player_y=0)
|
||||||
```
|
|
||||||
|
|
||||||
|
# Global function equivalents
|
||||||
|
channel = sg.obj_play(sounds, "footsteps", 5, 10, 0, 5, loop=True)
|
||||||
|
channel = sg.obj_update(channel, 6, 10, 0, 5)
|
||||||
|
channel = sg.obj_stop(channel)
|
||||||
|
|
||||||
### Scoreboard
|
# Play background music (now with loop parameter)
|
||||||
|
sg.play_bgm("sounds/background.ogg", loop=True)
|
||||||
Tracks scores and manages high score tables.
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Create a scoreboard
|
|
||||||
scoreboard = sg.Scoreboard()
|
|
||||||
|
|
||||||
# Manipulate score
|
|
||||||
scoreboard.increase_score(10)
|
|
||||||
scoreboard.decrease_score(5)
|
|
||||||
current_score = scoreboard.get_score()
|
|
||||||
|
|
||||||
# Check for high score
|
|
||||||
position = scoreboard.check_high_score()
|
|
||||||
if position:
|
|
||||||
print(f"You're in position {position}!")
|
|
||||||
|
|
||||||
# Add high score (prompts for player name)
|
|
||||||
scoreboard.add_high_score()
|
|
||||||
|
|
||||||
# Get all high scores
|
|
||||||
high_scores = scoreboard.get_high_scores()
|
|
||||||
for entry in high_scores:
|
|
||||||
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
|
|
||||||
|
|
||||||
### Game Initialization and Control
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Initialize game systems (traditional approach)
|
|
||||||
sounds = sg.initialize_gui("Game Title")
|
|
||||||
|
|
||||||
# Or use the Game class (modern approach)
|
|
||||||
game = sg.Game("Game Title").initialize()
|
|
||||||
|
|
||||||
# Pause the game (freezes until user presses backspace)
|
|
||||||
sg.pause_game()
|
|
||||||
|
|
||||||
# Check if user wants to exit
|
|
||||||
if sg.check_for_exit():
|
|
||||||
sg.exit_game()
|
|
||||||
|
|
||||||
# Exit the game properly
|
|
||||||
sg.exit_game()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Menu System
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Basic menu example (returns option string)
|
|
||||||
choice = sg.game_menu(sounds)
|
|
||||||
if choice == "play":
|
|
||||||
# Start the game
|
|
||||||
play_game()
|
|
||||||
elif choice == "instructions":
|
|
||||||
sg.instructions()
|
|
||||||
# etc...
|
|
||||||
|
|
||||||
# Menu with play callback (callback is executed directly)
|
|
||||||
def play_callback():
|
|
||||||
sg.speak("Game started with callback!")
|
|
||||||
# Game code goes here
|
|
||||||
|
|
||||||
sg.game_menu(sounds, play_callback)
|
|
||||||
|
|
||||||
# Menu with custom options
|
|
||||||
choice = sg.game_menu(sounds, None, "practice_mode")
|
|
||||||
if choice == "practice_mode":
|
|
||||||
# Handle practice mode
|
|
||||||
practice_game()
|
|
||||||
elif choice == "difficulty":
|
|
||||||
# Handle difficulty settings
|
|
||||||
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
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Display text with navigation
|
|
||||||
sg.display_text([
|
|
||||||
"Line 1 of instructions",
|
|
||||||
"Line 2 of instructions",
|
|
||||||
"Line 3 of instructions"
|
|
||||||
])
|
|
||||||
|
|
||||||
# Display a simple message box
|
|
||||||
sg.messagebox("Game Over! You scored 100 points.")
|
|
||||||
|
|
||||||
# Get text input from user
|
|
||||||
name = sg.get_input("Enter your name:", "Player")
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Sound Effects
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Play a random variation of a sound
|
|
||||||
sg.play_random(sounds, "explosion")
|
|
||||||
|
|
||||||
# Play positional sound with distance-based volume
|
|
||||||
sg.play_random_positional(sounds, "footsteps", player_x=5, object_x=10)
|
|
||||||
|
|
||||||
# Play directional sound (simplified left/right positioning)
|
|
||||||
sg.play_directional_sound(sounds, "voice", player_x=5, object_x=10)
|
|
||||||
|
|
||||||
# Play a sound as a cutscene (interrupts other sounds, waits until complete)
|
|
||||||
sg.cut_scene(sounds, "intro_speech")
|
|
||||||
|
|
||||||
# Play or update a falling sound
|
|
||||||
channel = sg.play_random_falling(sounds, "rock", player_x=5, object_x=8, start_y=10, currentY=5)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Utility Functions
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Check for game updates
|
|
||||||
update_info = sg.check_for_updates("1.0.0", "My Game", "https://example.com/version.json")
|
|
||||||
|
|
||||||
# Check compatibility with library version
|
|
||||||
is_compatible = sg.check_compatibility("1.0.0", "1.2.3")
|
|
||||||
|
|
||||||
# Sanitize a filename for any OS
|
|
||||||
safe_name = sg.sanitize_filename("User's File.txt")
|
|
||||||
|
|
||||||
# Calculate distance between points
|
|
||||||
distance = sg.distance_2d(x1=5, y1=10, x2=8, y2=15)
|
|
||||||
|
|
||||||
# Interpolation functions
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Advanced Examples
|
|
||||||
|
|
||||||
### Using the Game Class (Modern Approach)
|
|
||||||
|
|
||||||
```python
|
|
||||||
import libstormgames as sg
|
|
||||||
import pygame
|
|
||||||
import random
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Create and initialize the game
|
|
||||||
game = sg.Game("My Advanced Game").initialize()
|
|
||||||
|
|
||||||
# Set up game environment
|
|
||||||
game.play_bgm("sounds/background.ogg")
|
|
||||||
|
|
||||||
# Main game loop
|
|
||||||
running = True
|
|
||||||
player_x = 5
|
|
||||||
|
|
||||||
while running:
|
|
||||||
# Process events
|
|
||||||
for event in pygame.event.get():
|
|
||||||
if event.type == pygame.KEYDOWN:
|
|
||||||
if event.key == pygame.K_ESCAPE:
|
|
||||||
running = False
|
|
||||||
elif event.key == pygame.K_SPACE:
|
|
||||||
# Score points
|
|
||||||
game.scoreboard.increase_score(5)
|
|
||||||
game.speak(f"Score: {game.scoreboard.get_score()}")
|
|
||||||
|
|
||||||
# Update game state
|
|
||||||
player_x += random.uniform(-0.2, 0.2)
|
|
||||||
|
|
||||||
# Add random ambient sounds
|
|
||||||
if random.random() < 0.05:
|
|
||||||
sounds = game.sound.get_sounds()
|
|
||||||
if "ambient" in sounds:
|
|
||||||
sg.play_random_positional(sounds, "ambient", player_x,
|
|
||||||
player_x + random.uniform(-5, 5))
|
|
||||||
|
|
||||||
pygame.time.delay(50)
|
|
||||||
|
|
||||||
# Game over and cleanup
|
|
||||||
game.speak("Game over!")
|
|
||||||
game.exit()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Complex Sound Environment
|
|
||||||
|
|
||||||
```python
|
|
||||||
import libstormgames as sg
|
|
||||||
import time
|
|
||||||
import random
|
|
||||||
|
|
||||||
def create_sound_environment(player_x, player_y):
|
|
||||||
sounds = sg.initialize_gui("Sound Environment Demo")
|
|
||||||
|
|
||||||
# Place sound sources
|
|
||||||
water_x, water_y = 10, 5
|
|
||||||
fire_x, fire_y = 15, 8
|
|
||||||
wind_x, wind_y = 3, 12
|
|
||||||
|
|
||||||
# Play ambient sounds
|
|
||||||
water_channel = sg.obj_play(sounds, "water", player_x, water_x)
|
|
||||||
fire_channel = sg.obj_play(sounds, "fire", player_x, fire_x)
|
|
||||||
wind_channel = sg.obj_play(sounds, "wind", player_x, wind_x)
|
|
||||||
|
|
||||||
# Main loop
|
|
||||||
running = True
|
|
||||||
while running:
|
|
||||||
# Simulate player movement
|
|
||||||
player_x += random.uniform(-0.5, 0.5)
|
|
||||||
player_y += random.uniform(-0.5, 0.5)
|
|
||||||
|
|
||||||
# Update sound positions
|
|
||||||
water_channel = sg.obj_update(water_channel, player_x, water_x)
|
|
||||||
fire_channel = sg.obj_update(fire_channel, player_x, fire_x)
|
|
||||||
wind_channel = sg.obj_update(wind_channel, player_x, wind_x)
|
|
||||||
|
|
||||||
# Occasionally play random sound
|
|
||||||
if random.random() < 0.1:
|
|
||||||
sg.play_random_positional(sounds, "creature", player_x,
|
|
||||||
player_x + random.uniform(-5, 5))
|
|
||||||
|
|
||||||
# Check for exit
|
|
||||||
if sg.check_for_exit():
|
|
||||||
running = False
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
sg.obj_stop(water_channel)
|
|
||||||
sg.obj_stop(fire_channel)
|
|
||||||
sg.obj_stop(wind_channel)
|
|
||||||
sg.exit_game()
|
|
||||||
```
|
|
||||||
|
|
||||||
|
# Adjust volume
|
||||||
|
sg.adjust_master_volume(0.1) # Increase master volume
|
||||||
|
sg.adjust_bgm_volume(-0.1) # Decrease background music volume
|
||||||
|
sg.adjust_sfx_volume(0.1) # Increase sound effects volume
|
||||||
|
|
||||||
### Complete Game Structure with Class-Based Architecture
|
### Complete Game Structure with Class-Based Architecture
|
||||||
|
|
||||||
@ -658,7 +301,7 @@ class MyGame:
|
|||||||
|
|
||||||
# Game state
|
# Game state
|
||||||
self.player_x = 5
|
self.player_x = 5
|
||||||
self.player_y = 5
|
self.player_y = 0
|
||||||
self.difficulty = "normal"
|
self.difficulty = "normal"
|
||||||
|
|
||||||
# Load settings
|
# Load settings
|
||||||
@ -675,28 +318,53 @@ class MyGame:
|
|||||||
|
|
||||||
# Game loop
|
# Game loop
|
||||||
running = True
|
running = True
|
||||||
|
|
||||||
|
# Create enemies with positional audio
|
||||||
|
enemies = []
|
||||||
|
for i in range(3):
|
||||||
|
enemy = {
|
||||||
|
'x': random.uniform(10, 30),
|
||||||
|
'y': random.uniform(-5, 5),
|
||||||
|
'channel': None
|
||||||
|
}
|
||||||
|
# Start looping sound for each enemy
|
||||||
|
enemy['channel'] = self.game.sound.play_random_positional(
|
||||||
|
"enemy", self.player_x, enemy['x'], self.player_y, enemy['y'], loop=True
|
||||||
|
)
|
||||||
|
enemies.append(enemy)
|
||||||
|
|
||||||
while running:
|
while running:
|
||||||
# Update game state
|
# Update game state
|
||||||
self.player_x += random.uniform(-0.2, 0.2)
|
for enemy in enemies:
|
||||||
|
# Move enemies toward player
|
||||||
|
if enemy['x'] > self.player_x:
|
||||||
|
enemy['x'] -= 0.1
|
||||||
|
else:
|
||||||
|
enemy['x'] += 0.1
|
||||||
|
|
||||||
# Handle input
|
# Handle input
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
if event.key == pygame.K_ESCAPE:
|
if event.key == pygame.K_ESCAPE:
|
||||||
running = False
|
running = False
|
||||||
|
elif event.key == pygame.K_LEFT:
|
||||||
|
self.player_x -= 1
|
||||||
|
elif event.key == pygame.K_RIGHT:
|
||||||
|
self.player_x += 1
|
||||||
elif event.key == pygame.K_SPACE:
|
elif event.key == pygame.K_SPACE:
|
||||||
self.game.scoreboard.increase_score()
|
self.game.scoreboard.increase_score()
|
||||||
self.game.speak(f"Score: {self.game.scoreboard.get_score()}")
|
self.game.speak(f"Score: {self.game.scoreboard.get_score()}")
|
||||||
|
|
||||||
# Add some random sounds
|
# Update all sound positions with one call
|
||||||
if random.random() < 0.05:
|
self.game.sound.update_all_active_loops(self.player_x, self.player_y)
|
||||||
sounds = self.game.sound.get_sounds()
|
|
||||||
if "ambient" in sounds:
|
|
||||||
sg.play_random_positional(sounds, "ambient",
|
|
||||||
self.player_x, self.player_x + random.uniform(-10, 10))
|
|
||||||
|
|
||||||
pygame.time.delay(50)
|
pygame.time.delay(50)
|
||||||
|
|
||||||
|
# Stop all enemy sounds
|
||||||
|
for enemy in enemies:
|
||||||
|
if enemy['channel']:
|
||||||
|
self.game.sound.obj_stop(enemy['channel'])
|
||||||
|
|
||||||
# Game over
|
# Game over
|
||||||
position = self.game.scoreboard.check_high_score()
|
position = self.game.scoreboard.check_high_score()
|
||||||
if position:
|
if position:
|
||||||
@ -733,20 +401,12 @@ class MyGame:
|
|||||||
def run(self):
|
def run(self):
|
||||||
# Main menu loop
|
# Main menu loop
|
||||||
while True:
|
while True:
|
||||||
sounds = self.game.sound.get_sounds()
|
# Use playCallback for play option and add custom "settings" option
|
||||||
choice = sg.game_menu(sounds, self.play_game, "settings",
|
# Note: standard options like instructions, high_scores, etc. are handled automatically
|
||||||
"instructions", "credits", "donate", "exit_game")
|
choice = sg.game_menu(self.game.sound.sounds, self.play_game, "settings")
|
||||||
|
|
||||||
if choice == "settings":
|
if choice == "settings":
|
||||||
self.settings()
|
self.settings()
|
||||||
elif choice == "instructions":
|
|
||||||
sg.instructions()
|
|
||||||
elif choice == "credits":
|
|
||||||
sg.credits()
|
|
||||||
elif choice == "donate":
|
|
||||||
sg.donate()
|
|
||||||
elif choice == "exit_game":
|
|
||||||
self.game.exit()
|
|
||||||
|
|
||||||
# Run the game
|
# Run the game
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@ -757,24 +417,29 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Modern vs Traditional Approach**:
|
1. **Sound Looping and Positioning**:
|
||||||
|
- Use the `loop=True` parameter for sounds that need to loop
|
||||||
|
- For moving sound sources, use `update_all_active_loops()` to efficiently update all sounds at once
|
||||||
|
- Include vertical positioning (Y-axis) if you need 3D audio
|
||||||
|
|
||||||
|
2. **Modern vs Traditional Approach**:
|
||||||
- New projects: Use the Game class for better organization
|
- New projects: Use the Game class for better organization
|
||||||
- Existing projects: Continue with global functions for compatibility
|
- Existing projects: Continue with global functions for compatibility
|
||||||
- Both approaches are fully supported
|
- Both approaches are fully supported
|
||||||
|
|
||||||
2. **Always clean up resources**:
|
3. **Always clean up resources**:
|
||||||
- Use `exit_game()` or `game.exit()` when exiting to ensure proper cleanup
|
- Use `exit_game()` or `game.exit()` when exiting to ensure proper cleanup
|
||||||
- Stop sounds that are no longer needed
|
- Stop sounds that are no longer needed with `obj_stop()`
|
||||||
|
|
||||||
3. **Volume control**:
|
4. **Volume control**:
|
||||||
- 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. **Configuration**:
|
5. **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**:
|
6. **Path initialization**:
|
||||||
- Always initialize the framework with a proper game title
|
- Always initialize the framework with a proper game title
|
||||||
- Game title is used to determine configuration directory paths
|
- Game title is used to determine configuration directory paths
|
||||||
- Services are interconnected, so proper initialization ensures correct operation
|
- Services are interconnected, so proper initialization ensures correct operation
|
||||||
@ -799,6 +464,7 @@ 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
|
||||||
|
- Remember to use pygame.event.pump() especially in the game loop
|
||||||
|
|
||||||
### Scoreboard/Configuration Issues
|
### Scoreboard/Configuration Issues
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user