360 lines
11 KiB
Python
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
|