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

Перезапуск сцены — частый инструмент в арсенале геймдизайна, будь то рестарт уровня после поражения или быстрый сброс состояния. Однако в Phaser 3 метод `scene.restart()` имеет ключевую особенность, которую важно понимать, чтобы избежать неожиданного поведения и утечек памяти. Этот пример наглядно демонстрирует, как работает перезапуск и какие данные сохраняются между вызовами.

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

Живой запуск

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

Исходный код


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

function create()
{
    this.add.tileSprite(400, 300, 800, 600, 'apple');

    let x = Phaser.Math.Between(100, 400);

    this.add.text(x, 100, 'Phaser', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });

    this.add.text(x, 200, 'Phaser', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });

    this.add.text(x, 300, 'Phaser', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });

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

        this.scene.restart();

        console.log('restarted');

    });
}

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

const game = new Phaser.Game(config);

Что делает метод `scene.restart()`?

При вызове this.scene.restart() текущая сцена не уничтожается полностью. Вместо этого Phaser выполняет "мягкий" перезапуск: останавливает сцену, а затем снова запускает её, вызывая методы preload, create и т.д. Важнейший нюанс: **загруженные ассеты (изображения, звуки) не выгружаются из кэша**. Это сделано для производительности, чтобы избежать повторных загрузок одних и тех же ресурсов.

В примере перезапуск инициируется по клику мыши:

Инициализация и загрузка ресурсов

В методе preload задаётся базовый URL для загрузки и загружаются два изображения. Обратите внимание, что setBaseURL вызывается только один раз при первом запуске сцены. При рестарте метод preload выполнится снова, но повторной загрузки apple и image1 не произойдет, так как они уже находятся в кэше.

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

Создание игровых объектов и событие рестарта

В методе create создаётся фон-тайлспрайт, три текстовых объекта и назначается обработчик клика. Координата `xдля текстов генерируется случайно при каждом вызовеcreate. Это ключевой момент: при рестартеcreate` выполняется заново, поэтому позиция текстов изменится.

Однако тайлспрайт apple будет отрисован поверх предыдущего, так как старые объекты не удаляются автоматически. Это может привести к наложению и нежелательному накоплению объектов.

function create()
{
    this.add.tileSprite(400, 300, 800, 600, 'apple');

    let x = Phaser.Math.Between(100, 400);

    this.add.text(x, 100, 'Phaser', { fontFamily: 'Arial', fontSize: 64, color: '#00ff00' });
    // ... добавлены ещё два текста

    this.input.once('pointerdown', () => {
        this.scene.restart();
        console.log('restarted');
    });
}

Проблема накопления объектов и её решение

Главный подводный камень restart() — накопление игровых объектов, созданных в предыдущих запусках сцены. Они остаются в памяти и на дисплее, хотя логически считаются "устаревшими". В данном примере каждый клик добавит новый тайлспрайт и три текста поверх существующих.

Для чистого рестарта необходимо вручную очищать сцену перед перезапуском. Самый надёжный способ — слушать событие shutdown и удалять объекты там, либо использовать scene.start() с другим ключом сцены для полной её пересоздания.

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

Метод scene.restart() — это не полный сброс, а быстрый рецикл сцены с сохранением загруженных ресурсов. Используйте его, когда нужно сбросить игровую логику, но не загружать ассеты заново. Для экспериментов попробуйте

  1. Добавить счётчик кликов в тексте, чтобы визуально отслеживать рестарты
  2. Вместо restart() использовать комбинацию scene.stop() и scene.start()
  3. Очищать все объекты сцены в обработчике события shutdown