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

При создании игр с детализированными спрайтами часто возникает проблема: игрок может "зацепить" объект за его прозрачные области. Это ломает immersion и выглядит неаккуратно. Механизм Pixel Perfect в Phaser решает эту проблему, позволяя настраивать интерактивность с точностью до пикселя. В этой статье мы разберем, как использовать `pixelPerfect` и метод `makePixelPerfect` для создания продвинутой системы перетаскивания объектов. Вы научитесь контролировать, какие именно части спрайта должны реагировать на ввод пользователя, что особенно полезно для игр с нестандартными формами объектов или сложным UI.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
        this.load.image('logoAlpha', 'assets/sprites/phaser3-logo-alpha.png');
    }

    create ()
    {
        //  This sprite is draggable from any pixel that has an alpha value >= 1
        const sprite1 = this.add.sprite(400, 200, 'logo').setInteractive({ pixelPerfect: true });

        //  This sprite is draggable from any pixel that has an alpha value >= 100 (i.e. the left side of the sprite)
        const sprite2 = this.add.sprite(400, 400, 'logoAlpha').setInteractive(this.input.makePixelPerfect(100));

        sprite2.angle = 22;
        sprite2.setScale(1.4);

        this.input.setDraggable([ sprite1, sprite2 ]);

        sprite1.on('drag', function (pointer, dragX, dragY)
        {

            this.x = dragX;
            this.y = dragY;

        });

        sprite2.on('drag', function (pointer, dragX, dragY)
        {

            this.x = dragX;
            this.y = dragY;

        });
    }
}

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

const game = new Phaser.Game(config);

Суть Pixel Perfect

По умолчанию при назначении интерактивности спрайту (setInteractive) Phaser создает для него прямоугольную область (хитбокс), которая реагирует на все взаимодействия. Это быстро, но неточно для спрайтов со сложной формой или прозрачностью.

Pixel Perfect — это альфа-тестирование на уровне пикселей. Механика проверяет альфа-канал (прозрачность) конкретного пикселя спрайта, на который нажал курсор. Если прозрачность пикселя меньше заданного порога, взаимодействие не происходит. Это позволяет сделать спрайт "кликабельным" только по его видимой части.

В примере используются два спрайта с логотипом Phaser 3: обычный (logo) и с градиентной прозрачностью (logoAlpha).

Базовое использование: `pixelPerfect: true`

Самый простой способ включить точечную проверку — передать объект конфигурации в метод setInteractive. Установка pixelPerfect: true активирует проверку с порогом альфа-канала, равным 1. Это означает, что любая непрозрачная точка спрайта (альфа >= 1) станет интерактивной.

const sprite1 = this.add.sprite(400, 200, 'logo').setInteractive({ pixelPerfect: true });

Код выше делает спрайт sprite1 перетаскиваемым только по его непрозрачным пикселям. Порог в 1 альфа-единицу — самый строгий.

Расширенный контроль: `makePixelPerfect(alphaTolerance)`

Иногда нужен более гибкий контроль. Например, чтобы сделать интерактивной область с полупрозрачными пикселями (как в градиентах). Для этого используется фабричный метод this.input.makePixelPerfect().

Он создает объект конфигурации для setInteractive с заданным порогом прозрачности. Порог alphaTolerance — это целое число от 0 до 255.

const sprite2 = this.add.sprite(400, 400, 'logoAlpha').setInteractive(this.input.makePixelPerfect(100));

В данном примере для спрайта sprite2 интерактивными станут все пиксели, значение альфа-канала которых больше или равно 100. Это позволяет, например, сделать интерактивной только левую, более непрозрачную часть логотипа logoAlpha, в то время как его правая, почти прозрачная часть, будет игнорировать ввод.

Обработка событий перетаскивания

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

this.input.setDraggable([ sprite1, sprite2 ]);

sprite1.on('drag', function (pointer, dragX, dragY) {
    this.x = dragX;
    this.y = dragY;
});

Ключевые моменты: 1. this.input.setDraggable() — помечает один или несколько объектов как перетаскиваемые. 2. sprite.on('drag', ...) — назначает функцию, которая будет вызываться при каждом перемещении мыши во время перетаскивания. Аргументы dragX и dragY автоматически содержат новые координаты для объекта. 3. Внутри функции-обработчика this ссылается на сам спрайт (sprite1 или sprite2), что позволяет обновить его позицию.

Важные нюансы и производительность

Pixel Perfect — мощный, но более ресурсоемкий механизм по сравнению со стандартными прямоугольными хитбоксами. Проверка альфа-канала требует дополнительных вычислений.

Важно помнить: - Механика работает корректно только со спрайтами, у которых есть исходные данные об альфа-канале (например, PNG с прозрачностью). - Порог alphaTolerance применяется к исходному, *необработанному* изображению. Это видно в примере: хотя sprite2 повернут и масштабирован, интерактивная область по-прежнему определяется исходной текстурой logoAlpha. - Не используйте pixelPerfect для множества мелких или часто обновляемых объектов, если в этом нет острой необходимости, чтобы не нагружать CPU.

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

Pixel Perfect в Phaser — это точный инструмент для создания качественного пользовательского взаимодействия с объектами сложной формы. Он незаменим в казуальных играх, пазлах или интерфейсах, где важна визуальная точность. Идеи для экспериментов: 1. Создайте спрайт-«силуэт» и настройте alphaTolerance так, чтобы интерактивной была только его полностью непрозрачная сердцевина. 2. Комбинируйте pixelPerfect со стандартным setInteractive() (без настроек) для одного объекта, используя input.hitArea, чтобы создать сложную зону клика. 3. Проверьте, как меняется производительность при активном перетаскивании 50-100 объектов с включенным Pixel Perfect.