Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Special Effects Base

e

Sponsor

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.

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
 
Stauf":9o6r02rx said:
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.

No more easy to me than a Doctor degree Algebra 2 Mathe test.
What did I just say?
[not flaming] :P
Anyways, what I think this is is a script that allows you to change the X, Y, Z, and opacity of the animations.

Am I right?
 

e

Sponsor

Opacity, X, Y, Z, whatever you want, as long as its a numerical property, something that can be coerced into a function over time. That includes Colors, Font sizes, Tones, Hues, etc.
 
:p now all we need is a curve editor lol !! with bezier curves and control points!

Good work!
Really cool to smooth animated stuff ( instead of having a constant speed of motion ) !
 

e

Sponsor

As I've said, this is mostly for scripters to integrate within their scripts to animate objects.

I will be releasing an Effects pack which will contain pre-made effects for Sprites and Windows that you can use from Events.
 
Hey Stauf,

Love this Module! You did way more then stuff I used to have in my projects (I only used easein (so in the beginning slow) and easesin-easeout (so slow - quick - slow)). But this is very cool. To have objects be uopdated sync, just keep using the @normal var, but in this way:

Code:
for i in @now...(@now + MAXUPDATING)
 break if objects[i].nil?
 [...] # update object[i]....
end
@now = [@now + MAXUPDATING, objects.size].min % objects.size

That should work ;)
 

e

Sponsor

Wouldn't that cause some lag if there were too many effects going on at the same time?

Though that's a reasonable risk; it'd be the scripter's fault :p
 
Stauf":1vxjqfbx said:
Wouldn't that cause some lag if there were too many effects going on at the same time?

Though that's a reasonable risk; it'd be the scripter's fault :p

MAXUPDATING
^
Restricts the number of sequential updates at the same time.
 

e

Sponsor

I fixed it in another way; using the callbacks provided, onStart and onFinish, you can easily launch multiple effects on multiple objects, and keep them tightly controlled.

I'm updating it to add a delay function (i.e.: wait X FPS before launching the next effect) and some syntactic sugar, such:

Code:
# Make the object fade in over 30 FPS then morph (change sizes) to 200 width and 300 height over 30 FPS
animatedObject.fx.do('fadein', 30).then('morph', 30, 200, 300)

Mostly for game designers and whatnot.

Also, for some reason, the easing equations (other than Linear) utterly fail when morphing (scaling) an object. They're fine when it comes to moving things around, but scaling things just goes awry, for some reason...which is no big deal, I guess.
 
.then(arguments) would simply call onFinish: Prog fx.do(arguments) right?

Your fixing way is better either way - but I hope it works the way you coded it (if you want a simultaneous effect I would call it with onStart: right? But what if Sprite A and Sprite B have a method with a effect, how would one call the other and still be sync?
 

e

Sponsor

In theory, on each frame, the effects update the specified properties. If their properties' growth rate are the same, they should grow similarly. There might be a one frame delay if Sprite B's update method was called prior to Sprite A launching Sprite B's effect queue, but that could be fixed, if known beforehand, by lowering Sprite B's effect duration by 1 frame. Of course, if you don't know that it will be called, I guess I could add some way to skip to the next frame right away (i.e.: SpriteB.fx.next_frame() would update the animation without waiting for the game engine to do another loop).
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top