Last active
January 13, 2018 11:07
-
-
Save BrettWitty/bc0934d9e413ac5ca4f6223fcab7a9e2 to your computer and use it in GitHub Desktop.
Animation System for RogueAgent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import system | |
# Entities that need animation get an AnimationComponent | |
class AnimationComponent(object): | |
__slots__ = ('animations', 'enabled') | |
def __init__(self, *, animations=[]): | |
# List of currently applied (not necessarily running!) animations | |
self.animations = animations | |
# Enable/disable animations with a single flag (i.e. for pausing the game) | |
self.enabled = True | |
class AnimationSystem(system.System): | |
def __init__(self, mgr): | |
super().__init__(mgr) | |
# Boilerplate for the main Entity Manager to know how to interact with the AnimationSystem | |
self.component = AnimationComponent | |
self.component_name = 'Animation' | |
# Specifies the priority it gets to run. | |
# Different Systems run on different tick rates, but if they occur on the frame update, | |
self.priority = 25 | |
def startup(self): | |
super().startup() | |
def shutdown(self): | |
super().shutdown() | |
def update(self, tick): | |
# Receive a "tick" which is a time delta from the last game loop | |
super().update(tick) | |
# For each AnimationComponent and each active animation, update it. | |
# self._data is an entity->component lookup table for every System | |
entities = [ (entity, self._mgr.get_component(entity,'Render'), animation_comp.animations) | |
for entity, animation_comp in self._data.items() if animation_comp.enabled ] | |
# Loop over the active animation entities | |
for entity, render, animations in entities: | |
# Entities may have multiple simultaneous animations, run them in order | |
for animation in animations: | |
animation.update(tick) | |
def handle_event(self, event): | |
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from enum import Enum | |
from bisect import bisect_right | |
class AnimationLoop(Enum): | |
NOLOOP = 1 | |
LOOP = 2 | |
#PINGPONG = 3 | |
class AnimationState(Enum): | |
STOPPED = 1 | |
PLAYING = 2 | |
PAUSED = 3 | |
class Animation(object): | |
__slots__ = ('state', 'loop', 'length', 'accum_time') | |
def __init__(self, *, loop=AnimationLoop.NOLOOP, state=AnimationState.STOPPED, length=1.0): | |
"""Generic Animation object. | |
Arguments: | |
- loop : If the animation loops, ping-pongs or doesn't loop at all. | |
- state : The initial state of animation (playing, stopped or paused) | |
- length : How long the animation goes in seconds | |
""" | |
# The looping type | |
self.loop = loop | |
# The current play state | |
self.state = state | |
# The length of the animation (can be calculated) | |
self.length = length | |
# Accumulated time in the animation, so it can save its own progress in the animation. | |
self.accum_time = 0.0 | |
def update(self, delta): | |
"""Update the animation state based on the new delta time forward.""" | |
if self.state == AnimationState.PLAYING: | |
self.accum_time += delta | |
if self.accum_time > self.length: | |
if self.loop == AnimationLoop.NOLOOP: | |
self.accum_time = 0.0 | |
self.stop() | |
elif self.loop == AnimationLoop.LOOP: | |
self.accum_time %= self.length | |
# Paused or stopped animations have an empty update | |
def start(self): | |
"""Do things when the animation starts.""" | |
self.state = AnimationState.PLAYING | |
def stop(self): | |
"""Do things when the animation stopped. | |
This is mostly for cleanup.""" | |
self.state = AnimationState.STOPPED | |
def pause(self): | |
"""Do things when the animation pauses.""" | |
self.state = AnimationState.PAUSED | |
class FramedAnimation(Animation): | |
"""Animation that changes RenderBuffers on a time schedule.""" | |
__slots__ = ('render', 'old_buffer', 'frames', 'frame_times') | |
def __init__(self, render, frames, frame_lengths, | |
*, loop=AnimationLoop.NOLOOP, state=AnimationState.STOPPED, length=None): | |
"""Run an animation that is set frames with timings. | |
Note that frame_lengths[i] is the length of time that frame[i] lasts..""" | |
from itertools import accumulate | |
super().__init__(loop=loop, state=state, length=length) | |
# What RenderComponent are we modifying? | |
self.render = render | |
# Save out the old buffer from the RenderComponent, just in case | |
self.old_buffer = render.cur_buffer | |
# The frames of animation (RenderBuffers) | |
self.frames = frames | |
# It's more intuitive to provide frame lengths, but is more | |
# computationally useful to store the accumulate frame times. | |
self.frame_times = list(accumulate(frame_lengths)) | |
# Calculate the length of the animation | |
self.length = sum(frame_lengths) | |
def update(self,delta): | |
super().update(delta) | |
# Figure out which frame we are up to | |
if self.accum_time in self.frame_times: | |
frame = self.frame_times.index(self.accum_time) | |
else: | |
# This is the magic to find which frame of animation we're in. | |
frame = bisect_right(self.frame_times, self.accum_time) | |
# Replace the current RenderBuffer with the appropriate frame | |
self.render.cur_buffer = self.frames[frame] | |
def stop(self): | |
super().stop() | |
# Reinstate the old render buffer | |
self.render.cur_buffer = self.old_buffer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment