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

При разработке игр часто требуется запускать анимации не сразу, а по сложному расписанию: сначала один объект двигается в точку А, через 2 секунды начинает колебаться другой, а еще через секунду включается повторяющийся эффект. Прописывать все это вручную через таймеры и колбэки — долго и сложно для поддержки. Timeline (временная шкала) в Phaser 3 решает эту задачу элегантно, позволяя описывать последовательность твинов (анимаций) с привязкой к конкретным временным меткам в единой декларативной структуре. В этой статье разберем пример с пауком, которого Timeline заставляет спускаться на паутине, а затем раскачиваться из стороны в сторону, и научимся создавать свои сложные сценарии анимаций.

Версия 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');
    }

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

        this.thread = this.add.graphics();

        this.spider = this.add.sprite(400, -100, 'timeline', 'spider');

        const timeline = this.add.timeline([
            {
                at: 2000,
                tween: {
                    targets: this.spider,
                    y: 400,
                    ease: 'bounce.out',
                    duration: 1500
                }
            },
            {
                at: 4000,
                tween: {
                    targets: this.spider,
                    x: 200,
                    angle: 30,
                    ease: 'sine.out',
                    duration: 1000,
                    yoyo: true,
                    repeat: -1,
                    repeatDelay: 2000
                }
            },
            {
                at: 6000,
                tween: {
                    targets: this.spider,
                    x: 600,
                    angle: -30,
                    ease: 'sine.out',
                    duration: 1000,
                    yoyo: true,
                    repeat: -1,
                    repeatDelay: 2000
                }
            }
        ]);

        timeline.play();
    }

    update ()
    {
        this.thread.clear();
        this.thread.lineStyle(1, 0xffffff, 0.7);
        this.thread.lineBetween(400, 0, this.spider.x, this.spider.y);
    }
}

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. В отличие от одиночного твина, который выполняет одну анимацию, Timeline позволяет создать список действий (actions), каждое из которых привязано к определенному моменту времени на шкале. Это похоже на монтажный стол в видеоредакторе: вы расставляете клипы (твины) на временной линии, и они воспроизводятся в заданное время.

Основные преимущества: * **Централизованное управление**: Вся логика последовательности анимаций описана в одном месте — объекте конфигурации timeline. * **Точное планирование**: Каждое действие имеет параметр at — время в миллисекундах от старта таймлайна, когда оно должно начаться. * **Автоматический запуск**: Достаточно вызвать метод play(), и вся последовательность начнет воспроизводиться согласно плану. * **Читаемость**: Код становится намного чище и проще для понимания по сравнению с вложенными setTimeout или цепочками событий onComplete.

Разбор структуры Timeline

В примере Timeline создается в методе create сцены. Давайте посмотрим на его конфигурацию.

const timeline = this.add.timeline([
    {
        at: 2000,
        tween: {
            targets: this.spider,
            y: 400,
            ease: 'bounce.out',
            duration: 1500
        }
    },
    // ... другие действия
]);
Ключевые моменты:
1.  `this.add.timeline()` — фабричный метод для создания таймлайна. Он принимает массив объектов-действий.
2.  Каждое действие — это объект с обязательным полем `at` (время старта в мс) и одним из свойств, определяющих тип действия (в нашем случае — `tween`).
3.  Объект `tween` внутри действия имеет ту же структуру, что и конфиг для обычного твина, создаваемого через `this.tweens.add()`. Вы можете использовать все знакомые параметры: `targets`, свойства для анимации (`x`, `y`, `angle`, `alpha` и т.д.), `ease`, `duration`, `yoyo`, `repeat`.

В данном примере определены три действия: * **На 2000 мс**: Паук (this.spider) за 1500 мс спускается по оси Y до позиции 400 с эффектом отскока (bounce.out). * **На 4000 мс**: Паук начинает бесконечно повторяющуюся (repeat: -1) анимацию движения к X=200 и наклона на 30 градусов с эффектом sine.out. Анимация длится 1000 мс, после чего воспроизводится в обратном порядке (yoyo: true), а перед следующим повторением ждет 2000 мс (repeatDelay: 2000). * **На 6000 мс**: Аналогичное бесконечное действие, но паук движется к X=600 и наклоняется на -30 градусов. Обратите внимание, что оба колебательных действия стартуют в разное время и работают параллельно, создавая сложную траекторию.

Визуализация связи: графика в update

Чтобы визуально связать паука с точкой его старта, в примере используется графика (Graphics). В методе update каждый кадр рисуется линия, имитирующая паутину.

update ()
{
    this.thread.clear();
    this.thread.lineStyle(1, 0xffffff, 0.7);
    this.thread.lineBetween(400, 0, this.spider.x, this.spider.y);
}
Пояснение кода:
1.  `this.thread.clear()` — очищает графику от нарисованного в предыдущем кадре. Без этого вы увидите "шлейф" из всех предыдущих линий.
2.  `this.thread.lineStyle(1, 0xffffff, 0.7)` — задает стиль линии: толщина 1 пиксель, цвет белый (`0xffffff`), прозрачность 70% (`0.7`).
3.  `this.thread.lineBetween(400, 0, this.spider.x, this.spider.y)` — рисует линию от фиксированной точки вверху экрана (400, 0) до текущих координат паука. Поскольку `this.spider.x` и `this.spider.y` непрерывно меняются твинами из Timeline, линия следует за пауком, создавая эффект паутины.

Это отличный пример динамической графики, которая реагирует на состояние игровых объектов.

Практическое применение: идеи для ваших игр

Timeline — мощный инструмент не только для декоративных анимаций.

* **Сценарии кат-сцен**: Описать появление персонажей, их движение и диалоговые реплики по времени. * **Фазы босса**: Задать последовательность атак: через 5 секунд — залп снарядов, через 10 — призыв мобов, через 15 — мощный луч. * **Сложные UI-анимации**: Последовательное появление элементов меню, тряска кнопок, исчезновение всплывающих окон. * **Кооперативные паззлы**: Активация механизмов уровня с задержкой друг относительно друга.

Главное — помнить, что время at отсчитывается от момента вызова timeline.play(). Если вам нужно запустить таймлайн не с нуля, изучить методы pause, seek или stop.

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

Timeline в Phaser 3 — это ваш союзник в создании сложных, синхронизированных во времени анимаций без головной боли с таймерами. Он превращает spaghetti-код из колбэков в чистый, декларативный и легко редактируемый план действий. **Идеи для экспериментов:** 1. Добавьте в Timeline действие с типом event (не показано в примере, но есть в API) для вызова пользовательской функции в заданный момент, например, для воспроизведения звука скрипа паутины. 2. Создайте два независимых Timeline для разных групп объектов и запустите их с задержкой друг относительно друга. 3. Используйте timeline.seek(3000), чтобы начать воспроизведение не с начала, а с 3-й секунды сценария. 4. Свяжите прогресс Timeline с ползунком на экране, чтобы вручную перематывать анимацию.