О чем этот пример
Одновременная анимация множества одинаковых объектов часто выглядит неестественно и механистически. В реальных играх взрывы, появления врагов или магические эффекты должны запускаться с небольшими временными смещениями, создавая ощущение хаотичности и живости. В этой статье мы разберем технику добавления случайных задержек для анимаций в Phaser. Вы научитесь управлять временем старта анимаций для каждого спрайта индивидуально, что позволит создавать более органичные и визуально интересные групповые эффекты, от эпичных взрывов до фоновой анимации частиц.
Версия 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.spritesheet('boom', 'assets/sprites/explosion.png', { frameWidth: 64, frameHeight: 64, endFrame: 23 });
}
create ()
{
const config = {
key: 'explode',
frames: 'boom',
frameRate: 30,
repeat: -1,
repeatDelay: 2000
};
this.anims.create(config);
for (let i = 0; i < 256; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
let boom = this.add.sprite(x, y, 'boom', 23);
// Each one can have a random start delay
boom.play({
key: 'explode',
delay: Math.random() * 3000
});
}
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка спрайтшита и создание анимации
Перед созданием анимаций необходимо загрузить спрайтшит — изображение, содержащее все кадры одной анимации. В методе preload() мы указываем путь к файлу и его параметры.
this.load.spritesheet('boom', 'assets/sprites/explosion.png', { frameWidth: 64, frameHeight: 64, endFrame: 23 });
Здесь 'boom' — это ключ ассета, frameWidth и frameHeight задают размер одного кадра, а endFrame определяет последний кадр в последовательности (всего 24 кадра, с 0 по 23).
Далее в create() мы создаем саму анимацию, используя конфигурационный объект. Обратите внимание на параметр repeatDelay.
const config = {
key: 'explode',
frames: 'boom',
frameRate: 30,
repeat: -1,
repeatDelay: 2000
};
this.anims.create(config);
key — уникальное имя анимации. frames указывает на ключ загруженного спрайтшита. frameRate задает скорость воспроизведения (30 кадров в секунду). repeat: -1 означает бесконечное повторение. repeatDelay: 2000 — это пауза в 2000 миллисекунд (2 секунды) между циклами анимации. После проигрывания всех кадров анимация замрет на 2 секунды, прежде чем начаться заново.
Массовое создание спрайтов
Чтобы продемонстрировать технику, мы создаем 256 спрайтов, случайно разбросанных по сцене. Для генерации случайных координат используется встроенный генератор Phaser.Math.Between.
for (let i = 0; i < 256; i++)
{
let x = Phaser.Math.Between(0, 800);
let y = Phaser.Math.Between(0, 600);
let boom = this.add.sprite(x, y, 'boom', 23);
}
Каждый спрайт создается методом this.add.sprite. Последний аргумент 23 — это индекс стартового кадра. Мы используем последний кадр спрайтшита (как бы "спокойное" состояние после взрыва), чтобы до начала анимации все спрайты отображались одинаково.
Ключевой прием: случайная задержка старта
Секрет оживления сцены кроется в методе play(). Вместо простого вызова boom.play('explode') мы передаем ему объект с настройками, где и задаем задержку.
boom.play({
key: 'explode',
delay: Math.random() * 3000
});
Параметр delay принимает значение в миллисекундах. Math.random() * 3000 генерирует случайное число от 0 до 3000 (3 секунд). Это означает, что каждый из 256 спрайтов начнет свою анимацию "взрыва" в случайный момент в течение первых 3 секунд после запуска сцены. Из-за repeatDelay в 2 секунды и случайного старта анимации никогда не синхронизируются, создавая непрерывный "рваный" эффект.
Как работает полный цикл
Давайте проследим жизненный цикл одного спрайта:
1. **Создание:** Он появляется на сцене с кадром 23 ("тишина после взрыва").
2. **Ожидание:** Спрайт ждет случайное время (от 0 до 3 секунд), заданное параметром delay.
3. **Воспроизведение:** Запускается анимация explode. Она проигрывает 24 кадра со скоростью 30 FPS (длительность ~0.8 секунды).
4. **Пауза:** Срабатывает repeatDelay — спрайт снова замирает на кадре 23 на 2 секунды.
5. **Повтор:** Цикл (шаги 3-4) повторяется бесконечно (repeat: -1).
Поскольку задержка старта (delay) применяется только при первом запуске, а repeatDelay — между каждым повтором, анимации всех объектов постепенно рассинхронизируются, создавая иллюзию независимости.
Вариации и практическое применение
Эту технику можно адаптировать для разных игровых ситуаций.
**Разные диапазоны задержки:** Для врагов, появляющихся волнами, можно задать фиксированную задержку для первой группы и увеличивать ее для последующих.
delay: 1000 + (i * 200) // Первый через 1 сек, каждый следующий на 0.2 сек позже
**Комбинация с другими параметрами play():** Метод play() может принимать и другие настройки, например, startFrame или timeScale.
boom.play({
key: 'explode',
delay: Math.random() * 2000,
timeScale: Phaser.Math.Between(0.5, 1.5) // Случайная скорость анимации
});
**Типичные сценарии использования:** * **Взрывы и фейерверки:** Как в примере. * **Фоновая анимация среды:** Мерцание звезд, покачивание растений, капли дождя. * **Появление врагов или ресурсов:** Чтобы они возникали не строго по строчке, а "набегающей волной". * **Эффекты UI:** Постепенное появление элементов интерфейса.
Что попробовать дальше
Использование случайной или прогрессирующей задержки (delay) в методе play() — это простой, но мощный способ добавить реалистичности и визуального разнообразия в вашу игру. Это разрывает механическую синхронность, делая групповые анимации живыми и непредсказуемыми.
Для экспериментов попробуйте:
1. Заменить Math.random() на шум Перлина для более плавного, "природного" распределения задержек.
2. Связать значение delay с расстоянием спрайта от определенной точки на сцене, создавая "волну" анимации.
3. Управлять задержками динамически, например, останавливать и снова запускать все анимации с новыми случайными задержками по нажатию клавиши.
