← View other assets
asset

Tweens. everybody loves 'em, how do I use 'em?

Use the power of the tween to add some juice to the way things move around in your game!
Mimpy Mimpy
· 9 min read · 55 views

1
Introduction
A lot of things move in games. And in lots of different ways. Things don't move just by changing position; they also change in size, shape, rotation, color, and a whole lot of other ways.

Since movement is such an essential aspect of games, it stands to reason that we should have a wide range of tools to make things move around. I don't want my objects to move around at constant speeds, I want them to wiggle and bounce! I want to lerp from one value to another in fun shapes and over set amounts of time!

"Oh, I know about lerp," I hear you say. "I use it all the time in my project. I just write x = lerp(x, target, fraction); in my step event and I'm lerping all over the place. Pretty cool, right?" WRONG. That's not cool. You know what's cool? Using lerp in a robust way that lets us specify the exact amount of time our movement can take, and the exact shape that we move in.

Enter tweens. With a good tween system, you can make your objects change in tons of ways with the greatest of ease! Once you have the system set up, implementing tweens in new objects is as easy as calling a couple constructors.

2
The Manager
First, we'll need to make a constructor for our tween manager. Let's make a script resource and call it tween_manager. Inside it, we'll write our constructor:

function TweenManager() constructor {
    tweens = [];

    // Start a new tween
    add = function(_tween) {
        array_push(tweens, _tween);
    }

    // Run the tween manager
    run = function() {
        // Has a tween finished?
        if (array_length(tweens) > 0 && tweens[0].run()) {

            tweens[0].onEnd();

            // Remove finished tween
            array_delete(tweens, 0, 1);
        }
    }
}

Our manager has three variables:

tweens - this array holds all active tweens. The manager will run each tween in order to completion, letting us queue up several in a row.

add - this method lets us add new tweens to the manager in a nice, readable way.

run - this method is called in our object's step event to make the tween manager do is job.

3
The Tween
Next, we'll need to make a constructor for the tweens themselves. Let's add a second constructor to the end of the tween_manager script.

function Tween(_duration, _start = 0, _finish = 0, _property = function() { }, _shape = "Linear", _onEnd = function() { }) constructor {
    // Where does the tween start? Where will it end?
    start = _start;
    finish = _finish;

    // How long does the tween take?
    time = 0;
    duration = _duration;

    // What does the tween affect?
    property = method(other, _property);
    shape = animcurve_get_channel(anim_tweens, _shape);

    // What happens when the tween is over?
    onEnd = _onEnd;

    // Run the tween
    run = function() {
        time = min(time + 1, duration);

        if (is_array(start)) {
            var value = [];
            for (var i = 0; i < array_length(start); i++) {
                value[i] = lerp(start[i], finish[i], animcurve_channel_evaluate(shape, time / duration));
            }
            property(value);
        }
        else {
            property(lerp(start, finish, animcurve_channel_evaluate(shape, time / duration)));
        }
        return time == duration;
    }
}

Our tween has quite a few variables. Let's go over each one in order.

duration - how long the tween lasts.

start - what value to start the tween at

finish - what value to finish the tween at

property - what property the tween should change (x, y, size, rotation, color)

shape - what kind of shape the tween should take (linear motion, slow down at the end, speed up at the end, bounce around, wiggle). This variable uses animation curves to convert a name into a shape. We'll talk about Animation Curves in the upcoming section.

onEnd - you can specify code to run when the tween is over. This is useful for things like a menu button that needs to only become clickable after sliding into view, or for destroying an object after it plays a fading out tween.

Our tween also has its own run function, which the manager calls. The run function will update the tween, advance time, set the property to where it should currently be, and also tells the manager when the tween is over. You can see that there is handling for whether or not our start and finish is an array or not. If they are arrays, then we can tween multiple properties at the same time!

4
Animation Curves
In order to convert a shape name into an actual shape, we use an Animation Curve. Animation Curves can be used to define the way that a value changes over time. The slope of the line determines how fast the property changes, or even what direction it's changing in. A straight line animation curve would make our property change at a constant speed. An animation curve that starts flat and sharply rises towards the end would make our property change slowly at first, then very quickly at the end. A curve that dips downwards before rising to the end would make our property change in the wrong direction before turning around and correcting itself. A selection of different curves

