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

Правильная обработка событий ввода — основа отзывчивого и интуитивного геймплея. Этот пример наглядно демонстрирует работу двух важных событий: `pointerover` (курсор наведён на объект) и `gameout` (курсор покинул пределы игрового холста). Понимание их взаимодействия позволяет создавать сложную визуальную обратную связь для игрока, например, подсвечивать интерактивные элементы при наведении и менять их состояние, когда игрок убрал фокус с окна браузера. Мы разберём код и увидим, как избежать потенциальных багов в логике взаимодействия.

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

Живой запуск

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

Исходный код


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

var game = new Phaser.Game(config);

function preload ()
{
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}

function create ()
{
    var sprite = this.add.sprite(800, 300, 'eye').setInteractive();

    sprite.on('pointerover', function (event) {

        this.setTint(0xff0000);

    });

    sprite.on('pointerout', function (event) {

        this.clearTint();

    });

    this.input.on('gameout', () => {

        sprite.setTint(0x00ff00);

    });
}

Структура сцены и загрузка ресурсов

В Phaser вся игровая логика организована в сценах (Scenes). В данном примере сцена содержит две стандартные функции: preload и create. В preload мы загружаем изображение, которое будем использовать в качестве интерактивного спрайта.

function preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('eye', 'assets/pics/lance-overdose-loader-eye.png');
}

Метод this.load.setBaseURL() задаёт базовый URL для всех последующих загрузок, что удобно для указания общей части пути. Метод this.load.image() регистрирует загрузку изображения и присваивает ему ключ 'eye' для дальнейшего использования.

Создание интерактивного спрайта

В функции create происходит инициализация игровых объектов. Ключевой шаг — создание спрайта и немедленное назначение ему интерактивности.

function create ()
{
    var sprite = this.add.sprite(800, 300, 'eye').setInteractive();
}

Метод this.add.sprite() создаёт новый спрайт с указанными координатами X, Y и ключом текстуры. Вызов .setInteractive() без аргументов делает весь спрайт чувствительной областью для событий ввода (по умолчанию используется его прямоугольный контур — bounding box). Без этого вызова события pointerover и pointerout не будут срабатывать.

Обработка наведения курсора (pointerover/out)

Когда спрайт интерактивен, на него можно подписаться для обработки событий ввода. Событие pointerover генерируется, когда указатель (мышь, палец) входит в область спрайта. pointerout — когда покидает её.

sprite.on('pointerover', function (event) {
        this.setTint(0xff0000);
    });

    sprite.on('pointerout', function (event) {
        this.clearTint();
    });

Внутри функций-обработчиков контекст this ссылается на сам спрайт (sprite). Метод .setTint(0xff0000) применяет красный оттенок ко всему спрайту. Метод .clearTint() — убирает его. Это классический паттерн для визуальной обратной связи при наведении.

Обработка ухода курсора с игрового холста (gameout)

Событие gameout — это событие глобального менеджера ввода (this.input). Оно срабатывает, когда указатель покидает область всего игрового холста Phaser, а не отдельного спрайта. Это полезно для сброса состояния игры при потере фокуса.

this.input.on('gameout', () => {
        sprite.setTint(0x00ff00);
    });

Обратите внимание, что здесь используется стрелочная функция. Внутри неё sprite доступна из замыкания внешней функции create. В этом обработчике спрайту назначается зелёный оттенок (0x00ff00). Важный нюанс: если курсор ушёл с холста, находясь над спрайтом, то событие pointerout на спрайте НЕ срабатывает. Однако при возврате курсора на холст (но уже не на спрайт) сразу сработает pointerout.

Потенциальный конфликт логики

В текущей реализации есть особенность, которую нужно учитывать. Представьте последовательность: 1. Курсор наведён на спрайт — он красный. 2. Курсор быстро перемещён за пределы игрового окна — срабатывает gameout, спрайт становится зелёным. 3. Курсор возвращается обратно на холст, но мимо спрайта.

В момент (3) сработает событие pointerout на спрайте (так как он был последним объектом под указателем до ухода), и его зелёный оттенок будет сброшен. Это может быть неочевидным поведением. Для согласованной логики часто требуется дополнительная проверка состояний.

// Пример возможного улучшения
let cursorOverSprite = false;

sprite.on('pointerover', function () {
    cursorOverSprite = true;
    this.setTint(0xff0000);
});

sprite.on('pointerout', function () {
    cursorOverSprite = false;
    this.clearTint();
});

this.input.on('gameout', () => {
    sprite.setTint(0x00ff00);
});

this.input.on('gameover', () => {
    // При возврате на холст, если курсор не над спрайтом, убрать зелёный tint
    if (!cursorOverSprite) {
        sprite.clearTint();
    }
});

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

События pointerover/pointerout и gameout решают разные задачи: локальное взаимодействие с объектом и глобальный уход фокуса с игры. Их совместное использование требует внимания к сценариям, когда курсор покидает холст, находясь над интерактивным элементом. Для экспериментов попробуйте

  1. Добавить звук при наведении
  2. Использовать gameout для паузы игрового процесса
  3. Сделать спрайт перетаскиваемым и изменить логику tint при drag-событиях