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

Вы заметили, что спрайты при анимации иногда подёргиваются или движутся неровно, словно по пиксельной сетке? Это классическая проблема при работе с холстом (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.