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

В разработке игр часто требуется запускать последовательность событий: перемещение объектов, включение визуальных эффектов и запуск систем частиц с определенными задержками. Обычно для этого используется множество таймеров и твинов, что быстро превращает код в запутанный лабиринт. Timeline в Phaser решает эту проблему, позволяя описывать сложные сцены с временными метками в одном месте. Эта статья покажет, как с помощью `Timeline` и метода `repeat()` создать зацикленную сцену с плавной анимацией, эффектами свечения и частицами, управляемыми по расписанию.

Версия 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.atlas('timeline', 'assets/atlas/timeline.png', 'assets/atlas/timeline.json');
        this.load.image('bg', 'assets/skies/spookysky.jpg');
        this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        const crystalball = this.add.sprite(400, 800, 'timeline', 'crystalball');

        crystalball.enableFilters();
        const glowFX = crystalball.filters.internal.addGlow();

        const emitter = this.add.particles(400, 300, 'flares', {
            frame: 'white',
            blendMode: 'ADD',
            lifespan: 1200,
            gravityX: 100,
            gravityY: -100,
            scale: { start: 0.3, end: 0 },
            emitting: false
        });

        emitter.addEmitZone({ source: new Phaser.Geom.Circle(0, -20, 90) });

        const timeline = this.add.timeline([
            {
                at: 0,
                run: () => {
                    glowFX.setActive(false);
                    glowFX.outerStrength = 0;
                    glowFX.innerStrength = 0;
                },
                tween: {
                    targets: crystalball,
                    y: 300,
                    ease: 'sine.in',
                    duration: 750
                }
            },
            {
                at: 1000,
                loop: () => {
                    emitter.gravityX *= -1;
                },
                run: () => {
                    glowFX.setActive(true);
                    emitter.start();
                },
                tween: {
                    targets: glowFX,
                    outerStrength: 16,
                    innerStrength: 8,
                    ease: 'sine.in',
                    yoyo: true,
                    duration: 500,
                    repeat: 3
                }
            },
            {
                at: 4000,
                run: () => {
                    emitter.stop();
                },
                tween: {
                    targets: crystalball,
                    y: 800,
                    ease: 'sine.in',
                    duration: 500
                }
            },
            {
                at: 5000,
                stop: true
            }
        ]);

        // The `repeat` method accepts a positive value or undefined, true and a negative number values to go on indefinitely.
        timeline.repeat().play();

        // This is the same as setting the amount of times it should loop to 0.
        // timeline.repeat(false);
    }
}

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

const game = new Phaser.Game(config);

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

Timeline — это мощный инструмент Phaser для управления временной шкалой событий. Вместо того чтобы вручную настраивать setTimeout или цепочки промисов для последовательных или параллельных действий, вы описываете всю последовательность в виде массива объектов. Каждый объект определяет, что должно произойти в определенный момент времени (at).

Внутри каждого события вы можете: - Запускать функции (run). - Запускать твины (tween). - Выполнять код в начале каждой итерации цикла (loop). - Останавливать всю шкалу (stop).

Это делает код предсказуемым, легко читаемым и изменяемым. Вся логика анимации собрана в одном месте.

Разбор структуры Timeline в примере

В предоставленном примере шкала состоит из четырех ключевых событий, разнесенных во времени. Давайте посмотрим на их назначение.

const timeline = this.add.timeline([
    {
        at: 0,
        run: () => { /* ... */ },
        tween: { /* ... */ }
    },
    // ... другие события
]);

**Событие 1 (0 мс):** Инициализация. Отключается эффект свечения (glowFX) и запускается твин, поднимающий кристальный шар (crystalball) с нижней части экрана в центр.

**Событие 2 (1000 мс):** Основное действие. Включается свечение, запускается эмиттер частиц (emitter.start()). Твин для эффекта свечения делает его пульсирующим (yoyo: true, repeat: 3). Функция loop инвертирует гравитацию по оси X каждый раз, когда Timeline повторяется, создавая интересную динамику для частиц.

**Событие 3 (4000 мс):** Завершение активной фазы. Эмиттер частиц останавливается (emitter.stop()), и шар начинает опускаться вниз.

**Событие 4 (5000 мс):** Полная остановка. Параметр stop: true указывает Timeline остановиться. Однако, как мы увидим далее, метод repeat() перезапускает всю шкалу, делая эту остановку временной.

Сила метода repeat()

Ключ к созданию бесконечной или повторяющейся сцены лежит в методе timeline.repeat(). После создания шкалы ее нужно не только запустить (play()), но и настроить повторение.

timeline.repeat().play();

Вызов repeat() без аргументов (или со значением -1) делает шкалу бесконечно повторяющейся. После того как Timeline доходит до последнего события (в нашем случае — до остановки в 5000 мс), он автоматически начинает проигрываться заново с нулевой отметки.

Это поведение можно контролировать: - timeline.repeat(3) — повторит последовательность 3 раза. - timeline.repeat(0) или timeline.repeat(false) — отключит повторение, и шкала остановится после первого проигрыша.

В нашем примере бесконечное повторение создает циклическую анимацию: шар поднимается, светится с частицами, опускается — и все начинается снова. Функция loop в событии на 1000 мс выполняется каждый раз при *повторении* всей шкалы, меняя направление гравитации частиц и добавляя вариативность.

Практические советы по работе с Timeline

1. **Планирование:** Перед написанием кода набросайте временную шкалу на бумаге. Отметьте ключевые моменты (задержки at) и действия в них. 2. **Отладка:** Используйте run для вывода в консоль и отслеживания, какое событие сейчас выполняется.

run: () => { console.log('Достигнута метка 1000мс'); }

3. **Комбинация действий:** В одном событии можно одновременно выполнить run, tween и loop. Они запустятся параллельно в указанный момент at. 4. **Перезапуск сбросом:** Если вам нужно сбросить состояние объектов (например, позицию спрайта) перед каждым повторением, добавьте соответствующую логику в функцию run самого первого события (с at: 0).

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

Timeline — это идеальный инструмент для создания скриптованных кат-сцен, циклических заставок, сложных анимаций UI или поведения боссов в играх. Он превращает хаос из таймеров в четкий, декларативный план. Для экспериментов попробуйте: изменить порядок и задержки событий, добавить новые твины для масштабирования или цвета, заменить эмиттер частиц на другой визуальный эффект или использовать repeat(N) для создания точного количества циклов атаки босса.