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

При работе с геометрией в играх часто возникают ситуации, когда координаты объектов вычисляются с плавающей точкой, но для корректного отображения или физики требуются целочисленные значения. Накопление дробных частей может привести к визуальным артефактам или ошибкам коллизий. В этой статье мы разберем, как использовать метод `Phaser.Geom.Rectangle.CeilAll()` для автоматического округления координат и размеров прямоугольника. Это особенно полезно для пиксель-арт игр, сеточного позиционирования или привязки объектов к целочисленным координатам после анимаций и трансформаций.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    size = 50;
    y = 0;
    graphics;
    rect2;
    rect1;

    create ()
    {
        this.graphics = this.add.graphics({ fillStyle: { color: 0x0000aa } });

        this.rect1 = new Phaser.Geom.Rectangle(100, 0, 50, 50);
        this.rect2 = new Phaser.Geom.Rectangle(450, 0, 50, 50);
    }

    update ()
    {
        this.y += 0.05;
        this.size += 0.05;

        this.rect1.y = this.rect2.y = this.y;
        this.rect1.setSize(this.size);
        this.rect2.setSize(this.size);

        Phaser.Geom.Rectangle.CeilAll(this.rect2);

        this.graphics.clear();
        this.graphics.fillRectShape(this.rect1);
        this.graphics.fillRectShape(this.rect2);
    }
}

const config = {
    width: 800,
    height: 600,
    type: Phaser.AUTO,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Что делает CeilAll?

Метод Phaser.Geom.Rectangle.CeilAll() применяет математическую функцию округления вверх (Math.ceil) ко всем основным свойствам прямоугольника: координатам `x,y, ширинеwidthи высотеheight`. Это означает, что каждое значение будет увеличено до ближайшего целого числа, которое больше или равно исходному.

// Исходный прямоугольник с дробными значениями
let rect = new Phaser.Geom.Rectangle(10.2, 15.7, 45.1, 30.8);

// После вызова CeilAll
Phaser.Geom.Rectangle.CeilAll(rect);
// Теперь rect имеет значения: x=11, y=16, width=46, height=31

В исходном примере этот метод вызывается каждый кадр для rect2, в то время как rect1 остается с исходными дробными значениями. Это позволяет наглядно сравнить визуальный результат округления.

Разбор сцены и инициализации

В классе сцены Example определены свойства для управления размером, позицией и графикой. В методе create() инициализируются два одинаковых прямоугольника и объект Graphics для их отрисовки.

create ()
{
    this.graphics = this.add.graphics({ fillStyle: { color: 0x0000aa } });

    this.rect1 = new Phaser.Geom.Rectangle(100, 0, 50, 50);
    this.rect2 = new Phaser.Geom.Rectangle(450, 0, 50, 50);
}

Обрати внимание, что оба прямоугольника (rect1 и rect2) изначально создаются с целыми координатами и размерами. Различия между ними проявятся позже, в игровом цикле.

Игровой цикл и анимация

В методе update(), который выполняется каждый кадр, происходит увеличение переменных `yиsize`. Эти значения с плавающей точкой присваиваются свойствам обоих прямоугольников.

update ()
{
    this.y += 0.05;
    this.size += 0.05;

    this.rect1.y = this.rect2.y = this.y;
    this.rect1.setSize(this.size);
    this.rect2.setSize(this.size);
}

Таким образом, на каждом кадре rect1 и rect2 получают одинаковые дробные значения для позиции по оси Y и для размера. Без дополнительной обработки оба прямоугольника визуально выглядели бы идентично.

Ключевое применение CeilAll

После обновления свойств для rect2 вызывается метод CeilAll(). Это модифицирует его геометрию, в то время как rect1 остается с исходными дробными значениями.

Phaser.Geom.Rectangle.CeilAll(this.rect2);

Затем холст для рисования очищается, и оба прямоугольника отрисовываются заново с помощью fillRectShape().

this.graphics.clear();
    this.graphics.fillRectShape(this.rect1);
    this.graphics.fillRectShape(this.rect2);

Визуально прямоугольник rect2 будет двигаться и увеличиваться рывками, так как его координаты и размеры "привязываются" к целым числам, округленным вверх. Прямоугольник rect1 будет двигаться и расти плавно.

Когда это полезно на практике?

1. **Пиксель-арт и целочисленное позиционирование:** Гарантирует, что спрайты не будут размыты из-за субпиксельного рендеринга, сохраняя четкость пиксельной графики. 2. **Сетки и тайловые карты:** При расчете позиции объекта в ячейке сетки часто удобнее работать с целыми индексами. 3. **Оптимизация коллизий:** Некоторые алгоритмы проверки пересечений могут работать быстрее или надежнее с целочисленными значениями, особенно при использовании битовых операций или индексировании в массивы. 4. **Согласованность состояния:** Если логика игры (например, путь ИИ или сохранение) опирается на целые координаты, CeilAll помогает привести визуальное представление в соответствие с этой логикой после плавных движений.

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

Метод Phaser.Geom.Rectangle.CeilAll — это простой, но мощный инструмент для управления точностью геометрии. Он помогает избежать рассинхронизации между плавной визуальной анимацией и игровой логикой, требующей целочисленных значений. **Идеи для экспериментов:** * Сравните поведение с другими методами округления, например, FloorAll или RoundAll. * Примените CeilAll не ко всем, а только к определенным свойствам прямоугольника (например, только к `xиy`), комбинируя с ручным округлением. * Используйте этот метод для "привязки" курсора мыши или зоны взаимодействия к сетке в редакторе уровней.