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

Плавность анимации — ключ к приятному игровому опыту. Резкие, линейные движения выглядят неестественно и механически. Phaser предлагает мощную систему твинов (tweens) с поддержкой easing-функций, которые позволяют задавать нелинейную интерполяцию свойств объектов. В этой статье мы разберем, как работают базовые вариации одной из таких функций — `Sine` — и как их применять для создания органичного движения. Наглядный пример с тремя НЛО показывает разницу между типами easing: `in`, `out` и `inOut`. Понимание этих отличий позволит вам осознанно выбирать тип плавности для различных игровых ситуаций: от плавного появления интерфейса до реалистичного прыжка персонажа.

Версия 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('bg', 'assets/tweens/space.png');
        this.load.image('ufo1', 'assets/tweens/ufo1.png');
        this.load.image('ufo2', 'assets/tweens/ufo2.png');
        this.load.image('ufo3', 'assets/tweens/ufo3.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');
        this.add.text(20, 20, 'Sine Ease').setFontSize(32).setShadow(2, 2);

        this.add.text(750, 70, 'Sine.in').setShadow(2, 2).setOrigin(1, 0);
        this.add.text(750, 255, 'Sine.out').setShadow(2, 2).setOrigin(1, 0);
        this.add.text(750, 440, 'Sine.inOut').setShadow(2, 2).setOrigin(1, 0);

        const ufo1 = this.add.image(100, 140, 'ufo1');
        const ufo2 = this.add.image(100, 325, 'ufo2');
        const ufo3 = this.add.image(100, 510, 'ufo3');

        this.tweens.add({
            targets: ufo1,
            x: 700,
            duration: 2000,
            repeat: -1,
            hold: 500,
            repeatDelay: 500,
            ease: 'sine.in'
        });

        this.tweens.add({
            targets: ufo2,
            x: 700,
            duration: 2000,
            repeat: -1,
            hold: 500,
            repeatDelay: 500,
            ease: 'sine.out'
        });

        this.tweens.add({
            targets: ufo3,
            x: 700,
            duration: 2000,
            repeat: -1,
            hold: 500,
            repeatDelay: 500,
            ease: 'sine.inout'
        });
    }
}

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

const game = new Phaser.Game(config);

Что такое easing и зачем он нужен?

Easing (или плавность) — это математическая функция, которая определяет, как изменяется значение анимируемого свойства во времени. Вместо линейного изменения (когда объект движется с постоянной скоростью), easing позволяет задать ускорение, замедление или их комбинацию.

В Phaser easing-функции передаются в свойство ease конфигурации твина. Встроенные функции, такие как sine, quad, cubic, expo, имитируют естественные движения, подчиняющиеся законам физики (например, инерцию). Это делает анимацию визуально привлекательной и "живой".

Разбор примера: загрузка ресурсов и создание сцены

Код начинается с загрузки изображений в методе preload. Обратите внимание на использование this.load.setBaseURL — это удобный способ задать базовый путь для всех последующих загрузок, чтобы не указывать полные URL.

В методе create мы размещаем фон, текстовые подписи и три спрайта НЛО, которые станут целями для анимации. Важный момент: позиционирование текста с помощью .setOrigin(1, 0) выравнивает его по правому краю (origin X=1), что удобно для создания подписей у края экрана.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/tweens/space.png');
    this.load.image('ufo1', 'assets/tweens/ufo1.png');
    this.load.image('ufo2', 'assets/tweens/ufo2.png');
    this.load.image('ufo3', 'assets/tweens/ufo3.png');
}

create ()
{
    this.add.image(400, 300, 'bg');
    this.add.text(20, 20, 'Sine Ease').setFontSize(32).setShadow(2, 2);

    this.add.text(750, 70, 'Sine.in').setShadow(2, 2).setOrigin(1, 0);
    this.add.text(750, 255, 'Sine.out').setShadow(2, 2).setOrigin(1, 0);
    this.add.text(750, 440, 'Sine.inOut').setShadow(2, 2).setOrigin(1, 0);

    const ufo1 = this.add.image(100, 140, 'ufo1');
    const ufo2 = this.add.image(100, 325, 'ufo2');
    const ufo3 = this.add.image(100, 510, 'ufo3');
    // ... создание твинов
}

Создание и настройка твинов

Анимация создается с помощью метода this.tweens.add. Каждый твин принимает объект конфигурации. Давайте разберем ключевые свойства:

* targets: объект или массив объектов для анимации. * x: 700: целевое значение свойства (движение по горизонтали). * duration: длительность одного цикла анимации в миллисекундах. * repeat: -1: бесконечное повторение. Можно задать положительное число для ограниченного количества повторов. * hold: пауза (в мс) при достижении конечного значения перед началом повторения. * repeatDelay: пауза (в мс) перед началом каждого нового цикла после hold. * ease: строка, определяющая функцию плавности. Именно здесь мы задаем разные варианты sine.

this.tweens.add({
    targets: ufo1,
    x: 700,
    duration: 2000,
    repeat: -1,
    hold: 500,
    repeatDelay: 500,
    ease: 'sine.in'
});

В чем разница между Sine.in, Sine.out и Sine.inOut?

Все три варианта используют синусоидальную кривую для расчета плавности, но распределение ускорения разное:

1. **sine.in** (Ускорение): Анимация начинается медленно и плавно набирает скорость к концу. Визуально НЛО трогается еле-еле, но к финишу летит быстро. Идеально для объектов, на которые действует сила (например, запуск снаряда). 2. **sine.out** (Замедление): Анимация начинается быстро и плавно замедляется к концу. НЛО резко стартует и мягко "припарковывается". Отлично подходит для завершающих действий, UI-элементов, которые выезжают на экран. 3. **sine.inout** (Комбинация): Анимация плавно ускоряется в первой половине и плавно замедляется во второй. Это создает наиболее естественное и сбалансированное движение. Универсальный вариант для многих цикличных или переходных анимаций.

Выбор типа зависит от желаемого визуального эффекта и контекста движения в игре.

Инициализация игры: объект конфигурации

За пределами класса сцены создается объект конфигурации config, который передается в конструктор Phaser.Game. Это стандартная точка входа для любого проекта на Phaser.

* type: Определяет рендерер (WebGL или Canvas). Phaser.AUTO позволяет движку выбрать лучший из доступных. * width и height: Размер игрового холста. * backgroundColor: Цвет фона, который отобразится до загрузки и отрисовки сцены. * parent: ID HTML-элемента, в который будет встроен игровой холст. * scene: Класс или экземпляр основной сцены.

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

const game = new Phaser.Game(config);

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

Использование easing-функций — простой, но мощный способ значительно улучшить качество анимаций в вашей игре. Начните с sine.in, sine.out и sine.inOut, чтобы почувствовать разницу. **Идеи для экспериментов:** 1. Поменяйте функцию плавности на quad, cubic или expo и сравните характер движения. 2. Анимируйте не только `x, но и другие свойства:y,scale,angle,alpha`. 3. Создайте цепочку твинов с помощью chain или yoyo: true, чтобы объект возвращался в исходную точку с другой ease-функцией. 4. Используйте delay для создания последовательных анимаций нескольких объектов.