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

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

Версия 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('block', 'assets/sprites/checkerboard.png');
    }

    create ()
    {
        const blocks = this.add.group({ key: 'block', repeat: 107 });

        Phaser.Actions.GridAlign(blocks.getChildren(), {
            width: 12,
            height: 9,
            cellWidth: 64,
            cellHeight: 64
        });

        const a = [ 0, 90, 180, 270 ];

        blocks.children.forEach(child => {

            child.angle = Phaser.Math.RND.pick(a);

            this.tweens.add({
                targets: child,
                ease: 'Power1',
                duration: 250,
                delay: (Math.random() * 6000),
                repeatDelay: 3000 + (Math.random() * 6000),
                repeat: -1,
                angle: {

                    getEnd: (target, key, value) =>
                    {
                        var a = 90;

                        if (Math.random() > 0.5)
                        {
                            a = 180;
                        }

                        if (Math.random() > 0.5)
                        {
                            return target.angle + a;
                        }
                        else
                        {
                            return target.angle - a;
                        }
                    },

                    getStart: (target, key, value) =>
                    {
                        return target.angle;
                    }

                }

            });

        });
    }
}

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

const game = new Phaser.Game(config);

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

Первым делом мы загружаем текстуру и создаем из нее множество одинаковых спрайтов, выстроенных в аккуратную сетку. Это основа нашего анимированного фона.

В методе preload мы загружаем одно изображение — плитку в виде шахматной доски. Ключевой момент происходит в create: мы создаем группу (Group), которая сразу генерирует 108 спрайтов (1 исходный + 107 повторений) на основе загруженного изображения 'block'.

const blocks = this.add.group({ key: 'block', repeat: 107 });

Далее мы используем хелпер Phaser.Actions.GridAlign, чтобы расставить всех детей этой группы по сетке 12x9 с заданным размером ячейки. Это превращает хаотичную кучу спрайтов в упорядоченное поле.

Phaser.Actions.GridAlign(blocks.getChildren(), {
    width: 12,
    height: 9,
    cellWidth: 64,
    cellHeight: 64
});

Запуск анимации: основы твинов

Твин (tween) — это инструмент для плавного изменения свойств объекта с течением времени. В нашем случае мы будем изменять свойство angle каждого спрайта, чтобы он вращался.

Для начала мы задаем каждому блоку случайный начальный угол, выбранный из массива [0, 90, 180, 270]. Это создает визуально разнообразную стартовую позицию.

blocks.children.forEach(child => {
    child.angle = Phaser.Math.RND.pick(a);

Затем для каждого спрайта создается отдельный твин с помощью this.tweens.add(). Основные параметры: * targets: объект (наш спрайт), свойства которого будут анимироваться. * duration: длительность одного цикла анимации (250 мс — быстрое вращение). * delay: начальная задержка перед первым запуском твина (случайная, до 6 секунд). Это ключевой параметр для разнесения анимаций во времени. * repeatDelay: пауза между повторениями анимации (от 3 до 9 секунд). * repeat: -1: твин будет повторяться бесконечно.

this.tweens.add({
    targets: child,
    ease: 'Power1',
    duration: 250,
    delay: (Math.random() * 6000),
    repeatDelay: 3000 + (Math.random() * 6000),
    repeat: -1,
    // ... свойство angle будет описано ниже
});

Динамическое вычисление углов: getStart и getEnd

Стандартный твин плавно меняет свойство от значения from к значению to. Но нам нужно не просто вращение на фиксированный угол, а логика: "повернись от текущего угла на +90 или +180 градусов в случайном направлении". Для такой нестандартной логины Phaser позволяет использовать функции-геттеры getStart и getEnd.

Функция getStart вызывается в начале каждого цикла твина и просто возвращает текущий угол спрайта. Это отправная точка для анимации.

getStart: (target, key, value) => {
    return target.angle;
}

Функция getEnd определяет целевой угол. Ее логика: 1. С вероятностью 50% цель — поворот на 90 градусов, иначе — на 180. 2. С вероятностью 50% поворот будет по часовой стрелке (прибавить угол), иначе — против (вычесть угол).

Таким образом, каждый блок в случайный момент времени совершает резкий, но плавный поворот на 90 или 180 градусов в случайном направлении, создавая сложную, "живую" картину.

getEnd: (target, key, value) => {
    var a = 90;
    if (Math.random() > 0.5) {
        a = 180;
    }
    if (Math.random() > 0.5) {
        return target.angle + a;
    } else {
        return target.angle - a;
    }
}

Итоговая картина и настройка

В результате мы получаем сцену, где 108 плиток, выстроенных в сетку, периодически и независимо друг от друга совершают резкие повороты. Разброс в начальной задержке (delay) и задержке между повторами (repeatDelay) гарантирует, что анимация никогда не синхронизируется и выглядит хаотично и бесконечно разнообразно.

Конфигурация игры стандартна: мы указываем размеры холста, цвет фона и передаем наш класс сцены.

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

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

Использование твинов с функциями getStart и getEnd открывает огромные возможности для создания сложного поведения на основе простых правил. Вы можете экспериментировать: заменить angle на scale для пульсирующего фона, использовать alpha для мерцания или менять `x,y` для эффекта "плывущих" платформ. Попробуйте привязать логику вычисления конечного значения не к случайности, а, например, к положению мыши или времени, чтобы создать интерактивный фон.