О чем этот пример

Анимация — это не просто перемещение объекта из точки А в точку Б. Это ощущение массы, упругости и инерции. Phaser предоставляет разработчикам 32 встроенных уравнения плавности (easing equations), которые превращают механическое движение в живое и выразительное. В этой статье мы разберем готовый пример, который наглядно демонстрирует все доступные варианты, и научимся применять их для создания профессиональной игровой анимации с помощью системы твинов. Понимание и умелое применение easing-функций — ключ к тому, чтобы игровые персонажи двигались естественно, интерфейсные элементы появлялись элегантно, а физические взаимодействия ощущались «правильно». Это фундаментальный навык для любого геймдев-разработчика, работающего с 2D.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene {
    constructor() {
        super();
    }

    preload() {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bar', 'assets/sprites/bluebar.png');
    }

    create() {
        //  There are 32 built-in easing equations including Elastic which is shown in the Elasticity example

        var eases = [
            'Linear',
            'Quad.easeIn',
            'Cubic.easeIn',
            'Quart.easeIn',
            'Quint.easeIn',
            'Sine.easeIn',
            'Expo.easeIn',
            'Circ.easeIn',
            'Back.easeIn',
            'Bounce.easeIn',
            'Quad.easeOut',
            'Cubic.easeOut',
            'Quart.easeOut',
            'Quint.easeOut',
            'Sine.easeOut',
            'Expo.easeOut',
            'Circ.easeOut',
            'Back.easeOut',
            'Bounce.easeOut',
            'Quad.easeInOut',
            'Cubic.easeInOut',
            'Quart.easeInOut',
            'Quint.easeInOut',
            'Sine.easeInOut',
            'Expo.easeInOut',
            'Circ.easeInOut',
            'Back.easeInOut',
            'Bounce.easeInOut'
        ];

        const stepY = 19

        // labels
        for (const [index, easeFnName] of eases.entries()) {
            this.add.text(140, 23 + (index * stepY), easeFnName).setOrigin(1, 0).setFontSize(14)
        }

        var markers = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY }, setAlpha: { value: 0.3 } });

        var images = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY } });


        var _this = this;

        images.children.forEach(function (child) {

            _this.tweens.add({
                targets: child,
                x: 700,
                ease: eases.shift(),
                duration: 1500,
                delay: 1000,
                yoyo: true,
                repeat: -1,
                repeatDelay: 1000,
                hold: 1000
            });

        });

    }

}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ассетов

В методе preload() происходит базовая настройка загрузчика и загрузка единственного спрайта — синей полоски (bluebar.png). Этот спрайт будет использован как визуальный маркер для анимации.

preload() {
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bar', 'assets/sprites/bluebar.png');
}

Библиотека встроенных уравнений плавности

Ядро примера — массив eases, содержащий строковые имена 32 встроенных функций плавности. Phaser группирует их по типам: Quad, Cubic, Back, Bounce и другие. Каждый тип представлен тремя вариациями: * easeIn: Ускорение в начале анимации. * easeOut: Замедление в конце анимации. * easeInOut: Сочетание ускорения и замедления.

Первым в списке идет 'Linear' — равномерное, лишенное ускорения движение, которое служит точкой отсчета.

var eases = [
    'Linear',
    'Quad.easeIn',
    'Cubic.easeIn',
    // ... остальные уравнения
    'Bounce.easeInOut'
];

Создание визуальной легенды

Чтобы было понятно, какая анимация соответствует какому уравнению, для каждого элемента массива создается текстовый лейбл. Метод this.add.text() размещает текст с выравниванием по правому краю (.setOrigin(1, 0)). Координата Y для каждого лейбла рассчитывается с помощью шага stepY, что создает ровный столбец.

const stepY = 19;
for (const [index, easeFnName] of eases.entries()) {
    this.add.text(140, 23 + (index * stepY), easeFnName).setOrigin(1, 0).setFontSize(14);
}

Далее создаются две идентичные группы спрайтов-полосок с помощью this.add.group(). Первая группа (markers) служит статичным фоном с полупрозрачными полосками (.setAlpha: { value: 0.3 }), чтобы обозначить стартовую позицию. Вторая группа (images) содержит такие же полоски, но они будут анимированы.

var markers = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY }, setAlpha: { value: 0.3 } });
var images = this.add.group({ key: 'bar', repeat: 27, setXY: { x: 196, y: 32, stepY } });

Применение анимации к каждому спрайту

Здесь происходит самое важное — создание твинов. В цикле forEach для каждого спрайта из группы images создается отдельная анимация с помощью this.tweens.add().

Ключевой параметр — ease. Для каждого следующего спрайта из массива eases забирается (.shift()) и подставляется новое уравнение плавности. Таким образом, каждая полоска в столбце анимируется по своему уникальному закону.

Параметры твина: * targets: Цель анимации — текущий спрайт (child). * x: 700: Конечная точка перемещения по оси X. * duration: 1500: Длительность одного движения в миллисекундах. * yoyo: true: После достижения конечной точки анимация проигрывается в обратном порядке. * repeat: -1: Бесконечное повторение. * delay, repeatDelay, hold: Эти параметры создают паузы, формируя четкий, легко читаемый цикл "старт-движение-пауза-возврат-пауза".

images.children.forEach(function (child) {
    _this.tweens.add({
        targets: child,
        x: 700,
        ease: eases.shift(), // Берём следующее уравнение из массива
        duration: 1500,
        delay: 1000,
        yoyo: true,
        repeat: -1,
        repeatDelay: 1000,
        hold: 1000
    });
});

Что попробовать дальше

Этот пример — идеальная песочница для изучения easing-функций. Запустите его и понаблюдайте: Bounce имитирует отскок, Back создает эффект "перелета" за целевую точку с возвратом, Elastic похож на колебания пружины. Для экспериментов попробуйте изменить параметры твина: увеличьте duration, чтобы лучше увидеть разницу в кривых, или поменяйте свойство с `xнаscaleX`, чтобы анимировать масштаб. Понимание этих кривых позволит вам оживить любой объект в вашей игре, от плавного появления меню до реалистичного прыжка персонажа.