ibitfit/electric_sieve/pgfw/Sprite.py

360 lines
11 KiB
Python

from os import listdir
from os.path import isfile, join
from sys import exc_info, stdout
from glob import glob
from traceback import print_exc, print_stack
from pygame import Color, Rect, Surface
from pygame.image import load
from pygame.transform import flip
from pygame.locals import *
from Animation import Animation
from Vector import Vector
class Sprite(Animation):
def __init__(self, parent, framerate=None):
Animation.__init__(self, parent, self.shift_frame, framerate)
self.frames = []
self.mirrored = False
self.hidden = False
self.alpha = 255
self.locations = [Location(self)]
self.framesets = [Frameset(self, framerate=framerate)]
self.set_frameset(0)
self.motion_overflow = Vector()
self.display_surface = self.get_display_surface()
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 set_frameset(self, identifier):
if isinstance(identifier, str):
for ii, frameset in enumerate(self.framesets):
if frameset.name == identifier:
identifier = ii
break
self.frameset_index = identifier
self.register_interval()
self.update_location_size()
if self.get_current_frameset().length() > 1:
self.play()
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,
extension=None, omit=False):
if isfile(path):
paths = [path]
else:
if extension:
paths = sorted(glob(join(path, "*." + extension)))
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)
def fill_colorkey(self, 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()
if frameset.length() > 1:
self.play()
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):
other = other.rect
for location in self.locations:
if location.colliderect(other):
return location
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 xrange(count):
self.locations.append(Location(
self, Rect(topleft, self.locations[0].size)))
else:
base = self.locations[base]
current_offset = list(offset)
for ii in xrange(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:
location.fader.start(length, out)
else:
self.locations[index].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 add_frameset(self, order, framerate=None, name=None):
frameset = Frameset(self, order, framerate, name)
self.framesets.append(frameset)
return frameset
def hide(self):
self.hidden = True
def unhide(self):
self.hidden = False
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 update(self, flags=0):
Animation.update(self)
self.draw(flags)
def draw(self, flags=0):
for location in self.locations:
location.fader.draw(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)
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 = map(int, overflow)
overflow[0] -= int(overflow[0])
overflow[1] -= int(overflow[1])
return excess
def reset_motion_overflow(self):
self.motion_overflow.place_at_origin()
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.frames:
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, flags):
sprite = self.location.sprite
if self.fade_remaining >= 0:
self.update_alpha()
self.clear()
frame = sprite.get_current_frame()
frame.set_alpha(255)
self.blit(frame, (0, 0))
frame.set_alpha(sprite.alpha)
if not sprite.hidden:
self.blit_to_display(self, flags)
elif self.fade_remaining is None or self.get_alpha() >= sprite.alpha:
if self.fade_remaining >= 0:
self.update_alpha()
if not sprite.hidden:
self.blit_to_display(sprite.get_current_frame(), flags)
def blit_to_display(self, frame, flags):
self.location.sprite.display_surface.blit(frame, self.location, None,
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
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 = 1 if not self.reversed else -1
index = self.current_index + increment
if index < 0:
index = self.length() - 1
elif index >= self.length():
index = 0
self.current_index = index
def length(self):
return len(self.order)
def reverse(self):
self.reversed = not self.reversed