О чем этот пример
При разработке игр часто возникает необходимость создавать анимации из кадров, упакованных в общую текстуру — атлас. Phaser позволяет гибко работать с такими ресурсами. В этой статье мы разберем, как извлекать отдельные последовательности кадров из атласа и превращать их в независимые текстуры для спрайтов, что особенно полезно для повторного использования анимаций и управления памятью. Вы научитесь создавать динамические спрайт-листы, не загружая дополнительные файлы.
Версия 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/atlas/trimsheet/';
this.load.atlas('testanims', 'trimsheet.png', 'trimsheet.json');
}
create ()
{
// create a sprite sheet from a frame embedded in a texture atlas
// 'boom' is the unique local name we'll give the sprite sheet
// 'megaset' is the key of the texture atlas that contains the sprite sheet
// 'explosion' is the name of the frame within the texture atlas
// The rest of the values are the sprite sheet frame sizes and offsets
const t1 = this.textures.addSpriteSheetFromAtlas(
'boom1',
{
atlas: 'testanims',
frame: 'explosion-notrim',
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
const t2 = this.textures.addSpriteSheetFromAtlas(
'boom2',
{
atlas: 'testanims',
frame: 'explosion',
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
const b1 = this.textures.addSpriteSheetFromAtlas(
'bubble1',
{
atlas: 'testanims',
frame: 'bubble-notrim',
frameWidth: 34,
frameHeight: 68
});
const b2 = this.textures.addSpriteSheetFromAtlas(
'bubble2',
{
atlas: 'testanims',
frame: 'bubble',
frameWidth: 34,
frameHeight: 68
});
// There is a new texture available called 'boom1', which we can assign to game objects:
const config1 = {
key: 'explode1',
frames: this.anims.generateFrameNumbers('boom1', { start: 0, end: 23, first: 23 }),
frameRate: 20,
repeat: -1
};
const config2 = {
key: 'explode2',
frames: this.anims.generateFrameNumbers('boom2', { start: 0, end: 23, first: 23 }),
frameRate: 20,
repeat: -1
};
const config3 = {
key: 'bobble1',
frames: this.anims.generateFrameNumbers('bubble1', { start: 0, end: 6 }),
frameRate: 10,
repeat: -1
};
const config4 = {
key: 'bobble2',
frames: this.anims.generateFrameNumbers('bubble2', { start: 0, end: 6 }),
frameRate: 10,
repeat: -1
};
this.anims.create(config1);
this.anims.create(config2);
this.anims.create(config3);
this.anims.create(config4);
this.add.sprite(300, 200).play('explode1');
this.add.sprite(400, 200).play('explode2');
this.add.sprite(300, 400).play('bobble1');
this.add.sprite(400, 400).play('bobble2');
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка текстурного атласа
Перед работой с кадрами необходимо загрузить сам атлас. В методе preload мы используем this.load.atlas. Этот метод загружает PNG-изображение и JSON-файл с координатами и размерами каждого фрейма внутри атласа.
this.load.atlas('testanims', 'trimsheet.png', 'trimsheet.json');
Ключ 'testanims' — это уникальное имя, по которому мы будем обращаться к атласу в коде. Phaser автоматически парсит JSON и создает внутреннюю структуру фреймов.
Метод addSpriteSheetFromAtlas
Для создания нового спрайт-листа на основе одного фрейма из атласа используется метод this.textures.addSpriteSheetFromAtlas. Этот метод не загружает новые файлы, а создает виртуальную текстуру, «нарезая» указанный регион атласа на кадры заданного размера.
const t1 = this.textures.addSpriteSheetFromAtlas(
'boom1',
{
atlas: 'testanims',
frame: 'explosion-notrim',
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
Первый аргумент — 'boom1' — это уникальный ключ для новой текстуры. В объекте конфигурации:
- atlas: ключ ранее загруженного атласа.
- frame: имя конкретного фрейма внутри атласа, который содержит всю последовательность анимации.
- frameWidth и frameHeight: размер каждого отдельного кадра внутри этого фрейма.
- endFrame: опциональный параметр, указывающий последний кадр для извлечения. Если не указан, будет взята вся последовательность до конца фрейма атласа.
Таким образом, из одного большого изображения 'explosion-notrim' мы создаем спрайт-лист из 24 кадров (от 0 до 23) размером 64x64 пикселей каждый.
Создание анимаций из новых текстур
После создания текстуры 'boom1' мы можем использовать её для генерации анимации. Для этого применяется стандартный подход Phaser: создание конфигурации анимации с помощью this.anims.generateFrameNumbers и регистрация через this.anims.create.
const config1 = {
key: 'explode1',
frames: this.anims.generateFrameNumbers('boom1', { start: 0, end: 23, first: 23 }),
frameRate: 20,
repeat: -1
};
this.anims.create(config1);
this.anims.generateFrameNumbers принимает ключ текстуры ('boom1') и объект с параметрами start, end и first. Параметр first: 23 указывает, с какого кадра начинать воспроизведение анимации — в данном случае с последнего, чтобы анимация взрыва проигрывалась от конца к началу. repeat: -1 задает бесконечное повторение.
Разница между обрезанными и необрезанными фреймами
В исходном примере создаются две пары текстур: boom1/boom2 и bubble1/bubble2. Они используют разные исходные фреймы из одного атласа: 'explosion-notrim' и 'explosion', 'bubble-notrim' и 'bubble'.
const t1 = this.textures.addSpriteSheetFromAtlas('boom1', {
atlas: 'testanims',
frame: 'explosion-notrim', // Необрезанный фрейм
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
const t2 = this.textures.addSpriteSheetFromAtlas('boom2', {
atlas: 'testanims',
frame: 'explosion', // Обрезанный фрейм
frameWidth: 64,
frameHeight: 64,
endFrame: 23
});
Фреймы с суффиксом -notrim содержат исходные изображения с прозрачными областями (padding), которые были добавлены при упаковке в атлас. Фреймы без суффикса уже были обрезаны (trimmed) — из них удалены однородные прозрачные края. При создании спрайт-листа метод addSpriteSheetFromAtlas корректно работает с обоими типами, но визуально анимация может отличаться выравниванием. На практике использование обрезанных фреймов экономит видеопамять и может улучшить производительность.
Отображение спрайтов с анимацией
После создания анимаций их можно присвоить игровым объектам. В примере создаются спрайты без начальной текстуры, и сразу запускается анимация с помощью метода .play().
this.add.sprite(300, 200).play('explode1');
this.add.sprite(400, 200).play('explode2');
this.add.sprite(x, y) создает спрайт в указанных координатах. Поскольку мы не передали ключ текстуры, спрайт будет пустым до начала анимации. Вызов .play('explode1') запускает ранее созданную анимацию с ключом 'explode1'. Phaser автоматически подставляет соответствующие кадры из текстуры 'boom1'. Это удобно для динамического создания эффектов.
Что попробовать дальше
Метод addSpriteSheetFromAtlas — мощный инструмент для работы с упакованными ассетами. Он позволяет выделять отдельные анимации из больших атласов, создавая независимые текстуры без дублирования ресурсов. Для экспериментов попробуйте:
1. Изменять параметры frameWidth и frameHeight, чтобы «нарезать» фреймы по-разному.
2. Использовать разные значения start и end в generateFrameNumbers для создания укороченных или перетасованных анимаций.
3. Динамически создавать спрайт-листы на основе состояния игры, например, для procedural animation.
