О чем этот пример
Вы заметили, что спрайты при анимации иногда подёргиваются или движутся неровно, словно по пиксельной сетке? Это классическая проблема при работе с холстом (Canvas) и движением на субпиксельных значениях. В статье разберём пример из бага Phaser #5774, который демонстрирует важность корректной настройки двух ключевых свойств конфигурации — `roundPixels` и `pixelArt`. Понимание их работы позволит устранить артефакты анимации и сделать движение объектов плавным.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
/* global colors, Phaser */
class Example extends Phaser.Scene {
init() {
const { pixelArt, roundPixels } = this.game.config;
console.info("pixelArt", pixelArt);
console.info("roundPixels", roundPixels);
console.assert(
pixelArt === false,
"pixelArt should be true not %s",
pixelArt
);
console.assert(
roundPixels === true,
"roundPixels should be true not %s",
roundPixels
);
}
preload() {
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image("pic", "assets/pics/baal-loader.png");
this.load.image("spaceman", "assets/sprites/exocet_spaceman.png");
this.load.spritesheet("spritesheet", "assets/sprites/dude.png", {
frameWidth: 32,
frameHeight: 48
});
}
create() {
const img1 = this.add.image(0, 0, "pic").setOrigin(0, 0);
const img2 = this.add.image(0, 0, "spaceman").setOrigin(0, 0);
const img3 = this.add.image(0, 0, "spritesheet", 4).setOrigin(0, 0);
console.log(this.cameras.main);
this.tweens.add({
targets: [img1, img2, img3],
props: { x: 1, y: 1 },
repeat: -1,
yoyo: true
});
}
}
const config = {
type: Phaser.CANVAS,
roundPixels: true,
scene: [Example]
};
new Phaser.Game(config);
Проблема: дёрганная анимация при движении на субпикселях
В контексте рендеринга на Canvas, браузер вынужден позиционировать элементы с целочисленными координатами. Когда объект движется со скоростью, приводящей к дробным значениям координат (например, 10.5 пикселя), браузер округляет их для отрисовки. Это окружение на каждом кадре и вызывает визуальное подёргивание — объект "скачет" между целыми пикселями.
Исходный код примера создаёт простую сцену с тремя изображениями, которые анимируются твином по осям X и Y на очень маленькую величину (до 1 пикселя). Без специальной обработки такая анимация будет выглядеть рваной.
Решение: включение roundPixels в конфигурации игры
Phaser предоставляет настройку roundPixels в объекте конфигурации игры. Когда для рендерера Phaser.CANVAS это свойство установлено в true, движок автоматически округляет координаты всех отображаемых объектов (изображений, спрайтов, текста) до целых значений **перед их отрисовкой на холсте**. Это гарантирует, что на каждом кадре позиция фиксируется, устраняя субпиксельное дрожание.
const config = {
type: Phaser.CANVAS,
roundPixels: true, // Ключевая настройка для сглаживания анимации
scene: [Example]
};
В примере в методе init() сцены выполняется проверка через console.assert, что roundPixels действительно равен true. Это страховка от случайного изменения конфигурации.
Важное уточнение: отключение pixelArt для корректной работы
Вторая настройка, pixelArt, часто вызывает путаницу. По умолчанию в Phaser 3 она равна false. Когда pixelArt: true, движок отключает сглаживание изображений (интерполяцию) при масштабировании, что критично для пиксель-арт графики. Однако, **эта настройка также влияет на внутреннюю работу roundPixels**.
Если pixelArt установлен в true, то свойство roundPixels автоматически принудительно устанавливается в true, но это может привести к неочевидным побочным эффектам в зависимости от версии. В нашем примере требуется обратная ситуация: pixelArt должен быть false, чтобы roundPixels работал в своём чистом виде, предназначенном для сглаживания анимации.
init() {
const { pixelArt, roundPixels } = this.game.config;
console.assert(pixelArt === false, "pixelArt should be true not %s", pixelArt);
console.assert(roundPixels === true, "roundPixels should be true not %s", roundPixels);
}
Проверка в init() убеждается, что конфигурация задана верно: pixelArt: false, roundPixels: true.
Как работает анимация в примере
В методе create() сцены создаются три изображения с разными текстурами. Все они позиционируются в точке (0,0) с установленным в начало системы координат setOrigin(0, 0).
const img1 = this.add.image(0, 0, "pic").setOrigin(0, 0);
const img2 = this.add.image(0, 0, "spaceman").setOrigin(0, 0);
const img3 = this.add.image(0, 0, "spritesheet", 4).setOrigin(0, 0);
Затем к ним применяется твин с помощью this.tweens.add. Твин бесконечно (repeat: -1) двигает все три цели (targets) от их текущей позиции до позиции (1,1) по осям X и Y, а затем обратно (yoyo: true). Без roundPixels: true это микро-движение между 0 и 1 пикселем выглядело бы как постоянное мерцание.
this.tweens.add({
targets: [img1, img2, img3],
props: { x: 1, y: 1 },
repeat: -1,
yoyo: true
});
Что попробовать дальше
Корректная настройка roundPixels: true при использовании рендерера Phaser.CANVAS — это простой и эффективный способ избавиться от дёрганной анимации, вызванной движением на субпиксельных значениях. Главное — помнить о взаимосвязи с настройкой pixelArt и оставлять её равной false, если вы не работаете specifically с пиксель-артом.
**Идеи для экспериментов:**
1. Установите в конфигурации roundPixels: false и посмотрите, как изменится анимация.
2. Попробуйте изменить твин, задав props: { x: 0.5, y: 0.5 } — дёргания станут ещё заметнее.
3. Проверьте, как влияет настройка pixelArt: true на отображение ваших спрайтов (особенно при масштабировании) совместно с roundPixels.
