О чем этот пример
Разработка игр — это не только графика и физика, но и управление состоянием игровых объектов. В 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, который подписывается на события данных сразу нескольких объектов.
