pgfw/pgfw/Sprite.py

401 lines
12 KiB
Python

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
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.alpha = 255
self.locations = []
self.framesets = [Frameset(self, framerate=framerate)]
self.set_frameset(0)
self.locations.append(Location(self))
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,
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()
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, switch=False):
frameset = Frameset(self, order, framerate, name)
self.framesets.append(frameset)
if switch:
self.set_frameset(len(self.framesets) - 1)
return frameset
def hide(self):
for location in self.locations:
location.hide()
def unhide(self):
for location in self.locations:
location.unhide()
def toggle_hidden(self):
for location in self.locations:
location.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 update(self, areas=None, substitute=None):
Animation.update(self)
if self.get_current_frameset().length():
self.draw(areas, substitute)
def draw(self, areas=None, substitute=None):
for location in self.locations:
location.fader.draw(areas, substitute)
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 = 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()
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):
sprite = self.location.sprite
if substitute is None:
frame = sprite.get_current_frame()
else:
frame = substitute
if 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():
self.blit_to_display(self, areas)
elif self.fade_remaining is None or self.get_alpha() >= sprite.alpha:
if self.fade_remaining >= 0:
self.update_alpha()
if not self.location.is_hidden():
self.blit_to_display(frame, areas)
def blit_to_display(self, frame, areas=None):
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)
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