pgfw/pgfw/Sprite.py

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)