Creating an Animation Easing Library Using Lua (Guide)
Custom animation eases can add an additional layer of polish to your application.
In this post I'll walk you through:
- Understanding how easing works in Storyboard
- Creating an easing library using Lua
- Initializing the library for use within Storyboard
- Using a custom ease with an animation made using Lua
- Using a custom ease with an animation made using Storyboard
At the end of this post you'll find a Storyboard project file that uses everything in this post for your reference. It's also a great file to use for testing custom easing functions quickly since everything is already set up for you.
This guide assumes basic knowledge of Storyboard.
Initial Setup
If you'd like to create a file from scratch to follow along rather than using the completed file, follow these steps:
- Create a new Storyboard project, my example project is 800 x 480 pixels.
- Add the following folder/files to the scripts folder of your application:
scripts/
├── utils/
│ └── easing_library.lua
└── init.lua - Create a control with anything you'd like in it to use for testing your custom easing.
Understanding easing in Storyboard
Before creating our library, let's take a look at how custom easing is created in Storyboard.
To create a custom ease (aka tween) in Storyboard using Lua we need to use the function gre.animation_create_tween(name, tween_callback). Documentation for this function can be found here:
https://support.cranksoftware.com/hc/en-us/articles/360056944712-gre-animation-create-tween
Here's the example tween_callback callback function from our documentation that reimplements a linear ease:
local function CustomTweenCB(elapsed, base, change, duration)
return base + ((change * elapsed) / duration)
end
Our easing library will be a collection of callbacks containing easing math structured similarly to this.
Creating an easing library using Lua
To create our library of custom eases we're going to need easing math. Luckily for us, this repo of Lua easing math maps perfectly to our custom tween callback:
https://github.com/EmmanuelOga/easing/blob/master/lib/easing.lua
If you'd like to visualize these eases, this website is great for that:
https://easings.net/
Something very important to note is that at this time Storyboard is unable to use math to create overshoot eases. An example of an overshoot ease on easings.net is easeOutElastic- the animation overshoots its end point then settles back. For now all easing math must take place between point A and B.
This restriction may be lifted in the future, however for now if you need an overshoot you would need to create an animation with multiple steps.
Within utils/easing_library.lua add the following code:
--[[
Easing equations can be found here:
https://github.com/EmmanuelOga/easing
Disclaimer for Robert Penner's Easing Equations license:
Copyright © 2001 Robert Penner
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--]]
local pow = math.pow
local sin = math.sin
local cos = math.cos
local pi = math.pi
local sqrt = math.sqrt
local abs = math.abs
local asin = math.asin
local function in_out_quint(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * pow(t, 5) + b
else
t = t - 2
return c / 2 * (pow(t, 5) + 2) + b
end
end
local function in_out_quad(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * pow(t, 2) + b
else
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
end
return {
in_out_quad = in_out_quad,
in_out_quint = in_out_quint
}
To add more eases to your library, it's just a matter of adding more functions and returning them in this file.
Initializing the library for use within Storyboard
To initialize the library we just created, we'll need to use the Lua function we touched on earlier. Within init.lua add the following code:
-- Import and initialize custom easing in Storyboard.
local easing_lib = require("utils.easing_library")
gre.animation_create_tween("in_out_quad", easing_lib.in_out_quad)
gre.animation_create_tween("in_out_quint", easing_lib.in_out_quint)
Here we're calling the gre.animation_create_tween function with a string (the name of the tween) that we will reference whenever we want to use our custom ease and a callback function containing our easing math.
Please keep in mind that your ease name should not collide with any of the existing ease names. These include linear, bounce, easeinout, easein, easeout.
For the purposes of this demo, I'm initializing these eases globally on the startup of our application, you may wish to initialize them elsewhere. We just need to initialize them somewhere that any animation that uses them has access to them.
These eases are now ready to be used by animations created using Lua or Storyboard's Animation Timeline.
Using a custom ease with an animation made using Lua
If you're following along from scratch, within init.lua, underneath of the tweens we just created, add this code:
local ANIMATION_DURATION = 1200
local anim_id = "INIT"
local function animation(anim_position)
anim_id = gre.animation_create(30, 1)
-- An animation that moves between 200 and 600 on the x axis.
local anim_data = {}
anim_data["rate"] = "in_out_quad" -- YOUR EASING HERE
anim_data["duration"] = ANIMATION_DURATION
anim_data["to"] = anim_position == "LEFT" and 575 or 125
anim_data["key"] = "shapes.square.grd_x" -- YOUR CONTROL PATH HERE
-- Trigger the animation.
gre.animation_add_step(anim_id, anim_data)
gre.animation_trigger(anim_id)
end
local anim_pos = "LEFT" -- Can be LEFT or RIGHT.
local function interval_CB()
-- Prevents overlapping animations from being fired.
local anim_state = gre.animation_get_state(anim_id)
if (type(anim_state) == "table") then
if (anim_state.progress < 1) then
gre.animation_destroy(anim_id)
anim_id = {}
end
end
-- Triggers an animation and toggles the animation state.
if (anim_pos == "LEFT") then
animation(anim_pos)
anim_pos = "RIGHT"
elseif (anim_pos == "RIGHT") then
animation(anim_pos)
anim_pos = "LEFT"
end
end
function set_interval_CB()
interval_CB()
gre.timer_set_interval(ANIMATION_DURATION, interval_CB)
end
Then call the function set_interval_CB using a gre.screenshow.pre event on the main screen of your application.
You will need to update the key value in the animation function to match the path to your control. If you've given your ease a different name than what I have, you'll need to update that as well.
Using a custom ease with an animation made using Storyboard's Animation Timeline
To add your custom ease to an animation made using Storyboard's Animation Timeline, create an animation and then create a variable that has a string type and has a value of whatever you named your ease (in the example file I named my variable easing and gave it a value of in_out_quint).
Creating this variable at the application level could be wise so that that it can be used by any Timeline animation within your application.
Next, bind this variable to the rate of your Timeline animation:
Additional information
While animations can make your application more engaging and intuitive to use, it's important to remember that from a UX perspective animations must never get in the user's way by taking too long or by feeling unnatural (which is distracting).
There are a lot of great resources out there regarding the topic of when, where, and how to use animations in an application, here are a couple to get you started:
- https://uxdesign.cc/the-ultimate-guide-to-proper-use-of-animation-in-ux-10bd98614fa9
- https://www.nngroup.com/articles/animation-purpose-ux
Conclusion
If you'd like to download the example project for this post please do so here:
https://creaform3d.sharefile.com/share/view/sf93c67d0c8b44340966cdd34667b5fc1
For reference, in the example file, the square is animated using Lua and the circle is animated using the Animation Timeline.
Thanks for reading!
Comments
Please sign in to leave a comment.