- 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):
if not method:
method = self.default_method
elif isinstance(method, str):
return getattr(self, method)
return method
def halt(self, method=None):

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import os, re, shutil, pygame
import os, re, shutil, pygame, sys
from .GameChild import *
from .Sprite import *
@ -23,6 +23,8 @@ class Audio(GameChild):
self.subscribe(self.respond)
self.sfx = {}
self.load_sfx()
self.bgm = {}
self.load_bgm()
def set_volume(self, volume=None, increment=None, mute=False):
if mute:
@ -94,7 +96,7 @@ class Audio(GameChild):
def load_sfx_file(self, path, name=None, replace=False, prefix="",
volume=1.0, fade_out=0, loops=0, maxtime=0):
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:
name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path))
if not replace and name in self.sfx:
@ -106,6 +108,57 @@ class Audio(GameChild):
return True
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):
# for ii in range(pygame.mixer.get_num_channels()):
# channel = pygame.mixer.Channel(ii)
@ -231,9 +284,7 @@ class AudioPanel(Animation):
self.active = False
def respond(self, event):
if self.get_delegate().compare(event, "toggle-audio-panel") and \
pygame.key.get_mods() & KMOD_CTRL and pygame.key.get_mods() & KMOD_SHIFT and \
self.get_audio().sfx:
if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx:
if self.active:
self.deactivate()
else:
@ -246,12 +297,16 @@ class AudioPanel(Animation):
self.row_offset += 1
elif event.button == 4:
self.row_offset -= 1
elif event.button == 3:
self.deactivate()
def build(self):
for row in self.rows:
row.unsubscribe()
del row
self.rows = []
for key in sorted(self.parent.bgm):
self.rows.append(AudioPanelRow(self, key, True))
for key in sorted(self.parent.sfx):
self.rows.append(AudioPanelRow(self, key))
@ -285,42 +340,52 @@ class AudioPanelRow(BlinkingSprite):
SLIDER_W = 60
BUTTON_W = 30
def __init__(self, parent, key):
def __init__(self, parent, key, is_bgm=False):
BlinkingSprite.__init__(self, parent, 500)
self.key = key
self.selected = False
self.font = self.parent.font_large
self.is_bgm = is_bgm
self.build()
font_medium = self.parent.font_medium
font_small = self.parent.font_small
self.volume_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .05,
self.get_sound_effect().local_volume,
self.get_sound_effect().adjust_volume, self.FOREGROUND,
self.BACKGROUND, 2, "vol")
self.fade_out_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().fade_out_length,
self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND,
self.BACKGROUND, 1, "fade")
self.loops_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, 1,
self.get_sound_effect().loops,
self.get_sound_effect().adjust_loop_count, self.FOREGROUND,
self.BACKGROUND, 0, "loops")
self.maxtime_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().maxtime,
self.get_sound_effect().adjust_maxtime, self.FOREGROUND,
self.BACKGROUND, 1, "cutoff")
self.play_button = AudioPanelButton(self, self.get_sound_effect().play)
if not self.is_bgm:
font_medium = self.parent.font_medium
font_small = self.parent.font_small
self.volume_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .05,
self.get_sound_effect().local_volume,
self.get_sound_effect().adjust_volume, self.FOREGROUND,
self.BACKGROUND, 2, "vol")
self.fade_out_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().fade_out_length,
self.get_sound_effect().adjust_fade_out_length, self.FOREGROUND,
self.BACKGROUND, 1, "fade")
self.loops_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, 1,
self.get_sound_effect().loops,
self.get_sound_effect().adjust_loop_count, self.FOREGROUND,
self.BACKGROUND, 0, "loops")
self.maxtime_spinner = AudioPanelSpinner(
self, font_medium, font_small, self.SLIDER_W, self.location.h, .1,
self.get_sound_effect().maxtime,
self.get_sound_effect().adjust_maxtime, self.FOREGROUND,
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.fill(self.BACKGROUND)
stop_button_frame = frame.copy()
w, h = frame.get_size()
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.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))
self.stop_button.add_frame(stop_button_frame)
self.stop_blinking()
@ -346,8 +411,9 @@ class AudioPanelRow(BlinkingSprite):
GameChild.unsubscribe(self, self.respond, pygame.MOUSEBUTTONDOWN)
self.play_button.unsubscribe()
self.stop_button.unsubscribe()
for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner:
spinner.unsubscribe()
if not self.is_bgm:
for spinner in self.volume_spinner, self.fade_out_spinner, self.loops_spinner, self.maxtime_spinner:
spinner.unsubscribe()
def build(self):
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.display_surface = surface
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.display_surface = box
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):
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):
if not self.get_configuration().has_section("sfx"):
self.get_configuration().add_section("sfx")
sound_effect = self.get_sound_effect()
self.get_configuration().set(
"sfx", self.key, "{}, {:.2f}, {:.2f}, {}, {:.2f}".format(
if self.is_bgm:
section_name = "bgm"
else:
section_name = "sfx"
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.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()
backup_path = config_path + ".backup"
shutil.copyfile(config_path, backup_path)
self.get_configuration().write(open(config_path, "w"))
def update(self):
self.volume_spinner.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.play_button.location.midleft = self.location.move(5, 0).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)
self.volume_spinner.update()
self.fade_out_spinner.update()
self.loops_spinner.update()
self.maxtime_spinner.update()
if not self.is_bgm:
self.volume_spinner.update()
self.fade_out_spinner.update()
self.loops_spinner.update()
self.maxtime_spinner.update()
self.play_button.update()
self.stop_button.update()
@ -422,7 +504,9 @@ class AudioPanelFileBrowser(Sprite):
def __init__(self, parent):
Sprite.__init__(self, parent)
self.rows = []
self.font = self.parent.font_large
self.previewing_sound = None
ds = self.get_display_surface()
dsr = ds.get_rect()
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)
def reset(self):
self.visit(self.HOME)
if self.previewing_sound is not None:
self.previewing_sound.stop()
# self.visit(self.HOME)
self.hide()
def respond(self, event):
@ -442,14 +528,20 @@ class AudioPanelFileBrowser(Sprite):
if event.button == 1:
if self.collide(event.pos):
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)
if row.path == self.HOME or row.path == self.UP or \
os.path.isdir(full_path) and os.access(full_path, os.R_OK):
self.visit(row.path)
elif os.path.isfile(full_path) and os.access(full_path, os.R_OK):
loaded = False
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()
self.hide()
self.get_delegate().cancel_propagation()
@ -466,8 +558,24 @@ class AudioPanelFileBrowser(Sprite):
for row in self.parent.rows:
row.selected = False
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)
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):
if path == self.UP and len(self.trail) > 1:
path = self.trail[-2]
@ -478,7 +586,8 @@ class AudioPanelFileBrowser(Sprite):
if path == self.HOME:
self.trail = []
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):
if self.get_resource(sfx_location):
self.paths.append(self.get_resource(sfx_location))
@ -491,6 +600,10 @@ class AudioPanelFileBrowser(Sprite):
self.build()
def build(self):
for row in self.rows:
if row.has_child("button"):
row.get_child("button").unsubscribe()
del row
self.rows = []
for path in self.paths:
row = Sprite(self)
@ -498,10 +611,29 @@ class AudioPanelFileBrowser(Sprite):
text = self.font.render(path, True, self.COLORS[1])
surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA)
surface.blit(text, (0, 0))
surface.fill(self.COLORS[1], (0, surface.get_height() - 1, self.location.w, 1))
row.add_frame(surface)
row.display_surface = self.get_current_frame()
row.location.bottom = 0
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):
self.get_current_frame().blit(self.background, (0, 0))
@ -609,9 +741,12 @@ class AudioPanelSpinner(Sprite):
class AudioPanelButton(Sprite):
def __init__(self, parent, callback):
def __init__(self, parent, callback, callback_kwargs={}, containers=[]):
Sprite.__init__(self, parent)
self.callback = callback
self.callback_kwargs = callback_kwargs
self.containers = containers
self.clickable = True
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None):
@ -621,5 +756,12 @@ class AudioPanelButton(Sprite):
Sprite.unsubscribe(self, callback, kind)
def respond(self, event):
if event.button == 1 and self.collide(event.pos):
self.callback()
if self.clickable and event.button == 1:
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"
add_section(section)
set_option(section, "release-suffix", "-release", False)
set_option(section, "confirm-quit", "no", False)
section = "sprite"
add_section(section)
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-mute", "K_F3", 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"
add_section(section)
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-repository-path", "~/storage/audio/sfx/all", 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, "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)
section = "interpolator-gui"
add_section(section)
@ -407,6 +410,8 @@ class TypeDeclarations(dict):
"int-list": ["dimensions", "framerate-text-color",
"framerate-text-background"]},
"input": {"bool": "confirm-quit"},
"screen-captures": {"path": ["rel-path", "path"]},
"video-recordings": {"path": ["rel-path", "path"],
@ -443,7 +448,8 @@ class TypeDeclarations(dict):
"path": "panel-font",
"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"

View File

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

View File

@ -13,12 +13,16 @@ from .Profile import Profile
from .VideoRecorder import VideoRecorder
from .Interpolator import Interpolator
from .TimeFilter import TimeFilter
from .Sprite import Sprite
from .confirm_quit_message import CONFIRM_QUIT_MESSAGE
from .extension import *
class Game(Animation):
resource_path = None
def __init__(self, config_rel_path=None, type_declarations=None):
self.confirming_quit = False
self.profile = Profile(self)
self.time_filter = TimeFilter(self)
Animation.__init__(self, self)
@ -32,8 +36,6 @@ class Game(Animation):
os.putenv("SDL_NOMOUSE", "1")
pygame.init()
self.set_children()
self.subscribe(self.end, QUIT)
self.subscribe(self.end)
self.register(self.get_input().unsuppress)
self.delegate.enable()
@ -44,6 +46,8 @@ class Game(Animation):
def set_children(self):
self.delegate = Delegate(self)
self.subscribe(self.end, QUIT)
self.subscribe(self.end)
self.display = Display(self)
self.mainloop = Mainloop(self)
self.input = Input(self)
@ -57,13 +61,37 @@ class Game(Animation):
self.delegate.dispatch()
if not self.interpolator.is_gui_active():
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:
self.interpolator.gui.update()
self.audio.update()
if self.video_recorder.requested:
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):
self.mainloop.run()
@ -80,7 +108,16 @@ class Game(Animation):
self.get_input().suppress()
self.play(self.get_input().unsuppress, delay=length, play_once=True)
def end(self, evt):
if evt.type == QUIT or self.delegate.compare(evt, "quit"):
self.mainloop.stop()
self.profile.end()
def end(self, event):
if event.type == QUIT or self.delegate.compare(event, "quit"):
if self.confirming_quit or not self.get_configuration("input", "confirm-quit"):
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 pygame import joystick as joy
from pygame.key import get_pressed
from pygame.key import get_pressed, get_mods
from pygame.locals import *
from .GameChild import *
@ -18,6 +18,7 @@ class Input(GameChild):
self.load_configuration()
self.set_any_press_ignore_list()
self.unsuppress()
self.unsuppress_any_on_mods()
self.subscribe_to_events()
self.build_key_map()
self.build_joy_button_map()
@ -31,12 +32,19 @@ class Input(GameChild):
def set_any_press_ignore_list(self):
self.any_press_ignored = set(["capture-screen", "toggle-fullscreen",
"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()
def unsuppress(self):
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):
self.subscribe(self.translate_key, KEYDOWN)
self.subscribe(self.translate_key, KEYUP)
@ -72,7 +80,9 @@ class Input(GameChild):
self.post_command(cmd, cancel=cancel)
posted = cmd
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)
def post_command(self, cmd, **attributes):

