О чем этот пример
Создание процедурного контента — ключевой навык в разработке игр. Часто нужно, чтобы сгенерированные элементы (цвета, текстуры, ландшафт) были разными, но при этом воспроизводимыми при каждом запуске игры. Обычный `Math.random()` даёт случайность, но не даёт стабильности. В этой статье мы разберём, как с помощью `Phaser.Math.Hash` создавать стабильные, детерминированные значения, например, для генерации цветовой палитры, которая будет одинаковой каждый раз.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
rects = [];
create()
{
this.add.text(640, 64, 'Click to rebuild colors').setOrigin(0.5);
this.populate();
this.input.on('pointerdown', () => {
for (const rect of this.rects)
{
rect.destroy();
}
this.populate();
});
}
populate()
{
const color = new Phaser.Display.Color();
for (let y = 128; y < 640; y += 32)
{
// Add random colors. They're always different.
color.setGLTo(Math.random(), Math.random(), Math.random());
const r1 = this.add.rectangle(320, y, 384, 32, color.color);
this.rects.push(r1);
// Add noise-derived colors. They're always the same.
color.setGLTo(Phaser.Math.Hash(y), Phaser.Math.Hash(y + 1), Phaser.Math.Hash(y + 2));
const r2 = this.add.rectangle(960, y, 384, 32, color.color);
this.rects.push(r2);
}
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 1280,
height: 720,
scene: Example
};
const game = new Phaser.Game(config);
Проблема обычного рандома
Метод Math.random() отлично подходит для создания уникальных, непредсказуемых значений. Однако у него есть существенный недостаток для процедурной генерации: его результат нельзя воспроизвести. Каждый запуск сцены будет генерировать совершенно новую последовательность чисел.
В примере это наглядно показано: левый столбец прямоугольников каждый раз перерисовывается новыми, случайными цветами.
color.setGLTo(Math.random(), Math.random(), Math.random());
Решение: Детерминированный хэш Phaser.Math.Hash
Phaser предоставляет простую, но мощную функцию Phaser.Math.Hash(). Она принимает любое числовое значение (seed) и возвращает псевдослучайное число в диапазоне от 0 до 1. Ключевое свойство: для одного и того же входного значения (seed) функция всегда вернёт один и тот же результат.
Это делает её идеальным инструментом для детерминированной, но визуально "случайной" генерации. В примере seed-ами являются координата `y` и её смещения.
color.setGLTo(Phaser.Math.Hash(y), Phaser.Math.Hash(y + 1), Phaser.Math.Hash(y + 2));
Как работает пример: Анатомия сцены
Сцена создаёт два столбца прямоугольников. Левый (x=320) использует Math.random(), правый (x=960) — Phaser.Math.Hash. По клику все прямоугольники уничтожаются и создаются заново, что позволяет увидеть разницу в поведении.
1. **Инициализация:** В create() вызывается метод populate() для первичного заполнения и устанавливается обработчик клика.
2. **Очистка:** При клике все объекты в массиве this.rects уничтожаются через rect.destroy().
3. **Генерация:** Метод populate() проходит по вертикали с шагом в 32 пикселя.
* Для левого столбца цвет задаётся тремя независимыми вызовами Math.random().
* Для правого столбца в качестве компонентов RGB используются результаты хэширования `y,y+1иy+2. Это гарантирует, что для строки с координатойy=160` цвет будет всегда одним и тем же.
this.input.on('pointerdown', () => {
for (const rect of this.rects) {
rect.destroy();
}
this.populate();
});
Практическое применение в играх
Стабильная генерация на основе хэша открывает множество возможностей:
* **Создание уникальных, но воспроизводимых миров:** Игровое семя (seed) может хэшироваться для генерации ландшафта, расстановки врагов или предметов. Все игроки, введя один seed, увидят одинаковый мир. * **Генерация цветовых палитр для фракций или биомов:** Цвета дома игрока, команды или типа местности могут быть вычислены на основе их ID. * "Случайный" выбор, который нужно запомнить:** Например, порядок карт в колоде для режима реплея.
// Цвет фракции на основе её ID
const factionColor = Phaser.Display.Color.HSLToColor(
Phaser.Math.Hash(factionId), // Стабильный Hue
0.8, // Насыщенность
0.6 // Светлота
);
Что попробовать дальше
Phaser.Math.Hash — это простой и эффективный способ добавить детерминированную процедурность в вашу игру. Он обеспечивает баланс между визуальным разнообразием и необходимой стабильностью. Для экспериментов попробуйте использовать хэш от комбинации координат (x * 1000 + y) для генерации тайлов карты или создать плавный цветовой градиент, интерполируя между результатами хэша для соседних значений.
