Here's a little something I've been working on for the past day and a half, after finally understanding most of the math involved behind the easing equations.
This is a little animation toolkit; very basic for now, in the sense that it requires intermediate scripting skills, but I plan on offering easy to use interface and facades, which should allow anyone with basic RGSS knowledge and a little guidance to use this correctly to build beautiful cut scenes.
So, what exactly does this do? Precisely, it takes an object's numerical property's starting value and a user given end value to generate a function of time, which is called on every frame update. Easy, right? Except it uses Robert Penner's excellent (as in, free) easing equations to smooth out the animation process.
Less precisely, it allows one to manipulate graphics (in most cases, but any object can be supplied, since I follow duck typing) and easily animate their coordinates, opacity, color, tone, etc., allowing someone to do fade in/out, slide in/out, zoom in/out, etc., effects very easily.
I've made a short demo which shows some of the capabilities of the script.
Once again, this is right now most likely geared towards fellow scripters who want to add animations to their scripts.
HOW IT WORKS:
The script itself is based on Ruby's mix-in capabilities; by including the module Effects::Animated into a given class, you now have access to a bunch of processing methods. The only other necessary modification is made in the update method, or whatever method is called on every frame.
Just adding this tiny snippet would make the Sprite class animate-able.
Effects are processed as a job queue; in other words, you create new Effects::Effect objects which are pushed at the end of queue and processed sequentially. I'm afraid I don't quite know how to do simultaneous effects (without threads) short of cooperative multi-threading; simply performing all effects in the queue all at once would probably cause lag if the number of effects is very large.
You have a few methods to control the processing of the effects queue:
Please note that when pausing/resuming an effect, if you modify the properties which are modified in the effect, but through an external manner, you will get strange behaviors. I'm still unsure as to what stand to take on this matter.
To add an effect to the job queue of an object, you simply push (it is an Array, except handled in a queue-like fashion) a new Effects::Effect object, such as:
Where @s is a Sprite object.
The transition parameter is supposed to be a Method object; more specifically, a method from one of the Effects::Trans sub-modules.
The duration is the number of frames the effect should take to complete. A few aliases have been made for durations : Effects::Effect::SHORT (15 frames), Effects::Effect::NORMAL (30 frames), Effects::Effect::LONG (60 frames).
The properties parameter is an hash of the properties of the object which should be modified over time. The keys are the properties, the values the end values of each property (i.e.: the X coord goes from 0 to 300 under 80 frames in our example).
Sub-properties are supported, that is to say, properties of properties; for example, you want to change the tone property of a Sprite. You'd do :
The beforeUpdate param is a callback, or more specifically a Method or Proc object, which is called on every frame on every property before said property is updated.
The afterUpdate param is a callback which is called on every frame on every property after said property has been updated.
The onStart param is a callback which is called when a new effect becomes the current job being processed. In other words, at the beginning of the Effect, but before any update.
The onFinish param is a callback which is called when an effect is finished, before the queue is shifted and a new effect takes its place.
The object param is the object which should be updated. It is a required parameter.
Anyway, here's the script itself:
And here's a link to the RMXP demo: http://rgss.etheon.net/Animator.zip
And the RMVX demo: http://rgss.etheon.net/VXnimator.zip
The demo code is in the Scene_Map script. It's neatly identified, so no one should have any trouble with it.
I'll be posting a couple of Effects templates, such as fade in/out, slide in/out, grow/shrink, projectile curves, etc.
You can view a demo of some of the different easings available on Robert Penner's site: http://www.robertpenner.com/easing/easing_demo.html
This is a little animation toolkit; very basic for now, in the sense that it requires intermediate scripting skills, but I plan on offering easy to use interface and facades, which should allow anyone with basic RGSS knowledge and a little guidance to use this correctly to build beautiful cut scenes.
So, what exactly does this do? Precisely, it takes an object's numerical property's starting value and a user given end value to generate a function of time, which is called on every frame update. Easy, right? Except it uses Robert Penner's excellent (as in, free) easing equations to smooth out the animation process.
Less precisely, it allows one to manipulate graphics (in most cases, but any object can be supplied, since I follow duck typing) and easily animate their coordinates, opacity, color, tone, etc., allowing someone to do fade in/out, slide in/out, zoom in/out, etc., effects very easily.
I've made a short demo which shows some of the capabilities of the script.
Once again, this is right now most likely geared towards fellow scripters who want to add animations to their scripts.
HOW IT WORKS:
The script itself is based on Ruby's mix-in capabilities; by including the module Effects::Animated into a given class, you now have access to a bunch of processing methods. The only other necessary modification is made in the update method, or whatever method is called on every frame.
Code:
class Sprite
include Effects::Animated
def update
super
self.animate if processing? # the modification!
end
end
Just adding this tiny snippet would make the Sprite class animate-able.
Effects are processed as a job queue; in other words, you create new Effects::Effect objects which are pushed at the end of queue and processed sequentially. I'm afraid I don't quite know how to do simultaneous effects (without threads) short of cooperative multi-threading; simply performing all effects in the queue all at once would probably cause lag if the number of effects is very large.
You have a few methods to control the processing of the effects queue:
Code:
# - start: starts the queue processing (if any)
# - stop: stops the queue processing (if started)
# - pause: pauses the queue processing (if started)
# - resume: resumes processing where it left off (if paused)
Please note that when pausing/resuming an effect, if you modify the properties which are modified in the effect, but through an external manner, you will get strange behaviors. I'm still unsure as to what stand to take on this matter.
To add an effect to the job queue of an object, you simply push (it is an Array, except handled in a queue-like fashion) a new Effects::Effect object, such as:
Code:
@s.EffectsQueue.push(Effects::Effect.new({
:transition => Effects::Trans::Quad.method(:easeIn),
:duration => 80,
:properties => {:x => 300, :y => 300},
:beforeUpdate => nil,
:afterUpdate => nil,
:onStart => nil,
:onFinish => nil,
:object => @s
}))
Where @s is a Sprite object.
The transition parameter is supposed to be a Method object; more specifically, a method from one of the Effects::Trans sub-modules.
The duration is the number of frames the effect should take to complete. A few aliases have been made for durations : Effects::Effect::SHORT (15 frames), Effects::Effect::NORMAL (30 frames), Effects::Effect::LONG (60 frames).
The properties parameter is an hash of the properties of the object which should be modified over time. The keys are the properties, the values the end values of each property (i.e.: the X coord goes from 0 to 300 under 80 frames in our example).
Sub-properties are supported, that is to say, properties of properties; for example, you want to change the tone property of a Sprite. You'd do :
Code:
:properties => {:tone => {:red => 255, :blue => 255, :green => 255}}
The beforeUpdate param is a callback, or more specifically a Method or Proc object, which is called on every frame on every property before said property is updated.
The afterUpdate param is a callback which is called on every frame on every property after said property has been updated.
The onStart param is a callback which is called when a new effect becomes the current job being processed. In other words, at the beginning of the Effect, but before any update.
The onFinish param is a callback which is called when an effect is finished, before the queue is shifted and a new effect takes its place.
The object param is the object which should be updated. It is a required parameter.
Anyway, here's the script itself:
Code:
#==============================================================================
# ** Effects
# ----------------------------------------------------------------------------
# Special Effects manager for the any RGSS graphical component.
# Just a namespace, really.
#==============================================================================
module Effects
end
#==============================================================================
# Effects::Trans
# ----------------------------------------------------------------------------
# The following transition equations are all adapted from Robert Penner's
# easing equations.
#
# The module is broken up into smaller modules, each of which implement the
# easeIn, easeOut, easeInOut methods, except the Linear module, which has no
# easing.
#
# There is :
# Quadratic : t^2
# Cubic : t^3
# Quartic : t^4
# Quintic : t^5
# Sinusoidal: sin(t)
# Elastic : exponentially decaying sine wave
# Back : overshooting cubic easing (s+1)*t^3 - s*t^2
# Bounce : exponentially decaying parabolic bounce
#
# All equations (c) 2003 Robert Penner, all rights reserved.
# This work is subject to the terms in
# http://www.robertpenner.com/easing_terms_of_use.html
#==============================================================================
module Effects::Trans
# Simple Linear tweening - no easing effect.
module Linear
# t: current time, b: beginning value, c: change in value, d: duration
def self.tween(t, b, c, d)
return c*t/d + b
end
end
# Quadratic easing (t^2)
# t: current time, b: beginning value, c: change in value, d: duration
# t and d can be in frames or seconds/milliseconds
module Quad
# quadratic easing in - accelerating from zero velocity
def self.easeIn(t, b, c, d)
return c*(t/=d)*t + b
end
# quadratic easing out - decelerating to zero velocity
def self.easeOut(t, b, c, d)
return -c * (t/=d)*(t-2) + b
end
# quadratic easing in/out - acceleration until halfway, then deceleration
def self.easeInOut(t, b, c, d)
# Acceleration
if ((t/=d/2) < 1); return c/2*t*t + b; end
# Deceleration
return -c/2 * ((--t)*(t-2) - 1) + b
end
end
# Cubic easing : t^3
# t: current time, b: beginning value, c: change in value, d: duration
# t and d can be frames or seconds/milliseconds
module Cubic
# cubic easing in - accelerating from zero velocity
def self.easeIn(t, b, c, d)
return c*(t/=d)*t*t + b
end
# cubic easing out - decelerating to zero velocity
def self.easeOut(t, b, c, d)
return c*((t=t/d-1)*t*t + 1) + b
end
# cubic easing in/out - acceleration until halfway, then deceleration
def self.easeInOut(t, b, c, d)
# Acceleration
if ((t/=d/2) < 1); return c/2*t*t*t + b; end
# Deceleration
return c/2*((t-=2)*t*t + 2) + b
end
end
# Quartic easing: t^4
# t: current time, b: beginning value, c: change in value, d: duration
# t and d can be frames or seconds/milliseconds
module Quartic
# quartic easing in - accelerating from zero velocity
def self.easeIn(t, b, c, d)
return c*(t/=d)*t*t*t + b
end
# quartic easing out - decelerating to zero velocity
def self.easeOut(t, b, c, d)
return -c * ((t=t/d-1)*t*t*t - 1) + b
end
# quartic easing in/out - acceleration until halfway, then deceleration
def self.easeInOut(t, b, c, d)
# Acceleration
if ((t/=d/2) < 1); return c/2*t*t*t*t + b; end
# Decelaration
return -c/2 * ((t-=2)*t*t*t - 2) + b
end
end
# QUINTIC EASING: t^5
# t: current time, b: beginning value, c: change in value, d: duration
# t and d can be frames or seconds/milliseconds
module Quintic
# quintic easing in - accelerating from zero velocity
def self.easeIn(t, b, c, d)
return c*(t/=d)*t*t*t*t + b
end
# quintic easing out - decelerating to zero velocity
def self.easeOut(t, b, c, d)
return c*((t=t/d-1)*t*t*t*t + 1) + b
end
# quintic easing in/out - acceleration until halfway, then deceleration
def self.easeInOut(t, b, c, d)
# Acceleration
if ((t/=d/2) < 1); return c/2*t*t*t*t*t + b; end
# Deceleration
return c/2*((t-=2)*t*t*t*t + 2) + b
end
end
# Sinusoidal easing: sin(t)
# t: current time, b: beginning value, c: change in position, d: duration
module Sine
# sinusoidal easing in - accelerating from zero velocity
def self.easeIn(t, b, c, d)
return -c * Math.cos(t/d * (Math.PI/2)) + c + b
end
# sinusoidal easing out - decelerating to zero velocity
def self.easeOut(t, b, c, d)
return c * Math.sin(t/d * (Math.PI/2)) + b
end
# sinusoidal easing in/out - accelerating until halfway, then decelerating
def self.easeInOut(t, b, c, d)
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b
end
end
# Elastic easing: exponentially decaying sine wave
# t: current time, b: beginning value, c: change in value, d: duration,
# a: amplitude (optional), p: period (optional)
# t and d can be in frames or seconds/milliseconds
module Elastic
def self.easeInOut(t, b, c, d, a = 0, p = false)
if (t==0); return b; end
if ((t/=d)==1); return b+c; end
if (!p); p=d*0.3; end
if (a < Math.abs(c))
a=c
s=p/4
else
s = p/(2*Math.PI) * Math.asin(c/a)
end
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b
end
def self.easeInOut(t, b, c, d, a = 0, p = false)
if (t==0); return b; end
if ((t/=d)==1); return b+c; end
if (!p); p=d*0.3; end
if (a < Math.abs(c))
a=c
s=p/4
else
s = p/(2*Math.PI) * Math.asin(c/a)
end
return a*Math.pow(2,-10*t) * Math.sin((t*d-s)*(2*Math.PI)/p) + c + b
end
def self.easeInOut(t, b, c, d, a = 0, p = false)
if (t==0); return b; end
if ((t/=d)==1); return b+c; end
if (!p); p=d*0.3; end
if (a < Math.abs(c))
a=c
s=p/4
else
s=p/(2*Math.PI) * Math.asin(c/a)
end
return a*Math.pow(2,-10*t) * Math.sin((t*d-s)*(2*Math.PI)/p) + c + b
end
end
# Back easing: overshooting cubic easing: (s+1)*t^3 - s*t^2
# t: current time, b: beginning value, c: change in value, d: duration,
# s: overshoot amount (optional)
# t and d can be in frames or seconds/milliseconds
# s controls the amount of overshoot: higher s means greater overshoot
# s has a default value of 1.70158, which produces an overshoot of 10 percent
# s==0 produces cubic easing with no overshoot
module Back
OVERSHOOT = 1.70158
# back easing in - backtracking slightly, then reversing direction and
# moving to target
def self.easeIn(t, b, c, d, s = OVERSHOOT)
return c*(t/=d)*t*((s+1)*t - s) + b
end
# back easing out - moving towards target, overshooting it slightly, then
# reversing and coming back to target
def self.easeOut(t, b, c, d, s = OVERSHOOT)
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b
end
# back easing in/out - backtracking slightly, then reversing direction and
# moving to target, then overshooting target, reversing, and finally coming
# back to target
def self.easeInOut(t, b, c, d, s = OVERSHOOT)
if ((t/=d/2) < 1); return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b; end
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b
end
end
# Bounce easing: exponentially decaying parabolic bounce
# t: current time, b: beginning value, c: change in position, d: duration
module Bounce
# bounce easing in
def self.easeIn(t, b, c, d)
return c - easeOut(d-t, 0, c, d) + b
end
# bounce easing out
def self.easeOut(t, b, c, d)
if ((t/=d) < (1/2.75))
return c*(7.5625*t*t) + b
elsif (t < (2/2.75))
return c*(7.5625*(t-=(1.5/2.75))*t + 0.75) + b
elsif (t < (2.5/2.75))
return c*(7.5625*(t-=(2.25/2.75))*t + 0.9375) + b
else
return c*(7.5625*(t-=(2.625/2.75))*t + 0.984375) + b
end
end
# bounce easing in/out
def self.easeInOut(t, b, c, d)
if (t < d/2); return easeIn(t*2, 0, c, d) * 0.5 + b; end
return easeOut(t*2-d, 0, c, d) * 0.5 + c*0.5 + b
end
end
end
#==============================================================================
# ** Effects::Effect
# ----------------------------------------------------------------------------
# The Effect class provides an easy interface to perform a multitude of
# different effects, which are, by the by, largely:
#
# modifications of numerical properties of an object over a period of time
#
# Just so it's clear.
#==============================================================================
class Effects::Effect
#----------------------------------------------------------------------------
# * Constants
# {Integer} MS : Millisecond time unit.
# {Integer} FPS : Frames Per Second time unit. Default.
#----------------------------------------------------------------------------
MS = 1000
FPS = 1
SHORT = 15 # fps: 1/2 second or so
NORMAL = 30 # fps: 1 second or so
LONG = 60 # fps: 2 seconds or so
#----------------------------------------------------------------------------
# * Class Properties
#----------------------------------------------------------------------------
@@defaults = {
:quanta => FPS,
:transition => Effects::Trans::Linear.method(:tween),
:duration => NORMAL,
:properties => {},
:beforeUpdate => nil,
:afterUpdate => nil,
:onStart => nil,
:onFinish => nil,
:object => nil
}
#----------------------------------------------------------------------------
# * Public Properties
# {Integer} quanta : The time unit; milliseconds or frames per second.
# {Method} transition : The transition used for the effect.
# {Integer} duration : The time period over which the effect takes place.
# {Hash} properties : A list of the modified properties, in the following
# format:
# { :property => [start_value, end_value] }
#----------------------------------------------------------------------------
attr_accessor :quanta, :transition, :duration, :properties, :object
#----------------------------------------------------------------------------
# * Initialize
# {Hash} options - For format, see Effects::Effect#defaults
#----------------------------------------------------------------------------
def initialize(options)
# Remove all unwanted key/value pairs from the passed options
options.reject! { |k,v| !@@defaults.has_key?(k) }
# Merge with the defaults
options = @@defaults.merge(options)
# Assign accordingly
self.quanta = options[:quanta]
self.transition = options[:transition]
self.duration = options[:duration]
self.properties = options[:properties]
self.object = options[:object]
# Assign the starting values
@original = {}
# Assign callbacks
@callbacks = {
:beforeUpdate => options[:beforeUpdate],
:afterUpdate => options[:afterUpdate],
:onStart => options[:onStart],
:onFinish => options[:onFinish]
}
# Determine the delta value of the property based on the start and end
# values.
@now = 0
end
#----------------------------------------------------------------------------
# * Init
# Re-inits the beginning values.
#----------------------------------------------------------------------------
def init(properties, object)
properties.each do |property, value|
if value.is_a? Hash
obj = object.send(property)
unless obj.nil?
self.init(value, obj)
end
else
@original[property] = object.send(property).to_f
end
end
end
#----------------------------------------------------------------------------
# * Update
#----------------------------------------------------------------------------
def update
o = @object; return if o.nil?
# Is it over yet?
if @now > @duration
unless @callbacks[:onFinish].nil?
@callbacks[:onFinish].call(self)
end
o.send(:onAnimationFinish, self)
return
end
# Call on start if it exists and @now = 0
unless @now > 0
self.init(self.properties, self.object)
unless @callbacks[:onStart].nil?
@callbacks[:onStart].call(self)
end
end
# Iteirate over the properties
@properties.each do |property, value|
# Make sure the object can interact with us
if o.respond_to? property
self.updateProperty(o, property, value)
end
end
# Increment the "timer"
@now += (1 * @quanta)
end
#----------------------------------------------------------------------------
# * Update Property
# Updates a given property on a given object towards the given value.
#----------------------------------------------------------------------------
def updateProperty(o, property, value)
# Parse sub-properties
if value.is_a? Hash
obj = o.send(property)
unless obj.nil?
value.each do |p, v|
if obj.respond_to? p
self.updateProperty(o.send(property), p, v)
end
end
end
else
b = o.send(property)
newValue = @transition.call(@now.to_f, @original[property],
(value - @original[property]).to_f, @duration.to_f)
# Do we have a beforeUpdate?
unless @callbacks[:beforeUpdate].nil?
@callbacks[:beforeUpdate].call(o, property, value, newValue)
end
# Assign the new value
o.send(property.to_s + "=", newValue)
# After update
unless @callbacks[:afterUpdate].nil?
@callbacks[:afterUpdate].call(o, property, value, newValue)
end
end
end
end
#==============================================================================
# ** Effects::Animated
# ----------------------------------------------------------------------------
# The Animated module is used strictly as a mix-in component; it is to be
# included into a class and provide an interface to animate an object. Hence,
# classes which should implement this module will, in theory, be GUI-related.
#
# The module is basically a queue processor; it takes care of processing a
# queue of effects at certain times based on given settings.
#
# It provides the following methods:
# - start: starts the queue processing (if any)
# - stop: stops the queue processing (if started)
# - pause: pauses the queue processing (if started)
# - resume: resumes processing where it left off (if paused)
# - EffectsQueue: the job queue, which is accessed in an array-like fashion
#==============================================================================
#==============================================================================
# ** Effects::Animated
# ----------------------------------------------------------------------------
# The Animated module is used strictly as a mix-in component; it is to be
# included into a class and provide an interface to animate an object. Hence,
# classes which should implement this module will, in theory, be GUI-related.
#
# The module is basically a queue processor; it takes care of processing a
# queue of effects at certain times based on given settings.
#
# It provides the following methods:
# - start: starts the queue processing (if any)
# - stop: stops the queue processing (if started)
# - pause: pauses the queue processing (if started)
# - resume: resumes processing where it left off (if paused)
# - EffectsQueue: the job queue, which is accessed in an array-like fashion
#==============================================================================
module Effects::Animated
#----------------------------------------------------------------------------
# * Constants
#----------------------------------------------------------------------------
IDLE = 0
PROCESSING = 1
PAUSED = 2
#----------------------------------------------------------------------------
# * Public Properties
# {Array} EffectsQueue : A queue of Effects::Effect objects.
# {Integer} State : The current processing state. See constants.
#----------------------------------------------------------------------------
attr_accessor :EffectsQueue, :state
#----------------------------------------------------------------------------
# * Idle?
# @return {Boolean} True if the object is idle (not processing animations)
#----------------------------------------------------------------------------
def idle?
return (@state == IDLE)
end
#----------------------------------------------------------------------------
# * Paused?
# @return {Boolean} True if the animation processing is paused
#----------------------------------------------------------------------------
def paused?
return (@state == PAUSED)
end
#----------------------------------------------------------------------------
# * Processing?
# @return {Boolean} True if there are animations being processed.
#----------------------------------------------------------------------------
def processing?
return (@state == PROCESSING)
end
#----------------------------------------------------------------------------
# * EffectsQueue
# Returns or initializes the EffectsQueue.
#----------------------------------------------------------------------------
def EffectsQueue
if @EffectsQueue.nil?
@EffectsQueue = []
end
return @EffectsQueue
end
#----------------------------------------------------------------------------
# * State
#----------------------------------------------------------------------------
def state
if @state.nil?
@state = IDLE
end
return @state
end
#----------------------------------------------------------------------------
# * Start
# Starts the job processing if there are any animations currently in queue.
# Otherwise, silently returns nil and doesn't perform anything.
#----------------------------------------------------------------------------
def start
# Exit if we have no jobs to process
return if self.EffectsQueue.empty?
# Otherwise, set up the correct context.
# By setting the state to PROCESSING, on the next frame, the update method
# should automatically start the animation process.
@state = PROCESSING
end
#----------------------------------------------------------------------------
# * Stop
# Stops the current job processing and CANCELS the current one.
# If we're currently IDLE, it will simply exit silently.
#
# @param {Boolean} clear Clears the EffectsQueue if true.
#----------------------------------------------------------------------------
def stop(clear = false)
# Exit if we're not processing anything
return unless processing?
# Reset the state
@state = IDLE
if clear
# Clear the whole queue, thereby cancelling the current job.
self.EffectsQueue.clear
else
# Cancel the current job by removing it from the queue
self.EffectsQueue.shift unless self.EffectsQueue.empty?
end
end
#----------------------------------------------------------------------------
# * Pause
# Pauses the current job, but does not cancel it.
# If we're currently not processing, it will simply exit silently.
#----------------------------------------------------------------------------
def pause
# Exit if we're not processing
return unless processing?
# Set the state to PAUSED effectively prevents any data update.
@state = PAUSED
end
#----------------------------------------------------------------------------
# * Resume
# Resume is an alias of start, which simply makes more sense in the context
# of pausing/resuming, as opposed to stopping/starting.
#
# The only difference is that it will set the state to idle if there are no
# jobs in the queue.
#----------------------------------------------------------------------------
def resume
# Return if we aren't processing
unless processing?
@state = IDLE
return
end
# Start processing
@state = PROCESSING
end
#----------------------------------------------------------------------------
# * Animate
# Animate is where the main Effects processing is done; it should be called
# from the object's update method or whatever is used to graphically update
# the object.
#----------------------------------------------------------------------------
def animate
# Exit if we're not processing
return unless processing?
# Get the current job (make sure there's one too)
job = self.EffectsQueue.first; return if job.nil?
# Let the Effect object do its thing.
job.update
end
#----------------------------------------------------------------------------
# * onAnimationFinish
# The Effect object calls this callback once the animation is finished to
# notify the Animated object that it should procede with the next animation.
#----------------------------------------------------------------------------
def onAnimationFinish(fx)
# Shift the EffectQueue
self.EffectsQueue.shift
# Do we have any more jobs?
@state = IDLE if self.EffectsQueue.empty?
end
end
And here's a link to the RMXP demo: http://rgss.etheon.net/Animator.zip
And the RMVX demo: http://rgss.etheon.net/VXnimator.zip
The demo code is in the Scene_Map script. It's neatly identified, so no one should have any trouble with it.
I'll be posting a couple of Effects templates, such as fade in/out, slide in/out, grow/shrink, projectile curves, etc.
You can view a demo of some of the different easings available on Robert Penner's site: http://www.robertpenner.com/easing/easing_demo.html