- 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)
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):
if not method:

View File

@ -8,19 +8,20 @@ from .Input import *
from .Animation import *
from .extension import *
class Audio(GameChild):
class Audio(Animation):
UP, DOWN = .1, -.1
BASE_VOLUME = 1.0
CONFIG_SEPARATOR = ","
def __init__(self, game):
GameChild.__init__(self, game)
Animation.__init__(self, game)
# self.original_volumes = {}
self.current_bgm = None
self.volume = self.BASE_VOLUME
if self.check_command_line("-mute"):
self.volume = 0
self.register(self.play_sfx)
self.audio_panel = AudioPanel(self)
self.subscribe(self.respond)
self.sfx = {}
@ -106,6 +107,9 @@ class Audio(GameChild):
return True
return False
def play_sfx(self, name):
self.sfx[name].play()
#
# Loading BGM procedure
#
@ -180,13 +184,7 @@ class Audio(GameChild):
return True
def update(self):
# for ii in range(pygame.mixer.get_num_channels()):
# 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)
Animation.update(self)
self.audio_panel.update()
@ -209,6 +207,9 @@ class BGM(GameChild):
def get_volume(self):
return self.volume
def __eq__(self, other):
return self.path == other.path
class SoundEffect(GameChild, pygame.mixer.Sound):
@ -329,6 +330,7 @@ class AudioPanel(Animation):
self.active = False
if self.bgm_elapsed is not None:
self.get_audio().play_bgm(start=self.bgm_elapsed)
self.file_browser.hide()
def respond(self, event):
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)
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 not self.selected:
self.parent.file_browser.visit(self.parent.file_browser.HOME)
@ -530,6 +532,15 @@ class AudioPanelRow(BlinkingSprite):
shutil.copyfile(config_path, backup_path)
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):
self.play_button.location.midleft = self.location.move(5, 0).midright
self.stop_button.location.midleft = self.play_button.location.midright
@ -612,8 +623,7 @@ 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()
row.set_clickable(True)
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable(False)
@ -623,8 +633,7 @@ class AudioPanelFileBrowser(Sprite):
def unhide(self):
for row in self.parent.rows:
row.play_button.set_clickable(False)
row.stop_button.set_clickable(False)
row.set_clickable(False)
for row in self.rows:
if row.has_child("button"):
row.get_child("button").set_clickable()
@ -762,6 +771,7 @@ class AudioPanelSpinner(Sprite):
self.down_button.location.topleft = self.display.location.right - 1, \
self.up_button.location.bottom - 1
self.down_button.display_surface = self.get_current_frame()
self.set_clickable()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None):
@ -788,7 +798,7 @@ class AudioPanelSpinner(Sprite):
self.update_display()
def respond(self, event):
if event.button == 1:
if self.clickable and event.button == 1:
relative_position = Vector(*event.pos).get_moved(
-self.location.left, -self.location.top)
up_collides = self.up_button.collide(relative_position)
@ -800,6 +810,9 @@ class AudioPanelSpinner(Sprite):
self.increment(False)
self.parent.update_config()
def set_clickable(self, clickable=True):
self.clickable = clickable
def update(self):
self.get_current_frame().fill(self.background)
self.label.update()
@ -816,8 +829,8 @@ class AudioPanelButton(Sprite):
self.callback = callback
self.callback_kwargs = callback_kwargs
self.containers = containers
self.clickable = True
self.pass_mods = pass_mods
self.set_clickable()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def unsubscribe(self, callback=None, kind=None):
@ -827,7 +840,7 @@ class AudioPanelButton(Sprite):
Sprite.unsubscribe(self, callback, kind)
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)
for container in self.containers:
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, "windows-dist-path", "dist/win/", 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)
section = "display"
add_section(section)
@ -275,9 +275,7 @@ class Configuration(RawConfigParser):
# if type(value) == str or type(value) == unicode:
if type(value) == str:
if pair in types["bool"]:
if value.lower() == self.get("setup", "lowercase-boolean-true"):
return True
return False
return value.lower() in self.get("setup", "boolean-true-lowercase")
elif pair in types["int"]:
return int(value)
elif pair in types["float"]:
@ -422,7 +420,7 @@ class TypeDeclarations(dict):
"setup": {"list": ["classifiers", "resource-search-path",
"requirements", "data-exclude",
"additional-packages", "osx-includes"],
"additional-packages", "osx-includes", "boolean-true-lowercase"],
"path": ["installation-dir", "changelog", "description-file",
"main-object", "icon-path", "windows-dist-path",

View File

@ -1,6 +1,6 @@
import collections
from os import listdir
from os.path import isfile, join
from sys import exc_info, stdout
from glob import glob
from pygame import Color, Rect, Surface, PixelArray, mask
@ -10,11 +10,11 @@ from pygame.locals import *
from .Animation import Animation
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):
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)
self.frames = []
self.mirrored = False
@ -22,15 +22,20 @@ class Sprite(Animation):
self.locations = []
self.framesets = [Frameset(self, framerate=framerate)]
self.frameset_index = 0
self.neighbors = neighbors
self.mass = mass
self._step = EVector()
self.children = {}
self.children = collections.OrderedDict()
self.draw_children_on_frame = draw_children_on_frame
self.set_frameset(0)
self.locations.append(Location(self))
self.motion_overflow = Vector()
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):
if name in ("location", "rect"):
@ -61,6 +66,7 @@ class Sprite(Animation):
if self.get_current_frameset().length() > 1:
self.play()
self.get_current_frameset().reset()
return self.get_current_frameset()
def register_interval(self):
self.register(self.shift_frame,
@ -81,25 +87,10 @@ class Sprite(Animation):
def load_from_path(self, path, transparency=False, ppa=True, key=None,
query=None, omit=False):
if isfile(path):
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()
for frame in load_frames(self.get_resource(path), transparency, ppa, key, query):
self.add_frame(frame, omit)
def fill_colorkey(self, img, key=None):
def fill_colorkey(img, key=None):
if not key:
key = (255, 0, 255)
img = img.convert_alpha()
@ -116,6 +107,8 @@ class Sprite(Animation):
frameset = self.get_current_frameset()
frameset.add_index(self.frames.index(frame))
self.update_location_size()
self.wipe_blinds = get_blinds_rects(*self.location.size)
self.wipe_blinds.reverse()
if frameset.length() > 1:
self.play()
@ -205,14 +198,20 @@ class Sprite(Animation):
def hide(self):
for location in self.locations:
location.hide()
for child in self.children.values():
child.hide()
def unhide(self):
for location in self.locations:
location.unhide()
for child in self.children.values():
child.unhide()
def toggle_hidden(self):
for location in self.locations:
location.toggle_hidden()
for child in self.children.values():
child.toggle_hidden()
def is_hidden(self):
return all(location.is_hidden() for location in self.locations)
@ -248,19 +247,41 @@ class Sprite(Animation):
def has_child(self, name):
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):
Animation.update(self)
if self.is_stepping():
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.is_playing(self.wipe_out):
areas = self.get_current_blinds()
self.draw(areas, substitute, flags)
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()
# for location in self.locations:
# location.update()
if not self.draw_children_on_frame:
child.location.topleft = save_child_topleft
def draw(self, areas=None, substitute=None, flags=0):
for location in self.locations:
@ -316,18 +337,6 @@ class Location(Rect):
def is_hidden(self):
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):
@ -493,6 +502,7 @@ class BlinkingSprite(Sprite):
def __init__(self, parent, blink_rate, framerate=None):
Sprite.__init__(self, parent, framerate)
self.register(self.blink, interval=blink_rate)
self.register(self.start_blinking, self.stop_blinking)
self.play(self.blink)
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
import pygame
from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw
from pygame.mixer import get_num_channels, Channel
from pygame.locals import *
@ -119,6 +120,16 @@ def place_in_rect(rect, incoming, contain=True, *args):
if not collides:
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
def get_intersection(p0, p1, p2, p3):
x0, y0 = p0
@ -215,16 +226,21 @@ def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background
surface.fill(background)
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,
padding=0):
words = text.split()
if words:
padding=0, align="left"):
lines = []
height = 0
for chunk in text.split("\n"):
line_text = ""
lines = []
height = 0
ii = 0
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:
line_width = font.size(line_text + " " + words[ii])[0]
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 += words[ii]
finished = True
line = font.render(line_text, antialias, color, background)
line = font.render(line_text, antialias, color)
height += line.get_height()
lines.append(line)
line_text = ""
else:
line_text += " " + words[ii]
ii += 1
top = 0
surface = Surface((width, height), SRCALPHA)
if background:
surface.fill(background)
rect = surface.get_rect()
for line in lines:
line_rect = line.get_rect()
line_rect.midtop = rect.centerx, top
surface.blit(line, line_rect)
top += line_rect.h
else:
surface = Surface((0, 0), SRCALPHA)
font.set_italic(False)
top = 0
surface = Surface((width, height), pygame.SRCALPHA)
if background:
surface.fill(background)
rect = surface.get_rect()
for line in lines:
line_rect = line.get_rect()
line_rect.top = top
if align == "center":
line_rect.centerx = rect.centerx
surface.blit(line, line_rect)
top += line_rect.h
return get_boxed_surface(surface, background, border, border_width, padding)
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):
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),
colorkey=(255, 0, 255)):
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))
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)):
frames = []
rects = []