О чем этот пример
При разработке игр на Phaser часто возникает необходимость не просто перезапустить сцену, а полностью пересоздать игровой экземпляр. Это особенно важно при работе с несколькими активными сценами, где стандартные методы перезапуска могут оставить "хвосты" в памяти или логике. Данный пример демонстрирует корректную процедуру уничтожения игры (`game.destroy`) и её последующего создания заново по событию пользователя, что полезно для реализации функций полного рестарта или смены режимов игры без перезагрузки страницы.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Background extends Phaser.Scene
{
constructor ()
{
super({ key: 'background', active: true });
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('face', 'assets/pics/bw-face.png');
this.load.image('arrow', 'assets/sprites/longarrow.png');
}
create ()
{
this.face = this.add.image(400, 300, 'face');
this.arrow = this.add.image(300, 300, 'arrow').setOrigin(0, 0.5);
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);
}
update (time, delta)
{
this.arrow.rotation += 0.01;
}
}
class Demo extends Phaser.Scene
{
constructor ()
{
super({ key: 'demo', active: true });
}
preload ()
{
// this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('arrow', 'assets/sprites/longarrow.png');
}
create ()
{
this.arrow = this.add.image(400, 300, 'arrow').setOrigin(0, 0.5);
}
update ()
{
this.arrow.rotation += 0.01;
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: [ Background, Demo ]
};
let game = new Phaser.Game(config);
Архитектура примера: две независимые сцены
В примере определены две сцены, Background и Demo, которые добавлены в конфигурацию игры и запущены одновременно (свойство active: true в конструкторе). Это типичный паттерн для разделения логики, например, когда одна сцена отвечает за фон и UI, а другая — за геймплей.
scene: [ Background, Demo ]
Каждая сцена загружает свои ресурсы и в методе create создает спрайт стрелки. Важно отметить, что обе сцены используют один и тот же ключ ассета ('arrow'), но загружают его независимо друг от друга. В методе update обе стрелки вращаются, визуализируя активность каждой сцены.
Механизм уничтожения игры: `this.sys.game.destroy`
В сцене Background на событие клика (pointerdown) назначен обработчик. Его основная задача — полностью уничтожить текущий экземпляр игры Phaser.
this.input.on('pointerdown', function () {
this.sys.game.destroy(true);
// ... Действия после уничтожения
}, this);
Вызов this.sys.game.destroy(true) является ключевым. Через this.sys.game мы получаем доступ к корневому объекту Phaser.Game. Метод destroy выполняет полную очистку: останавливает все системы (ввод, анимации, физику), удаляет все слушатели событий, освобождает канвасы и текстуры из памяти. Передача аргумента true указывает, что нужно также удалить элемент canvas из DOM-дерева. Без этого шага возможны утечки памяти и конфликты при создании новой игры.
Пересоздание игры по событию пользователя
После уничтожения игры необходимо дать браузеру небольшой "отдых" и дождаться нового действия пользователя перед созданием нового экземпляра. В примере это реализовано через добавление слушателя на событие mousedown на уровне документа (document).
document.addEventListener('mousedown', function newGame () {
game = new Phaser.Game(config);
document.removeEventListener('mousedown', newGame);
});
Внутри этой функции-обработчика создается новый экземпляр игры new Phaser.Game(config), который присваивается той же переменной game. Конфигурация config используется повторно. Сразу после создания игры слушатель события удаляется (removeEventListener), чтобы предотвратить множественное срабатывание. Этот подход гарантирует, что новая игра будет инициализирована на чистом состоянии страницы.
Важные детали и практические замечания
1. **Доступ к объекту игры:** Внутри сцены безопаснее всего получать ссылку на главный объект игры через this.sys.game, а не через глобальную переменную game. Это делает код более переносимым.
2. **Загрузка ресурсов:** Обратите внимание, что в сцене Demo строка setBaseURL закомментирована. В данном примере это не вызывает ошибки, так как сцена Background уже загрузила нужное изображение arrow в текстуры игры. Однако, в реальном проекте лучше выносить общую загрузку ресурсов в отдельную сцену-загрузчик.
3. **Сброс состояния:** Метод destroy не сбрасывает ваши глобальные переменные или состояние вне Phaser. Если в вашем коде есть кастомные глобальные объекты или подписки, их очистку нужно проводить отдельно перед вызовом destroy.
Что попробовать дальше
Полное уничтожение и пересоздание игры — мощный инструмент для реализации "жесткого" перезапуска, смены игрового режима или обработки критических ошибок. Для экспериментов попробуйте
- Уничтожать игру не по клику, а по таймеру или достижению игрового условия
- Менять конфигурацию
config(например, размер холста или набор сцен) перед созданием нового экземпляраPhaser.Game - Реализовать промежуточный экран "Рестарт?" между вызовом
destroyи новымaddEventListener
