О чем этот пример
Работая над интерфейсом игры, часто возникает задача отобразить множество кнопок или панелей разного размера, которые должны сохранять четкие края при масштабировании. Использование обычных спрайтов приводит к размытию и артефактам. Техника Nine Slice решает эту проблему, позволяя растягивать центральную часть изображения, сохраняя неизменными углы и края. В этой статье разберем готовый пример из официальной коллекции, который создает 256 анимированных кнопок, демонстрируя производительность и гибкость Nine Slice в Phaser 3.
Версия 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.atlas('ui', 'assets/ui/nine-slice.png', 'assets/ui/nine-slice.json');
}
create ()
{
for (let i = 0; i < 256; i++)
{
const x = Phaser.Math.Between(0, 3000);
const y = Phaser.Math.Between(0, 3000);
const frame = Phaser.Utils.Array.GetRandom([ 'YellowButtonSml', 'PinkButtonSml', 'GreenButtonSml', 'RedButtonSml' ]);
const width = Phaser.Math.Between(256, 1024);
const height = Phaser.Math.Between(128, 384);
const slice = this.add.nineslice(x, y, 'ui', frame, 132, 98, 64, 64, 48, 48);
const duration = Phaser.Math.Between(1000, 4000);
this.tweens.add({
targets: slice,
width,
height,
duration,
ease: 'sine.inout',
yoyo: true,
repeat: -1
});
}
this.tweens.add({
targets: this.cameras.main,
props: {
scrollX: { value: 1000, duration: 4000 },
scrollY: { value: 2000, duration: 8000 },
zoom: { value: 0.20, duration: 3000, hold: 2000 },
},
ease: 'sine.inout',
yoyo: true,
repeat: -1
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#003663',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что такое Nine Slice и зачем он нужен
Nine Slice (9-Slice или 9-фрагментное масштабирование) — это техника отрисовки двумерной графики, при которой изображение делится на девять частей: четыре угла, четыре края и центр. Углы не масштабируются, края растягиваются только в одном направлении, а центр — в обоих.
Это идеально подходит для элементов UI: кнопок, окон, панелей. Вместо того чтобы хранить сотни спрайтов под каждый размер, вы используете одно изображение-атлас и настраиваемые параметры срезов. Phaser предоставляет для этого специальный игровой объект add.nineslice.
В примере используется атлас ui с несколькими вариантами маленьких кнопок разных цветов, что позволяет легко создавать разнообразные UI-элементы.
Разбор загрузки ресурсов и создания сцены
В методе preload загружается JSON-атлас. Атлас — это файл, содержащий одно большое изображение (nine-slice.png) и данные о координатах каждого фрейма внутри него (nine-slice.json). Это позволяет эффективно использовать память.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('ui', 'assets/ui/nine-slice.png', 'assets/ui/nine-slice.json');
Конструктор сцены пустой, так как вся логика инициализации находится в методах preload и create. Конфигурация игры стандартная, задает размеры окна, цвет фона и указывает класс сцены.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#003663',
parent: 'phaser-example',
scene: Example
};
Массовое создание и анимация кнопок
Сердце примера — цикл в методе create, который создает 256 объектов Nine Slice.
Для каждого объекта случайным образом определяются:
- Позиция (`x,y`) в большом мире (до 3000 пикселей).
- Тип кнопки (фрейм из атласа).
- Целевая ширина и высота для анимации.
for (let i = 0; i < 256; i++) {
const x = Phaser.Math.Between(0, 3000);
const y = Phaser.Math.Between(0, 3000);
const frame = Phaser.Utils.Array.GetRandom([ 'YellowButtonSml', 'PinkButtonSml', 'GreenButtonSml', 'RedButtonSml' ]);
const width = Phaser.Math.Between(256, 1024);
const height = Phaser.Math.Between(128, 384);
Затем создается сам объект Nine Slice. Ключевые параметры (128, 98, 64, 64, 48, 48) определяют размеры исходного фрейма и отступы для срезов, но в данном примере они одинаковы для всех кнопок, так как фреймы в атласе подготовлены заранее.
const slice = this.add.nineslice(x, y, 'ui', frame, 132, 98, 64, 64, 48, 48);
Сразу после создания к объекту применяется твин (плавная анимация) из системы this.tweens. Анимация бесконечно (repeat: -1) меняет ширину и высоту кнопки до целевых случайных значений и обратно (yoyo: true), создавая эффект "пульсации".
const duration = Phaser.Math.Between(1000, 4000);
this.tweens.add({
targets: slice,
width,
height,
duration,
ease: 'sine.inout',
yoyo: true,
repeat: -1
});
}
Управление камерой для обзора сцены
Чтобы можно было рассмотреть все созданные объекты, камера также анимируется. Ей задается сложный твин, который одновременно меняет несколько свойств (props): прокрутку по осям X и Y и уровень зума.
this.tweens.add({
targets: this.cameras.main,
props: {
scrollX: { value: 1000, duration: 4000 },
scrollY: { value: 2000, duration: 8000 },
zoom: { value: 0.20, duration: 3000, hold: 2000 },
},
ease: 'sine.inout',
yoyo: true,
repeat: -1
});
Анимация зума уменьшает вид камеры до 20% от исходного (zoom: 0.20), позволяя увидеть сразу большую область мира. Параметр hold добавляет паузу в 2000 миллисекунд после достижения минимального зума, прежде чем начать движение обратно. Это создает плавное и разнообразное движение камеры, демонстрирующее масштаб созданной сцены.
Что попробовать дальше
Пример наглядно показывает, что Nine Slice — это мощный и производительный инструмент для создания динамического интерфейса в Phaser. Даже 256 одновременно анимированных объектов не вызывают проблем с рендерингом, а их края остаются четкими при любом размере.
Для экспериментов попробуйте:
1. Изменить параметры срезов (последние 4 числа в вызове add.nineslice), чтобы увидеть, как меняется растяжение краев кнопки.
2. Добавить интерактивность, назначив объектам обработчики событий pointerdown.
3. Использовать текстуру из отдельного изображения вместо атласа, чтобы понять разницу в подходах.
4. Заменить твины на физическое тело, чтобы кнопки сталкивались друг с другом.