There are many different types of curve that you can store in a single Animation Curve asset. As long as the curve starts at 0,0 and ends at 1,1, it doesn't matter what happens in between, the tween will work. This means you are free to create any kind of shape you want. But if you don't have anything specific in mind, you can use the preset options located in the dropdown menu on the left. Presets!

5
Properties
Lastly, we'll want to create a new script to store the different properties that we can tween. I'm going to make a new script and call it tween_properties and fill it with a few global functions that we can use later on.

function TWEEN_X(_value) { x = _value; };

function TWEEN_Y(_value) { y = _value; };

function TWEEN_XY(_value) { x = _value[0]; y = _value[1]; };

function TWEEN_ANGLE(_value) { image_angle = _value; };

function TWEEN_SIZE(_value) { image_xscale = _value; image_yscale = _value; };

function TWEEN_ALPHA(_value) { image_alpha = _value; };

These are just some common properties to tween, but you can add more if you need to.

Note that some functions accept a single value and set a single property, like TWEEN_X which only sets x position.

Some functions can change multiple properties at the same time, like TWEEN_XY which sets x and y individually.

And some functions set multiple properties using one value, like TWEEN_SIZE which sets image_xscale and image_yscale to the same value. You have a lot of options!

6
Using it in an Object
And that's all of the nitty gritty code that we need to write in order to get our tween system in a usable condition. So, how do we get an object to actually use this system? Well, luckily the answer is pretty simple.

We just need to add a tween manager in its create event:

/// Create Event
tm = new TweenManager();

And then we need to run the tween manager in our step event:

/// Step Event
tm.run();

And we are completely done! Our object is now a tween user! Whenever we want to start a new tween, we'd just create one and add it to our tween manager with add.

As an example, let's say we want to tween from x = 0 to x = 100 over one second. And let's say that we want to overshoot the destination a little bit and move back and settle on it.

/// Start a new tween!
tm.add(new Tween(room_speed, 0, 100, TWEEN_X, "BackOut"));

Or, say we want our object to output "I'm finished!" when the tween is over. We could add an onEnd function like so:

/// Start a new tween!
tm.add(new Tween(room_speed, 0, 100, TWEEN_X, "BackOut", function() {
    show_message("I'm finished!");
}));

7
Examples
Let's take a look at some of the possibilities tweens have to offer.

Smooth animations!
// Smooth animations!
tm.add(new Tween(room_speed, 0, 360, TWEEN_ANGLE, "EaseOut")); // spin counterclockwise
tm.add(new Tween(room_speed, 360, 0, TWEEN_ANGLE, "EaseIn")); // then spin clockwise

Here we spin around in a circle and back using the TWEEN_ANGLE function.

Dramatic scrolling text!

// Dramatic Scrolling Text!
var width = string_width(text) * 0.5; // grab text width
tm.add(new Tween(room_speed * 3, -width, room_width + width, TWEEN_X, "MidSlow")); // left to right

Here we scroll left to right and slow down in the middle using the MidSlow shape.

Queued actions!

// Queued actions!
tm.add(new Tween(room_speed, x, x + 96, TWEEN_X, "BackOut")); // move right
tm.add(new Tween(15)); // wait 15 frames
tm.add(new Tween(room_speed, y, y + 96, TWEEN_Y, "BounceOut")); // drop down
tm.add(new Tween(15)); // wait 15 frames
tm.add(new Tween(room_speed * 2, x + 96, x,  TWEEN_X, "ElasticOut")); // slingshot left
tm.add(new Tween(room_speed, y + 96 * 2, y, TWEEN_Y, "EaseOut")); // rise up

Here, we queue up multiple tweens in a row to make a sequence of actions. We even use empty tweens with nothing but a duration to wait for a period of time!

Move in fun ways!

// Move in fun ways!
tm.add(new Tween(room_speed, [x, y], [mouse_x, mouse_y], TWEEN_XY, "BounceOut")); // move to mouse

This tween uses the multiple property function TWEEN_XY to move on both the x and y axis.

Watch out!

// Explode!
tm.add(new Tween(room_speed, 1, 2, TWEEN_SIZE, "ElasticInOut", function() {
    sprite_index = spr_explosion;
}));

This tween takes advantage of the onEnd method to make the bomb explode after the tween ends.

8
Outro
And there you have it! You are now a certified tweening expert. Go out and move, shake, wiggle, wobble, and zip around any which way you please!

If you don't feel like setting up the system and making all the different curves yourself, then here's a dropbox link to an example with the code we talked about.