View File

@ -25,6 +25,7 @@ class Sprite(Animation):
self.neighbors = neighbors
self.mass = mass
self._step = EVector()
self.children = {}
self.set_frameset(0)
self.locations.append(Location(self))
self.motion_overflow = Vector()
@ -38,6 +39,15 @@ class Sprite(Animation):
return Animation.__getattr__(self, 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):
if isinstance(identifier, str):
for ii, frameset in enumerate(self.framesets):
@ -109,6 +119,10 @@ class Sprite(Animation):
if frameset.length() > 1:
self.play()
def add_frames(self, frames):
for frame in frames:
self.add_frame(frame)
def shift_frame(self):
self.get_current_frameset().shift()
@ -188,13 +202,6 @@ class Sprite(Animation):
for location in self.locations:
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):
for location in self.locations:
location.hide()
@ -232,6 +239,15 @@ class Sprite(Animation):
def is_stepping(self):
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):
Animation.update(self)
if self.is_stepping():
@ -241,6 +257,8 @@ class Sprite(Animation):
neighbor.mass))
if self.get_current_frameset().length():
self.draw(areas, substitute, flags)
for child in self.children.values():
child.update()
# for location in self.locations:
# location.update()

View File

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

View File

@ -1,12 +1,20 @@
import itertools, random
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.locals import *
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):
x0, y0 = start
x1, y1 = end
@ -352,6 +360,27 @@ def get_lightened_color(color, lightness):
h, s, _, a = color.hsla
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
def compute_bezier_points(vertices, numPoints=60):
points = []