О чем этот пример
При разработке кроссплатформенных игр на 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-камере, оставив игровой мир с плавным движением; или написать кастомную логику округления координат только для определенных слоев объектов.
