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

Определение, находится ли курсор мыши (или любая другая точка) над спрайтом — одна из базовых задач во многих типах игр. В Phaser 3 для этого есть удобный метод `transform.hasPoint()`. В этой статье мы разберем, как работает этот метод на практическом примере, где проверка выполняется для цепочки связанных объектов и для двух разных камер. Освоив этот подход, вы сможете легко реализовывать логику наведения, кликов по сложным составным объектам и корректно обрабатывать взаимодействие в играх с несколькими областями просмотра (камерами).

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

Живой запуск

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

Исходный код


var config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    scene: {
        preload: preload,
        create: create,
        update: update
    },
    width: 800,
    height: 600
};

var game = new Phaser.Game(config);

var image0;
var image1;
var image2;
var image3;
var image4;
var camera0;
var mainCamera;

var mouse = { x: 0, y: 0 };

function preload() {

    
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('atari', 'assets/sprites/atari130xe.png');

}

function create() {

    image0 = this.add.image(0, 0, 'atari');
    image1 = this.add.image(100, 108, 'atari');
    image2 = this.add.image(100, 108, 'atari');
    image3 = this.add.image(100, 108, 'atari');
    image4 = this.add.image(100, 108, 'atari');

    image0.transform.add(image1.transform);
    image1.transform.add(image2.transform);
    image2.transform.add(image3.transform);
    image3.transform.add(image4.transform);

    document.getElementsByTagName('canvas')[0].onmousemove = function (e) {
        mouse.x = e.clientX;
        mouse.y = e.clientY;
    };

    mainCamera = this.cameras.main;
    camera0 = this.cameras.add(0, 300, 200, 200);
    mainCamera.x = 200;
}

function update()
{
    // main camera
    if (image0.transform.hasPoint(mouse.x, mouse.y, mainCamera))
    {
        console.log('point over image0 on mainCamera');
    }
     if (image1.transform.hasPoint(mouse.x, mouse.y, mainCamera))
    {
        console.log('point over image1 on mainCamera');
    }
     if (image2.transform.hasPoint(mouse.x, mouse.y, mainCamera))
    {
        console.log('point over image2 on mainCamera');
    }
     if (image3.transform.hasPoint(mouse.x, mouse.y, mainCamera))
    {
        console.log('point over image3 on mainCamera');
    }
     if (image4.transform.hasPoint(mouse.x, mouse.y, mainCamera))
    {
        console.log('point over image4 on mainCamera');
    }

    // second camera
    if (image0.transform.hasPoint(mouse.x, mouse.y, camera0))
    {
        console.log('point over image0 on camera0');
    }
     if (image1.transform.hasPoint(mouse.x, mouse.y, camera0))
    {
        console.log('point over image1 on camera0');
    }
     if (image2.transform.hasPoint(mouse.x, mouse.y, camera0))
    {
        console.log('point over image2 on camera0');
    }
     if (image3.transform.hasPoint(mouse.x, mouse.y, camera0))
    {
        console.log('point over image3 on camera0');
    }
     if (image4.transform.hasPoint(mouse.x, mouse.y, camera0))
    {
        console.log('point over image4 on camera0');
    }
}



Суть примера: иерархия объектов и несколько камер

В данном примере создается пять одинаковых спрайтов, связанных в иерархическую цепочку с помощью метода transform.add(). Это означает, что преобразования (позиция, масштаб, поворот) image1 будут относительно image0, image2 — относительно image1 и так далее.

Также в сцене работают две камеры: основная (mainCamera) и дополнительная (camera0), которая отображает уменьшенную область. В функции update() для каждой картинки и для каждой камеры проверяется, попадает ли текущее положение мыши в границы этого спрайта с учетом его преобразований и конкретной камеры. Результат выводится в консоль.

image0.transform.add(image1.transform);
image1.transform.add(image2.transform);
// ...
if (image0.transform.hasPoint(mouse.x, mouse.y, mainCamera)) {
    console.log('point over image0 on mainCamera');
}

Как работает transform.hasPoint()

