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

При создании ретро-игр или проектов с фиксированным разрешением часто возникает необходимость полного контроля над процессом масштабирования. Phaser предоставляет для этого режим `NONE`, который отключает автоматические реакции на изменение размера окна. Это полезно, когда вы хотите вручную задавать уровень зума, например, для пиксель-арт игр, чтобы сохранить чёткость графики и избежать размытия. В этой статье мы разберём пример, где масштабирование инициируется только при явном событии ресайза окна.

Версия 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('bg', 'assets/tests/zoom/background.png');
        this.load.image('ship', 'assets/tests/zoom/viper.png');
        this.load.image('bullet', 'assets/tests/zoom/bullet.png');
    }

    create ()
    {
        this.add.image(0, 0, 'bg').setOrigin(0);

        const bullet = this.add.image(-10, -10, 'bullet');

        const ship = this.add.image(40, 40, 'ship').setInteractive();

        let tween;

        ship.on('pointerdown', function ()
        {
            if (tween)
            {
                tween.stop();
            }

            bullet.setPosition(40, 40);

            tween = this.tweens.add({
                targets: bullet,
                x: 200
            });

        }, this);
    }
}

const config = {
    type: Phaser.AUTO,
    backgroundColor: '#000000',
    scale: {
        mode: Phaser.Scale.NONE,
        autoCenter: Phaser.Scale.CENTER_BOTH,
        parent: 'phaser-example',
        width: 160,
        height: 144,
        zoom: Phaser.Scale.MAX_ZOOM
    },
    scene: Example
};

const game = new Phaser.Game(config);

//  In scaleMode NONE the Scale Manager is effectively disabled, so you need to
//  tell it when a resize happens yourself:

window.addEventListener('resize', event =>
{

    game.scale.setMaxZoom();

}, false);

document.body.style.backgroundColor = '#000000';

Настройка конфигурации: режим NONE и фиксированный зум

Ключевая настройка происходит в объекте config.scale. Здесь мы явно указываем движку не использовать автоматическое масштабирование под размер окна.

scale: {
    mode: Phaser.Scale.NONE,
    autoCenter: Phaser.Scale.CENTER_BOTH,
    parent: 'phaser-example',
    width: 160,
    height: 144,
    zoom: Phaser.Scale.MAX_ZOOM
}

- mode: Phaser.Scale.NONE — это главный параметр. Он отключает встроенный менеджер масштабирования Phaser. Игра больше не будет реагировать на изменение размеров окна браузера автоматически. - width и height задают внутреннее, игровое разрешение. В нашем случае это 160x144, типичное для ретро-консолей. - zoom: Phaser.Scale.MAX_ZOOM — это начальный уровень масштабирования. Phaser рассчитает максимальный целочисленный зум, при котором игра поместится в родительский контейнер (parent) без выхода за его границы. Это гарантирует чёткое отображение пикселей. - autoCenter: Phaser.Scale.CENTER_BOTH отвечает за выравнивание игровой области по центру контейнера после применения зума.

Сцена: загрузка ресурсов и создание объектов

В сцене мы загружаем фоновое изображение, спрайт корабля и пули. Важно отметить, что позиционирование объектов (this.add.image(x, y, 'key')) происходит относительно внутренней системы координат игры (160x144), а не окна браузера.

create ()
{
    this.add.image(0, 0, 'bg').setOrigin(0);
    const bullet = this.add.image(-10, -10, 'bullet');
    const ship = this.add.image(40, 40, 'ship').setInteractive();
}

- setOrigin(0) для фона устанавливает точку привязки в левый верхний угол (0,0), что удобно для его выравнивания. - Корабль (ship) делается интерактивным с помощью setInteractive(), чтобы на него можно было кликнуть. - Пуля (bullet) изначально помещается за пределы видимой области (-10, -10).

Интерактивность: ручное управление анимацией

По клику на корабль мы запускаем анимацию движения пули. Это демонстрирует, что вся игровая логика (взаимодействие, твины) работает независимо от настроек масштабирования.

let tween;
ship.on('pointerdown', function ()
{
    if (tween)
    {
        tween.stop();
    }
    bullet.setPosition(40, 40);
    tween = this.tweens.add({
        targets: bullet,
        x: 200
    });
}, this);

- При каждом клике существующий твин останавливается (tween.stop()), пуля возвращается в начальную позицию рядом с кораблём, и запускается новый твин для её движения до координаты x=200. - Обратите внимание на передачу контекста this в колбэк события, чтобы внутри функции можно было обратиться к this.tweens.

Ручная обработка ресайза окна

Поскольку мы отключили Scale Manager (Phaser.Scale.NONE), игра больше не слушает события изменения размера окна. Ответственность за это перекладывается на разработчика. В примере мы вручную добавляем слушатель на глобальный объект window.

window.addEventListener('resize', event =>
{
    game.scale.setMaxZoom();
}, false);

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

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

Режим Phaser.Scale.NONE — мощный инструмент для проектов, требующих полного контроля над отображением. Он идеально подходит для пиксель-арт игр, ретро-проектов или любых случаев, где критически важна чёткость и детализация. Для экспериментов попробуйте изменить начальное значение zoom в конфиге на произвольное число, реализовать плавное изменение зума с помощью tweens вместо setMaxZoom() или привязать обработчик ресайза к кнопкам на игровом интерфейсе для ручного управления масштабом.