Skip to content

Instantly share code, notes, and snippets.

@BrettWitty
Last active January 13, 2018 11:07
Show Gist options
  • Save BrettWitty/bc0934d9e413ac5ca4f6223fcab7a9e2 to your computer and use it in GitHub Desktop.
Save BrettWitty/bc0934d9e413ac5ca4f6223fcab7a9e2 to your computer and use it in GitHub Desktop.
Animation System for RogueAgent
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
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