Метод hasPoint(x, y, camera) объекта transform (который есть у любого игрового объекта, например, Image) возвращает true или false. Он проверяет, находится ли переданная точка с координатами (x, y) **в мировых координатах** внутри прямоугольной области (bounding box) этого объекта.

Ключевой нюанс — метод учитывает все текущие преобразования объекта: его позицию, масштаб, поворот, а также преобразования всех его родителей в иерархии. Третий аргумент — camera — определяет, через какую камеру мы «смотрим» на объект. Это критически важно, потому что камера может смещать, масштабировать или обрезать видимую область.

// Проверка для image2 через основную камеру
let isOverImage2 = image2.transform.hasPoint(mouseX, mouseY, mainCamera);

// Проверка для того же image2, но через камеру camera0
let isOverImage2InSmallCam = image2.transform.hasPoint(mouseX, mouseY, camera0);

В примере координаты мыши (mouse.x, mouse.y) — это координаты относительно окна браузера. Phaser внутри метода корректно преобразует их для сравнения с объектом в контексте указанной камеры.

Создание иерархии объектов

Чтобы продемонстрировать, что hasPoint учитывает иерархию, спрайты связываются друг с другом. image0 служит корневым родителем для image1, image1 — для image2 и т.д.

image0 = this.add.image(0, 0, 'atari');
image1 = this.add.image(100, 108, 'atari');
// ...
image0.transform.add(image1.transform);

После такой привязки позиция image1 становится относительной к image0. Если переместить image0, все его дочерние элементы (image1-image4) также сместятся. Метод hasPoint для дочернего объекта автоматически учитывает преобразования всех его предков в цепочке, что делает проверку корректной для сложных составных объектов.

Работа с несколькими камерами

В примере создается вторая камера camera0. Она отображает только область с координатами (0, 300) размером 200x200 пикселя.

mainCamera = this.cameras.main;
camera0 = this.cameras.add(0, 300, 200, 200);
mainCamera.x = 200; // Сдвигаем основную камеру вправо

Одна и та же точка в координатах окна браузера может находиться над объектом в одной камере, но не находиться в другой, если эта область не видна во второй камере или объект там отображается иначе. Поэтому в update проверка выполняется дважды: для mainCamera и для camera0. Это позволяет реализовывать интерфейсы с мини-картами или несколькими независимыми областями просмотра.

// Проверка для image0 в двух камерах
if (image0.transform.hasPoint(mouse.x, mouse.y, mainCamera)) { /* ... */ }
if (image0.transform.hasPoint(mouse.x, mouse.y, camera0)) { /* ... */ }

Собираем всё вместе в update()

Вся логика проверки сосредоточена в функции update(), которая выполняется на каждом кадре. Координаты мыши обновляются через слушатель событий DOM.

document.getElementsByTagName('canvas')[0].onmousemove = function (e) {
    mouse.x = e.clientX;
    mouse.y = e.clientY;
};

Затем для каждого спрайта (image0...image4) и для каждой камеры (mainCamera, camera0) вызывается hasPoint. Если проверка возвращает true, в консоль выводится соответствующее сообщение, позволяя в реальном времени наблюдать, над каким объектом и в каком «видепорте» находится курсор.

if (image2.transform.hasPoint(mouse.x, mouse.y, mainCamera))
{
    console.log('point over image2 on mainCamera');
}

Такой подход обеспечивает точное и эффективное определение взаимодействия даже в сложных сценах.

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

Метод transform.hasPoint() — это мощный и точный инструмент Phaser для проверки попадания точки в объект с учетом всех преобразований и конкретной камеры. Он идеально подходит для реализации наведения, выбора объектов или нестандартных областей клика в иерархических структурах. **Идеи для экспериментов:** 1. Добавьте объектам вращение или масштаб и убедитесь, что hasPoint продолжает работать корректно. 2. Создайте составной объект (например, группу частей корабля) и проверяйте попадание в него целиком. 3. Реализуйте логику «клика» по объекту только в одной из нескольких камер, имитируя взаимодействие с элементами мини-карты.