TweenXCore
TweenXCore is a cross-platform tween library for easier creation of pleasant motion with Haxe.
It is designed to make execution speed faster, development speed faster, less buggy, less learning cost, and make motion more fun.
Feature
-
44 kinds of easing function
-
Yoyo, zigzag movement
-
Custom functions such as easing mix, crossfade, concatenation
-
HSV color, RGB color
-
Polar coordinates
-
Bezier curve
-
Static extension of
Float
, such aslerp
,inverseLerp
,clamp
-
Cooperation with custom easing tool
Benchmark
TweenXCore does not do any extra processing or instance creation, so it works very fast.
The following is a comparison with each tween library on the Flash platform. I measured the FPS when tweening 250,000 objects at the same time.
License
MIT License
Start TweenXCore
Haxe version
Haxe 3.1.3 or later is supported.
Installation
After installing Haxe, enter the following command on the command line interface.
haxelib install tweenxcore
Hello TweenXCore
As the first sample of TweenXCore, let’s look at the code to move the rectangular x coordinate from 0 to 450.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 import sample.Sprite;
import sample.Square;
using tweenxcore.Tools;
class SimplestSample extends Sprite {
public static var TOTAL_FRAME:Int = 20;
private var square:Square;
private var frameCount = 0;
public function new() {
super();
// Place a square in the initial position
addChild(square = new Square());
square.y = Square.SIZE * 2.0;
}
// Call this function every frame
public function update():Void {
var rate = frameCount / TOTAL_FRAME;
// An animation when rate is 0 to 1.
if (rate <= 1) {
// Move x from 0 to 450 according to the value of rate.
square.x = rate.quintOut().lerp(0, 450);
}
frameCount++;
}
}
The code related to TweenXCore is the following part.
using tweenxcore.Tools;
square.x = rate.quintOut().lerp(0, 450);
Let’s see each one one by one.
using tweenxcore.Tools
The tweenxcore.Tools
module that we load here has four classes.
- Easing class
-
It has easing functions used as a curve of motion
- FloatTools class
-
Static extension of Float.
- PointTools class
-
Static extension of point on XY coordinates. For example, Bezier curve. It can be used not only for the
Point
class in Flash, but also forPoint
types in various libraries. - MatrixTools class
-
Extension of matrix of affine transformation of XY coordinates. It adds a function for similarity transformation of two-dimensional motion. It can be used not only for the Flash
Matrix
class but also for theMatrix
type of other library with similar interface.
using tweenxcore.Tools;
will read this all as an extension.
Many of the functions introduced in the tutorial are in the module of this Tools
, so I think whether it is easier to understand by reading the source.
Easing function
quintOut
is an easing function. It changes the value of rate
which changes from 0 to 1 to the value after the quintic function.
Of course not only quintOut
is the easing function provided by TweenXCore.
Based on the Robert Penner’s easing function, TweenXCore provides a total of 44 functions include OutIn
mode which decelerates at the center and accelerates again, and warp
which instantaneously moves.
Let’s see a bit of the implementation of these functions.
The cubicIn
function in TweenXCore is as follows.
function cubicIn(t:Float):Float {
return t * t * t;
}
It is a simple function that receives a Float and returns Float.
Let’s replace the easing of the previous sample code with this function. Just one line, make the changes as follows.
square.x = rate.cubicIn().lerp(0, 450);
You can see that the movement has changed from the quintOut
.
lerp
lerp
is a function of linear interpolation, which is included in tweenxcore.Tools.FloatTools
.
In the example above, it converts values in the specified range from 0.0 to 1.0 to values from 0 to 450. This will cause square.x
to move from 0 to 450 while rate
changes from 0.0 to 1.0.
Principle of TweenXCore
Starting from 0.0 and ending with 1.0
In the TweenXCore world, the starting value is 0.0 and the ending value is 1.0.
That is,
-
For time of motion, start time is expressed as 0.0 and end time is used as 1.0.
-
For alpha, the complete transparency is 0.0 and the complete opacity is 1.0.
-
For circle 1 turn, 0 degree is 0.0 and 360 degree is 1.0.
-
For red values of RGB color, 0.0 is no redness and 1.0 is completely red.
In TweenXCore, we often use the variable name rate
for such values that are based on 0.0 to 1.0.
No black box
TweenXCore does not have such a function as to automatically move objects by designating a departure point or a reaching point.
The function that the tween library automatically moves the object is easy if you just play the motion, but it will be difficult when you try to do somewhat elaborate things.
For example, it is as follows
-
You want to pause animations in the game when the pause button is pressed and display the popup with animation.
-
You want to switch a indicator to fade out immediately after process is finished while it is fading in
-
For some motion, You want to play slow motion only while mouse is down
With many tween libraries, you can not realize these behaviors or you have to remember complicated specifications.
In contrast, the solution for TweenXCore is straightforward.
-
If you want to stop motion, stop updating the place you want to stop.
-
Fade in and fade out can be switched with a simple
if
statement. -
If you increment the
frameCount
by 0.5, the playback speed of motion will be 0.5 times.
Nothing is difficult.
TweenXCore offers three things:
-
Function to convert values not in the range of 0.0 to 1.0 from 0.0 to 1.0. (e.g.
FloatTools.inverseLerp
function,FloatChange
class) -
Function to convert a number between 0.0 and 1.0 to a number between 0.0 and 1.0 with another curve. (e.g.
Easing
and custom easing functions) -
Function to convert a number between 0.0 and 1.0 to various values. (e.g.
FloatTools.lerp
andTimeline
class)
If tween library has only these functions, you can freely create your own motions. The method will be explained in the tutorial.
Can be used anywhere
TweenXCore can be used without being interfering with the platform, the framework used together, and the programming paradigm.
-
The style that you are aiming is well matched with TweenXCore whether it is object oriented, procedural programming, or functional programming.
-
Whether the framework you use is OpenFL, React or Unity, it will work the same way.
-
It works on client side, server side, even at compile time.
TweenXCore Tutorial
Create your own easing
There are 44 kinds of easing in TweenXCore, but using these simply tends to be mediocre motion. In TweenXCore you can combine and mix easing to create your own easing.
Composition
By combining two or more easings, you can create new movements.
square.x = rate.cubicIn().bounceOut().lerp(0, 450);
By using cubicIn
and using bounceOut
, we are making an easing of accelerating bounds.
Mix
mixEasing
is intermediate easing between the two easings.
square.x = rate.mixEasing(Easing.expoOutIn, Easing.linear, 0.18).lerp(0, 450);
The sample is likely to be in the cut-in animation of the game. Mixing the linear
function to expoOutIn
0.18 eliminates stillness in the middle of OutIn
easing.
Crossfade
crossfadeEasing
is easing that gradually changes to another easing at the beginning and at the end.
square.x = rate.crossfadeEasing(
Easing.quintOut,
Easing.bounceOut,
Easing.sineInOut
).lerp(0, 450);
The sample begins as quintOut
and gradually changes to Easing.bounceOut
in easing. I used Easing.sineInOut
as a curve of how to change.
Yoyo
yoyo
is a motion that goes from 0.0 to 1.0 and returns to 0.0 in the reverse playback movement.
square.x = rate.yoyo(Easing.quintOut).lerp(0, 450);
Zigzag
zigzag
is a motion that goes from 0.0 to 1.0 and returns to 0.0 with the movement in which the moving direction is reversed.
square.x = rate.zigzag(Easing.quintOut).lerp(0, 450);
Connect
connectEasing
is a function that connects two easings.
square.x = rate.connectEasing(Easing.backOut, Easing.linear, 0.9, 0.4).lerp(0, 450);
In the sample, it moves backOut
to 0.4
in the first 0.9
time and then moves the rest in linear
.
One two
oneTwoEasing
is easing to move twice in different easing.
square1.x = rate.oneTwoEasing(Easing.backIn, Easing.linear, 0.7).lerp(30, 420);
backIn
makes the first move, linear
makes the second move.
CustomEasing class
If you use such easing custom functions more than once, it is useful to create a CustomEasing class that collects your own easing.
using tweenxcore.Tools;
class CustomEasing {
public static inline function quintQuintInOut(rate:Float) {
return rate.quintInOut().quintInOut();
}
}
If you define the CustomEasing
class like this, you can easily use it by using packageName.CustomEasing;
, for your own easing.
Easing Editor
The easing editor is a tool for making Easing yourself. You can make easing while actually trying combinations on a browser.
Handle changes in Float (FloatChange)
Previous samples used only the current value, but you can make various actions by using both the previous value and the current value.
TweenXCore provides FloatChange
class that handles the previous and current values.
Get the moment across the value
An example using FloatChange
is the resolution of the moment the frame count crosses a particular value.
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
// At the moment the frame count crosses 30.0, a rectangle is displayed
if (floatChange.isCrossOver(30.0)) {
addChild(square = new Square());
square.width = 481;
square.height = 151;
}
}
The first argument of new FloatChange
is the previous
value, the second argument is the current
value, FloatChange
provides convenience functions to handle these two values.
The isCrossOver
function is true
only at the moment when this previous
and current
crossed the specified value.
In this example, it is solved by the condition of previous <= 30.0 && 30.0 < current
or current <= 30.0 && 30.0 < previous
FloatChange
is useful, for example, to make time-base motion. Even if previousTime
and currentTime
happen to have the same value when new FloatChange (previousTime, currentTime)
is set, the processing judged by isCrossOver
will not be called twice.
Get while a value is in a section
The motion when the frame is in a specific section.
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
floatChange.handlePart(20.0, 50.5, updatePart);
}
private function updatePart(part:FloatChangePart):Void {
var left = part.previous.expoOutIn().lerp(0, 480);
var right = part.current.expoOutIn().lerp(0, 480);
square.x = left;
square.width = right - left;
}
The handlePart
function calls the function given as the third argument in synchronization when moving in the section specified by FloatChange
.
In this example, when passing through the section 20.0
to` 50.5`, call the updatePart
function.
FloatChangePart
of the first argument of updatePart
is FloatChange
whose start value is 0.0
and whose exit value is 1.0
. In this case, it is passed in correspondence so that it becomes 0.0
when the original FloatChange
value is 20.0
and 1.0
when it is 50.5
.
At this time, updatePart
will not be called with current
and previous
of FloatChangePart
lower than 0.0
or higher than` 1.0`.
Acquire the start and end of the section
In FloatChangePart
, functions are provided to acquire the start timing and end timing of motion.
private function updatePart(part:FloatChangePart) {
if (part.isEntrance()) {
var topBar = new Square();
addChild(topBar);
topBar.width = 481;
}
square.x = part.current.expoIn().lerp(0, 450);
if (part.isExit()) {
var bottomBar = new Square();
addChild(bottomBar);
bottomBar.y = 120;
bottomBar.width = 481;
}
}
Repeat
If you want to repeat a part more than once, use handleRepeatPart
instead of handlePart
.
change.handleRepeatPart(20, 40, 3, updatePart);
In this sample, FloatChangePart
movement of 0.0 to 1.0 has been repeated three times during the 60 frames from the 20th frame to the 80th frame.
"handleRepeatPart" passes "FloatChangeRepeatPart" which extends "FloatChangePart" as an argument to "updateSquare", from which you can get additional information such as how many times the current iteration is.
Handle consecutive motions
To handle consecutive motions, you can use handleTimelinePart
of FloatChange
.
We made three movements, right, down, left.
var timeline:Timeline<FloatChangeTimelinePart->Void>;
public function new() {
// (Abbreviation)
// Create an array of weighted update functions.
timeline = new Timeline().add(update1, 1).add(update2, 2).add(update3, 5);
}
public function update():Void {
var floatChange = new FloatChange(frameCount, frameCount += 1);
floatChange.handleTimelinePart(0, 80, timeline);
}
private function update1(part:FloatChangeTimelinePart):Void {
// right
square.x = part.current.lerp(0, 450);
}
private function update2(part:FloatChangeTimelinePart):Void {
// down
square.y = part.current.cubicInOut().lerp(0, 120);
}
private function update3(part:FloatChangeTimelinePart):Void {
// left
square.x = part.current.quartIn().cubicIn().lerp(450, 0);
}
Timeline
is a weighted array. Each element of the array is weighted with Float
. In the sample, update1, update2, update3
is weighted by 1: 2: 5
.
handleTimelinePart
calls the update function according to this weight. In the sample, it calls update1
over 10 frames, update2
over 20 frames, and update3
over 50 frames.
FloatChangeTimelinePart
inherits from FloatChangePart
and you can get additional information such as where you are currently on the timeline.
2-dimensional motion
Simple motion
All of the motion up to now has been compensated for movement in the X direction, but I will also add motion in the Y direction.
square.x = part.current.lerp(0, 450);
square.y = part.current.sinByRate().lerp(60, 105);
I swung the square in the Y direction. The sinByRate
used here is a sin
function which treats circumference of a circle as 1.0.
This sample is not very new. The problem is when doing similar motion diagonally.
Similarity transformation
We have used the lerp
function to convert a value between 0.0 and 1.0 to the actual x
coordinates, but it can not be expressed by the lerp
function when rotation is added.
In that case, use MatrixTools.createSimilarityTransform
.
private var matrix:MatrixImpl;
public function new() {
// (Abbreviation)
// For the Flash platform, you can use flash.geom.Matrix.
// In the case of pixi.js, OpenFL, etc., you can use Matrix defined in each framework.
// In actual sample code, I use classes I defined for my own sample.
matrix = new MatrixImpl();
// Make a matrix that similarly transforms the movement from (0, 0) to (1, 0) to the movement from (100, 0) to (350, 120).
matrix.createSimilarityTransform(100, 0, 350, 120);
}
private function updatePart(part:FloatChangePart):Void {
var x = part.current;
var y = part.current.sinByRate().lerp(0, 0.1);
square.x = matrix.a * x + matrix.c * y + matrix.tx;
square.y = matrix.b * x + matrix.d * y + matrix.ty;
}
The createSimilarityTransform(fromX, fromY, toX, toY)
function creates a matrix that similarly transforms "the movement in the X direction from 0.0 to 1.0" to "the movement fromX
to toX
in the X direction and fromY
to toY
in the Y direction".
Polar coordinates
TweenXCore supports polar coordinates.
public function new() {
// (Abbreviation)
// Make a similarly transform
matrix = new MatrixImpl();
matrix.createSimilarityTransform(210, 60, 0, 0);
}
private function updatePart(part:FloatChangePart) {
// approaching the origin
var distance = part.current.expoOut().lerp(1, 0);
// Make two rounds counterclockwise.
var angle = part.current.lerp(0, -2);
// Convert from polar coordinates to XY coordinates
var polarPoint = new PolarPoint(distance, angle);
var x = polarPoint.x;
var y = polarPoint.y;
// Convert to actual coordinates
square.x = matrix.a * x + matrix.c * y + matrix.tx;
square.y = matrix.b * x + matrix.d * y + matrix.ty;
}
The sample is a motion that rotates from (0, 0)
position with (210, 60)
as the center of polar coordinates and approaches there.
Bezier curve
TweenXCore also supports Bezier curves.
square.x = rate.bezier3(0, 50, 400, 450);
square.y = rate.bezier3(0, 200, -50, 120);
The sample is a cubic Bezier curve whose starting point is (0, 0)
, the control points are (50, 200)
and (400, -50)
, and the end point is (450, 120)
.
Bezier curves can be used for Bezier curves of arbitrary order as well as third order. Please check the tweenxcore.Tools
module for details.
Move various things
RGB color, HSV color
In TweenXCore you can use RGB color and HSV color.
var curve = part.current.expoInOut();
var hue = hsvCurve.lerp(0.0, 1.0); // Make one round of hue
var saturation = hsvCurve.lerp(0.0, 0.8); // Increase saturation
var value = 0.95; // Lightness fixed
var color = new HsvColor(hue, saturation, value);
The sample draws a band while moving each value of HSV.
Image
I introduced a weighted array Timeline
to deal with consecutive motions, but this Timeline
can also be used to move non-contiguous values.
In other words, it can be used to create a frame animation of images, for example.
Tween with other than time
Tween using mouse
If it is possible to convert from 0.0 to 1.0, the value that is the source of the tween does not have to be time. In the following sample, we move the position of the square using the mouse coordinates.
var rateX = mouseX.inverseLerp(10, 800).clamp(0, 1);
var rateY = mouseY.repeat(0, 400);
square.x = rateX.expoInOut().lerp(0, 450);
square.y = rateY.yoyo(Easing.expoInOut).lerp(0, 120);
inverseLerp
is a linear interpolation in the opposite direction to lerp
, in our example it converts "values from 10 to 800" "from 0 to 1".
clamp
is a function to set the lower and upper limits, in our example we convert a value less than 0 to 0 and a value greater than 1 to 1.
repeat
is a repeating function, which is the same as inverseLerp
until converting a value from 0 to 400 from 0 to 1, but outside the specified interval such as a value less than 0 or a value greater than 400 Behavior is different.
For example, repeat
converts from 0 to 1 with values such as -400 to 0, 0 to 400, 400 to 800, 800 to 1200.
Random
Shake
TweenXCore can make various expressions by using it together with random numbers. For example below, I created a effect to shake a square.
public function update():Void {
var scale = 3;
square1.x = scale.shake( 90);
square1.y = scale.shake( 60);
square2.x = scale.shake(225, random2);
square2.y = scale.shake( 60, random2);
square3.x = scale.shake(360, random3);
square3.y = scale.shake( 60, random3);
}
private static function random2():Float {
// Make closer to 0 or 1
return Math.random().quintInOut();
}
private static function random3():Float {
// Make closer to 0.5
return Math.random().quintOutIn();
}
FloatTools.shake
receives magnitude to shake as the first argument, center value as the second argument, and function to generate random number as the third argument.
In the sample, by applying easing to the generated random numbers, I changed the way of shaking.