О чем этот пример
Создание сложных игровых персонажей, состоящих из множества отдельных спрайтов — частая задача в 2D-разработке. Phaser 3 предлагает элегантное решение: класс `Container`. Он позволяет группировать игровые объекты, управляя ими как единым целым, сохраняя при этом возможность тонкой настройки каждого элемента. В этой статье мы разберем практический пример сборки анимированного персонажа девушки из частей атласа, что особенно полезно для создания персонажей с кастомизацией или пошаговой анимацией.
Версия 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.atlas('girl', 'assets/atlas/girl.png', 'assets/atlas/girl.json');
}
create ()
{
const girl = this.add.container(400, 450);
const body = this.add.image(-30, 18, 'girl', 'Body').setName('body');
const hair = this.add.image(110, -310, 'girl', 'HairTop').setName('hair');
const head = this.add.image(0, -200, 'girl', 'Head').setName('head');
const eyes = this.add.image(-34, -182, 'girl', 'EyesOpen').setName('eyes');
const mouth = this.add.image(-58, -114, 'girl', 'MouthOpen').setName('mouth');
const braidLeft = this.add.image(14, -74, 'girl', 'BraidLeft').setName('braidLeft');
const braidRight = this.add.image(98, -72, 'girl', 'BraidRight').setName('braidRight');
const armLeft = this.add.image(-48, -62, 'girl', 'ArmLeft').setName('armLeft').setOrigin(1, 0.5);
const armRight = this.add.image(26, 28, 'girl', 'ArmRight').setName('armRight');
const hand = this.add.image(18, 96, 'girl', 'HandRight').setName('hand');
// armLeft angle = -10 to 10
girl.add([
armLeft,
body,
braidLeft,
braidRight,
hair,
head,
eyes,
mouth,
armRight,
hand
]);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#010101',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Загрузка атласа: основа для сборки
Перед сборкой необходимо загрузить ресурсы. В данном примере используется текстура с атласом — единое изображение, содержащее все части персонажа, и JSON-файл, описывающий координаты каждого фрейма.
Метод this.load.setBaseURL() задает базовый URL для загрузки, что удобно при хранении ресурсов в одном месте. Затем this.load.atlas() загружает сам атлас по его ключу 'girl'.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('girl', 'assets/atlas/girl.png', 'assets/atlas/girl.json');
Создание контейнера и его частей
Контейнер (Container) — это основной объект, который будет объединять все части. Он создается в точке (400, 450) на сцене. Это будет точка привязки (origin) для всей сборки.
Каждая часть тела — это отдельный спрайт (Image), созданный из фрейма атласа 'girl'. Ключевой момент: координаты каждого спрайта задаются относительно контейнера! Например, голова (head) помещается в точку (0, -200), то есть на 200 пикселей выше центра контейнера.
Метод .setName() задает уникальное имя каждому спрайту, что очень полезно для дальнейшего доступа к ним внутри контейнера.
const girl = this.add.container(400, 450);
const body = this.add.image(-30, 18, 'girl', 'Body').setName('body');
const head = this.add.image(0, -200, 'girl', 'Head').setName('head');
const eyes = this.add.image(-34, -182, 'girl', 'EyesOpen').setName('eyes');
// ... и так далее для hair, mouth, braidLeft, braidRight, armLeft, armRight, hand
Обратите внимание на руку armLeft: для нее задается нестандартная точка привязки .setOrigin(1, 0.5). Это смещает точку вращения спрайта к его правому краю по центру, что типично для вращения конечностей вокруг сустава.
Компоновка иерархии: добавление в контейнер
После создания всех спрайтов их необходимо добавить в контейнер, чтобы система отрисовки и трансформаций Phaser воспринимала их как группу. Это делается методом container.add(), который принимает массив объектов.
Порядок добавления имеет критическое значение — он определяет порядок отрисовки (z-index). Объекты, добавленные первыми, рисуются ниже тех, что добавлены позже. В нашем примере armLeft добавлена первой и будет на заднем плане, а hand — последней и будет поверх всех.
girl.add([
armLeft,
body,
braidLeft,
braidRight,
hair,
head,
eyes,
mouth,
armRight,
hand
]);
Теперь, применяя трансформации (позицию, масштаб, вращение) к объекту girl, мы будем двигать всю сборку целиком, сохраняя взаимное расположение частей.
Преимущества подхода и дальнейшее управление
Использование Container дает несколько преимуществ:
1. **Единое управление:** Вы можете двигать, вращать и масштабировать всего персонажа одной операцией.
2. **Сохранение иерархии:** Каждая часть сохраняет свою локальную позицию относительно контейнера.
3. **Простой доступ:** К любому дочернему элементу можно обратиться по индексу или имени, используя методы контейнера, например, container.getByName('eyes').
Для анимации можно изменять свойства отдельных частей, обращаясь к ним через контейнер. Например, чтобы качнуть левой рукой, как намекает комментарий в коде:
// Получаем ссылку на левую руку
const leftArm = girl.getByName('armLeft');
// Устанавливаем угол вращения
leftArm.angle = 5;
Контейнер также автоматически обновляет свою визуальную и физическую границу (bounds), учитывая всех своих детей.
Что попробовать дальше
Контейнеры в Phaser 3 — мощный инструмент для структурирования сложных игровых объектов. Показанный пример со сборкой персонажа — лишь отправная точка. Вы можете экспериментировать: анимировать части тела, меняя их angle или x/y; динамически заменять части (например, смена прически) через методы container.add() и container.remove(); или привязывать к контейнеру физическое тело, чтобы цельный персонаж взаимодействовал с миром. Это открывает путь к созданию сложных, детализированных и гибких в управлении персонажей для вашей игры.
