While it's true that there aren't lots of python audiogame examples, there are a few. Also, as stated above, part of being a programmer is... Well... Programming. If you can't find an example that does what you want, then get your hands dirty and start experimenting. Find something that works and keep optimizing it, adding features as you want / need them.
To get you started, here are a few files from a game I'm working on. These are basics (screens and menus), so feel free to modify them; they assume you have a game class, an instance of that class with a sound manager (I use a custom libaudioverse wrapper, but you can easily make this use pyglet's sound functions), and an output object with an output method (I use accessible_output2 for this).
It also assumes you've made certain choices (on_keys instead of motion events for scrolling, game instance with an instance of screen stack, etc).
Also, while you should feel free to modify this code so it fits your game, if you leave it unmodified please don't claim that it is entirely yours; I don't expect any kind of attribution, but I really hate when people plagiarize.
That being said, on to the code:
# screens/__init__.py
# Basic screen module
from pyglet.window import key
class Screen(object):
def __init__(self, game, escapable=True, *a, **k):
"""Base class that represents one screen in the game;
this can be a menu, inventory list, normal gameplay, etc.
When a screen is made active, it's 'activate' method gets run. This needs to bind any hotkeys / mouse / joystick events.
Likewise when it is popped off the stack, 'deactivate' runs, and this needs to remove any events that were previously set for this screen, stop any music, etc.
Args and options:
game: a reference to the game object which has a sound manager, screen stack, etc.
escapable: a boolean that determines if pressing escape when this screen is active will allow the event to propogate.
If the user shouldn't be able to exit this menu with escape (e.g., exit by choosing an option from a menu), set this to false.
"""
self.game = game
self.escapable=escapable
def activate(self, push_self=True):
"""Base class activate method; handles adding all events in the current screen to the window
"""
if push_self:
self.game.window.push_handlers(self)
self.already_popped = False
def deactivate(self, remove_self=True):
"""Base class deactivate method; handles removing all previously added screen-specific events that belong to this screen.
"""
if remove_self:
self.game.window.remove_handlers(self)
def on_key_press(self, symbol, modifiers):
"""Event that runs when any key is pressed; children should use super() to get the default functionality before they add their own
This is also important because this method properly handles inescapable screens (where using escape isn't a valid way to exit the screen).
"""
if symbol == key.ESCAPE:
if self.escapable:
self.game.screens.pop()
# return true on principal, so that we don't exit
return True
class ScreenStack(object):
"""Class that manages a stack of screens and activation / deactivation of pushed / popped ones.
"""
def __init__(self, game):
self.game = game
self.screens = []
def push(self, screen):
"""Make the provided screen the current one, deactivating the previous one"""
if self.screens:
self.screens[-1].deactivate()
self.screens.append(screen)
screen.activate()
self.game.screen = screen
def pop(self):
"""Remove the current screen from the stack, and run the necessary methods to deactivate the screen in question and restore the previous one.
"""
screen = self.screens.pop()
screen.deactivate()
self.screens[-1].activate()
self.game.screen = self.screens[-1]
# screens/menu.py
# Module that holds menu-specific screens
from pyglet.window import key
import libaudioverse as lav
from sound import Sound
from . import Screen
class MenuScreen(Screen):
def __init__(self, prompt="", choices=None, dynamic=False, dynamic_callback=None, quick=False, sound_volume=None, music_volume=None, selection_sound=None, activation_sound=None, boundary_sound=None, open_sound=None, close_sound=None, music=None, *args, **kwargs):
"""Represents a standard menu with volume-controlable sounds and background music and first-letter navigation.
Args and options:
Prompt: The message first spoken when the menu opens, and can be repeated with space.
Choices: a list of menu options in the form of tuples (menutext, associated_callback).
The associated callback will run when it's menutext is selected from the menu.
If dynamic is set to true, this option should just be a list of strings.
dynamic: If this is a dynamic menu. False by default.
Dynamic menus are where the player needs to select from a list of choices that will not always be the same, so callbacks for individual items aren't defined in code.
Rather, one callback is provided and the item itself is passed as an argument to it.
An example of when such a menu would be useful is when selecting items from a list of files.
dynamic_callback: The callback to pass the current item (as a string) to. Set to none by default, and only useful when dynamic is true.
quick: If this menu should not persist after an item is activated. False bydefault.
When this is set to true, when an item is chosen, this menu will pop itself off the stack before running the callback.
This is intended for short-lived menus that, for example have a list of options to change and once the player presses enter on them, should go away and either leave them in the menu that just opened, or drop back to the previous screen instead of staying in the menu.
sound_volume: The volume for all menu sounds; set to None by default, which means 1.0 or 100 percent.
music_volume: The volume for background music, represented as a float between 0.0 and 1.0 for normal volume levels. None by default, which means 1.0, or 100 percent.
selection_sound: Plays each time the item changes.
boundary_sound: Plays when the botum or top of the menu is reached.
activation_sound: Plays when an item from the menu is chosen.
open_sound: Plays when the menu is first opened.
close_sound: Plays only if no item is chosen and the menu is closed (e.g. Escape).
music: Will be played on a loop in the background if provided.
"""
super(MenuScreen, self).__init__(*args, **kwargs)
self.prompt = prompt
if choices is None:
choices = []
self.choices = choices
self.dynamic = dynamic
self.dynamic_callback = dynamic_callback
self.quick = quick
self.sound_volume = sound_volume
self.music_volume = music_volume
self.selection_sound = selection_sound
self.activation_sound = activation_sound
self.boundary_sound = boundary_sound
self.open_sound = open_sound
self.close_sound = close_sound
self.music = music
self.music_node = None
self.index = 0
def current_item(self):
if not self.choices:
return
self.index %= len(self.choices)
return self.choices[self.index]
def next_item(self):
if self.index + 1 >= len(self.choices):
self.play_boundary()
self.index += 1
self.read_current_item()
def previous_item(self):
if self.index - 1 < 0:
self.play_boundary()
self.index -= 1
self.read_current_item()
def read_current_item(self, play_selection_sound=True, interrupt=True):
if self.dynamic == False:
item = self.current_item()[0]
else: # this is a dynamic menu, no need to extract text from a tuple
item = self.current_item()
if item is None:
return
if play_selection_sound == True:
self.play_selection()
self.game.output.output(str(item), interrupt)
def first_item(self):
self.index = 0
self.read_current_item()
def last_item(self):
self.index = len(self.choices)
self.read_current_item()
def activate(self):
"""Method that runs when the menu is first opened"""
super(MenuScreen, self).activate()
self.item_activated=False # always reset upon menu open
self.play_open()
if self.music is not None:
self.music_node = self.game.sound_manager.play_ambient(self.music, self.music_volume)
self.read_prompt()
self.read_current_item(play_selection_sound=False, interrupt=False)
def read_prompt(self):
self.game.output.output(self.prompt)
def on_key_press(self, symbol, modifiers):
r = super(MenuScreen, self).on_key_press(symbol, modifiers) # do default behavior
if r is not None: # catch the return value
return r
if symbol == key.DOWN:
self.next_item()
elif symbol == key.UP:
self.previous_item()
elif symbol == key.HOME:
self.first_item()
elif symbol == key.END:
self.last_item()
elif symbol == key.ENTER:
self.activate_item()
elif symbol == key.SPACE:
self.read_prompt()
char = key.symbol_string(symbol).lower()
for num, item in enumerate(self.choices):
if self.dynamic == False:
item = item[0] # since this isn't a dynamic menu, the menu text is the first item in the tuple
if unicode(item).lower().startswith(char):
self.index = num
self.read_current_item()
def activate_item(self, run_callback=True):
"""Method that gets run when a user presses enter on a menu item.
args:
run_callback: if true (which is the default), the callback (dynamic or for this specific item) is ran; otherwise it isn't. Everything else happens normally (sounds playing, screen gets popped if this is a quick instance).
This is so subclasses can call a function a certain way, or use a different way of storing a reference to a function in the choices list and still get the other "default" functionality of a menu.
"""
item = self.current_item()
if item is None:
return
self.play_activation()
self.item_activated = True # don't play menu close sound
if self.quick: # this screen shouldn't persist on the stack anymore, since we are about to activate something
# we do this first because the callback we activate
# could push another menu onto the stack, and then we'd just
# pop it off by mistake
self.game.screens.pop()
if run_callback:
if self.dynamic == True:
self.dynamic_callback(item)
else: # call the associated item's callback
if item[1] is not None:
item[1]() # run the associated callback
def play_boundary(self):
if self.boundary_sound:
self.game.sound_manager.play_UI(self.boundary_sound, self.sound_volume)
def play_selection(self):
if self.selection_sound is not None:
self.game.sound_manager.play_UI(self.selection_sound, self.sound_volume)
def play_activation(self):
if self.activation_sound is not None:
self.game.sound_manager.play_UI(self.activation_sound, self.sound_volume)
def play_open(self):
"""Play the menu open sound"""
if self.open_sound is not None:
self.game.sound_manager.play_UI(self.open_sound, self.sound_volume)
def play_close(self):
"""Play the menu close sound, but only if a menu item wasn't activated"""
if self.close_sound is not None and self.item_activated == False:
self.game.sound_manager.play_UI(self.close_sound, self.sound_volume)
def deactivate(self):
"""Stop any background music and if an item wasn't chosen, play the menu close sound"""
super(MenuScreen, self).deactivate() # inherit deactivate code
if self.music is not None and self.music_node is not None:
self.music_node.stop()
if self.item_activated == True:
self.play_close()
def exit_menu(self, *args, **kwargs):
"""Method for use in quick menus that takes any arguments and just does nothing
This works because quick menus close themselves (pop off the stack) when an item is activated, or when escape is pressed.
"""
pass
class SoundBrowserMenuScreen(MenuScreen):
def __init__(self, relative_paths=True, *args, **kwargs):
"""A menu who's choices are sound files.
Just about everything about this menu is standard, accept that all sounds and music options are automatically overridden and set to None so as to not interfere with listening to the selected sound, and the menu is made dynamic.
The only option that changes is choices;
instead of normal menu text like "play the game", the first item of each tuple should be a valid sound path, as accepted by the sound manager unless relative_paths is false, in which case the file paths given in choices are interpreted relative to the current working directory, not to sounds_path.
Each sound will be played when it's name is selected, until it ends, another one is chosen, or the menu is closed.
"""
kwargs['selection_sound'] = None
kwargs['activation_sound'] = None
kwargs['boundary_sound'] = None
kwargs['open_sound'] = None
kwargs['close_sound'] = None
kwargs['music'] = None
super(SoundBrowserMenuScreen, self).__init__(dynamic=True, quick=True, *args, **kwargs)
self.relative_paths = relative_paths
self.sound = Sound(lav.BufferNode(self.game.sound_manager.server))
self.sound.buffer_node.connect(0, self.game.sound_manager.server)
def read_current_item(self, *args, **kwargs):
"""Add to the original functionality of this method by playing the item as well (since the name is a sound path).
"""
super(SoundBrowserMenuScreen, self).read_current_item(*args, **kwargs)
# stop and swap buffers attached to the buffer node this instance has, and start it playing again
item = self.current_item()
b = self.game.sound_manager.get_buffer(item, self.relative_paths)
if self.sound.is_playing():
self.sound.stop()
self.sound.buffer_node.position = 0
self.sound.buffer_node.buffer = b
self.sound.play()
def deactivate(self):
"""Standard screen / menu deactivate method, just with code that stops any playing sounds.
"""
super(SoundBrowserMenuScreen, self).deactivate()
# if self.sound.is_playing():
self.sound.stop()
Regards,
Blademan
Twitter: @bladehunter2213