- confirm quit

- bgm added to audio panel
- delegate maintains order subscribers are added by object
- optionally suppress "any key" event when mods are pressed
- dict of named child objects added to sprites
- generate glow animation effect frames
This commit is contained in:
Frank DeMarco 2020-05-01 02:26:12 -04:00
parent 8ce09138d2
commit cf8021c480
9 changed files with 333 additions and 77 deletions

View File

@ -38,6 +38,8 @@ class Animation(GameChild):
def get_default(self, method=None): def get_default(self, method=None):
if not method: if not method:
method = self.default_method method = self.default_method
elif isinstance(method, str):
return getattr(self, method)
return method return method
def halt(self, method=None): def halt(self, method=None):

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os, re, shutil, pygame import os, re, shutil, pygame, sys
from .GameChild import * from .GameChild import *
from .Sprite import * from .Sprite import *
@ -23,6 +23,8 @@ class Audio(GameChild):
self.subscribe(self.respond) self.subscribe(self.respond)
self.sfx = {} self.sfx = {}
self.load_sfx() self.load_sfx()
self.bgm = {}
self.load_bgm()
def set_volume(self, volume=None, increment=None, mute=False): def set_volume(self, volume=None, increment=None, mute=False):
if mute: if mute:
@ -94,7 +96,7 @@ class Audio(GameChild):
def load_sfx_file(self, path, name=None, replace=False, prefix="", def load_sfx_file(self, path, name=None, replace=False, prefix="",
volume=1.0, fade_out=0, loops=0, maxtime=0): volume=1.0, fade_out=0, loops=0, maxtime=0):
path = self.get_resource(path) path = self.get_resource(path)
if path and path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions"): if path and self.is_sound_file(path):
if name is None: if name is None:
name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path)) name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path))
if not replace and name in self.sfx: if not replace and name in self.sfx:
@ -106,6 +108,57 @@ class Audio(GameChild):
return True return True
return False return False
#
# Loading BGM procedure
#
# - load config file name/path definitions
# - check project specific bgm paths, load any that don't conflict
# - other paths can replace loaded paths through the audio panel and get
# written to config file
#
def load_bgm(self):
for name, path in self.get_configuration("bgm").items():
self.set_bgm(path, name)
for root in self.get_configuration("audio", "bgm-project-path"):
if os.path.exists(root):
print("checking {} for music".format(root))
if os.path.isfile(root):
self.set_bgm(root)
else:
for node, branches, leaves in os.walk(root, followlinks=True):
for leaf in leaves:
prefix = re.sub(root, "", node)
prefix = re.sub("^/", "", prefix)
if prefix:
prefix = re.sub("/", "_", prefix) + "_"
self.set_bgm(os.path.join(node, leaf), prefix=prefix)
def set_bgm(self, path, name=None, prefix=""):
print("setting {} music to {}".format(name, path))
try:
pygame.mixer.music.load(path)
except:
print("can't load {} as music".format(path))
return False
if name is None:
name = os.path.basename(path).split(".")[0]
self.bgm[prefix + name] = path
return True
def play_bgm(self, name):
pygame.mixer.music.load(self.bgm[name])
pygame.mixer.music.play(-1)
def is_sound_file(self, path):
return path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions")
def is_loadable(self, path):
try:
pygame.mixer.Sound(path)
except:
return False
return True
def update(self): def update(self):
# for ii in range(pygame.mixer.get_num_channels()): # for ii in range(pygame.mixer.get_num_channels()):
# channel = pygame.mixer.Channel(ii) # channel = pygame.mixer.Channel(ii)
@ -231,9 +284,7 @@ class AudioPanel(Animation):
self.active = False self.active = False
def respond(self, event): def respond(self, event):
if self.get_delegate().compare(event, "toggle-audio-panel") and \ if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx:
pygame.key.get_mods() & KMOD_CTRL and pygame.key.get_mods() & KMOD_SHIFT and \
self.get_audio().sfx:
if self.active: if self.active:
self.deactivate() self.deactivate()
else: else:
@ -246,12 +297,16 @@ class AudioPanel(Animation):
self.row_offset += 1 self.row_offset += 1
elif event.button == 4: elif event.button == 4:
self.row_offset -= 1 self.row_offset -= 1
elif event.button == 3:
self.deactivate()
def build(self): def build(self):
for row in self.rows: for row in self.rows:
row.unsubscribe() row.unsubscribe()
del row del row
self.rows = [] self.rows = []
for key in sorted(self.parent.bgm):
self.rows.append(AudioPanelRow(self, key, True))
for key in sorted(self.parent.sfx): for key in sorted(self.parent.sfx):
self.rows.append(AudioPanelRow(self, key)) self.rows.append(AudioPanelRow(self, key))
@ -285,42 +340,52 @@ class AudioPanelRow(BlinkingSprite):
SLIDER_W = 60 SLIDER_W = 60
BUTTON_W = 30 BUTTON_W = 30
def __init__(self, parent, key): def __init__(self, parent, key, is_bgm=False):
BlinkingSprite.__init__(self, parent, 500) BlinkingSprite.__init__(self, parent, 500)
self.key = key self.key = key
self.selected = False self.selected = False
self.font = self.parent.font_large self.font = self.parent.font_large
self.is_bgm = is_bgm
self.build() self.build()
font_medium = self.parent.font_medium if not self.is_bgm:
font_small = self.parent.font_small font_medium = self.parent.font_medium
self.volume_spinner = AudioPanelSpinner( font_small = self.parent.font_small
self, font_medium, font_small, self.SLIDER_W, self.location.h, .05, self.volume_spinner = AudioPanelSpinner(
self.get_sound_effect().local_volume, self, font_medium, font_small, self.SLIDER_W, self.location.h, .05,
self.get_sound_effect().adjust_volume, self.FOREGROUND, self.get_sound_effect().local_volume,
self.BACKGROUND, 2, "vol") self.get_sound_effect().adjust_volume, self.FOREGROUND,
self.fade_out_spinner = AudioPanelSpinner( self.BACKGROUND, 2, "vol")
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1, self.fade_out_spinner = AudioPanelSpinner(
self.get_sound_effect().fade_out_length, self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND, self.get_sound_effect().fade_out_length,
self.BACKGROUND, 1, "fade") self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND,
self.loops_spinner = AudioPanelSpinner( self.BACKGROUND, 1, "fade")
self, font_medium, font_small, self.SLIDER_W, self.location.h, 1, self.loops_spinner = AudioPanelSpinner(
self.get_sound_effect().loops, self, font_medium, font_small, self.SLIDER_W, self.location.h, 1,
self.get_sound_effect().adjust_loop_count, self.FOREGROUND, self.get_sound_effect().loops,
self.BACKGROUND, 0, "loops") self.get_sound_effect().adjust_loop_count, self.FOREGROUND,
self.maxtime_spinner = AudioPanelSpinner( self.BACKGROUND, 0, "loops")
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1, self.maxtime_spinner = AudioPanelSpinner(
self.get_sound_effect().maxtime, self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().adjust_maxtime, self.FOREGROUND, self.get_sound_effect().maxtime,
self.BACKGROUND, 1, "cutoff") self.get_sound_effect().adjust_maxtime, self.FOREGROUND,
self.play_button = AudioPanelButton(self, self.get_sound_effect().play) self.BACKGROUND, 1, "cutoff")
if self.is_bgm:
callback, kwargs = self.get_game().get_audio().play_bgm, {"name": self.key}
else:
callback, kwargs = self.get_sound_effect().play, {}
self.play_button = AudioPanelButton(self, callback, kwargs)
frame = pygame.Surface((self.BUTTON_W, self.location.h), SRCALPHA) frame = pygame.Surface((self.BUTTON_W, self.location.h), SRCALPHA)
frame.fill(self.BACKGROUND) frame.fill(self.BACKGROUND)
stop_button_frame = frame.copy() stop_button_frame = frame.copy()
w, h = frame.get_size() w, h = frame.get_size()
pygame.draw.polygon(frame, self.FOREGROUND, ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5))) pygame.draw.polygon(frame, self.FOREGROUND, ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
self.play_button.add_frame(frame) self.play_button.add_frame(frame)
self.stop_button = AudioPanelButton(self, self.get_sound_effect().stop) if self.is_bgm:
callback = pygame.mixer.music.stop
else:
callback = self.get_sound_effect().stop
self.stop_button = AudioPanelButton(self, callback)
stop_button_frame.fill(self.FOREGROUND, (w * .25, h * .25, w * .5, h * .5)) stop_button_frame.fill(self.FOREGROUND, (w * .25, h * .25, w * .5, h * .5))
self.stop_button.add_frame(stop_button_frame) self.stop_button.add_frame(stop_button_frame)
self.stop_blinking() self.stop_blinking()
@ -346,8 +411,9 @@ class AudioPanelRow(BlinkingSprite):
GameChild.unsubscribe(self, self.respond, pygame.MOUSEBUTTONDOWN) GameChild.unsubscribe(self, self.respond, pygame.MOUSEBUTTONDOWN)
self.play_button.unsubscribe() self.play_button.unsubscribe()
self.stop_button.unsubscribe() self.stop_button.unsubscribe()
for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner: if not self.is_bgm:
spinner.unsubscribe() for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner:
spinner.unsubscribe()
def build(self): def build(self):
ds = self.get_display_surface() ds = self.get_display_surface()
@ -374,7 +440,11 @@ class AudioPanelRow(BlinkingSprite):
file_sprite.location.midright = self.location.right - self.INDENT, self.location.centery file_sprite.location.midright = self.location.right - self.INDENT, self.location.centery
file_sprite.display_surface = surface file_sprite.display_surface = surface
file_name_sprite = Sprite(self) file_name_sprite = Sprite(self)
file_name_text = self.font.render(self.get_sound_effect().path, True, self.FOREGROUND) if self.is_bgm:
file_name = self.get_bgm()
else:
file_name = self.get_sound_effect().path
file_name_text = self.font.render(file_name, True, self.FOREGROUND)
file_name_sprite.add_frame(file_name_text) file_name_sprite.add_frame(file_name_text)
file_name_sprite.display_surface = box file_name_sprite.display_surface = box
file_name_sprite.location.midright = file_sprite.location.w - self.INDENT, file_sprite.location.h / 2 file_name_sprite.location.midright = file_sprite.location.w - self.INDENT, file_sprite.location.h / 2
@ -384,31 +454,43 @@ class AudioPanelRow(BlinkingSprite):
def get_sound_effect(self): def get_sound_effect(self):
return self.get_game().get_audio().sfx[self.key] return self.get_game().get_audio().sfx[self.key]
def get_bgm(self):
return self.get_game().get_audio().bgm[self.key]
def update_config(self): def update_config(self):
if not self.get_configuration().has_section("sfx"): if self.is_bgm:
self.get_configuration().add_section("sfx") section_name = "bgm"
sound_effect = self.get_sound_effect() else:
self.get_configuration().set( section_name = "sfx"
"sfx", self.key, "{}, {:.2f}, {:.2f}, {}, {:.2f}".format( if not self.get_configuration().has_section(section_name):
self.get_configuration().add_section(section_name)
if self.is_bgm:
config_value = self.get_bgm()
else:
sound_effect = self.get_sound_effect()
config_value = "{}, {:.2f}, {:.2f}, {}, {:.2f}".format(
sound_effect.path, sound_effect.local_volume, sound_effect.fade_out_length, sound_effect.path, sound_effect.local_volume, sound_effect.fade_out_length,
sound_effect.loops, sound_effect.maxtime)) sound_effect.loops, sound_effect.maxtime)
self.get_configuration().set(section_name, self.key, config_value)
config_path = self.get_configuration().locate_project_config_file() config_path = self.get_configuration().locate_project_config_file()
backup_path = config_path + ".backup" backup_path = config_path + ".backup"
shutil.copyfile(config_path, backup_path) shutil.copyfile(config_path, backup_path)
self.get_configuration().write(open(config_path, "w")) self.get_configuration().write(open(config_path, "w"))
def update(self): def update(self):
self.volume_spinner.location.midleft = self.location.move(5, 0).midright self.play_button.location.midleft = self.location.move(5, 0).midright
self.fade_out_spinner.location.midleft = self.volume_spinner.location.midright
self.loops_spinner.location.midleft = self.fade_out_spinner.location.midright
self.maxtime_spinner.location.midleft = self.loops_spinner.location.midright
self.play_button.location.midleft = self.maxtime_spinner.location.move(5, 0).midright
self.stop_button.location.midleft = self.play_button.location.midright self.stop_button.location.midleft = self.play_button.location.midright
if not self.is_bgm:
self.volume_spinner.location.midleft = self.stop_button.location.move(5, 0).midright
self.fade_out_spinner.location.midleft = self.volume_spinner.location.midright
self.loops_spinner.location.midleft = self.fade_out_spinner.location.midright
self.maxtime_spinner.location.midleft = self.loops_spinner.location.midright
Sprite.update(self) Sprite.update(self)
self.volume_spinner.update() if not self.is_bgm:
self.fade_out_spinner.update() self.volume_spinner.update()
self.loops_spinner.update() self.fade_out_spinner.update()
self.maxtime_spinner.update() self.loops_spinner.update()
self.maxtime_spinner.update()
self.play_button.update() self.play_button.update()
self.stop_button.update() self.stop_button.update()
@ -422,7 +504,9 @@ class AudioPanelFileBrowser(Sprite):
def __init__(self, parent): def __init__(self, parent):
Sprite.__init__(self, parent) Sprite.__init__(self, parent)
self.rows = []
self.font = self.parent.font_large self.font = self.parent.font_large
self.previewing_sound = None
ds = self.get_display_surface() ds = self.get_display_surface()
dsr = ds.get_rect() dsr = ds.get_rect()
surface = pygame.Surface((dsr.w * self.WIDTH - 2, dsr.h * self.HEIGHT - 2), SRCALPHA) surface = pygame.Surface((dsr.w * self.WIDTH - 2, dsr.h * self.HEIGHT - 2), SRCALPHA)
@ -434,7 +518,9 @@ class AudioPanelFileBrowser(Sprite):
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def reset(self): def reset(self):
self.visit(self.HOME) if self.previewing_sound is not None:
self.previewing_sound.stop()
# self.visit(self.HOME)
self.hide() self.hide()
def respond(self, event): def respond(self, event):
@ -442,14 +528,20 @@ class AudioPanelFileBrowser(Sprite):
if event.button == 1: if event.button == 1:
if self.collide(event.pos): if self.collide(event.pos):
for row in self.rows: for row in self.rows:
if row.collide(Vector(*event.pos).get_moved(-self.location.left, -self.location.top)): pos = Vector(*event.pos).get_moved(-self.location.left, -self.location.top)
if (not row.has_child("button") or pos.x < row.get_child("button").location.left) and row.collide(pos):
full_path = os.path.join(os.path.sep.join(self.trail), row.path) full_path = os.path.join(os.path.sep.join(self.trail), row.path)
if row.path == self.HOME or row.path == self.UP or \ if row.path == self.HOME or row.path == self.UP or \
os.path.isdir(full_path) and os.access(full_path, os.R_OK): os.path.isdir(full_path) and os.access(full_path, os.R_OK):
self.visit(row.path) self.visit(row.path)
elif os.path.isfile(full_path) and os.access(full_path, os.R_OK): elif os.path.isfile(full_path) and os.access(full_path, os.R_OK):
loaded = False
selected = self.parent.get_selected() selected = self.parent.get_selected()
if self.get_audio().load_sfx_file(full_path, selected.key, True): if selected.is_bgm:
loaded = self.get_audio().set_bgm(full_path, selected.key)
else:
loaded = self.get_audio().load_sfx_file(full_path, selected.key, True)
if loaded:
selected.update_config() selected.update_config()
self.hide() self.hide()
self.get_delegate().cancel_propagation() self.get_delegate().cancel_propagation()
@ -466,8 +558,24 @@ class AudioPanelFileBrowser(Sprite):
for row in self.parent.rows: for row in self.parent.rows:
row.selected = False row.selected = False
row.stop_blinking() row.stop_blinking()
row.play_button.set_clickable()
row.stop_button.set_clickable()
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable(False)
if self.previewing_sound is not None:
self.previewing_sound.stop()
Sprite.hide(self) Sprite.hide(self)
def unhide(self):
for row in self.parent.rows:
row.play_button.set_clickable(False)
row.stop_button.set_clickable(False)
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable()
Sprite.unhide(self)
def visit(self, path): def visit(self, path):
if path == self.UP and len(self.trail) > 1: if path == self.UP and len(self.trail) > 1:
path = self.trail[-2] path = self.trail[-2]
@ -478,7 +586,8 @@ class AudioPanelFileBrowser(Sprite):
if path == self.HOME: if path == self.HOME:
self.trail = [] self.trail = []
self.paths = ["/"] self.paths = ["/"]
for option in "sfx-repository-path", "sfx-default-path", "sfx-project-path": for option in "sfx-repository-path", "sfx-default-path", "sfx-project-path", \
"bgm-repository-path", "bgm-project-path":
for sfx_location in self.get_configuration("audio", option): for sfx_location in self.get_configuration("audio", option):
if self.get_resource(sfx_location): if self.get_resource(sfx_location):
self.paths.append(self.get_resource(sfx_location)) self.paths.append(self.get_resource(sfx_location))
@ -491,6 +600,10 @@ class AudioPanelFileBrowser(Sprite):
self.build() self.build()
def build(self): def build(self):
for row in self.rows:
if row.has_child("button"):
row.get_child("button").unsubscribe()
del row
self.rows = [] self.rows = []
for path in self.paths: for path in self.paths:
row = Sprite(self) row = Sprite(self)
@ -498,10 +611,29 @@ class AudioPanelFileBrowser(Sprite):
text = self.font.render(path, True, self.COLORS[1]) text = self.font.render(path, True, self.COLORS[1])
surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA) surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA)
surface.blit(text, (0, 0)) surface.blit(text, (0, 0))
surface.fill(self.COLORS[1], (0, surface.get_height() - 1, self.location.w, 1))
row.add_frame(surface) row.add_frame(surface)
row.display_surface = self.get_current_frame() row.display_surface = self.get_current_frame()
row.location.bottom = 0 row.location.bottom = 0
self.rows.append(row) self.rows.append(row)
full_path = os.path.join(os.path.sep.join(self.trail), path)
if self.get_audio().is_sound_file(full_path):
button = AudioPanelButton(self, self.preview, {"path": full_path}, [row, self])
row.set_child("button", button)
frame = pygame.Surface([text.get_height()] * 2, SRCALPHA)
w, h = frame.get_size()
pygame.draw.polygon(
frame, self.COLORS[1], ((w * .25, h * .25), (w * .25, h * .75), (w * .75, h * .5)))
button.add_frame(frame)
button.display_surface = row.get_current_frame()
button.location.right = self.location.w
def preview(self, path):
if self.get_audio().is_sound_file(path):
if self.previewing_sound is not None:
self.previewing_sound.stop()
self.previewing_sound = SoundEffect(self, path)
self.previewing_sound.play()
def update(self): def update(self):
self.get_current_frame().blit(self.background, (0, 0)) self.get_current_frame().blit(self.background, (0, 0))
@ -609,9 +741,12 @@ class AudioPanelSpinner(Sprite):
class AudioPanelButton(Sprite): class AudioPanelButton(Sprite):
def __init__(self, parent, callback): def __init__(self, parent, callback, callback_kwargs={}, containers=[]):
Sprite.__init__(self, parent) Sprite.__init__(self, parent)
self.callback = callback self.callback = callback
self.callback_kwargs = callback_kwargs
self.containers = containers
self.clickable = True
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None): def unsubscribe(self, callback=None, kind=None):
@ -621,5 +756,12 @@ class AudioPanelButton(Sprite):
Sprite.unsubscribe(self, callback, kind) Sprite.unsubscribe(self, callback, kind)
def respond(self, event): def respond(self, event):
if event.button == 1 and self.collide(event.pos): if self.clickable and event.button == 1:
self.callback() pos = Vector(*event.pos)
for container in self.containers:
pos.move(-container.location.left, -container.location.top)
if self.collide(pos):
self.callback(**self.callback_kwargs)
def set_clickable(self, clickable=True):
self.clickable = clickable

