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

Создание сложных визуальных эффектов — ключ к запоминающимся играм. В этом примере мы разберем, как с помощью всего четырех спрайтов и контейнеров Phaser построить гипнотическую спиральную анимацию. Вы научитесь эффективно управлять множеством объектов, применяя твины к целым группам и настраивая задержки для каждого элемента. Этот подход экономит ресурсы и открывает двери для создания фоновых анимаций, эффектов заклинаний или психоделических переходов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('raster', 'assets/demoscene/rastercarpet32.png');
    }

    create ()
    {
        const sprite1 = this.add.sprite(0, 100, 'raster');
        const sprite2 = this.add.sprite(-100, 0, 'raster').setAngle(90);
        const sprite3 = this.add.sprite(0, -100, 'raster').setAngle(180);
        const sprite4 = this.add.sprite(100, 0, 'raster').setAngle(270);

        const containers = [];

        for (let i = 0; i < 128; i++)
        {
            const container = this.add.container(400, 300);

            if (i > 0)
            {
                container.setExclusive(false);
            }

            container.add([ sprite1, sprite2, sprite3, sprite4 ]);

            containers.push(container);
        }

        this.tweens.add({
            targets: sprite1,
            y: -200,
            ease: 'Sine.easeInOut',
            duration: 4000,
            repeat: -1,
            yoyo: true
        });

        this.tweens.add({
            targets: sprite2,
            x: 300,
            ease: 'Sine.easeInOut',
            duration: 4000,
            repeat: -1,
            yoyo: true
        });

        this.tweens.add({
            targets: sprite3,
            y: 200,
            ease: 'Sine.easeInOut',
            duration: 4000,
            repeat: -1,
            yoyo: true
        });

        this.tweens.add({
            targets: sprite4,
            x: -300,
            ease: 'Sine.easeInOut',
            duration: 4000,
            repeat: -1,
            yoyo: true
        });

        this.tweens.add({
            targets: containers,
            angle: { value: 360, duration: 6000 },
            scaleX: { value: 2, duration: 3000, yoyo: true, ease: 'Quad.easeInOut' },
            scaleY: { value: 4, duration: 3000, yoyo: true, ease: 'Cubic.easeInOut' },
            repeat: -1,
            delay: function (target, key, value, index, total, tween)
            {
                return index * 32;
            }
        });
    }
}

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

const game = new Phaser.Game(config);

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

Вся логика примера содержится в классе сцены Example. В методе preload мы загружаем единственное изображение — текстуру 'raster'. Важно отметить использование setBaseURL, которое задает базовый путь для всех последующих загрузок, что удобно для работы с удаленными ресурсами.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('raster', 'assets/demoscene/rastercarpet32.png');

Создание базовых спрайтов и их позиционирование

В методе create создаются четыре спрайта из одной текстуры. Они размещаются по четырем сторонам от условного центра (точки с координатами 0,0) и поворачиваются на 90 градусов друг относительно друга с помощью метода setAngle. Эти спрайты станут «кирпичиками» для нашего сложного составного объекта.

const sprite1 = this.add.sprite(0, 100, 'raster');
const sprite2 = this.add.sprite(-100, 0, 'raster').setAngle(90);
const sprite3 = this.add.sprite(0, -100, 'raster').setAngle(180);
const sprite4 = this.add.sprite(100, 0, 'raster').setAngle(270);

Формирование массива контейнеров

Здесь происходит основная магия. В цикле создается 128 контейнеров (this.add.container). Каждый контейнер размещается в центре экрана (400, 300). Метод setExclusive(false) вызывается для всех контейнеров, кроме первого. Это ключевой момент: он позволяет спрайтам принадлежать нескольким контейнерам одновременно, что и делает анимацию такой легковесной. В каждый контейнер добавляется один и тот же набор из четырех спрайтов.

const containers = [];
for (let i = 0; i < 128; i++) {
    const container = this.add.container(400, 300);
    if (i > 0) {
        container.setExclusive(false);
    }
    container.add([ sprite1, sprite2, sprite3, sprite4 ]);
    containers.push(container);
}

Анимация исходных спрайтов

Чтобы создать динамику внутри каждого контейнера, к каждому из четырех базовых спрайтов применяется отдельный твин. Спрайты движутся по оси X или Y, используя плавную Sine.easeInOut анимацию с бесконечным повторением (repeat: -1) и возвратом (yoyo: true). Это заставляет «лопасти» внутри контейнеров постоянно колебаться.

this.tweens.add({
    targets: sprite2,
    x: 300,
    ease: 'Sine.easeInOut',
    duration: 4000,
    repeat: -1,
    yoyo: true
});
// ... аналогичные твины для sprite1, sprite3, sprite4

Массовая анимация всех контейнеров

Самый мощный твин применяется ко всему массиву containers. Он одновременно вращает контейнеры на 360 градусов и меняет их масштаб по осям X и Y с разной динамикой (разные длительности и функции плавности ease). Параметр delay — это функция, которая вычисляет задержку старта анимации для каждого контейнера в зависимости от его индекса в массиве. Это создает эффект волны или спирали.

this.tweens.add({
    targets: containers,
    angle: { value: 360, duration: 6000 },
    scaleX: { value: 2, duration: 3000, yoyo: true, ease: 'Quad.easeInOut' },
    scaleY: { value: 4, duration: 3000, yoyo: true, ease: 'Cubic.easeInOut' },
    repeat: -1,
    delay: function (target, key, value, index, total, tween) {
        return index * 32;
    }
});

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

Комбинируя контейнеры, общие спрайты и твины с задержкой, можно генерировать невероятно сложные на вид эффекты с минимальным количеством графических объектов. Для экспериментов попробуйте изменить количество контейнеров, формулу задержки в функции delay, параметры масштабирования или добавьте твин для изменения прозрачности (alpha). Это отличная основа для создания вращающихся ловушек, порталов или гипнотических фоновых узоров.