Tuesday, April 14, 2015

Phaser tutorial: custom easing functions for tweening and easing functions with parameters






Previous Phaser tutorials:
Phaser tutorial: sprites and custom properties for atlas frames
Phaser tutorial: manage different screen sizes
Phaser tutorial: How to wrap bitmap text


 Phaser has many easing functions you can use for tweens. But it may happen, that none of them is what you need. Secondly, default easing functions are not possible to parametrize. Fortunately, it is quite easy to write your own easing function and also create ones that allow you to use additional input parameters.


Gravity bounce easing

 In this tutorial I will create "Gravity bounce" easing function. It will produce gravity-like bouncing and it will be possible to parametrize it with three arguments:
  • number of bounces (or -1 if we let the the function calculate it based on other values),
  • elasticity (or -1 if we let the function calculate it based on other values),
  • start from top, if we want first bounce start from top, which means it is half-bounce
 Main target of this article is not the function itself, but way how to use it with Phaser tweens and also how to pass parameters to it. If you want to know very details of the function, you can read article on it here (the same function, but in C++). You can also download final project here and check source.

 Below you can see it in action. Four usages of final function are shown there - just press 1, 2, 3 or 4:




Tweens and easing functions

 Let's create simple tween:

            // move tween
            var moveTween: Phaser.Tween = this.game.add.tween(target);
            moveTween.to({ x: 150 }, 1000, Phaser.Easing.Linear.None);
            moveTween.start();

 This tween moves "target", which can be for example sprite, to x position 150 in 1 second (1000 ms). The used easing function is Phaser.Easing.Linear.None. If you look into Phaser source tween\Easing.js you can see that this function is defined like this:

Phaser.Easing = {

    /**
    * Linear easing.
    *
    * @class Phaser.Easing.Linear
    */
    Linear: {

        /**
        * Linear Easing (no variation).
        *
        * @method Phaser.Easing.Linear#None
        * @param {number} k - The value to be tweened.
        * @returns {number} k.
        */
        None: function ( k ) {

            return k;

        }

    },

 Other easing function follows it. Each one is taking just one parameter "k". This parameter is between 0 and 1 and function is called from update function in tween\TweenData.js like this:

        this.percent = this.dt / this.duration;

        this.value = this.easingFunction(this.percent);

 So, if you want to create you own easing function and use it with tweens you have to create function that takes "k" between 0 and 1 and returns also number between 0 and 1 (ok, you can violate this and return values out of this range to "overshoot"). Unfortunately, this does not allow us to pass some additional parameters to the function. We also have to solve context issue.

 We can do this:

                :
                :

            // context
            var self = this;
            var param1 = 10;
            var param2 = 20;

            // bounce tween 1
            var bounceTween1: Phaser.Tween = this.game.add.tween(target);
            bounceTween1.to({ y: 150 }, 1000, function (k) {
                return self.myEasing(k, param1, param2);
            });
        }

        public myEasing(k: number, param1: number, param2: number) {
            // do something with parameters
            return k;
        }

 We are passing "wrapper" function to tween that satisfies request for "k" parameter, but it just wraps call to another function that takes additional parameters from variables. This will work, but we can do better. First, we can encapsulate parameters in object and second, we can split easing function workload between some initialization part which is called only once (in constructor) and second part that is called during tween update.


Gravity bounce class

 This is structure of  Gravity easing class with empty methods (for detail see source in final project, or read detailed article here (explained C++ source)):

module Utils {

    export class GravityBounce {
        // constants and variables

        // -------------------------------------------------------------------------
        constructor(aBounces: number, aElasticity: number = -1.0, aHalveFirstBounce: boolean = true) {
            // initial calculations
        }

        // -------------------------------------------------------------------------
        public easing(aDurationProgress: number): number {
            // easing function calculates height of bounce in range 0 - 1
            return height;
        }
    }
}

 Class takes initial parameters in constructor. It does quite a heavy calculations on number of bounces, elasticity and so on. It stores several arrays with bounce parameters like duration, height, velocity. Thanks to this the final easing function is then fast. More, the easing function itself does not store or change anything - it just says what output in range 0 - 1 is for input 0 - 1, so it can be shared among multiple objects. In constructor you say for example: "I want gravity bounce function, that will bounce 3 times, elasticity will be 0.25 and I want the first bounce start from top". Constructor does all the calculations and you can then use easing() function in this object for any number of tweens.

 We can then use this class where we want. In final project see boot.ts file. There are created four private variables, that store four different gravity bounce objects:

        private _easing1: Utils.GravityBounce = null;
        private _easing2: Utils.GravityBounce = null;
        private _easing3: Utils.GravityBounce = null;
        private _easing4: Utils.GravityBounce = null;

 Each is initialized with different parameters in create() function:

            // initialize easing functions
            this._easing1 = new Utils.GravityBounce(3 /*, -1, true*/);
            this._easing2 = new Utils.GravityBounce(3, -1, false);
            this._easing3 = new Utils.GravityBounce(-1, 0.5 /*, true*/);
            this._easing4 = new Utils.GravityBounce(4, 1.1 /*,true*/);

 Tweens are then created like this:

            // context
            var self = this;

            // bounce tween 1
            var bounceTween1: Phaser.Tween = this.game.add.tween(ball);
            bounceTween1.to({ y: ballY - 150 }, 1500, function (k) {
                return self._easing1.easing(k);
            });


Conclusion

 In the end we have reusable object with easing function that simulates real world gravity bouncing. It is flexible as we can parametrize it with number of bounces or elasticity (or both). It has initialization part, that does all the heavy calculations and easing function itself is fast.


Download final project here: CustomEasing.zip





No comments:

Post a Comment