- fix include delay bug in Animation class

- multiple values for boolean true in config files
- sprite wipe out animation
- sprite children optionally drawn on parent surface
- text wrapping function handles line breaks
- blinds animation function for returning rects
This commit is contained in:
Frank DeMarco 2020-05-20 03:49:28 -04:00
parent 0878ad6181
commit 301f547a25
5 changed files with 171 additions and 84 deletions

View File

@ -58,7 +58,7 @@ class Animation(GameChild):
include_delay) include_delay)
def is_account_playing(self, account, include_delay): def is_account_playing(self, account, include_delay):
return account.playing and (not include_delay or not account.delay) return account.playing and (include_delay or not account.delay)
def reset_timer(self, method=None): def reset_timer(self, method=None):
if not method: if not method:

View File

@ -8,19 +8,20 @@ from .Input import *
from .Animation import * from .Animation import *
from .extension import * from .extension import *
class Audio(GameChild): class Audio(Animation):
UP, DOWN = .1, -.1 UP, DOWN = .1, -.1
BASE_VOLUME = 1.0 BASE_VOLUME = 1.0
CONFIG_SEPARATOR = "," CONFIG_SEPARATOR = ","
def __init__(self, game): def __init__(self, game):
GameChild.__init__(self, game) Animation.__init__(self, game)
# self.original_volumes = {} # self.original_volumes = {}
self.current_bgm = None self.current_bgm = None
self.volume = self.BASE_VOLUME self.volume = self.BASE_VOLUME
if self.check_command_line("-mute"): if self.check_command_line("-mute"):
self.volume = 0 self.volume = 0
self.register(self.play_sfx)
self.audio_panel = AudioPanel(self) self.audio_panel = AudioPanel(self)
self.subscribe(self.respond) self.subscribe(self.respond)
self.sfx = {} self.sfx = {}
@ -106,6 +107,9 @@ class Audio(GameChild):
return True return True
return False return False
def play_sfx(self, name):
self.sfx[name].play()
# #
# Loading BGM procedure # Loading BGM procedure
# #
@ -180,13 +184,7 @@ class Audio(GameChild):
return True return True
def update(self): def update(self):
# for ii in range(pygame.mixer.get_num_channels()): Animation.update(self)
# channel = pygame.mixer.Channel(ii)
# sound = channel.get_sound()
# if sound is not None:
# if sound not in self.original_volumes.keys():
# self.original_volumes[sound] = sound.get_volume()
# sound.set_volume(self.original_volumes[sound] * self.volume)
self.audio_panel.update() self.audio_panel.update()
@ -209,6 +207,9 @@ class BGM(GameChild):
def get_volume(self): def get_volume(self):
return self.volume return self.volume
def __eq__(self, other):
return self.path == other.path
class SoundEffect(GameChild, pygame.mixer.Sound): class SoundEffect(GameChild, pygame.mixer.Sound):
@ -329,6 +330,7 @@ class AudioPanel(Animation):
self.active = False self.active = False
if self.bgm_elapsed is not None: if self.bgm_elapsed is not None:
self.get_audio().play_bgm(start=self.bgm_elapsed) self.get_audio().play_bgm(start=self.bgm_elapsed)
self.file_browser.hide()
def respond(self, event): def respond(self, event):
if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx: if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx:
@ -444,7 +446,7 @@ class AudioPanelRow(BlinkingSprite):
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def respond(self, event): def respond(self, event):
if event.button == 1: if self.parent.active and event.button == 1:
if self.parent.file_browser.is_hidden() and self.location.collidepoint(event.pos): if self.parent.file_browser.is_hidden() and self.location.collidepoint(event.pos):
if not self.selected: if not self.selected:
self.parent.file_browser.visit(self.parent.file_browser.HOME) self.parent.file_browser.visit(self.parent.file_browser.HOME)
@ -530,6 +532,15 @@ class AudioPanelRow(BlinkingSprite):
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 set_clickable(self, clickable=True):
self.play_button.set_clickable(clickable)
self.stop_button.set_clickable(clickable)
self.volume_spinner.set_clickable(clickable)
if not self.is_bgm:
self.fade_out_spinner.set_clickable(clickable)
self.loops_spinner.set_clickable(clickable)
self.maxtime_spinner.set_clickable(clickable)
def update(self): def update(self):
self.play_button.location.midleft = self.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 self.stop_button.location.midleft = self.play_button.location.midright
@ -612,8 +623,7 @@ 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.set_clickable(True)
row.stop_button.set_clickable()
for row in self.rows: for row in self.rows:
if row.has_child("button"): if row.has_child("button"):
row.get_child("button").set_clickable(False) row.get_child("button").set_clickable(False)
@ -623,8 +633,7 @@ class AudioPanelFileBrowser(Sprite):
def unhide(self): def unhide(self):
for row in self.parent.rows: for row in self.parent.rows:
row.play_button.set_clickable(False) row.set_clickable(False)
row.stop_button.set_clickable(False)
for row in self.rows: for row in self.rows:
if row.has_child("button"): if row.has_child("button"):
row.get_child("button").set_clickable() row.get_child("button").set_clickable()
@ -762,6 +771,7 @@ class AudioPanelSpinner(Sprite):
self.down_button.location.topleft = self.display.location.right - 1, \ self.down_button.location.topleft = self.display.location.right - 1, \
self.up_button.location.bottom - 1 self.up_button.location.bottom - 1
self.down_button.display_surface = self.get_current_frame() self.down_button.display_surface = self.get_current_frame()
self.set_clickable()
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):
@ -788,7 +798,7 @@ class AudioPanelSpinner(Sprite):
self.update_display() self.update_display()
def respond(self, event): def respond(self, event):
if event.button == 1: if self.clickable and event.button == 1:
relative_position = Vector(*event.pos).get_moved( relative_position = Vector(*event.pos).get_moved(
-self.location.left, -self.location.top) -self.location.left, -self.location.top)
up_collides = self.up_button.collide(relative_position) up_collides = self.up_button.collide(relative_position)
@ -800,6 +810,9 @@ class AudioPanelSpinner(Sprite):
self.increment(False) self.increment(False)
self.parent.update_config() self.parent.update_config()
def set_clickable(self, clickable=True):
self.clickable = clickable
def update(self): def update(self):
self.get_current_frame().fill(self.background) self.get_current_frame().fill(self.background)
self.label.update() self.label.update()
@ -816,8 +829,8 @@ class AudioPanelButton(Sprite):
self.callback = callback self.callback = callback
self.callback_kwargs = callback_kwargs self.callback_kwargs = callback_kwargs
self.containers = containers self.containers = containers
self.clickable = True
self.pass_mods = pass_mods self.pass_mods = pass_mods
self.set_clickable()
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):
@ -827,7 +840,7 @@ class AudioPanelButton(Sprite):
Sprite.unsubscribe(self, callback, kind) Sprite.unsubscribe(self, callback, kind)
def respond(self, event): def respond(self, event):
if self.clickable and event.button == 1: if self.get_audio().audio_panel.active and self.clickable and event.button == 1:
pos = Vector(*event.pos) pos = Vector(*event.pos)
for container in self.containers: for container in self.containers:
pos.move(-container.location.left, -container.location.top) pos.move(-container.location.left, -container.location.top)

