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

Реализация полноэкранного режима — стандартная задача для браузерных игр. Однако, если просто вызвать `toggleFullscreen()`, интерфейс может перестать соответствовать состоянию игры. В этой статье мы разберем готовый пример из базы Phaser, который показывает, как правильно отслеживать события входа и выхода из полноэкранного режима и синхронизировать с ними визуальное состояние кнопки. Этот подход избавит вашу игру от багов и улучшит пользовательский опыт.

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

Живой запуск

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

Исходный код


class GameScene extends Phaser.Scene {

    preload() {
        this.load.spritesheet('fullscreen', 'https://labs.phaser.io/assets/ui/fullscreen.png', { frameWidth: 64, frameHeight: 64 });
    }

    create() {
        const button = this.add.image(100, 100, 'fullscreen', 0).setInteractive();
        button.on('pointerup', () => {
            if (this.scale.isFullscreen) {
                button.setFrame(0);
            } else {
                button.setFrame(1);
            }
            this.scale.toggleFullscreen();
        });

        this.scale.on(Phaser.Scale.Events.ENTER_FULLSCREEN, () => console.log('enter fullscreen'));
        this.scale.on(Phaser.Scale.Events.LEAVE_FULLSCREEN, () => console.log('leave fullscreen'));
    }

}
const game = new Phaser.Game({
    type: Phaser.AUTO,
    backgroundColor: 0xaaaaaa,
    scene: [GameScene]
});

Подготовка ассетов и создание кнопки

В методе preload() загружается спрайтшит с двумя кадрами для кнопки. Первый кадр (индекс 0) будет означать состояние "развернуть", второй (индекс 1) — состояние "свернуть".

this.load.spritesheet('fullscreen', 'https://labs.phaser.io/assets/ui/fullscreen.png', { frameWidth: 64, frameHeight: 64 });

В методе create() кнопка создается как интерактивное изображение. Изначально отображается кадр 0. Обработчик события pointerup будет менять кадр и переключать режим экрана.

const button = this.add.image(100, 100, 'fullscreen', 0).setInteractive();

Логика переключения в обработчике кнопки

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

button.on('pointerup', () => {
    if (this.scale.isFullscreen) {
        button.setFrame(0);
    } else {
        button.setFrame(1);
    }
    this.scale.toggleFullscreen();
});

Надежный способ: реакция на события Scale Manager

Вместо предположений о результате toggleFullscreen(), нужно реагировать на фактические события, которые генерирует Phaser.Scale.ScaleManager. Это гарантирует, что интерфейс всегда синхронизирован с реальным состоянием.

this.scale.on(Phaser.Scale.Events.ENTER_FULLSCREEN, () => {
    console.log('enter fullscreen');
    button.setFrame(1); // Меняем на кадр "свернуть"
});

this.scale.on(Phaser.Scale.Events.LEAVE_FULLSCREEN, () => {
    console.log('leave fullscreen');
    button.setFrame(0); // Меняем на кадр "развернуть"
});

Таким образом, состояние кнопки меняется только после успешного входа или выхода из полноэкранного режима, о чем явно сообщает система.

Итоговая структура сцены и конфигурация игры

Полный код сцены, включающий оба подхода, и базовая конфигурация игры. Обратите внимание, что для работы полноэкранного режима может потребоваться запуск игры через веб-сервер (не через file:// протокол).

class GameScene extends Phaser.Scene {
    preload() {
        this.load.spritesheet('fullscreen', 'https://labs.phaser.io/assets/ui/fullscreen.png', { frameWidth: 64, frameHeight: 64 });
    }
    create() {
        const button = this.add.image(100, 100, 'fullscreen', 0).setInteractive();
        // Обработчик кнопки (меняет кадр ПЕРЕД вызовом)
        button.on('pointerup', () => {
            this.scale.toggleFullscreen();
        });
        // Слушатели событий (меняют кадр ПОСЛЕ успешного изменения)
        this.scale.on(Phaser.Scale.Events.ENTER_FULLSCREEN, () => {
            button.setFrame(1);
        });
        this.scale.on(Phaser.Scale.Events.LEAVE_FULLSCREEN, () => {
            button.setFrame(0);
        });
    }
}

const game = new Phaser.Game({
    type: Phaser.AUTO,
    backgroundColor: 0xaaaaaa,
    scene: [GameScene]
});

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

Для надежного управления полноэкранным режимом всегда используйте события ENTER_FULLSCREEN и LEAVE_FULLSCREEN из Phaser.Scale.Events для обновления UI. Это защищает от рассинхронизации, если браузер запретит действие. Для экспериментов попробуйте: добавить анимацию переключения кадра кнопки, обработать ошибку, если полноэкранный режим не поддерживается, или создать отдельный плагин для переиспользуемого компонента "FullscreenButton".