О чем этот пример
В мобильных браузерах и некоторых графических средах WebGL контекст может быть неожиданно утерян из-за переключения вкладок, нехватки памяти или системных прерываний. Это приводит к "черному экрану" и краху игры. Пример демонстрирует, как в Phaser можно программно симулировать эту потерю и корректно восстановить рендеринг, обеспечивая стабильность игрового процесса. Понимание этого механизма критически важно для разработчиков, стремящихся создать надежные игры, которые выдерживают нестандартные условия работы браузера.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/sprites/x2kship.png');
}
create ()
{
/** Period to lose WebGL, in ms. Enlarge this to do things while context is lost. */
const delay = 1000;
// Create objects which will be affected by context loss.
// These objects should recover properly.
// Note that objects like `DynamicTexture` will not recover properly.
this.spinner = this.add.container(640, 360);
const ship = this.add.image(0, -200, 'ship');
this.spinner.add(ship);
this.rect = this.add.rectangle(640, 360, 600, 100, 0xdd3333);
this.text = this.add.text(640, 360, 'Click to lose WebGL context.\nRecovery in 1s.', { align: 'center' })
.setOrigin(0.5);
// Context loss.
const glLoser = this.renderer.getExtension('WEBGL_lose_context');
this.input.on('pointerdown', () => {
glLoser.loseContext();
this.time.delayedCall(delay, () => {
glLoser.restoreContext();
});
});
}
update (time, delta)
{
// 1 rotation every 60 seconds
this.spinner.rotation = time * Math.PI * 2 / 60000;
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1280,
height: 720,
backgroundColor: '#2d2d2d',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и объектов
В методе preload загружается единственный спрайт — изображение корабля. В create создаются три визуальных объекта, которые будут проверены на устойчивость к потере контекста: контейнер с вращающимся кораблем, прямоугольник и текстовое поле.
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ship', 'assets/sprites/x2kship.png');
}
create ()
{
this.spinner = this.add.container(640, 360);
const ship = this.add.image(0, -200, 'ship');
this.spinner.add(ship);
this.rect = this.add.rectangle(640, 360, 600, 100, 0xdd3333);
this.text = this.add.text(640, 360, 'Click to lose WebGL context.\nRecovery in 1s.', { align: 'center' })
.setOrigin(0.5);
}
Эти объекты (Container, Image, Rectangle, Text) являются стандартными для Phaser и должны автоматически восстановиться после возобновления рендеринга.
Получение доступа к расширению WEBGL_lose_context
Ключевой инструмент для управления контекстом — это расширение WebGL WEBGL_lose_context. Оно не является частью основного API Phaser, но доступно через рендерер.
const glLoser = this.renderer.getExtension('WEBGL_lose_context');
Вызов this.renderer.getExtension('WEBGL_lose_context') возвращает объект, который предоставляет два метода: loseContext() и restoreContext(). Этот объект сохраняется в переменную glLoser для последующего использования. Без этого расширения симулировать потерю контекста программно было бы невозможно.
Обработка клика и симуляция сбоя
Механика примера активируется по клику мыши. При срабатывании события pointerdown вызывается метод loseContext(), что приводит к немедленной остановке рендеринга — экран "замирает".
this.input.on('pointerdown', () => {
glLoser.loseContext();
this.time.delayedCall(delay, () => {
glLoser.restoreContext();
});
});
Сразу после потери контекста планируется его восстановление через this.time.delayedCall. Через заданный delay (1000 мс) вызывается restoreContext(). Phaser автоматически перезапускает рендерер и восстанавливает отрисовку всех объектов, созданных стандартными фабриками (this.add).
Анимация и обновление состояния
В методе update реализована простая анимация вращения контейнера this.spinner. Она зависит от глобального времени игры time.
update (time, delta)
{
// 1 rotation every 60 seconds
this.spinner.rotation = time * Math.PI * 2 / 60000;
}
Эта анимация служит индикатором работы игрового цикла. Во время потери WebGL контекста (loseContext) кадры перестают обновляться, и вращение останавливается. После успешного восстановления (restoreContext) анимация возобновляется с того момента, где остановилась, так как значение time продолжает накапливаться.
Конфигурация игры и важные замечания
Конфигурация игры использует Phaser.AUTO, что позволяет движку выбрать WebGL рендерер, если он доступен. Именно этот рендерер подвержен потере контекста.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 1280,
height: 720,
backgroundColor: '#2d2d2d',
scene: Example
};
Важно отметить, что автоматическому восстановлению подлежат объекты, созданные через встроенные фабрики (this.add.*). Динамические текстуры (DynamicTexture) или кастомные шейдеры могут требовать ручной переинициализации. В реальном сценарии потеря контекста инициируется системой, а не кодом, но механизм восстановления будет тем же.
Что попробовать дальше
Phaser предоставляет инфраструктуру для корректного восстановления после потери WebGL контекста, что критично для мобильных и веб-игр. Стандартные игровые объекты восстанавливаются автоматически.
**Идеи для экспериментов:**
1. Увеличьте задержку delay и попробуйте взаимодействовать с игрой (передвигать объекты) во время "черного экрана". Проверьте, восстановится ли их состояние.
2. Создайте объект DynamicTexture и проверьте, перерисовывается ли он после restoreContext.
3. Обработайте события webglcontextlost и webglcontextrestored напрямую через this.game.canvas.addEventListener для кастомной логики.
