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

При разработке игр часто возникает необходимость перезапустить игровую сцену или отдельные анимации. Пример с багом #6253 наглядно демонстрирует тонкость работы с системой анимаций Tween в Phaser 3 при использовании `Scene.restart()`. Понимание этого механизма поможет избежать скрытых ошибок и неожиданного поведения анимаций при управлении состоянием игры.

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

Живой запуск

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

Исходный код


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

var game = new Phaser.Game(config);

function init ()
{
    this.input.once('pointerdown', () => {

        console.log('restarting');

        this.scene.restart();

    });
}

function preload ()
{
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('block', 'assets/sprites/block.png');
}

function create ()
{
    var image = this.add.image(100, 300, 'block');

    var tween = this.tweens.add({
        delay: 100,
        targets: image,
        x: 700
    });

    console.log(tween);
}

В чём суть примера и потенциальной проблемы?

В представленном коде создаётся базовая сцена Phaser 3. В её функции create() запускается Tween-анимация, которая перемещает спрайт с координаты x=100 до x=700. Уникальность примера в обработчике события pointerdown в функции init(), который по клику перезапускает всю сцену с помощью this.scene.restart().

Ключевой вопрос: что происходит с объектом tween при таком перезапуске? Перезапуск сцены повторно выполняет её функции preload() и create(), что означает повторную загрузку ассетов (хотя они могут быть кэшированы) и повторное создание всех игровых объектов.

this.scene.restart();

Жизненный цикл Tween и перезапуск сцены

Система Tween в Phaser (this.tweens) управляется на уровне игры или сцены. Когда создаётся Tween через this.tweens.add(), он добавляется в менеджер анимаций.

Однако метод Scene.restart() останавливает и уничтожает текущую сцену, а затем создаёт её экземпляр заново. При этом все созданные в предыдущей инкарнации сцены объекты, включая Tween'ы, должны быть корректно удалены. В идеале старый Tween должен быть автоматически уничтожен, а в новой сцене создан новый.

Исходный код фиксирует созданный объект tween в переменную и выводит её в консоль. Это полезно для отладки.

var tween = this.tweens.add({
    delay: 100,
    targets: image,
    x: 700
});
console.log(tween);

Практические рекомендации по управлению Tween

Хотя в данном примере код работает, при более сложной логике могут возникнуть проблемы, если ссылка на старый Tween сохранилась где-то ещё. Лучшие практики для избежания ошибок:

1. **Не храните долгоживущие ссылки на Tween'ы**, если сцена может быть перезапущена. Ссылка станет недействительной. 2. **Для принудительной остановки** анимации перед перезапуском сцены можно использовать методы менеджера this.tweens. Например, можно остановить все твины сцены. 3. **Для перезапуска конкретной анимации** лучше не перезапускать всю сцену, а управлять жизненным циклом Tween через его методы.

Пример безопасной остановки всех твинов сцены перед перезапуском:

// В обработчике события перед restart()
this.tweens.killAll();
this.scene.restart();

Или перезапуск конкретного твина:

// Если tween — это ваша сохранённая ссылка
if (tween) {
    tween.stop();
    tween.seek(0); // Возврат к началу
    tween.play(); // Запуск с начала
}

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

Перезапуск сцены через Scene.restart() — мощный инструмент для сброса состояния, но он требует внимательного отношения к созданным анимациям и другим объектам. Система Phaser в большинстве случаев корректно очищает их, однако для полного контроля и избежания утечек памяти или конфликтов рекомендуется явно управлять жизненным циклом Tween-анимаций. **Идеи для экспериментов:** Попробуйте создать несколько независимых твинов и перезапустить сцену, отслеживая их состояние в консоли. Или реализуйте кнопку 'Пауза', которая будет останавливать твины через this.tweens.pauseAll(), а не перезапускать сцену.