590 lines
20 KiB
Python
590 lines
20 KiB
Python
import collections
|
|
from os import listdir
|
|
from os.path import isfile, join
|
|
from glob import glob
|
|
|
|
from pygame import Color, Rect, Surface, PixelArray, mask
|
|
from pygame.image import load
|
|
from pygame.transform import flip
|
|
from pygame.locals import *
|
|
|
|
from .Animation import Animation
|
|
from .Vector import Vector, EVector
|
|
from .extension import get_hue_shifted_surface, get_step, load_frames, get_blinds_rects
|
|
|
|
class Sprite(Animation):
|
|
|
|
def __init__(self, parent, framerate=None, draw_children_on_frame=True):
|
|
Animation.__init__(self, parent, self.shift_frame, framerate)
|
|
self.frames = []
|
|
self.mirrored = False
|
|
self.alpha = 255
|
|
self.locations = []
|
|
self.framesets = [Frameset(self, framerate=framerate)]
|
|
self.frameset_index = 0
|
|
self._step = EVector()
|
|
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.wipe_blinds = []
|
|
self.current_wipe_index = 0
|
|
self.areas = None
|
|
self.register(self.hide, self.unhide, self.toggle_hidden, self.wipe_out, self.set_frameset)
|
|
|
|
def reset(self):
|
|
self.halt(self.wipe_out)
|
|
self.halt(self.set_frameset)
|
|
self.current_wipe_index = 0
|
|
|
|
def __getattr__(self, name):
|
|
if name in ("location", "rect"):
|
|
return self.locations[0]
|
|
if hasattr(Animation, "__getattr__"):
|
|
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):
|
|
if frameset.name == identifier:
|
|
identifier = ii
|
|
break
|
|
if self.frameset_index != identifier:
|
|
self.frameset_index = identifier
|
|
self.register_interval()
|
|
self.update_location_size()
|
|
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,
|
|
interval=self.get_current_frameset().framerate)
|
|
|
|
def get_current_frameset(self):
|
|
return self.framesets[self.frameset_index]
|
|
|
|
def update_location_size(self):
|
|
size = self.get_current_frameset().rect.size
|
|
for location in self.locations:
|
|
location.size = size
|
|
location.fader.init_surface()
|
|
|
|
def set_framerate(self, framerate):
|
|
self.get_current_frameset().set_framerate(framerate)
|
|
self.register_interval()
|
|
|
|
def load_from_path(self, path, transparency=False, ppa=True, key=None, query=None, omit=False):
|
|
for frame in load_frames(self.get_resource(path), transparency, ppa, key, query):
|
|
self.add_frame(frame, omit)
|
|
|
|
def fill_colorkey(img, key=None):
|
|
if not key:
|
|
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 add_frame(self, frame, omit=False):
|
|
self.frames.append(frame)
|
|
frame.set_alpha(self.alpha)
|
|
if not omit:
|
|
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()
|
|
|
|
def add_frames(self, frames):
|
|
for frame in frames:
|
|
self.add_frame(frame)
|
|
|
|
def shift_frame(self):
|
|
self.get_current_frameset().shift()
|
|
|
|
def get_current_frame(self):
|
|
return self.frames[self.get_current_frameset().get_current_id()]
|
|
|
|
def move(self, dx=0, dy=0):
|
|
for location in self.locations:
|
|
location.move_ip(dx, dy)
|
|
|
|
def reset_motion_overflow(self):
|
|
for location in self.locations:
|
|
location.reset_motion_overflow()
|
|
|
|
def collide(self, other):
|
|
if not isinstance(other, Rect):
|
|
if hasattr(other, "rect"):
|
|
other = other.rect
|
|
else:
|
|
other = Rect(other[0], other[1], 1, 1)
|
|
for location in self.locations:
|
|
if location.colliderect(other):
|
|
return location
|
|
|
|
def collide_mask(self, other):
|
|
location = self.collide(other)
|
|
if location:
|
|
current_mask = mask.from_surface(self.get_current_frame())
|
|
other_mask = mask.from_surface(other.get_current_frame())
|
|
offset = other.location.left - location.left, \
|
|
other.location.top - location.top
|
|
return current_mask.overlap(other_mask, offset) is not None
|
|
|
|
def mirror(self):
|
|
frames = self.frames
|
|
for ii, frame in enumerate(frames):
|
|
frames[ii] = flip(frame, True, False)
|
|
self.mirrored = not self.mirrored
|
|
|
|
def clear_frames(self):
|
|
self.frames = []
|
|
for frameset in self.framesets:
|
|
frameset.order = []
|
|
frameset.reset()
|
|
frameset.measure_rect()
|
|
|
|
def add_location(self, topleft=None, offset=(0, 0), count=1, base=0):
|
|
if topleft is not None:
|
|
for ii in range(count):
|
|
self.locations.append(Location(
|
|
self, Rect(topleft, self.locations[0].size)))
|
|
else:
|
|
base = self.locations[base]
|
|
current_offset = list(offset)
|
|
for ii in range(count):
|
|
self.locations.append(Location(self,
|
|
base.move(*current_offset)))
|
|
current_offset[0] += offset[0]
|
|
current_offset[1] += offset[1]
|
|
return self.locations[-1]
|
|
|
|
def fade(self, length=0, out=None, index=None):
|
|
if index is None:
|
|
for location in self.locations:
|
|
fader = location.fader
|
|
fader.reset()
|
|
fader.start(length, out)
|
|
else:
|
|
fader = self.locations[index].fader
|
|
fader.reset()
|
|
fader.start(length, out)
|
|
|
|
def set_alpha(self, alpha):
|
|
self.alpha = alpha
|
|
for frame in self.frames:
|
|
frame.set_alpha(alpha)
|
|
for location in self.locations:
|
|
location.fader.set_alpha()
|
|
|
|
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)
|
|
|
|
def remove_locations(self, location=None):
|
|
if location:
|
|
self.locations.remove(location)
|
|
else:
|
|
self.locations = self.locations[:1]
|
|
|
|
def reverse(self, frameset=None):
|
|
if frameset:
|
|
frameset.reverse()
|
|
else:
|
|
for frameset in self.framesets:
|
|
frameset.reverse()
|
|
|
|
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):
|
|
self._step.set_step(dx, dy, magnitude, angle)
|
|
|
|
def cancel_step(self):
|
|
self._step.set_step(0, 0)
|
|
|
|
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 remove_child(self, name):
|
|
if self.has_child(name):
|
|
self.children.pop(name)
|
|
|
|
def wipe_out(self):
|
|
"""
|
|
Increase index of wipe animation, causing blinds frames to grow. To launch the animation use
|
|
sprite.play(sprite.wipe_out)
|
|
"""
|
|
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 set_areas(self, areas):
|
|
self.areas = areas
|
|
|
|
def update(self, areas=None, substitute=None, flags=0):
|
|
Animation.update(self)
|
|
if self.is_stepping():
|
|
self.move(self._step.dx, self._step.dy)
|
|
if self.get_current_frameset().length():
|
|
if self.is_playing(self.wipe_out):
|
|
areas = self.get_current_blinds()
|
|
elif not areas and self.areas:
|
|
areas = self.areas
|
|
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()
|
|
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:
|
|
location.fader.draw(areas, substitute, flags)
|
|
|
|
|
|
class Location(Rect):
|
|
|
|
def __init__(self, sprite, rect=(0, 0, 0, 0)):
|
|
self.sprite = sprite
|
|
Rect.__init__(self, rect)
|
|
self.motion_overflow = Vector()
|
|
self.fader = Fader(self)
|
|
self.unhide()
|
|
|
|
def move_ip(self, dx, dy):
|
|
if isinstance(dx, float) or isinstance(dy, float):
|
|
excess = self.update_motion_overflow(dx, dy)
|
|
Rect.move_ip(self, int(dx) + excess[0], int(dy) + excess[1])
|
|
else:
|
|
Rect.move_ip(self, dx, dy)
|
|
|
|
def update_motion_overflow(self, dx, dy):
|
|
overflow = self.motion_overflow
|
|
overflow.move(dx - int(dx), dy - int(dy))
|
|
excess = [int(value) for value in overflow]
|
|
# excess = map(int, overflow)
|
|
overflow[0] -= int(overflow[0])
|
|
overflow[1] -= int(overflow[1])
|
|
return excess
|
|
|
|
def move_to(self, x, y, base=None):
|
|
ox, oy = self.apply_motion_overflow(base)
|
|
self.move_ip(x - ox, y - oy)
|
|
|
|
def reset_motion_overflow(self):
|
|
self.motion_overflow.place_at_origin()
|
|
|
|
def apply_motion_overflow(self, coordinates=None):
|
|
if coordinates is None:
|
|
coordinates = self.topleft
|
|
return self.motion_overflow + coordinates
|
|
|
|
def hide(self):
|
|
self.hidden = True
|
|
|
|
def unhide(self):
|
|
self.hidden = False
|
|
|
|
def toggle_hidden(self):
|
|
self.hidden = not self.hidden
|
|
|
|
def is_hidden(self):
|
|
return self.hidden
|
|
|
|
|
|
class Fader(Surface):
|
|
|
|
def __init__(self, location):
|
|
self.location = location
|
|
self.time_filter = location.sprite.get_game().time_filter
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.init_surface()
|
|
self.fade_remaining = None
|
|
|
|
def init_surface(self):
|
|
Surface.__init__(self, self.location.size)
|
|
if self.location.sprite.get_current_frameset().length():
|
|
background = Surface(self.get_size())
|
|
sprite = self.location.sprite
|
|
key = sprite.get_current_frame().get_colorkey() or (255, 0, 255)
|
|
self.set_colorkey(key)
|
|
background.fill(key)
|
|
self.background = background
|
|
self.set_alpha()
|
|
|
|
def set_alpha(self, alpha=None):
|
|
if alpha is None:
|
|
alpha = self.location.sprite.alpha
|
|
Surface.set_alpha(self, alpha)
|
|
|
|
def start(self, length, out=None):
|
|
if self.fade_remaining <= 0:
|
|
alpha = self.get_alpha()
|
|
maximum = self.location.sprite.alpha
|
|
if out is None:
|
|
out = alpha == maximum
|
|
if out and alpha > 0 or not out and alpha < maximum:
|
|
self.fade_length = self.fade_remaining = length
|
|
self.start_time = self.time_filter.get_ticks()
|
|
self.fading_out = out
|
|
|
|
def draw(self, areas=None, substitute=None, flags=0):
|
|
sprite = self.location.sprite
|
|
if substitute is None:
|
|
frame = sprite.get_current_frame()
|
|
else:
|
|
frame = substitute
|
|
if self.fade_remaining is not None and self.fade_remaining >= 0:
|
|
self.update_alpha()
|
|
self.clear()
|
|
frame.set_alpha(255)
|
|
self.blit(frame, (0, 0))
|
|
frame.set_alpha(sprite.alpha)
|
|
if not self.location.is_hidden():
|
|
if frame.get_colorkey() is None:
|
|
ratio = self.get_alpha() / 255.0
|
|
pixels = PixelArray(frame.copy())
|
|
color = Color(0, 0, 0)
|
|
for x in range(len(pixels)):
|
|
for y in range(len(pixels[0])):
|
|
h, s, l, a = Color(*frame.unmap_rgb(pixels[x][y])).hsla
|
|
if a:
|
|
color.hsla = h, s, l, int(a * ratio)
|
|
pixels[x][y] = color
|
|
self.blit_to_display(pixels.make_surface(), areas, flags)
|
|
del pixels
|
|
else:
|
|
self.blit_to_display(self, areas, flags)
|
|
elif self.fade_remaining is None or self.get_alpha() >= sprite.alpha:
|
|
if self.fade_remaining is not None and self.fade_remaining >= 0:
|
|
self.update_alpha()
|
|
if not self.location.is_hidden():
|
|
self.blit_to_display(frame, areas, flags)
|
|
|
|
def blit_to_display(self, frame, areas=None, flags=0):
|
|
if not isinstance(areas, list):
|
|
areas = [areas]
|
|
for area in areas:
|
|
if area:
|
|
dest = area.left + self.location.left, \
|
|
area.top + self.location.top
|
|
else:
|
|
dest = self.location
|
|
self.location.sprite.display_surface.blit(frame, dest, area, flags)
|
|
|
|
def update_alpha(self):
|
|
remaining = self.fade_remaining = self.fade_length - \
|
|
(self.time_filter.get_ticks() - self.start_time)
|
|
ratio = self.fade_length and float(remaining) / self.fade_length
|
|
if not self.fading_out:
|
|
ratio = 1 - ratio
|
|
maximum = self.location.sprite.alpha
|
|
alpha = int(ratio * maximum)
|
|
if alpha > maximum:
|
|
alpha = maximum
|
|
elif alpha < 0:
|
|
alpha = 0
|
|
self.set_alpha(alpha)
|
|
|
|
def clear(self):
|
|
self.blit(self.background, (0, 0))
|
|
|
|
|
|
class Frameset:
|
|
|
|
def __init__(self, sprite, order=[], framerate=None, name=None):
|
|
self.sprite = sprite
|
|
self.name = name
|
|
self.reversed = False
|
|
self.order = []
|
|
self.rect = Rect(0, 0, 0, 0)
|
|
self.add_index(order)
|
|
self.set_framerate(framerate)
|
|
self.reset()
|
|
|
|
def add_index(self, order):
|
|
if isinstance(order, int):
|
|
order = [order]
|
|
self.order += order
|
|
self.measure_rect()
|
|
|
|
def set_framerate(self, framerate):
|
|
self.framerate = framerate
|
|
|
|
def reset(self):
|
|
self.current_index = 0
|
|
self.sprite.accounts[self.sprite.shift_frame].reset_interval()
|
|
|
|
def get_current_id(self):
|
|
return self.order[self.current_index]
|
|
|
|
def measure_rect(self):
|
|
max_width, max_height = 0, 0
|
|
frames = self.sprite.frames
|
|
for index in self.order:
|
|
frame = frames[index]
|
|
width, height = frame.get_size()
|
|
max_width = max(width, max_width)
|
|
max_height = max(height, max_height)
|
|
self.rect.size = max_width, max_height
|
|
|
|
def shift(self):
|
|
if len(self.order) > 1:
|
|
self.increment_index()
|
|
|
|
def increment_index(self, increment=None):
|
|
if increment is None:
|
|
increment = 1 if not self.reversed else -1
|
|
index = self.current_index + increment
|
|
while index < 0:
|
|
index += self.length()
|
|
while index >= self.length():
|
|
index -= self.length()
|
|
self.current_index = index
|
|
|
|
def length(self):
|
|
return len(self.order)
|
|
|
|
def reverse(self):
|
|
self.reversed = not self.reversed
|
|
|
|
def __str__(self):
|
|
if self.name is None:
|
|
return ""
|
|
else:
|
|
return self.name
|
|
|
|
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):
|
|
self.unhide()
|
|
|
|
def blink(self):
|
|
self.toggle_hidden()
|
|
|
|
def start_blinking(self):
|
|
self.play(self.blink)
|
|
|
|
def stop_blinking(self):
|
|
self.halt(self.blink)
|
|
self.unhide()
|
|
|
|
|
|
class RainbowSprite(Sprite):
|
|
|
|
def __init__(self, parent, image=None, hue_shift=8, framerate=None):
|
|
Sprite.__init__(self, parent, framerate)
|
|
self.hue_shift = hue_shift
|
|
if image is not None:
|
|
self.set_frames(image)
|
|
|
|
def set_frames(self, image):
|
|
for hue in range(0, 360, self.hue_shift):
|
|
self.add_frame(get_hue_shifted_surface(image, hue))
|
|
|
|
|
|
class ShadowedSprite(Sprite):
|
|
|
|
def __init__(self, parent, offset=Vector(3, 3), opacity=1, color=(0, 0, 0), framerate=None):
|
|
Sprite.__init__(self, parent, framerate)
|
|
if not isinstance(offset, Vector):
|
|
offset = Vector(*offset)
|
|
if not isinstance(color, Color):
|
|
color = Color(*color)
|
|
self.shadow_color = color
|
|
self.shadow_offset = offset
|
|
self.shadow_opacity = opacity
|
|
self.shadow_color = color
|
|
self.shadow = Sprite(self)
|
|
|
|
def add_frame(self, frame, omit=False):
|
|
Sprite.add_frame(self, frame, omit)
|
|
fr = frame.get_rect()
|
|
shadow = Surface(fr.size, SRCALPHA)
|
|
pixels = PixelArray(frame)
|
|
shadow_pixels = PixelArray(shadow)
|
|
r, g, b = self.shadow_color.r, self.shadow_color.g, self.shadow_color.b
|
|
for y in range(len(pixels)):
|
|
for x in range(len(pixels[0])):
|
|
frame_pixel_color = frame.unmap_rgb(pixels[y][x])
|
|
if frame_pixel_color == frame.get_colorkey():
|
|
shadow_pixel_color = 0, 0, 0, 0
|
|
else:
|
|
shadow_pixel_color = r, g, b, frame_pixel_color.a * self.shadow_opacity
|
|
shadow_pixels[y][x] = shadow_pixel_color
|
|
self.shadow.add_frame(shadow)
|
|
|
|
def clear_frames(self):
|
|
self.shadow.clear_frames()
|
|
Sprite.clear_frames(self)
|
|
|
|
def update(self):
|
|
self.shadow.location.topleft = Vector(*self.location.topleft).get_moved(
|
|
*self.shadow_offset)
|
|
self.shadow.update()
|
|
Sprite.update(self)
|