О чем этот пример
Когда в игре десятки одинаковых персонажей или врагов, запускающих одну и ту же анимацию синхронно, это выглядит неестественно и выдает шаблонность. Phaser предоставляет простой, но мощный инструмент `randomFrame` в методе `play()`, который позволяет каждому спрайту начать анимацию со случайного кадра. Это мгновенно добавляет разнообразие и живую, органичную динамику на сцену, экономя ресурсы на создание отдельных анимаций или сложной логики. В этой статье мы разберем конкретный пример, где три одинаковых мумии начинают "идти" с разных точек анимационного цикла, и посмотрим, как это сочетается с другими параметрами, такими как задержка (`delay`) и твины.
Версия 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('bg', 'assets/skies/sky5.png')
this.load.spritesheet('mummy', 'assets/animations/mummy37x45.png', { frameWidth: 37, frameHeight: 45 });
}
create ()
{
this.add.image(400, 300, 'bg');
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('mummy'),
frameRate: 12,
repeat: -1
});
const sprite1 = this.add.sprite(50, 100, 'mummy').setScale(4);
const sprite2 = this.add.sprite(50, 300, 'mummy').setScale(4);
const sprite3 = this.add.sprite(50, 500, 'mummy').setScale(4);
// By setting randomFrame to `true` it will pick a random frame to *START* the animation from
sprite1.play({ key: 'walk', randomFrame: true, delay: 2000, showBeforeDelay: true });
sprite2.play({ key: 'walk', randomFrame: true, delay: 2000, showBeforeDelay: true });
sprite3.play({ key: 'walk', randomFrame: true, delay: 2000, showBeforeDelay: true });
this.tweens.add({
targets: [ sprite1, sprite2, sprite3 ],
x: 750,
flipX: true,
yoyo: true,
repeat: -1,
duration: 8000,
delay: 2000
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка анимации: основа без изменений
Перед тем как использовать случайный старт, нужно создать саму анимацию. Это стандартный процесс в Phaser. В методе preload() загружается спрайтшит, а в create() определяется анимация с ключом 'walk'. Обратите внимание, что generateFrameNumbers('mummy') автоматически создаст массив кадров из всего спрайтшита, что удобно для пошаговых анимаций.
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('mummy'),
frameRate: 12,
repeat: -1
});
Анимация 'walk' будет проигрываться с частотой 12 кадров в секунду и зацикливаться бесконечно (repeat: -1). Параметр randomFrame здесь еще не используется — он применяется позже, при запуске на конкретном спрайте.
Запуск анимации с параметром randomFrame
Вот где происходит магия. После создания трех спрайтов мы вызываем для каждого метод play(). Вместо простой строки с ключом анимации, мы передаем объект конфигурации. Ключевой параметр randomFrame: true указывает, что анимация должна стартовать не с первого, а со случайного кадра из своей последовательности.
sprite1.play({ key: 'walk', randomFrame: true, delay: 2000, showBeforeDelay: true });
Это делает мумий визуально несинхронизированными с самого начала, как если бы они шли в разном ритме. Параметр delay: 2000 добавляет задержку в 2 секунды перед началом циклического воспроизведения, а showBeforeDelay: true гарантирует, что выбранный случайный кадр будет отображаться на экране сразу, а не после задержки. Без этого флага спрайт был бы невидим первые 2 секунды.
Сочетание с движением: твины для завершения картины
Чтобы продемонстрировать эффект от randomFrame в действии, пример добавляет движение спрайтов с помощью системы твинов Phaser. Твин анимирует свойства `xиflipX` для всех трех мумий одновременно.
this.tweens.add({
targets: [ sprite1, sprite2, sprite3 ],
x: 750,
flipX: true,
yoyo: true,
repeat: -1,
duration: 8000,
delay: 2000
});
Движение начинается с той же задержки в 2 секунды (delay: 2000), что и старт цикла анимации. Параметры yoyo: true и repeat: -1 заставляют мумий ходить туда-обратно бесконечно, а flipX: true автоматически разворачивает спрайт по горизонтали при изменении направления. Несмотря на одинаковое движение, разный стартовый кадр анимации создает иллюзию независимого поведения.
Почему это работает: техническая сторона
Параметр randomFrame влияет только на начальный кадр анимации. Сама последовательность кадров, ее скорость и порядок воспроизведения не меняются. После старта анимация идет как обычно, с первого кадра последовательности? Нет. Phaser запоминает выбранный случайный кадр как новую точку отсчета для этого конкретного экземпляра анимации.
Это важно понимать: если анимация имеет 10 кадров, и она стартовала с кадра 7, то далее она проиграет кадры 8, 9, 10, 1, 2... и так по циклу. Это не "перемешивание" кадров, а просто сдвиг начальной точки. Такой подход идеально подходит для циклических анимаций (ходьба, полет, мигание), где нужно избежать монотонности, не нарушая логику цикла.
Что попробовать дальше
Использование randomFrame: true — это минималистичный и эффективный способ добавить разнообразия в сцену с множеством однотипных анимированных объектов. Он не требует дополнительных ресурсов или сложного кода. Для экспериментов попробуйте применить этот параметр к анимациям частиц, фоновых элементов или интерфейсных индикаторов. Например, можно сделать так, чтобы костры в таверне или мигающие огни на панели управления начинали работать с разной фазы, создавая более живое и убедительное окружение для игрока.
