Return to repo list

heart-of-gold

Tactical RPG written in python, using pygame.
Return to HMagellan.com

commit 24d4c3234c0a52c74ff588627679eb7beca0946e
parent 27a80d1b2329cca72718422dc16be5742704e7a7
Author: Erik Letson <hmagellan@hmagellan.com>
Date:   Sun, 18 Oct 2020 02:46:17 -0500

Sound manager + scene voice implemented

Diffstat:
Mdata/json/scenes/testscene.json | 6+++---
Adata/json/sounds.json | 14++++++++++++++
Adata/snd/voice1.ogg | 0
Adata/snd/voice2.ogg | 0
Mmain.py | 3+++
Msrc/game.py | 4+++-
Msrc/scene.py | 29++++++++++++++++++++++-------
Asrc/sound.py | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 107 insertions(+), 11 deletions(-)

diff --git a/data/json/scenes/testscene.json b/data/json/scenes/testscene.json @@ -11,8 +11,8 @@ "name_font" : "A", "name_pos" : [20, 480], "line_font" : "A", - "voice" : 0, - "line" : "Hi there to anyone who is listening to this message. How are you? I am writing today in this huge string defined in a large JSON file that is used by the program to create a scene such as the one you are viewing right now. This is a test of the Scene system. This test is currently being done. This is a very long line meant to be automatically split appart several lines in the actual rendered display in the pygame window. This string is extremely long, of course. I'm going to keep talking for a little while longer in order to make sure this string is sufficiently long for my purposes. Whew!!!", + "voice" : "Voice2", + "line" : "Hello there. How are you doing today? I'm fine, thanks for asking.", "characters" : [ { "name" : "Jisella1", @@ -29,7 +29,7 @@ "name_font" : "A", "name_pos" : [20, 480], "line_font" : "A", - "voice" : 0, + "voice" : null, "line" : "", "characters" : [ { diff --git a/data/json/sounds.json b/data/json/sounds.json @@ -0,0 +1,14 @@ +{ + "sounds" : [ + { + "name" : "Voice1", + "filename" : "voice1.ogg", + "volume" : "0.1" + }, + { + "name" : "Voice2", + "filename" : "voice2.ogg", + "volume" : "0.1" + } + ] +} diff --git a/data/snd/voice1.ogg b/data/snd/voice1.ogg Binary files differ. diff --git a/data/snd/voice2.ogg b/data/snd/voice2.ogg Binary files differ. diff --git a/main.py b/main.py @@ -13,8 +13,11 @@ from src import game # EntityManager object. # 4. Entities should probably be passed their manager. VGOs probably do not need this. +# Initialization of pygame and submodules +pygame.mixer.pre_init(44100, -16, 4, 1024) pygame.init() +# Main game logic if __name__ == "__main__": g1 = game.Game() g1.mainloop() diff --git a/src/game.py b/src/game.py @@ -1,5 +1,5 @@ import pygame -from . import images, board, vgo, unit, menu, scene +from . import images, sound, board, vgo, unit, menu, scene from .constants import * ########### @@ -44,6 +44,7 @@ class Game(object): # Managers self.sheet_manager = images.SheetManager(self) + self.sound_manager = sound.SoundManager(self) self.menu_manager = menu.MenuManager(self) self.board_manager = board.BoardManager(self) self.entity_manager = vgo.EntityManager(self) @@ -53,6 +54,7 @@ class Game(object): # Setup (This is WIP) self.sheet_manager.load_sheets_from_json("sheets.json") self.sheet_manager.load_animations_from_json("anims.json") + self.sound_manager.load_sounds_from_json("sounds.json") # Switch to game control self.switch_mode(STATE_MODES.Main_Menu_Mode) diff --git a/src/scene.py b/src/scene.py @@ -96,7 +96,8 @@ class StillScene(object): self.current_line_starting_index = 0 self.current_font = None # An index of the 'fonts' value self.current_font_height = 0 - self.current_text_voice = None + self.current_voice = None + self.voice_delay = 0 self.script_index = -1 # Load the scene @@ -144,6 +145,7 @@ class StillScene(object): self.current_font_height = self.current_font.get_height() self.max_line = int((3 * SCREEN_WIDTH) / self.current_font_height) self.total_lines = int(self.text_area_height / self.current_font_height) + self.current_voice = self.script[self.script_index]["voice"] self.current_text_string = self.script[self.script_index]["line"] for c in self.script[self.script_index]["characters"]: nc = vgo.VisibleGameObject(self.manager.game.sheet_manager.loaded_sheets[c["sheet"]], tuple(c["sprite"])) @@ -155,11 +157,6 @@ class StillScene(object): Write out the text of a script and render it to a pygame Font surface. """ - # TODO: Needs to be able to scroll down when too many lines - # are written at once. Just delete the 0th line and - # drop all lines down 1 in index. Hard part will be - # to dynamically determine how many lines is the max - # based on font height and screen dimensions. # If we aren't ready to write a new char yet, don't if self.text_write_timer < self.text_speed and not self.continue_ready: self.text_write_timer += 1 @@ -203,7 +200,6 @@ class StillScene(object): self.rendered_text_surfaces[ln] = self.current_font.render(st, False, (0, 0, 0)).convert() ln += 1 - # Compensate for initial white space compen = self.displayed_strings[self.line_number] while len(compen) > 0 and compen[0] == " ": @@ -216,6 +212,22 @@ class StillScene(object): if self.current_text_char_index >= len(self.current_text_string): self.continue_ready = True + def play_voice(self): + """ + Play a voice sound along with the text write. + """ + # TODO: This error checking should be a part of play() + # This could be something for the bus to handle in + # the future (internal errors) + # Voice delay calculations + if self.current_text_string[self.current_text_char_index] == " ": + self.voice_delay += 2 + # Decide what to play + if self.voice_delay > 0: + self.voice_delay -= 1 + else: + self.manager.game.sound_manager.play_sound(self.current_voice, 0) + def continue_script(self): """ Trigger the script cycle. Activated by something @@ -243,3 +255,6 @@ class StillScene(object): if t != None: surface.blit(t, (self.text_area_topleft[0], self.text_area_topleft[1] + (self.current_font_height * l) + 2)) l += 1 + + if self.current_voice != None and not self.continue_ready: + self.play_voice() diff --git a/src/sound.py b/src/sound.py @@ -0,0 +1,62 @@ +import pygame, os, json +from . import manager +from .constants import SOUND_PATH, JSON_PATH + +############ +# sound.py # +############ + +# This file contains: +# 1. SoundManager, which loads up all sounds and is then called to play them through the pygame mixer. + +############################ +# Section 1 - SoundManager # +############################ + +class SoundManager(manager.Manager): + """ + This object loads sounds as defined in JSON + files and is called to play them. + """ + + def __init__(self, game): + + # Parent initialization + super().__init__(game) + + # Important values + self.sounds = {} + self.channels = [pygame.mixer.Channel(x) for x in range(0, pygame.mixer.get_num_channels() - 1)] + + def load_sounds_from_json(self, soundjson): + """ + Load up sounds from a definition JSON + file. + """ + j = json.load(open(os.path.join(JSON_PATH, soundjson))) + + for s in j["sounds"]: + self.sounds[s["name"]] = pygame.mixer.Sound(os.path.join(SOUND_PATH, s["filename"])) + self.sounds[s["name"]].set_volume(float(s["volume"])) + + def play_sound(self, soundname, channel = None, concurrent = False): + """ + Play a loaded sound by name. If the sound is + already playing, stop it unless concurrent is + True. A new channel should be chosen to play + the sound on dynamically, unless channel is + a legal channel id int. + """ + # TODO: There could (should?) be some channel-checking logic here + if channel != None and not self.channels[channel].get_busy(): + try: + self.channels[channel].play(self.sounds[soundname]) + except IndexError: + # TODO: Should be bus error here + print("Failed to play sound on channel " + channel) + elif not concurrent: + if self.sounds[soundname].get_num_channels() == 0: + self.sounds[soundname].play() + else: + self.sounds[soundname].play() +