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

При разработке кроссплатформенных игр на Phaser вы могли замечать "дрожание" или размытость спрайтов при анимации на мобильных устройствах. Это особенно заметно при субпиксельном позиционировании объектов. В статье разберем, как работает свойство `cameras.main.roundPixels` и почему его контроль может стать ключом к визуальной четкости вашей игры. Мы рассмотрим практический пример, демонстрирующий проблему и её изящное решение.

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

Живой запуск

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

Исходный код


class Demo extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('truck', 'assets/sprites/astorm-truck.png');
    }

    create ()
    {
        this.input.on('pointerdown', () => {

            this.cameras.main.roundPixels = !this.cameras.main.roundPixels;

        });

        this.add.text(10, 10, 'Mobile Pipeline', { font: 'bold 32px Arial', fill: '#ffffff' });
        this.add.text(10.8, 50.3, 'Mobile Pipeline', { font: 'bold 32px Arial', fill: '#ffffff' });

        const a = this.add.sprite(0, 200, 'truck');

        this.tweens.add({
            targets: a,
            x: 800,
            duration: 5000,
            ease: 'linear',
            yoyo: true,
            repeat: -1
        });

        this.add.sprite(400, 300, 'truck');
        this.add.sprite(400.5, 400, 'truck');
        this.add.sprite(400.85, 500.2, 'truck');
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    // pixelArt: true,
    backgroundColor: '#00007d',
    scene: Demo
};

const game = new Phaser.Game(config);

В чём проблема? Субпиксельное позиционирование и артефакты

Phaser, как и многие движки, позволяет задавать координаты объектов с дробной частью (например, 400.5). Это полезно для плавной анимации. Однако, на экранах с низким плотностью пикселей (не retina-дисплеи) или при использовании определенных конвейеров рендеринга (pipeline), это может приводить к визуальным артефактам.

Графический процессор при отрисовке спрайта с координатой 400.5 вынужден "растягивать" тексели между двумя физическими пикселями, что приводит к их размытию или появлению неровных, "дрожащих" краев при движении. Особенно это критично для пиксель-арт графики, где важна чёткость каждого пикселя.

В исходном коде мы создаем несколько спрайтов с дробными координатами и один анимированный спрайт, который движется по горизонтали. Без включенной опции pixelArt: true в конфиге эти объекты могут рендериться с размытием.

Решение: Включаем и выключаем округление пикселей

Phaser предоставляет свойство roundPixels у камеры. Когда оно установлено в true, координаты всех отображаемых объектов (включая спрайты, текст, графику) в рамках этой камеры округляются до ближайшего целого числа перед отрисовкой на холсте. Это гарантирует, что объекты всегда "привязываются" к целой пиксельной сетке, устраняя размытие.

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

this.input.on('pointerdown', () => {
    this.cameras.main.roundPixels = !this.cameras.main.roundPixels;
});

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

Наглядная демонстрация: текст и спрайты

Давайте посмотрим, как это влияет на разные типы объектов в сцене.

**Текст:** Создаются две текстовые метки с небольшим смещением по дробной части. При включенном roundPixels они, скорее всего, будут отображены идентично, так как их координаты округлятся до одного значения.

this.add.text(10, 10, 'Mobile Pipeline', { font: 'bold 32px Arial', fill: '#ffffff' });
this.add.text(10.8, 50.3, 'Mobile Pipeline', { font: 'bold 32px Arial', fill: '#ffffff' });

**Статические спрайты:** Три грузовика создаются с разной дробной частью в координате X. С roundPixels: true они могут "схлопнуться" на одну вертикальную линию, если их дробные части округлятся к одному целому числу (400).

this.add.sprite(400, 300, 'truck');
this.add.sprite(400.5, 400, 'truck');
this.add.sprite(400.85, 500.2, 'truck');

**Анимированный спрайт:** Это самый показательный пример. Движущийся грузовик при включенном округлении будет перемещаться "скачкообразно" от одного целого пикселя к другому, но его изображение останется четким. Без округления его движение будет математически плавным, но визуально — размытым.

const a = this.add.sprite(0, 200, 'truck');
this.tweens.add({
    targets: a,
    x: 800,
    duration: 5000,
    ease: 'linear',
    yoyo: true,
    repeat: -1
});

`roundPixels` vs `pixelArt: true` в конфиге

Важно не путать локальное свойство камеры с глобальной настройкой игры. В конфигурационном объекте игры можно установить опцию pixelArt: true.

const config = {
    // ... другие настройки ...
    pixelArt: true, // Раскомментируйте эту строку
    backgroundColor: '#00007d',
    scene: Demo
};

**Что делает pixelArt: true?** 1. Автоматически устанавливает antialias: false для WebGL и Canvas контекста. 2. Устанавливает roundPixels: true для *всех* камер по умолчанию. 3. Отключает сглаживание при масштабировании текстур.

**Ключевое отличие:** Свойство cameras.main.roundPixels дает вам точечный контроль. Вы можете включать округление только для определенных камер или менять его динамически во время выполнения игры (как в нашем примере). Опция pixelArt в конфиге — это статическая, глобальная настройка, идеальная для проектов, полностью выполненных в стиле пиксель-арт.

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

Свойство roundPixels — это мощный инструмент для борьбы с размытием на мобильных устройствах и не только. Используйте его, когда важна чёткость графики, а плавность анимации на субпиксельном уровне вторична. Для экспериментов попробуйте: включить pixelArt: true в конфиге и посмотреть, как изменится поведение; применить roundPixels только к UI-камере, оставив игровой мир с плавным движением; или написать кастомную логику округления координат только для определенных слоев объектов.