View File

@ -89,6 +89,7 @@ class Configuration(RawConfigParser):
section = "input" section = "input"
add_section(section) add_section(section)
set_option(section, "release-suffix", "-release", False) set_option(section, "release-suffix", "-release", False)
set_option(section, "confirm-quit", "no", False)
section = "sprite" section = "sprite"
add_section(section) add_section(section)
set_option(section, "transparent-color", "magenta", False) set_option(section, "transparent-color", "magenta", False)
@ -124,7 +125,7 @@ class Configuration(RawConfigParser):
set_option(section, "volume-up", "K_F2", False) set_option(section, "volume-up", "K_F2", False)
set_option(section, "volume-mute", "K_F3", False) set_option(section, "volume-mute", "K_F3", False)
set_option(section, "toggle-interpolator", "K_F7", False) set_option(section, "toggle-interpolator", "K_F7", False)
set_option(section, "toggle-audio-panel", "K_a", False) set_option(section, "toggle-audio-panel", "K_F6", False)
section = "joy" section = "joy"
add_section(section) add_section(section)
set_option(section, "advance", "7", False) set_option(section, "advance", "7", False)
@ -145,8 +146,10 @@ class Configuration(RawConfigParser):
set_option(section, "sfx-default-path", "~/storage/audio/sfx/default", False) set_option(section, "sfx-default-path", "~/storage/audio/sfx/default", False)
set_option(section, "sfx-repository-path", "~/storage/audio/sfx/all", False) set_option(section, "sfx-repository-path", "~/storage/audio/sfx/all", False)
set_option(section, "sfx-project-path", "sfx", False) set_option(section, "sfx-project-path", "sfx", False)
set_option(section, "sfx-volume", "1.0", False)
set_option(section, "sfx-extensions", "wav, ogg, mp3", False) set_option(section, "sfx-extensions", "wav, ogg, mp3", False)
set_option(section, "bgm-repository-path", "~/storage/audio/bgm", False)
set_option(section, "bgm-project-path", "bgm", False)
set_option(section, "sfx-volume", "1.0", False)
set_option(section, "panel-font", None, False) set_option(section, "panel-font", None, False)
section = "interpolator-gui" section = "interpolator-gui"
add_section(section) add_section(section)
@ -407,6 +410,8 @@ class TypeDeclarations(dict):
"int-list": ["dimensions", "framerate-text-color", "int-list": ["dimensions", "framerate-text-color",
"framerate-text-background"]}, "framerate-text-background"]},
"input": {"bool": "confirm-quit"},
"screen-captures": {"path": ["rel-path", "path"]}, "screen-captures": {"path": ["rel-path", "path"]},
"video-recordings": {"path": ["rel-path", "path"], "video-recordings": {"path": ["rel-path", "path"],
@ -443,7 +448,8 @@ class TypeDeclarations(dict):
"path": "panel-font", "path": "panel-font",
"path-list": [ "path-list": [
"sfx-default-path", "sfx-repository-path", "sfx-project-path" "sfx-default-path", "sfx-repository-path", "sfx-project-path",
"bgm-repository-path", "bgm-project-path"
], ],
"float": "sfx-volume" "float": "sfx-volume"

View File

@ -1,3 +1,5 @@
import collections
from pygame.event import get, pump, Event, post from pygame.event import get, pump, Event, post
from pygame.locals import * from pygame.locals import *
@ -8,7 +10,7 @@ class Delegate(GameChild):
def __init__(self, game): def __init__(self, game):
GameChild.__init__(self, game) GameChild.__init__(self, game)
self.subscribers = dict() self.subscribers = collections.OrderedDict()
self.load_configuration() self.load_configuration()
self.disable() self.disable()
self.cancelling_propagation = False self.cancelling_propagation = False

View File

@ -13,12 +13,16 @@ from .Profile import Profile
from .VideoRecorder import VideoRecorder from .VideoRecorder import VideoRecorder
from .Interpolator import Interpolator from .Interpolator import Interpolator
from .TimeFilter import TimeFilter from .TimeFilter import TimeFilter
from .Sprite import Sprite
from .confirm_quit_message import CONFIRM_QUIT_MESSAGE
from .extension import *
class Game(Animation): class Game(Animation):
resource_path = None resource_path = None
def __init__(self, config_rel_path=None, type_declarations=None): def __init__(self, config_rel_path=None, type_declarations=None):
self.confirming_quit = False
self.profile = Profile(self) self.profile = Profile(self)
self.time_filter = TimeFilter(self) self.time_filter = TimeFilter(self)
Animation.__init__(self, self) Animation.__init__(self, self)
@ -32,8 +36,6 @@ class Game(Animation):
os.putenv("SDL_NOMOUSE", "1") os.putenv("SDL_NOMOUSE", "1")
pygame.init() pygame.init()
self.set_children() self.set_children()
self.subscribe(self.end, QUIT)
self.subscribe(self.end)
self.register(self.get_input().unsuppress) self.register(self.get_input().unsuppress)
self.delegate.enable() self.delegate.enable()
@ -44,6 +46,8 @@ class Game(Animation):
def set_children(self): def set_children(self):
self.delegate = Delegate(self) self.delegate = Delegate(self)
self.subscribe(self.end, QUIT)
self.subscribe(self.end)
self.display = Display(self) self.display = Display(self)
self.mainloop = Mainloop(self) self.mainloop = Mainloop(self)
self.input = Input(self) self.input = Input(self)
@ -57,13 +61,37 @@ class Game(Animation):
self.delegate.dispatch() self.delegate.dispatch()
if not self.interpolator.is_gui_active(): if not self.interpolator.is_gui_active():
Animation.update(self) Animation.update(self)
self.update() if self.confirming_quit:
self.time_filter.close()
self.draw_confirm_quit_dialog()
else:
self.time_filter.open()
self.update()
else: else:
self.interpolator.gui.update() self.interpolator.gui.update()
self.audio.update() self.audio.update()
if self.video_recorder.requested: if self.video_recorder.requested:
self.video_recorder.update() self.video_recorder.update()
def draw_confirm_quit_dialog(self):
dialog = Sprite(self)
dialog.add_frame(self.get_confirm_quit_surface())
dialog.location.center = self.get_display_surface().get_rect().center
dialog.update()
def get_confirm_quit_surface(self):
lines = CONFIRM_QUIT_MESSAGE.strip().split("\n")
w, h = len(lines[0]), len(lines)
text = pygame.Surface((w, h), SRCALPHA)
for y, line in enumerate(lines):
for x, char in enumerate(line.strip()):
if char == "#":
text.set_at((x, y), (0, 0, 0))
text = pygame.transform.scale2x(text)
text = pygame.transform.scale2x(text)
return get_boxed_surface(
text, background=(255, 255, 255), border=(0, 0, 0), border_width=24, padding=12)
def run(self): def run(self):
self.mainloop.run() self.mainloop.run()
@ -80,7 +108,16 @@ class Game(Animation):
self.get_input().suppress() self.get_input().suppress()
self.play(self.get_input().unsuppress, delay=length, play_once=True) self.play(self.get_input().unsuppress, delay=length, play_once=True)
def end(self, evt): def end(self, event):
if evt.type == QUIT or self.delegate.compare(evt, "quit"): if event.type == QUIT or self.delegate.compare(event, "quit"):
self.mainloop.stop() if self.confirming_quit or not self.get_configuration("input", "confirm-quit"):
self.profile.end() self.mainloop.stop()
self.profile.end()
else:
print("press quit again to confirm or press any other button to cancel...")
self.confirming_quit = True
elif self.delegate.compare(event, "any"):
if self.confirming_quit and self.delegate.compare(event, "any"):
print("cancelled quit...")
self.confirming_quit = False
self.delegate.cancel_propagation()

View File

@ -1,7 +1,7 @@
from time import time as get_secs from time import time as get_secs
from pygame import joystick as joy from pygame import joystick as joy
from pygame.key import get_pressed from pygame.key import get_pressed, get_mods
from pygame.locals import * from pygame.locals import *
from .GameChild import * from .GameChild import *
@ -18,6 +18,7 @@ class Input(GameChild):
self.load_configuration() self.load_configuration()
self.set_any_press_ignore_list() self.set_any_press_ignore_list()
self.unsuppress() self.unsuppress()
self.unsuppress_any_on_mods()
self.subscribe_to_events() self.subscribe_to_events()
self.build_key_map() self.build_key_map()
self.build_joy_button_map() self.build_joy_button_map()
@ -31,12 +32,19 @@ class Input(GameChild):
def set_any_press_ignore_list(self): def set_any_press_ignore_list(self):
self.any_press_ignored = set(["capture-screen", "toggle-fullscreen", self.any_press_ignored = set(["capture-screen", "toggle-fullscreen",
"reset-game", "record-video", "quit", "reset-game", "record-video", "quit",
"mute", "toggle-interpolator"]) "toggle-interpolator", "toggle-audio-panel",
"volume-down", "volume-up", "volume-mute"])
self.any_press_ignored_keys = set() self.any_press_ignored_keys = set()
def unsuppress(self): def unsuppress(self):
self.suppressed = False self.suppressed = False
def unsuppress_any_on_mods(self):
self.suppressed_any_key_on_mods = False
def suppress_any_key_on_mods(self):
self.suppressed_any_key_on_mods = True
def subscribe_to_events(self): def subscribe_to_events(self):
self.subscribe(self.translate_key, KEYDOWN) self.subscribe(self.translate_key, KEYDOWN)
self.subscribe(self.translate_key, KEYUP) self.subscribe(self.translate_key, KEYUP)
@ -72,7 +80,9 @@ class Input(GameChild):
self.post_command(cmd, cancel=cancel) self.post_command(cmd, cancel=cancel)
posted = cmd posted = cmd
if (not posted or posted not in self.any_press_ignored) and \ if (not posted or posted not in self.any_press_ignored) and \
key not in self.any_press_ignored_keys: key not in self.any_press_ignored_keys and \
(not self.suppressed_any_key_on_mods or \
(not get_mods() & KMOD_CTRL and not get_mods() & KMOD_ALT)):
self.post_any_command(key, cancel) self.post_any_command(key, cancel)
def post_command(self, cmd, **attributes): def post_command(self, cmd, **attributes):

View File

@ -25,6 +25,7 @@ class Sprite(Animation):
self.neighbors = neighbors self.neighbors = neighbors
self.mass = mass self.mass = mass
self._step = EVector() self._step = EVector()
self.children = {}
self.set_frameset(0) self.set_frameset(0)
self.locations.append(Location(self)) self.locations.append(Location(self))
self.motion_overflow = Vector() self.motion_overflow = Vector()
@ -38,6 +39,15 @@ class Sprite(Animation):
return Animation.__getattr__(self, name) return Animation.__getattr__(self, name)
raise AttributeError(name) raise AttributeError(name)
def add_frameset(self, order=[], framerate=None, name=None, switch=False):
if framerate is None:
framerate = self.accounts[self.default_method].interval
frameset = Frameset(self, order, framerate, name)
self.framesets.append(frameset)
if switch:
self.set_frameset(len(self.framesets) - 1)
return frameset
def set_frameset(self, identifier): def set_frameset(self, identifier):
if isinstance(identifier, str): if isinstance(identifier, str):
for ii, frameset in enumerate(self.framesets): for ii, frameset in enumerate(self.framesets):
@ -109,6 +119,10 @@ class Sprite(Animation):
if frameset.length() > 1: if frameset.length() > 1:
self.play() self.play()
def add_frames(self, frames):
for frame in frames:
self.add_frame(frame)
def shift_frame(self): def shift_frame(self):
self.get_current_frameset().shift() self.get_current_frameset().shift()
@ -188,13 +202,6 @@ class Sprite(Animation):
for location in self.locations: for location in self.locations:
location.fader.set_alpha() location.fader.set_alpha()
def add_frameset(self, order=[], framerate=None, name=None, switch=False):
frameset = Frameset(self, order, framerate, name)
self.framesets.append(frameset)
if switch:
self.set_frameset(len(self.framesets) - 1)
return frameset
def hide(self): def hide(self):
for location in self.locations: for location in self.locations:
location.hide() location.hide()
@ -232,6 +239,15 @@ class Sprite(Animation):
def is_stepping(self): def is_stepping(self):
return bool(self._step) return bool(self._step)
def set_child(self, name, child):
self.children[name] = child
def get_child(self, name):
return self.children[name]
def has_child(self, name):
return name in self.children
def update(self, areas=None, substitute=None, flags=0): def update(self, areas=None, substitute=None, flags=0):
Animation.update(self) Animation.update(self)
if self.is_stepping(): if self.is_stepping():
@ -241,6 +257,8 @@ class Sprite(Animation):
neighbor.mass)) neighbor.mass))
if self.get_current_frameset().length(): if self.get_current_frameset().length():
self.draw(areas, substitute, flags) self.draw(areas, substitute, flags)
for child in self.children.values():
child.update()
# for location in self.locations: # for location in self.locations:
# location.update() # location.update()

