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

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

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

        //  The first even will start at a random time between 1 and 3 seconds
        //  All events after that will start 1 second after the previous one

        const timeline = this.add.timeline([
            {
                at: Phaser.Math.Between(1000, 3000),
                run: () => { this.add.sprite(100, 300, 'timeline', 'bat') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(200, 300, 'timeline', 'zombie') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(300, 300, 'timeline', 'spider') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(400, 300, 'timeline', 'pumpkin') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(500, 300, 'timeline', 'spider') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(600, 300, 'timeline', 'zombie') }
            },
            {
                from: 1000,
                run: () => { this.add.sprite(700, 300, 'timeline', 'bat') }
            }
        ]);

        timeline.play();
    }
}

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 используется для появления ряда спрайтов (летучая мышь, зомби, паук и т.д.) на экране с заданными интервалами.

Создание и конфигурация Timeline

Таймлайн создаётся с помощью метода this.add.timeline(), который принимает массив объектов-событий. Каждое событие — это объект с двумя основными свойствами: at или from и run.

const timeline = this.add.timeline([
    // Событие 1
    {
        at: 2000,
        run: () => { console.log('Случилось через 2 секунды') }
    },
    // Событие 2
    {
        from: 1000,
        run: () => { console.log('Случилось через 1 секунду после предыдущего') }
    }
]);

После создания таймлайн нужно явно запустить методом play().

timeline.play();

Ключевые свойства событий: `at` и `from`

Отличие между at и from — основа гибкости Timeline.

* **at** — задаёт абсолютное время в миллисекундах относительно момента запуска таймлайна (play()). В примере первое событие использует Phaser.Math.Between(1000, 3000), что означает его запуск через случайное время от 1 до 3 секунд после play().

{
    at: Phaser.Math.Between(1000, 3000),
    run: () => { this.add.sprite(100, 300, 'timeline', 'bat') }
}

* **from** — задаёт относительную задержку в миллисекундах от **предыдущего** события в цепочке. Все события, кроме первого, в примере используют from: 1000. Это значит, что каждое следующее существо появится ровно через 1 секунду после появления предыдущего, независимо от случайной задержки первого.

{
    from: 1000, // Запустится через 1 секунду после появления зомби
    run: () => { this.add.sprite(300, 300, 'timeline', 'spider') }
}

Запуск кода: свойство `run`

Свойство run ожидает функцию, которая выполнится в заданный момент времени. Это может быть любая логика вашей игры.

В примере внутри run создаются спрайты с помощью this.add.sprite(). Обратите внимание на параметры: * Координаты X и Y (меняются для каждого спрайта). * Ключ атласа 'timeline'. * Имя кадра из этого атласа ('bat', 'zombie', 'spider', 'pumpkin').

run: () => {
    // Добавляем спрайт зомби на координаты (200, 300)
    // Используем кадр 'zombie' из атласа 'timeline'
    this.add.sprite(200, 300, 'timeline', 'zombie');
}

Именно здесь происходит "магия" появления объектов в игре в запланированный момент.

Полный разбор примера из исходника

Давайте пройдёмся по коду примера шаг за шагом, чтобы закрепить понимание.

1. **Preload:** Загружаются ресурсы — атлас с изображениями существ и фон. 2. **Create:** * Добавляется фон. * Создаётся Timeline с массивом из 7 событий. * **Событие 1:** Через случайное время (1-3 сек.) появляется спрайт летучей мыши (bat) на позиции (100, 300). * **События 2-7:** Каждое следующее существо появляется на своей позиции (X увеличивается на 100) ровно через 1 секунду (from: 1000) после появления предыдущего. Порядок: Зомби, Паук, Тыква, Паук, Зомби, Летучая мышь. 3. **Запуск:** Цепочка событий активируется вызовом timeline.play().

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

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

Timeline в Phaser — это элегантное и мощное решение для планирования действий во времени. Он избавляет код от хаоса вложенных таймеров и делает временные скрипты наглядными. **Идеи для экспериментов:** * Создайте таймлайн для вступительной кат-сцены, где диалоги и анимации персонажей следуют друг за другом. * Используйте timeline.pause() и timeline.resume() для управления скриптовой сценой по клику игрока. * Организуйте волны противников в аркадной игре: первая волна — через 5 секунд, вторая — через 10 секунд после окончания первой. * Комбинируйте at и from в одной цепочке для создания сложных ритмических паттернов.