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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    timedEvent;
    text;
    image;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('einstein', 'assets/pics/ra-einstein.png');
    }

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

        this.text = this.add.text(32, 32);

        //  A 10 second delay, but the first time it begins it'll start 5 seconds in, then on repeat will repeat for the full 10 seconds
        this.timedEvent = this.time.addEvent({ delay: 10000, callback: this.onEvent, callbackScope: this, repeat: 1, startAt: 5000 });
    }

    update ()
    {
        this.text.setText(`Event.progress: ${this.timedEvent.getProgress().toString().substr(0, 4)}\nEvent.repeatCount: ${this.timedEvent.repeatCount}`);
    }

    onEvent ()
    {
        this.image.scaleX *= 0.75;
        this.image.scaleY *= 0.75;
        this.image.rotation += 0.04;
    }
}

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

const game = new Phaser.Game(config);

Создание события с отложенным стартом

В методе create() сцены создается временное событие с помощью this.time.addEvent(). Ключевой параметр здесь – startAt: 5000.

this.timedEvent = this.time.addEvent({ delay: 10000, callback: this.onEvent, callbackScope: this, repeat: 1, startAt: 5000 });

Конфигурация объекта события: * delay: 10000 – общая длительность одного цикла события составляет 10 секунд (10000 миллисекунд). * callback: this.onEvent – функция, которая будет вызвана по завершении каждого цикла. * callbackScope: this – контекст, в котором будет выполнена функция обратного вызова (обычно сама сцена). * repeat: 1 – событие повторится один раз. Вместе с первоначальным запуском это означает, что callback выполнится дважды. * startAt: 5000 – **самый важный параметр**. Он указывает, что первый запуск таймера начнется не с 0, а с отметки в 5 секунд. Таким образом, первый вызов onEvent произойдет не через 10, а через 5 секунд после создания события. Все последующие повторения (repeat) будут уже работать на полном интервале в 10 секунд.

Отслеживание прогресса события

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

this.text.setText(`Event.progress: ${this.timedEvent.getProgress().toString().substr(0, 4)}\nEvent.repeatCount: ${this.timedEvent.repeatCount}`);
*   `this.timedEvent.getProgress()` – метод, возвращающий число от 0 до 1, которое показывает, какая часть текущего цикла события уже прошла. При `startAt: 5000` и `delay: 10000` начальное значение прогресса будет равно 0.5 (5/10). Метод `substr(0,4)` используется для округления значения при выводе.
*   `this.timedEvent.repeatCount` – свойство, которое показывает, сколько раз событие уже было повторено. Уменьшается на 1 с каждым завершением цикла.

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

Действие по завершению цикла

Функция onEvent – это callback, который срабатывает каждый раз, когда таймер события достигает конца своего цикла (прогресс становится равным 1).

onEvent ()
{
    this.image.scaleX *= 0.75;
    this.image.scaleY *= 0.75;
    this.image.rotation += 0.04;
}

При каждом вызове функция: 1. Уменьшает масштаб изображения по осям X и Y на 25% (умножая текущий масштаб на 0.75). 2. Поворачивает изображение на небольшой угол (0.04 радиана).

Из-за параметра startAt первое такое изменение произойдет не через 10, а через 5 секунд после загрузки сцены, что и демонстрирует практическую пользу этого параметра для нестандартного старта анимаций.

Практическое применение startAt в играх

Параметр startAt — это не просто особенность API, а инструмент для решения конкретных игровых задач.

// Бонус, который активируется через 3 секунды после появления, но исчезнет через 10.
this.time.addEvent({ delay: 10000, callback: this.deactivateBonus, startAt: 3000 });

// Волна врагов, которая начинается не сразу, а когда игрок достигнет определенной точки.
// Таймер создания врагов запускается с прогрессом 0.8, чтобы первая группа появилась почти мгновенно.
this.waveTimer = this.time.addEvent({ delay: 5000, callback: this.spawnEnemy, repeat: 9, startAt: 4000 });

* **Постепенное начало:** Можно создать событие с большим delay (например, 30 секунд для смены времени суток), но установить startAt в 25 секунд. Это создаст эффект, будто цикл уже давно идет, и изменение наступит очень скоро. * **Синхронизация:** Если у вас несколько событий должны сработать в разное время, но вы хотите создать их одновременно в коде инициализации уровня, startAt поможет задать им разное время первого срабатывания, сохранив общую длительность цикла.

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

Параметр startAt в Phaser.Time.TimerEvent — это простой, но мощный способ получить точный контроль над временем в вашей игре. Он позволяет создавать события, которые начинаются не с чистого листа, а с уже пройденной части своего цикла, что открывает двери для более сложных и интересных временных механик. **Идеи для экспериментов:** 1. Создайте таймер обратного отсчета, который изначально показывает не полное время, а только последние 5 секунд. 2. Реализуйте систему сезонов (лето, осень, зима, весна), где смена происходит циклично, но игра начинается, например, с середины осени. 3. Сделайте «заряжающуюся» атаку, визуальный индикатор которой (progress bar) при активации сразу заполнен на 50%.