View File

@ -67,7 +67,7 @@ class Configuration(RawConfigParser):
set_option(section, "whitespace-placeholder", "-", False) set_option(section, "whitespace-placeholder", "-", False)
set_option(section, "windows-dist-path", "dist/win/", False) set_option(section, "windows-dist-path", "dist/win/", False)
set_option(section, "windows-icon-path", "", False) set_option(section, "windows-icon-path", "", False)
set_option(section, "lowercase-boolean-true", "yes", False) set_option(section, "boolean-true-lowercase", "yes, true, t, 1", False)
set_option(section, "osx-includes", "", False) set_option(section, "osx-includes", "", False)
section = "display" section = "display"
add_section(section) add_section(section)
@ -275,9 +275,7 @@ class Configuration(RawConfigParser):
# if type(value) == str or type(value) == unicode: # if type(value) == str or type(value) == unicode:
if type(value) == str: if type(value) == str:
if pair in types["bool"]: if pair in types["bool"]:
if value.lower() == self.get("setup", "lowercase-boolean-true"): return value.lower() in self.get("setup", "boolean-true-lowercase")
return True
return False
elif pair in types["int"]: elif pair in types["int"]:
return int(value) return int(value)
elif pair in types["float"]: elif pair in types["float"]:
@ -422,7 +420,7 @@ class TypeDeclarations(dict):
"setup": {"list": ["classifiers", "resource-search-path", "setup": {"list": ["classifiers", "resource-search-path",
"requirements", "data-exclude", "requirements", "data-exclude",
"additional-packages", "osx-includes"], "additional-packages", "osx-includes", "boolean-true-lowercase"],
"path": ["installation-dir", "changelog", "description-file", "path": ["installation-dir", "changelog", "description-file",
"main-object", "icon-path", "windows-dist-path", "main-object", "icon-path", "windows-dist-path",

View File

@ -1,6 +1,6 @@
import collections
from os import listdir from os import listdir
from os.path import isfile, join from os.path import isfile, join
from sys import exc_info, stdout
from glob import glob from glob import glob
from pygame import Color, Rect, Surface, PixelArray, mask from pygame import Color, Rect, Surface, PixelArray, mask
@ -10,11 +10,11 @@ from pygame.locals import *
from .Animation import Animation from .Animation import Animation
from .Vector import Vector, EVector from .Vector import Vector, EVector
from .extension import get_hue_shifted_surface, get_step from .extension import get_hue_shifted_surface, get_step, load_frames, get_blinds_rects
class Sprite(Animation): class Sprite(Animation):
def __init__(self, parent, framerate=None, neighbors=[], mass=None): def __init__(self, parent, framerate=None, draw_children_on_frame=True):
Animation.__init__(self, parent, self.shift_frame, framerate) Animation.__init__(self, parent, self.shift_frame, framerate)
self.frames = [] self.frames = []
self.mirrored = False self.mirrored = False
@ -22,15 +22,20 @@ class Sprite(Animation):
self.locations = [] self.locations = []
self.framesets = [Frameset(self, framerate=framerate)] self.framesets = [Frameset(self, framerate=framerate)]
self.frameset_index = 0 self.frameset_index = 0
self.neighbors = neighbors
self.mass = mass
self._step = EVector() self._step = EVector()
self.children = {} self.children = collections.OrderedDict()
self.draw_children_on_frame = draw_children_on_frame
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()
self.display_surface = self.get_display_surface() self.display_surface = self.get_display_surface()
self.register(self.toggle_hidden) self.wipe_blinds = []
self.current_wipe_index = 0
self.register(self.toggle_hidden, self.wipe_out)
def reset(self):
self.halt(self.wipe_out)
self.current_wipe_index = 0
def __getattr__(self, name): def __getattr__(self, name):
if name in ("location", "rect"): if name in ("location", "rect"):
@ -61,6 +66,7 @@ class Sprite(Animation):
if self.get_current_frameset().length() > 1: if self.get_current_frameset().length() > 1:
self.play() self.play()
self.get_current_frameset().reset() self.get_current_frameset().reset()
return self.get_current_frameset()
def register_interval(self): def register_interval(self):
self.register(self.shift_frame, self.register(self.shift_frame,
@ -81,25 +87,10 @@ class Sprite(Animation):
def load_from_path(self, path, transparency=False, ppa=True, key=None, def load_from_path(self, path, transparency=False, ppa=True, key=None,
query=None, omit=False): query=None, omit=False):
if isfile(path): for frame in load_frames(self.get_resource(path), transparency, ppa, key, query):
paths = [path]
else:
if query:
paths = sorted(glob(join(path, query)))
else:
paths = [join(path, name) for name in sorted(listdir(path))]
for path in paths:
img = load(path)
if transparency:
if ppa:
frame = img.convert_alpha()
else:
frame = self.fill_colorkey(img, key)
else:
frame = img.convert()
self.add_frame(frame, omit) self.add_frame(frame, omit)
def fill_colorkey(self, img, key=None): def fill_colorkey(img, key=None):
if not key: if not key:
key = (255, 0, 255) key = (255, 0, 255)
img = img.convert_alpha() img = img.convert_alpha()
@ -116,6 +107,8 @@ class Sprite(Animation):
frameset = self.get_current_frameset() frameset = self.get_current_frameset()
frameset.add_index(self.frames.index(frame)) frameset.add_index(self.frames.index(frame))
self.update_location_size() self.update_location_size()
self.wipe_blinds = get_blinds_rects(*self.location.size)
self.wipe_blinds.reverse()
if frameset.length() > 1: if frameset.length() > 1:
self.play() self.play()
@ -205,14 +198,20 @@ class Sprite(Animation):
def hide(self): def hide(self):
for location in self.locations: for location in self.locations:
location.hide() location.hide()
for child in self.children.values():
child.hide()
def unhide(self): def unhide(self):
for location in self.locations: for location in self.locations:
location.unhide() location.unhide()
for child in self.children.values():
child.unhide()
def toggle_hidden(self): def toggle_hidden(self):
for location in self.locations: for location in self.locations:
location.toggle_hidden() location.toggle_hidden()
for child in self.children.values():
child.toggle_hidden()
def is_hidden(self): def is_hidden(self):
return all(location.is_hidden() for location in self.locations) return all(location.is_hidden() for location in self.locations)
@ -248,19 +247,41 @@ class Sprite(Animation):
def has_child(self, name): def has_child(self, name):
return name in self.children return name in self.children
def remove_child(self, name):
if self.has_child(name):
self.children.pop(name)
def wipe_out(self):
for child in self.children.values():
if not child.is_playing(child.wipe_out):
child.play(child.wipe_out)
self.current_wipe_index += 1
if self.current_wipe_index == len(self.wipe_blinds):
self.current_wipe_index = 0
self.halt(self.wipe_out)
self.hide()
def get_current_blinds(self):
return self.wipe_blinds[self.current_wipe_index]
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():
self.move(self._step.dx, self._step.dy) self.move(self._step.dx, self._step.dy)
for neighbor in self.neighbors:
self.move(*get_step(self.location.center, neighbor.location.center,
neighbor.mass))
if self.get_current_frameset().length(): if self.get_current_frameset().length():
if self.is_playing(self.wipe_out):
areas = self.get_current_blinds()
self.draw(areas, substitute, flags) self.draw(areas, substitute, flags)
for child in self.children.values(): for child in self.children.values():
if self.draw_children_on_frame:
child.display_surface = self.get_current_frame()
else:
child.display_surface = self.get_display_surface()
save_child_topleft = child.location.topleft
child.move(*self.location.topleft)
child.update() child.update()
# for location in self.locations: if not self.draw_children_on_frame:
# location.update() child.location.topleft = save_child_topleft
def draw(self, areas=None, substitute=None, flags=0): def draw(self, areas=None, substitute=None, flags=0):
for location in self.locations: for location in self.locations:
@ -316,18 +337,6 @@ class Location(Rect):
def is_hidden(self): def is_hidden(self):
return self.hidden return self.hidden
def update(self):
pass
# for neighbor in self.sprite.neighbors:
# if neighbor.mass:
# closest = neighbor.location
# for location in neighbor.locations[1:]:
# if get_distance(self.center, location.center) < \
# get_distance(self.center, closest.center):
# closest = location
# self.move_ip(get_step(self.center, closest.center,
# neighbor.mass))
class Fader(Surface): class Fader(Surface):
@ -493,6 +502,7 @@ class BlinkingSprite(Sprite):
def __init__(self, parent, blink_rate, framerate=None): def __init__(self, parent, blink_rate, framerate=None):
Sprite.__init__(self, parent, framerate) Sprite.__init__(self, parent, framerate)
self.register(self.blink, interval=blink_rate) self.register(self.blink, interval=blink_rate)
self.register(self.start_blinking, self.stop_blinking)
self.play(self.blink) self.play(self.blink)
def reset(self): def reset(self):

View File

@ -1,6 +1,7 @@
import itertools, random import itertools, random, os, glob
from math import sin, cos, atan2, radians, sqrt, pi from math import sin, cos, atan2, radians, sqrt, pi
import pygame
from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw 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 *
@ -119,6 +120,16 @@ def place_in_rect(rect, incoming, contain=True, *args):
if not collides: if not collides:
break break
def constrain_dimensions_2d(vector, container):
dw = vector[0] - container[0]
dh = vector[1] - container[1]
if dw > 0 or dh > 0:
if dw > dh:
size = container[0], int(round(container[0] / vector[0] * vector[1]))
else:
size = int(round(container[1] / vector[1] * vector[0])), container[1]
return size
# from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c # from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c
def get_intersection(p0, p1, p2, p3): def get_intersection(p0, p1, p2, p3):
x0, y0 = p0 x0, y0 = p0
@ -215,16 +226,21 @@ def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background
surface.fill(background) surface.fill(background)
return get_boxed_surface(surface, background, border, border_width, padding) return get_boxed_surface(surface, background, border, border_width, padding)
def get_wrapped_text_surface(font, text, width, antialias, color, def get_wrapped_text_surface(font, text, width, antialias=True, color=(0, 0, 0),
background=None, border=None, border_width=1, background=None, border=None, border_width=1,
padding=0): padding=0, align="left"):
words = text.split() lines = []
if words: height = 0
for chunk in text.split("\n"):
line_text = "" line_text = ""
lines = []
height = 0
ii = 0 ii = 0
finished = False finished = False
if chunk.startswith("\*") and chunk.endswith("\*"):
chunk = chunk.replace("\*", "*")
elif chunk.startswith("*") and chunk.endswith("*"):
chunk = chunk[1:-1]
font.set_italic(True)
words = chunk.split(" ")
while not finished: while not finished:
line_width = font.size(line_text + " " + words[ii])[0] line_width = font.size(line_text + " " + words[ii])[0]
if line_width > width or ii == len(words) - 1: if line_width > width or ii == len(words) - 1:
@ -233,25 +249,26 @@ def get_wrapped_text_surface(font, text, width, antialias, color,
line_text += " " line_text += " "
line_text += words[ii] line_text += words[ii]
finished = True finished = True
line = font.render(line_text, antialias, color, background) line = font.render(line_text, antialias, color)
height += line.get_height() height += line.get_height()
lines.append(line) lines.append(line)
line_text = "" line_text = ""
else: else:
line_text += " " + words[ii] line_text += " " + words[ii]
ii += 1 ii += 1
top = 0 font.set_italic(False)
surface = Surface((width, height), SRCALPHA) top = 0
if background: surface = Surface((width, height), pygame.SRCALPHA)
surface.fill(background) if background:
rect = surface.get_rect() surface.fill(background)
for line in lines: rect = surface.get_rect()
line_rect = line.get_rect() for line in lines:
line_rect.midtop = rect.centerx, top line_rect = line.get_rect()
surface.blit(line, line_rect) line_rect.top = top
top += line_rect.h if align == "center":
else: line_rect.centerx = rect.centerx
surface = Surface((0, 0), SRCALPHA) surface.blit(line, line_rect)
top += line_rect.h
return get_boxed_surface(surface, background, border, border_width, padding) return get_boxed_surface(surface, background, border, border_width, padding)
def replace_color(surface, color, replacement): def replace_color(surface, color, replacement):
@ -305,6 +322,37 @@ def fill_tile(surface, tile, rect=None):
for y in range(0, surface.get_height(), h): for y in range(0, surface.get_height(), h):
surface.blit(tile, (x, y)) surface.blit(tile, (x, y))
def load_frames(path, transparency=False, ppa=True, key=None, query=None):
if os.path.isfile(path):
paths = [path]
else:
if query:
paths = sorted(glob.glob(os.path.join(path, query)))
else:
paths = [os.path.join(path, name) for name in sorted(os.listdir(path))]
frames = []
for path in paths:
img = pygame.image.load(path)
if transparency:
if ppa:
frame = img.convert_alpha()
else:
frame = fill_colorkey(img, key)
else:
frame = img.convert()
frames.append(frame)
return frames
def fill_colorkey(img, key=None):
if key is None:
key = 255, 0, 255
img = img.convert_alpha()
frame = Surface(img.get_size())
frame.fill(key)
frame.set_colorkey(key)
frame.blit(img, (0, 0))
return frame
def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0), def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0),
colorkey=(255, 0, 255)): colorkey=(255, 0, 255)):
foreground = font.render(text, antialias, color) foreground = font.render(text, antialias, color)
@ -321,6 +369,24 @@ def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0
(abs(offset[1]) - offset[1]) / 2)) (abs(offset[1]) - offset[1]) / 2))
return surface return surface
def get_blinds_rects(w, h, step=.05, count=4):
blinds = []
blind_h = int(round(h / float(count)))
for ii in range(1, count + 1):
blinds.append(Rect(0, blind_h * ii, w, 0))
inflate_h = int(round(blind_h * step))
if inflate_h < 1:
inflate_h = 1
rects = []
while blinds[0].h < blind_h:
rects.append([])
for blind in blinds:
bottom = blind.bottom
blind.inflate_ip(0, inflate_h)
blind.bottom = bottom
rects[-1].append(blind.copy())
return rects
def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)): def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)):
frames = [] frames = []
rects = [] rects = []