О чем этот пример
Анимация персонажа — это сердце любой 2D игры. В этом руководстве мы разберем, как превратить статичный спрайтшит в оживленного героя с множеством анимаций. Вы научитесь загружать таблицу спрайтов, нарезать её на кадры, создавать и управлять анимациями (ходьба, прыжок, атака), а также переключать их по клику. Этот пример — фундамент для создания любого анимированного персонажа в вашем проекте.
Версия 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('brawler', 'assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
this.load.image('grid', 'assets/textures/grid-ps2.png');
}
create ()
{
// Text section
this.add.tileSprite(400, 300, 800, 600, 'grid');
this.add.image(0, 0, 'brawler', '__BASE').setOrigin(0, 0);
this.add.grid(0, 0, 192, 384, 48, 48).setOrigin(0, 0).setStrokeStyle(2, 0x00ff00);
this.add.text(200, 24, '<- walk', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 72, '<- idle', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 120, '<- kick', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 168, '<- punch', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 216, '<- jump', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 264, '<- jump kick', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 312, '<- win', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(200, 360, '<- die', { color: '#00ff00' }).setOrigin(0, 0.5);
this.add.text(48, 440, 'Click to change animation', { color: '#00ff00' });
const current = this.add.text(48, 460, 'Playing: walk', { color: '#00ff00' });
// Animation set
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
this.anims.create({
key: 'idle',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 5, 6, 7, 8 ] }),
frameRate: 8,
repeat: -1
});
this.anims.create({
key: 'kick',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 10, 11, 12, 13, 10 ] }),
frameRate: 8,
repeat: -1,
repeatDelay: 2000
});
this.anims.create({
key: 'punch',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 15, 16, 17, 18, 17, 15 ] }),
frameRate: 8,
repeat: -1,
repeatDelay: 2000
});
this.anims.create({
key: 'jump',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 20, 21, 22, 23 ] }),
frameRate: 8,
repeat: -1
});
this.anims.create({
key: 'jumpkick',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 20, 21, 22, 23, 25, 23, 22, 21 ] }),
frameRate: 8,
repeat: -1
});
this.anims.create({
key: 'win',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 30, 31 ] }),
frameRate: 8,
repeat: -1,
repeatDelay: 2000
});
this.anims.create({
key: 'die',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 35, 36, 37 ] }),
frameRate: 8,
});
const keys = [ 'walk', 'idle', 'kick', 'punch', 'jump', 'jumpkick', 'win', 'die' ];
const cody = this.add.sprite(600, 370);
cody.setScale(8);
cody.play('walk');
let c = 0;
this.input.on('pointerdown', function () {
c++;
if (c === keys.length)
{
c = 0;
}
cody.play(keys[c]);
current.setText('Playing: ' + keys[c]);
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
pixelArt: true,
scene: Example
};
const game = new Phaser.Game(config);
Загрузка ресурсов: спрайтшит и фон
Всё начинается с метода preload. Здесь мы загружаем два ключевых ресурса. Первый — это сам спрайтшит, изображение, содержащее все кадры анимаций персонажа в одной сетке. Второй — фоновая текстура для визуального оформления сцены.
Загрузка спрайтшита отличается от загрузки обычного изображения. Метод load.spritesheet принимает три параметра: ключ для дальнейшего обращения, путь к файлу и объект конфигурации с размерами одного кадра. Phaser использует эти размеры, чтобы "нарезать" большое изображение на отдельные кадры.
Загрузка фона происходит стандартным методом load.image.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('brawler', 'assets/animations/brawler48x48.png', { frameWidth: 48, frameHeight: 48 });
this.load.image('grid', 'assets/textures/grid-ps2.png');
Подготовка сцены: визуализация и подписи
В методе create мы сначала создаем визуальный контекст. Фон добавляется как tileSprite, чтобы он заполнил всю сцену. Затем для наглядности отображается первый кадр (__BASE) всего спрайтшита и поверх него рисуется зеленая сетка с помощью add.grid. Размер сетки (192x384) рассчитан так, чтобы покрыть 4 колонки и 8 строк кадров (по 48 пикселей каждый). Это помогает визуально понять структуру спрайтшита.
Рядом со строками сетки добавляются текстовые подписи, которые объясняют, какая анимация находится в каждой строке.
this.add.tileSprite(400, 300, 800, 600, 'grid');
this.add.image(0, 0, 'brawler', '__BASE').setOrigin(0, 0);
this.add.grid(0, 0, 192, 384, 48, 48).setOrigin(0, 0).setStrokeStyle(2, 0x00ff00);
// ... добавление текстовых подписей ...
const current = this.add.text(48, 460, 'Playing: walk', { color: '#00ff00' });
Создание анимаций: `anims.create`
Сердце примера — создание анимаций через менеджер анимаций сцены this.anims. Каждая анимация создается методом create, который принимает объект конфигурации.
- key: Уникальное строковое имя анимации для её последующего воспроизведения.
- frames: Массив кадров. Для его генерации из ранее загруженного спрайтшита используется хелпер this.anims.generateFrameNumbers. Он принимает ключ спрайтшита и объект с массивом frames, где указаны индексы нужных кадров. Индексы соответствуют порядку кадров в спрайтшите (слева направо, сверху вниз, начиная с 0).
- frameRate: Скорость воспроизведения в кадрах в секунду.
- repeat: Количество повторений. Значение -1 означает бесконечный цикл.
- repeatDelay: Необязательная задержка (в миллисекундах) перед повторением цикла, использована для анимаций kick, punch и win для создания паузы.
Анимация die не имеет repeat, поэтому проиграется только один раз.
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('brawler', { frames: [ 0, 1, 2, 3 ] }),
frameRate: 8,
repeat: -1
});
Спрайт персонажа и воспроизведение
После создания анимаций мы создаём сам объект спрайта, который будет их отображать. Спрайт размещается в правой части экрана и масштабируется в 8 раз для лучшей видимости с помощью setScale(8).
Чтобы запустить анимацию по умолчанию, у спрайта вызывается метод play с ключом нужной анимации.
const cody = this.add.sprite(600, 370);
cody.setScale(8);
cody.play('walk');
Интерактивность: переключение анимаций по клику
Для демонстрации всех созданных анимаций добавим обработчик клика мыши (pointerdown). Мы заранее подготовили массив keys с именами всех анимаций.
При каждом клике индекс `cувеличивается, и когда он достигает конца массива, сбрасывается в ноль. Текущая анимация останавливается, и спрайт начинает проигрывать новую с помощьюcody.play(keys[c]). Текстовый объектcurrent` обновляется, чтобы отображать название активной анимации.
const keys = [ 'walk', 'idle', 'kick', 'punch', 'jump', 'jumpkick', 'win', 'die' ];
let c = 0;
this.input.on('pointerdown', function () {
c++;
if (c === keys.length) {
c = 0;
}
cody.play(keys[c]);
current.setText('Playing: ' + keys[c]);
});
Что попробовать дальше
Вы освоили базовый, но мощный пайплайн создания анимаций в Phaser 3: от загрузки спрайтшита до интерактивного управления. Для экспериментов попробуйте изменить frameRate для ускорения или замедления анимаций, добавьте новые последовательности кадров из этого же спрайтшита или настройте плавное перетекание между анимациями с помощью системы состояний (State Machine).
