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

Визуализация прогресса — ключевой элемент игрового интерфейса. Жизнь героя, заряд способности, заполнение шкалы опыта — всё это требует интуитивно понятного и плавного отображения. В этой статье мы разберем, как создать анимированные прогресс-бары в Phaser 3, используя технику Nine Slice для идеального растягивания текстур без искажений и встроенную систему твинов для плавной анимации заполнения.

Версия 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 ()
    {
        const bar1 = this.add.nineslice(400, 200, 'ui', 'ButtonOrange');
        const fill1 = this.add.nineslice(286, 198, 'ui', 'ButtonOrangeFill1', 13, 39, 6, 6);

        fill1.setOrigin(0, 0.5);

        const bar2 = this.add.nineslice(400, 400, 'ui', 'ButtonOrange');
        const fill2 = this.add.nineslice(286, 398, 'ui', 'ButtonOrangeFill2', 13, 39, 6, 6);

        fill2.setOrigin(0, 0.5);

        this.tweens.add({
            targets: fill1,
            width: 228,
            duration: 3000,
            ease: 'sine.inout',
            yoyo: true,
            repeat: -1,
        });

        this.tweens.add({
            targets: fill2,
            width: 228,
            duration: 2000,
            ease: 'bounce.out',
            yoyo: true,
            repeat: -1,
            hold: 1000
        });
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#00746b',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Что такое Nine Slice и зачем он нужен?

Nine Slice (9-slice scaling) — это метод масштабирования двумерных спрайтов, при котором текстура делится на 9 частей: 4 угла, 4 стороны и центральная часть. Углы не масштабируются, стороны растягиваются только по одной оси, а центр — по обеим. Это позволяет растягивать UI-элементы (кнопки, панели, рамки) до любых размеров без "размытия" или "расползания" закругленных краев или декоративных элементов.

В Phaser 3 объект Nine Slice создается через this.add.nineslice(). Именно его мы будем использовать для создания рамки бара и его заполняемой части.

Подготовка ресурсов и загрузка атласа

В нашем примере используется атлас текстур ui, который содержит два спрайта для рамки и два для заполнения. Атлас — это один большой файл изображения и JSON-файл с координатами отдельных кадров (спрайтов) внутри него. Это оптимизирует загрузку и рендеринг.

В методе preload() мы загружаем этот атлас.

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');
}

Создание статичной рамки прогресс-бара

Рамка (контейнер) бара — это статичный элемент, который определяет границы нашего прогресс-бара. Мы создаем его как объект Nine Slice, используя текстуру 'ButtonOrange'. Первые два аргумента метода — координаты X и Y центра объекта на сцене.

const bar1 = this.add.nineslice(400, 200, 'ui', 'ButtonOrange');

Здесь мы не передаем последние четыре аргумента (width, height, leftWidth, rightHeight), поэтому объект принимает размеры по умолчанию из текстуры. Это готовая кнопка или рамка с уже заданными в текстуре размерами.

Создание анимируемой полосы заполнения

Сама полоса заполнения — это второй объект Nine Slice. Ключевые отличия от рамки: 1. **Текстура:** Используется 'ButtonOrangeFill1' (или Fill2 для второго бара) — это сплошная полоса без рамки. 2. **Размеры:** Явно задаются ширина и высота (13 и 39 пикселей), а также размеры левой и правой "не растягиваемых" частей (оба по 6 пикселей). Это нужно, чтобы концы полосы (возможно, с закруглениями или эффектами) не искажались при растягивании. 3. **Точка трансформации (origin):** Устанавливается в (0, 0.5) с помощью setOrigin(0, 0.5). Это значит, что все трансформации (в нашем случае — изменение ширины) будут происходить относительно левого края (x=0) и центра по вертикали (y=0.5). Так полоса будет увеличиваться вправо, оставаясь выровненной по левому краю рамки.

const fill1 = this.add.nineslice(286, 198, 'ui', 'ButtonOrangeFill1', 13, 39, 6, 6);
fill1.setOrigin(0, 0.5);

Координаты fill1 (286, 198) рассчитаны так, чтобы левый край полосы совпадал с внутренним левым краем рамки bar1 (400 - (ширина_рамки/2) + небольшой отступ).

Анимация заполнения с помощью Tween

Phaser предоставляет мощную систему анимаций Tweens. Мы создаем твин для объекта fill1, который анимирует его свойство width от исходного значения до 228 пикселей.

this.tweens.add({
    targets: fill1,
    width: 228,
    duration: 3000,
    ease: 'sine.inout',
    yoyo: true,
    repeat: -1,
});

Разберем параметры: - targets: объект или массив объектов для анимации. - width: целевое значение свойства. - duration: длительность анимации в миллисекундах. - ease: функция плавности ('sine.inout' для плавного ускорения и замедления). - yoyo: true: после достижения цели анимация проиграется в обратном порядке. - repeat: -1: бесконечное повторение (включая йо-йо-эффект).

Второй твин для fill2 использует функцию 'bounce.out' (эффект отскока в конце) и параметр hold: 1000, который добавляет паузу в 1 секунду перед началом обратной анимации.

Что попробовать дальше

Комбинируя объекты Nine Slice для отрисовки и систему Tweens для анимации, вы можете легко создавать в Phaser 3 любые прогресс-бары, полосы здоровья или индикаторы загрузки, которые идеально масштабируются и анимируются. Для экспериментов попробуйте: 1. Изменить функции плавности (ease) на 'quad.inout', 'back.out' или 'elastic.out' для разных визуальных эффектов. 2. Анимировать не только ширину, но и, например, цвет заполнения (tint). 3. Привязать ширину полосы fill1.width к реальному игровому параметру (например, здоровью игрока) и обновлять ее в реальном времени, убрав твин.