libstormgames
A Python library to make creating audio games easier.
Overview
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
- Text-to-speech integration
- Configuration management
- Score tracking and high score tables
- Input handling and keyboard controls
- Menu systems and text display
- GUI initialization
- NEW: Statistics tracking with level/total separation
- NEW: Save/load management with atomic operations
- NEW: Combat systems with weapons and projectiles
Installation
Requirements
- pygame>=2.0.0
- pyperclip>=1.8.0
- requests>=2.25.0
- pyxdg>=0.27
- setproctitle>=1.2.0
- numpy>=1.19.0
- wxpython
Speech Providers (one required)
- Linux/Unix:
python-speechd
oraccessible-output2>=0.14
- Windows/macOS:
accessible-output2>=0.14
Install from source
If you are on Linux, check your package manager first to see if the packages in requirements.txt are available.
git clone https://git.stormux.org/storm/libstormgames
cd libstormgames
pip install -e .
Getting Started
You can use libstormgames in two ways: the traditional function-based approach or the new class-based approach.
Traditional Function-Based Approach
#!/usr/bin/env python3
import libstormgames as sg
def main():
# Initialize the game
sounds = sg.initialize_gui("My First Audio Game")
# Welcome message
sg.speak("Welcome to My First Audio Game!")
# Create a scoreboard
scoreboard = sg.Scoreboard()
# Main game loop
def play_game():
sg.speak("Game started!")
scoreboard.increase_score(10)
sg.speak(f"Your score is {scoreboard.get_score()}")
scoreboard.add_high_score()
return "menu" # Return to menu after game ends
# Define menu options
while True:
choice = sg.game_menu(sounds)
if choice == "play":
play_game()
if __name__ == "__main__":
main()
Modern Class-Based Approach
#!/usr/bin/env python3
import libstormgames as sg
def main():
# Create and initialize a game
game = sg.Game("My First Audio Game").initialize()
# Welcome message
game.speak("Welcome to My First Audio Game!")
# Main game loop
def play_game():
game.speak("Game started!")
game.scoreboard.increase_score(10)
game.speak(f"Your score is {game.scoreboard.get_score()}")
game.scoreboard.add_high_score()
return "menu"
# Define menu options
while True:
choice = sg.game_menu(game.sound.get_sounds())
if choice == "play":
play_game()
if __name__ == "__main__":
main()
Library Structure
The library is organized into modules, each with a specific focus:
- config: Configuration management
- services: Core services that replace global variables
- sound: Sound and music playback
- speech: Text-to-speech functionality
- scoreboard: High score tracking
- input: Input handling and dialogs
- display: Text display and GUI functionality
- menu: Menu systems
- utils: Utility functions and Game class
- stat_tracker: Statistics tracking system
- save_manager: Save/load management
- combat: Weapon and projectile systems
Core Classes
Game
The Game class provides a central way to manage all game systems:
# Create and initialize a game
game = sg.Game("My Game").initialize()
# Use fluent API for chaining commands
game.speak("Hello").play_bgm("music/theme")
# Access components directly
game.scoreboard.increase_score(10)
game.sound.play_random("explosion")
# Display text
game.display_text(["Line 1", "Line 2"])
# Clean 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
The library includes several service classes that replace global variables:
# Volume service manages all volume settings
volume = sg.VolumeService.get_instance()
volume.adjust_master_volume(0.1)
# Path service manages file paths
paths = sg.PathService.get_instance()
print(paths.game_path)
# Config service manages configuration
config = sg.ConfigService.get_instance()
Config
Handles configuration file management with local and global settings.
# Create a configuration manager
config = sg.Config("My Game")
# Access the local configuration
config.local_config.add_section("settings")
config.local_config.set("settings", "difficulty", "easy")
config.write_local_config()
# Read settings
config.read_local_config()
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
Manages sound loading and playback with positional audio support.
# Create a sound manager
sound_system = sg.Sound("sounds/")
# Get the dictionary of loaded sounds
sounds = sound_system.get_sounds()
# Play a sound
sg.play_sound(sounds["explosion"])
# Play a sound with positional audio (player at x=5, object at x=10)
channel = sg.obj_play(sounds, "footsteps", 5, 10)
# Update sound position as player or object moves
channel = sg.obj_update(channel, 6, 10) # Player moved to x=6
# Stop the sound
channel = sg.obj_stop(channel)
# Play background music
sg.play_bgm("sounds/background.ogg")
# 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
Speech
Provides text-to-speech functionality using available speech providers.
# Create a speech manager (usually you'll use the global instance)
speech = sg.Speech()
# Speak text
speech.speak("Hello, world!")
# Or use the global function for convenience
sg.speak("Hello, world!")
# Speak without interrupting previous speech
sg.speak("This won't interrupt", interrupt=False)
# Clean up when done
exit_game() or as the class, game.exit_game()
Scoreboard
Tracks scores and manages high score tables.
# 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
.
StatTracker
Flexible statistics tracking with separate level and total counters:
# Initialize with default stats
stats = sg.StatTracker({
"kills": 0,
"deaths": 0,
"score": 0,
"time_played": 0.0
})
# Update stats during gameplay
stats.update_stat("kills", 1) # Increment kills
stats.update_stat("time_played", 1.5) # Add playtime
# Access current values
level_kills = stats.get_stat("kills") # Current level kills
total_kills = stats.get_stat("kills", from_total=True) # All-time kills
# Reset level stats for new level (keeps totals)
stats.reset_level()
# Add new stats dynamically
stats.add_stat("boss_kills", 0)
# Serialization for save/load
stats_data = stats.to_dict()
restored_stats = sg.StatTracker.from_dict(stats_data)
SaveManager
Atomic save/load operations with corruption detection:
# Initialize save manager
save_manager = sg.SaveManager("my-rpg-game")
# Create a comprehensive save
game_state = {
"player": {
"level": 5,
"hp": 80,
"inventory": ["sword", "health_potion"]
},
"world": {
"current_area": "enchanted_forest",
"completed_quests": ["tutorial", "first_boss"]
},
"stats": stats.to_dict() # Include StatTracker data
}
# Add metadata for save selection screen
metadata = {
"display_name": "Enchanted Forest - Level 5",
"level": 5,
"playtime": "2.5 hours"
}
# Create the save (atomic operation)
save_path = save_manager.create_save(game_state, metadata)
# List and load saves
save_files = save_manager.get_save_files() # Newest first
if save_files:
loaded_data, loaded_metadata = save_manager.load_save(save_files[0])
restored_stats = sg.StatTracker.from_dict(loaded_data["stats"])
Combat Systems
Weapons
# Create weapons using factory methods
sword = sg.Weapon.create_sword("Iron Sword", damage=15)
dagger = sg.Weapon.create_dagger("Steel Dagger", damage=12)
# Custom weapon with stat bonuses
bow = sg.Weapon(
name="Elvish Bow", damage=18, range_value=8,
cooldown=600, stat_bonuses={"speed": 1.15}
)
# Combat usage
if sword.can_attack(): # Check cooldown
damage = sword.attack() # Start attack
if sword.can_hit_target(enemy_pos, player_pos, facing_right, "enemy1"):
actual_damage = sword.hit_target("enemy1")
sg.speak(f"Hit for {actual_damage} damage!")
Projectiles
# Create projectiles
arrow = sg.Projectile.create_arrow(
start_pos=(player_x, player_y),
direction=(1, 0) # Moving right
)
# Game loop integration
active_projectiles = []
def update_projectiles():
for projectile in active_projectiles[:]:
if not projectile.update(): # Move and check range
active_projectiles.remove(projectile)
continue
# Check enemy collisions
for enemy in enemies:
if projectile.check_collision(enemy.position, enemy.size):
damage = projectile.hit()
enemy.take_damage(damage)
active_projectiles.remove(projectile)
break
Key Functions
Game Initialization and Control
# 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
# 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
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
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
# 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
# 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
# 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)
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
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()
Complete Game Structure with New Systems
import libstormgames as sg
import pygame
import random
class ModernRPGGame:
def __init__(self):
# Create a Game instance that manages all subsystems
self.game = sg.Game("Modern RPG Demo").initialize()
# Initialize new game systems
self.player_stats = sg.StatTracker({
"level": 1, "exp": 0, "hp": 100, "mp": 50,
"kills": 0, "deaths": 0, "playtime": 0.0,
"items_found": 0, "gold": 0
})
self.save_manager = sg.SaveManager("modern-rpg-demo")
# Combat system
self.player_weapon = sg.Weapon.create_sword("Starting Sword", damage=10)
self.projectiles = []
self.enemies = []
# Game state
self.player_x = 5
self.player_y = 5
self.current_area = "village"
self.difficulty = "normal"
# Load settings
try:
self.difficulty = self.game.configService.localConfig.get("settings", "difficulty")
except:
self.game.configService.localConfig.add_section("settings")
self.game.configService.localConfig.set("settings", "difficulty", "normal")
self.game.configService.write_local_config()
def play_game(self):
"""Main game loop demonstrating new systems."""
self.game.speak(f"Starting game on {self.difficulty} difficulty")
self.game.play_bgm("sounds/game_music.ogg")
start_time = pygame.time.get_ticks()
# Game loop
running = True
while running:
current_time = pygame.time.get_ticks()
# Update playtime stats
playtime_hours = (current_time - start_time) / 3600000.0 # Convert to hours
self.player_stats.set_stat("playtime", playtime_hours, level_only=True)
# Handle input
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:
# Combat example
self.player_attack()
elif event.key == pygame.K_s:
# Quick save
self.quick_save()
elif event.key == pygame.K_l:
# Quick load
self.quick_load()
# Update game systems
self.update_combat()
self.update_player_stats()
# Random events
if random.random() < 0.01:
self.random_encounter()
pygame.time.delay(50)
# Game over - update total stats
total_playtime = self.player_stats.get_stat("playtime", from_total=True)
self.game.speak(f"Session ended. Total playtime: {total_playtime:.1f} hours")
return "menu"
def player_attack(self):
"""Handle player combat."""
if self.player_weapon.can_attack():
damage = self.player_weapon.attack()
self.game.speak("Attack!")
# Simulate hitting enemies
if self.enemies and self.player_weapon.is_attack_active():
enemy = self.enemies[0] # Attack first enemy
if self.player_weapon.can_hit_target(
enemy.position, (self.player_x, self.player_y),
facing_right=True, target_id=enemy.id
):
actual_damage = self.player_weapon.hit_target(enemy.id)
self.player_stats.update_stat("damage_dealt", actual_damage)
# Remove enemy if defeated
if enemy.take_damage(actual_damage):
self.enemies.remove(enemy)
self.player_stats.update_stat("kills", 1)
self.player_stats.update_stat("exp", 25)
self.game.speak("Enemy defeated!")
def update_combat(self):
"""Update combat systems."""
# Update projectiles
for projectile in self.projectiles[:]:
if not projectile.update():
self.projectiles.remove(projectile)
continue
# Check enemy collisions
for enemy in self.enemies[:]:
if projectile.check_collision(enemy.position, enemy.size):
damage = projectile.hit()
if enemy.take_damage(damage):
self.enemies.remove(enemy)
self.player_stats.update_stat("kills", 1)
self.projectiles.remove(projectile)
break
def update_player_stats(self):
"""Handle player progression."""
exp = self.player_stats.get_stat("exp")
level = self.player_stats.get_stat("level")
# Level up check
exp_needed = level * 100
if exp >= exp_needed:
self.player_stats.set_stat("level", level + 1)
self.player_stats.set_stat("exp", exp - exp_needed)
self.player_stats.set_stat("hp", 100) # Full heal on level up
self.game.speak(f"Level up! Now level {level + 1}!")
def random_encounter(self):
"""Create random encounters."""
self.game.speak("An enemy appears!")
# Add enemy logic here
self.player_stats.update_stat("encounters", 1)
def quick_save(self):
"""Create a quick save."""
try:
save_name = f"Quick Save - Level {self.player_stats.get_stat('level')}"
self.create_complete_save(save_name)
self.game.speak("Game saved!")
except Exception as e:
self.game.speak(f"Save failed: {e}")
def quick_load(self):
"""Load the most recent save."""
try:
saves = self.save_manager.get_save_files()
if saves:
self.load_complete_save(saves[0])
self.game.speak("Game loaded!")
else:
self.game.speak("No saves found!")
except Exception as e:
self.game.speak(f"Load failed: {e}")
def create_complete_save(self, save_name):
"""Create comprehensive save with all systems."""
complete_state = {
"player_stats": self.player_stats.to_dict(),
"weapon": self.player_weapon.to_dict(),
"player_position": (self.player_x, self.player_y),
"current_area": self.current_area,
"difficulty": self.difficulty,
"enemies": [enemy.to_dict() for enemy in self.enemies],
"projectiles": [proj.to_dict() for proj in self.projectiles]
}
metadata = {
"display_name": save_name,
"level": self.player_stats.get_stat("level"),
"location": self.current_area,
"playtime": f"{self.player_stats.get_stat('playtime', from_total=True):.1f}h"
}
return self.save_manager.create_save(complete_state, metadata)
def load_complete_save(self, save_path):
"""Load comprehensive save restoring all systems."""
data, metadata = self.save_manager.load_save(save_path)
# Restore all systems
self.player_stats = sg.StatTracker.from_dict(data["player_stats"])
self.player_weapon = sg.Weapon.from_dict(data["weapon"])
self.player_x, self.player_y = data["player_position"]
self.current_area = data["current_area"]
self.difficulty = data["difficulty"]
# Restore dynamic objects (implementation depends on your enemy/projectile classes)
# self.enemies = [Enemy.from_dict(e) for e in data["enemies"]]
# self.projectiles = [sg.Projectile.from_dict(p) for p in data["projectiles"]]
def settings(self):
options = ["easy", "normal", "hard", "back"]
current = options.index(self.difficulty) if self.difficulty in options else 1
while True:
self.game.speak(f"Current difficulty: {options[current]}")
# Wait for input
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
self.difficulty = options[current]
self.game.config_service.local_config.set("settings", "difficulty", self.difficulty)
self.game.config_service.write_local_config()
self.game.speak(f"Difficulty set to {self.difficulty}")
return
elif event.key == pygame.K_ESCAPE:
return
def run(self):
# Main menu loop
while True:
sounds = self.game.sound.get_sounds()
choice = sg.game_menu(sounds, self.play_game, "settings",
"instructions", "credits", "donate", "exit_game")
if choice == "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
if __name__ == "__main__":
game = MyGame()
game.run()
Best Practices
-
Modern vs Traditional Approach:
- New projects: Use the Game class for better organization
- Existing projects: Continue with global functions for compatibility
- Both approaches are fully supported
-
Always clean up resources:
- Use
exit_game()
orgame.exit()
when exiting to ensure proper cleanup - Stop sounds that are no longer needed
- Use
-
Volume control:
- Implement the Alt+key volume controls in your game
- Use volume services for better control
-
Configuration:
- Save user preferences using the Config class
- Load settings at startup
-
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
-
New game systems:
- Use StatTracker for comprehensive statistics with level/total separation
- Implement SaveManager for reliable save/load with metadata
- Leverage Combat systems for professional weapon and projectile mechanics
- Combine all systems for rich, full-featured games
-
Performance considerations:
- Reset level stats regularly to prevent memory bloat
- Clean up old saves periodically using SaveManager methods
- Remove inactive projectiles from update loops
- Use weapon cooldowns to prevent spam attacks
Troubleshooting
No Sound
- Ensure pygame mixer is properly initialized
- Check if sound files exist in the correct directory
- Verify file formats (OGG and WAV are supported)
No Speech
- Make sure at least one speech provider is installed
- Linux/Unix:
python-speechd
oraccessible-output2
- Windows/macOS:
accessible-output2
- Linux/Unix:
- Check if pygame display is initialized properly
Input Issues
- Ensure pygame is properly handling events
- 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:
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
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the GPL v3 License - see the LICENSE file for details.