О чем этот пример

Работая над интерфейсом игры, часто возникает задача отобразить множество кнопок или панелей разного размера, которые должны сохранять четкие края при масштабировании. Использование обычных спрайтов приводит к размытию и артефактам. Техника 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. Заменить твины на физическое тело, чтобы кнопки сталкивались друг с другом.