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

Разработка игр — это не только графика и физика, но и управление состоянием игровых объектов. В Phaser каждый объект может хранить произвольные пользовательские данные, что позволяет легко отслеживать здоровье, очки, инвентарь или статусы. Эта система, интегрированная прямо в ядро фреймворка, избавляет от необходимости создавать сложные внешние менеджеры данных для простых случаев, делая код чище и реактивнее. В этой статье мы разберем, как привязать пользовательские данные к спрайту, реагировать на их изменение с помощью событий и напрямую манипулировать этими значениями. Это фундаментальный паттерн, который используется для создания интерактивных UI, систем прокачки и логики игровых предметов.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('gem', 'assets/sprites/gem.png');
    }

    create ()
    {
        const text = this.add.text(350, 270, '', { font: '16px Courier', fill: '#00ff00' });
        const gem = this.add.image(300, 300, 'gem');

        //  Store some data about this Gem:
        gem.setData('name', 'Red Gem Stone');
        gem.setData('level', 2);
        gem.setData('owner', 'Link');

        //  Whenever a data value is updated we call this function:
        gem.on('setdata', function (gameObject, key, value) {
            text.setText([
                'Name: ' + gem.getData('name'),
                'Level: ' + gem.getData('level'),
                'Value: ' + gem.getData('gold') + ' gold',
                'Owner: ' + gem.getData('owner')
            ]);
        });

        //  Set the value, this will emit the `setdata` event.
        gem.setData('gold', 50);

        //  Change the 'value' property when the mouse is clicked
        this.input.on('pointerdown', function () {
            gem.data.values.gold += 50;
            if (gem.data.values.gold % 200 === 0)
            {
                gem.data.values.level++;
            }
        });
    }
}

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

const game = new Phaser.Game(config);

Инициализация данных: метод `setData`

Любой игровой объект в Phaser, наследующий от Phaser.GameObjects.GameObject, имеет встроенный компонент Data Manager, доступный через свойство .data. Для простой записи данных используется метод setData(key, value).

В нашем примере мы создаем спрайт драгоценного камня и сразу присваиваем ему три свойства: название, уровень и владельца. Это похоже на добавление пользовательских полей к объекту, но с важным бонусом — Phaser будет отслеживать эти изменения.

gem.setData('name', 'Red Gem Stone');
gem.setData('level', 2);
gem.setData('owner', 'Link');

После вызова setData данные сохраняются внутри объекта gem.data.values. Этот метод является предпочтительным способом инициализации, так как генерирует события обновления.

Реактивность через события: обработчик `setdata`

Настоящая мощь системы данных проявляется в её реактивности. Вы можете подписаться на событие setdata, которое срабатывает каждый раз, когда значение устанавливается или изменяется через метод setData.

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

gem.on('setdata', function (gameObject, key, value) {
    text.setText([
        'Name: ' + gem.getData('name'),
        'Level: ' + gem.getData('level'),
        'Value: ' + gem.getData('gold') + ' gold',
        'Owner: ' + gem.getData('owner')
    ]);
});

Обратите внимание: событие сработает и при первоначальной установке данных. Следующая строка gem.setData('gold', 50); вызовет этот обработчик, и текст на экране впервые отобразит все четыре свойства.

Прямое изменение и чтение данных

Phaser также предоставляет прямой доступ к хранилищу значений. Вы можете читать и изменять данные, обращаясь к объекту gameObject.data.values. Это быстрый способ, но с важной оговоркой: прямое присваивание (например, gem.data.values.gold = 100) НЕ вызовет событие setdata.

В нашем примере обработчик клика мыши использует прямое изменение для увеличения значения gold. Поскольку событие не генерируется, интерфейс не обновился бы. Однако в коде есть хитрость: после увеличения gold проверяется условие, и если оно кратно 200, напрямую увеличивается level. Но сам UI обновляется только при следующем срабатывании события setdata, которого в этом блоке нет. Это демонстрирует ключевое различие между методами.

this.input.on('pointerdown', function () {
    // Прямое изменение. Событие 'setdata' НЕ сгенерируется.
    gem.data.values.gold += 50;
    if (gem.data.values.gold % 200 === 0)
    {
        gem.data.values.level++; // Тоже прямое изменение
    }
});

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

Практическое применение и паттерны

Как использовать это в реальной игре? Рассмотрим несколько паттернов: 1. **Характеристики персонажа:** Храните здоровье (hp), ману (mp) и силу (strength) в данных спрайта игрока. Событие setdata может автоматически обновлять полоски здоровья в UI. 2. **Инвентарь:** Предмет, подобранный игроком, может иметь данные type, power, durability. Их можно легко проверять и изменять. 3. **Состояния и баффы:** Добавьте объекту данные isPoisoned или speedMultiplier. Логика игры может проверять эти флаги.

Важный паттерн — разделение ответственности. Данные объекта описывают его *состояние*, а события (setdata) уведомляют систему *представления* (например, UI) об изменениях этого состояния. Это основа реактивного дизайна.

// Пример: лечение персонажа
player.setData('hp', player.getData('hp') + 25); // UI обновится через событие

// Пример: проверка на подобранный ключ
if (door.getData('requiredKey') === player.getData('hasKey')) {
    door.setData('isLocked', false);
}

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

Встроенный Data Manager в Phaser — это элегантный и мощный инструмент для управления состоянием объектов. Используйте setData/getData для гарантированной генерации событий и реактивного обновления игры. Применяйте прямое обращение к .data.values для внутренних вычислений, где реактивность не требуется. Для экспериментов попробуйте: создать инвентарь, где каждый предмет — это спрайт с данными; реализовать систему квестов, где цель хранится в данных NPC; или сделать «умный» UI, который подписывается на события данных сразу нескольких объектов.