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

Бывают ситуации, когда игру на Phaser нужно не просто поставить на паузу, а полностью перезагрузить — например, для сброса состояния, перехода на новую сцену с другим набором ассетов или перезапуска после Game Over. Простое обновление страницы — неоптимальное решение. Встроенный метод `game.destroy()` позволяет корректно завершить работу текущего экземпляра игры, освободить ресурсы и создать новый. Эта статья на практическом примере покажет, как безопасно уничтожить игру и инициировать её перезапуск по действию игрока, что полезно для создания перезапускаемых мини-игр или смены режимов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

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

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

        var chtholly = this.add.image(400, 500, 'sukasuka-chtholly');

        this.tweens.add({
            targets: chtholly,
            y: Math.random() * 600,
            x: Math.random() * 200,
            ease: 'Sine.easeInOut',
            duration: 2000,
            yoyo: true,
            repeat: -1
        });

        this.input.on('pointerdown', function () {

            this.sys.game.destroy(true);

            document.addEventListener('mousedown', function newGame () {

                game = new Phaser.Game(config);

                document.removeEventListener('mousedown', newGame);

            });

        }, this);
    }
}

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

let game = new Phaser.Game(config);

Подготовка сцены и анимация объектов

В примере создаётся простая сцена с двумя изображениями. Одно служит фоном, а второе — анимируемым объектом.

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

В методе preload задаётся базовый URL для загрузки и загружаются два изображения. Это стандартная практика подготовки ресурсов.

create ()
{
    this.add.image(400, 300, 'taikodrummaster');
    var chtholly = this.add.image(400, 500, 'sukasuka-chtholly');
    this.tweens.add({
        targets: chtholly,
        y: Math.random() * 600,
        x: Math.random() * 200,
        ease: 'Sine.easeInOut',
        duration: 2000,
        yoyo: true,
        repeat: -1
    });

В create сначала добавляется фоновое изображение. Затем создаётся спрайт chtholly и на него навешивается бесконечная твин-анимация (repeat: -1), которая случайным образом перемещает его по полю. Это создаёт динамичную картинку, которую мы будем сбрасывать.

Обработчик клика и вызов game.destroy()

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

this.input.on('pointerdown', function () {
    this.sys.game.destroy(true);
    // ... Действия после уничтожения
}, this);

Обратите внимание на путь this.sys.game. this здесь — это контекст сцены (Scene), this.sys — её системный менеджер, а this.sys.game — ссылка на главный объект игры (Phaser.Game). Вызов destroy(true) с параметром true гарантирует, что будут удалены не только игровые циклы и рендерер, но и очищен Canvas-элемент и его родительский контейнер в DOM. Без этого флага DOM-элементы останутся в памяти, что может привести к утечкам при многократных перезапусках.

Механизм перезапуска игры

После уничтожения игры её объект больше не работает. Чтобы начать заново, нужно создать новый экземпляр Phaser.Game. Однако делать это сразу в том же потоке выполнения нельзя — браузер может не успеть завершить очистку. Поэтому создание новой игры оборачивается в обработчик нативного события.

document.addEventListener('mousedown', function newGame () {
    game = new Phaser.Game(config);
    document.removeEventListener('mousedown', newGame);
});

Этот код вешает на документ временный слушатель события mousedown. Как только пользователь кликнет в любом месте страницы (после уничтожения старой игры), выполнится функция newGame. Она создаёт новую игру, используя ту же конфигурацию config, и сразу удаляет себя из слушателей, чтобы не срабатывать повторно. Переменная game объявлена глобально (let game = ...), поэтому её перезапись корректно создаёт новый экземпляр.

Конфигурация игры и глобальная переменная

Для работы механизма перезапуска конфиг игры и ссылка на её экземпляр должны быть доступны глобально (в области видимости модуля или скрипта).

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

let game = new Phaser.Game(config);

Константа config определяет настройки игры, включая корневую сцену Example. Переменная game хранит текущий рабочий экземпляр. Именно её перезаписывает функция newGame. Важно, что config остаётся неизменным и используется повторно для инициализации идентичной игры.

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

Метод this.sys.game.destroy(true) — это правильный способ полной остановки игры и очистки её ресурсов. В связке с отложенным созданием нового экземпляра через событие (например, mousedown) это позволяет реализовать плавный перезапуск. Для экспериментов попробуйте

  1. Уничтожать игру не по клику, а по таймеру или условию (окончание уровня)
  2. Перед перезапуском динамически менять параметры в объекте config (например, scene или width)
  3. Реализовать перезапуск не с нуля, а с передачей каких-либо данных в новую сцену через глобальное хранилище