О чем этот пример
При создании анимаций в Phaser вы можете столкнуться с ситуацией, когда заданная вами длительность каждого кадра анимации игнорируется, и анимация проигрывается с равномерной скоростью. Это происходит из-за особенностей работы метода `generateFrameNames` и порядка создания анимации. В этой статье мы разберем, почему это происходит и как правильно задавать индивидуальную длительность для каждого кадра, чтобы получить полный контроль над временной шкалой ваших анимаций, что особенно важно для эффектов вроде взрывов или затуханий.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
// this.load.path = 'assets/animations/aseprite/';
// this.load.aseprite('paladin', 'paladin.png', 'paladin.json');
this.load.spritesheet('boom', 'assets/sprites/explosion.png', { frameWidth: 64, frameHeight: 64, endFrame: 23 });
}
create ()
{
const durationArray = [ 10, 20, 30, 40 ]
const frames = this.anims.generateFrameNames("boom", { start: 0, end: 23 })
for (let i = 0; i < frames.length; i++)
{
frames[ i ].duration = 10 + (i * 10);
}
this.game.anims.create({ key: "test", frames: frames })
const sprite = this.add.sprite(400, 300, "boom").play({ key: "test" }) // will run at 24 FPS, ignore durationArray
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Проблема: Длительность кадров игнорируется
В примере разработчик хочет создать анимацию взрыва, где каждый следующий кадр отображается дольше предыдущего, создавая эффект замедления. Он загружает спрайтшит и использует this.anims.generateFrameNames для создания массива кадров.
Затем в цикле он пытается задать каждому кадру свою длительность (свойство .duration).
for (let i = 0; i < frames.length; i++)
{
frames[ i ].duration = 10 + (i * 10);
}
Однако после создания анимации с помощью this.game.anims.create и ее воспроизведения через .play(), анимация проигрывается с постоянной скоростью (например, 24 кадра в секунду), полностью игнорируя заданные значения duration. Это и есть баг (issue #7070), с которым можно столкнуться.
Причина: Конфликт генерации и модификации
Корень проблемы в том, что метод generateFrameNames предназначен для генерации кадров по определенным правилам из данных атласа или листа. Внутренняя логика Phaser может переопределять или сбрасывать пользовательские свойства duration, заданные после генерации, в момент создания самой анимации (create).
Ключевой момент: модифицировать объекты кадров, полученные из generateFrameNames, **после** их генерации, но **до** передачи в anims.create, — ненадежно. Нужен другой подход, который явно задает длительность на этапе конфигурации анимации.
Решение: Явное создание массива кадров
Вместо использования generateFrameNames и последующей модификации, создадим массив кадров вручную, используя объекты Phaser.Types.Animations.AnimationFrame. Это дает полный контроль над каждым параметром.
Сначала получим общее количество кадров из конфигурации загрузки спрайтшита. В нашем примере endFrame равен 23, значит, кадры от 0 до 23 включительно.
create()
{
// Создаем пустой массив для кадров
const framesArray = [];
// Количество кадров = endFrame + 1
const totalFrames = 24;
// Заполняем массив объектами кадров с заданной длительностью
for (let i = 0; i < totalFrames; i++)
{
framesArray.push({
key: 'boom', // Ключ текстуры
frame: i, // Номер кадра в спрайтшите
duration: 10 + (i * 10) // Индивидуальная длительность кадра
});
}
// Создаем анимацию с нашим массивом
this.anims.create({
key: 'customExplosion',
frames: framesArray,
repeat: 0 // Без повтора
});
// Создаем спрайт и проигрываем анимацию
const sprite = this.add.sprite(400, 300, 'boom').play('customExplosion');
}
Этот метод гарантирует, что длительность каждого кадра будет применена корректно, так как мы с самого начала передаем в create готовые объекты с нужными свойствами.
Альтернатива: Использование generateFrameNumbers с duration
Phaser также предоставляет метод generateFrameNumbers, который может быть более удобным для простых спрайтшитов. Его можно использовать, если передать параметр duration в его конфигурацию. Однако для сложных случаев с разной длительностью кадров ручное создание массива (как показано выше) является предпочтительным и самым надежным способом.
// Этот метод подходит, если все кадры имеют ОДИНАКОВУЮ длительность
const frames = this.anims.generateFrameNumbers('boom', {
start: 0,
end: 23,
duration: 50 // Длительность будет применена ко ВСЕМ кадрам
});
Для нашей задачи с нарастающей длителькой этот метод не подходит, так как он задает одну длительность для всех кадров.
Что попробовать дальше
Чтобы задать индивидуальную длительность для кадров анимации в Phaser, избегайте модификации массива, полученного из generateFrameNames. Вместо этого создавайте массив объектов-кадров вручную, явно указывая свойства key, frame и duration для каждого элемента. Это надежный способ обойти известную проблему и получить полный контроль над временными параметрами анимации.
**Идеи для экспериментов:**
1. Создайте анимацию, где длительность кадра зависит от его индекса по более сложной формуле (например, синусоидальной).
2. Реализуйте эффект "бумеранга" для анимации, где длительность кадров увеличивается до середины, а затем уменьшается.
3. Используйте разные спрайтшиты для одного и того же объекта, переключая key в объектах кадров.