View File

@ -0,0 +1,10 @@
CONFIRM_QUIT_MESSAGE = '''
............................................................
..###..###..#....#.####.#.####..#...#....###..#...#.#.#####.
.#....#...#.##...#.#....#.#...#.##.##...#...#.#...#.#...#...
.#....#...#.#.#..#.####.#.#...#.#.#.#...#...#.#...#.#...#...
.#....#...#.#..#.#.#....#.####..#...#...#.#.#.#...#.#...#...
.#....#...#.#...##.#....#.#...#.#...#...#..#..#...#.#...#...
..###..###..#....#.#....#.#...#.#...#....##.#..###..#...#...
............................................................
'''

View File

@ -1,12 +1,20 @@
import itertools, random import itertools, random
from math import sin, cos, atan2, radians, sqrt, pi from math import sin, cos, atan2, radians, sqrt, pi
from pygame import Surface, PixelArray, Color, Rect, draw from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw
from pygame.mixer import get_num_channels, Channel from pygame.mixer import get_num_channels, Channel
from pygame.locals import * from pygame.locals import *
from .Vector import Vector from .Vector import Vector
def clamp(n, min_n, max_n):
if n < min_n:
return min_n
elif n > max_n:
return max_n
else:
return n
def get_step(start, end, speed): def get_step(start, end, speed):
x0, y0 = start x0, y0 = start
x1, y1 = end x1, y1 = end
@ -352,6 +360,27 @@ def get_lightened_color(color, lightness):
h, s, _, a = color.hsla h, s, _, a = color.hsla
return get_hsla_color(h, s, lightness, a) return get_hsla_color(h, s, lightness, a)
def get_glow_frames(radius, segments, colors=[(0, 0, 0), (255, 255, 255)], minsize=4, transparency=True):
frames = []
radius = int(round(radius))
sizes = [int(round(minsize + float(ii) / (segments - 1) * (radius - minsize))) for ii in range(segments)]
if transparency:
alpha_step = 255.0 / segments
alpha = alpha_step
else:
alpha = 255
for color_offset in range(len(colors)):
frame = Surface([radius * 2] * 2, SRCALPHA if transparency else None)
if transparency:
alpha = alpha_step
for segment_ii, segment_radius in enumerate(reversed(sizes)):
color = Color(*(colors[(color_offset + segment_ii) % len(colors)] + (int(round(alpha)),)))
gfxdraw.filled_circle(frame, radius, radius, int(round(segment_radius)), color)
if transparency:
alpha += alpha_step
frames.append(frame)
return frames
# http://www.pygame.org/wiki/BezierCurve # http://www.pygame.org/wiki/BezierCurve
def compute_bezier_points(vertices, numPoints=60): def compute_bezier_points(vertices, numPoints=60):
points = [] points = []