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

При создании игровых интерфейсов, инвентарей или карточных игр часто возникает задача расположить множество объектов по сетке. Вручную рассчитывать координаты для каждого спрайта — утомительно и чревато ошибками. Phaser предлагает элегантное решение: метод `Phaser.Actions.GridAlign`. Эта статья покажет, как с его помощью быстро и точно расположить группу спрайтов в идеальном порядке, сконцентрировавшись на игровой логике, а не на рутинной математике.

Версия 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.image('bg', 'assets/skies/deepblue.png');
        this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        const frames = this.textures.get('cards').getFrameNames();

        const cards = [];

        //  Create 8 cards and push them into an array

        for (var i = 0; i < 8; i++)
        {
            cards.push(this.add.sprite(0, 0, 'cards', Phaser.Math.RND.pick(frames)));
        }

        //  The cards are 140x190 in size

        //  Let's lay them out in a 4x2 grid, with 10px spacing between them

        Phaser.Actions.GridAlign(cards, {
            width: 4,
            height: 2,
            cellWidth: 150,
            cellHeight: 200,
            x: 100,
            y: 100
        });
    }
}

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

const game = new Phaser.Game(config);

Зачем нужен GridAlign?

Представьте, что вам нужно отобразить колоду карт, элементы инвентаря или ряд кнопок меню. Каждый объект имеет свои координаты (x, y). Чтобы расположить их ровными рядами и колонками, нужно для каждого рассчитывать позицию, учитывая его индекс, размер ячейки и отступы. Phaser.Actions.GridAlign автоматизирует этот процесс.

Метод принимает массив игровых объектов (спрайтов, изображений, текста) и конфигурационный объект, а затем мгновенно расставляет все элементы по заданной сетке. Это не только экономит время, но и делает код чище и легче для поддержки.

Подготовка сцены и создание спрайтов

В первую очередь, как и в любом проекте Phaser, нужно загрузить ресурсы и создать сцену. В методе preload загружаем фон и атлас спрайтов с картами.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('bg', 'assets/skies/deepblue.png');
    this.load.atlas('cards', 'assets/atlas/cards.png', 'assets/atlas/cards.json');
}

В методе create добавляем фон и готовим массив для будущих карт. Ключевой момент: мы создаем спрайты с нулевыми координатами (0, 0). Их фактическое положение будет задано позже.

create ()
{
    this.add.image(400, 300, 'bg');
    const frames = this.textures.get('cards').getFrameNames();
    const cards = [];
    for (var i = 0; i < 8; i++)
    {
        cards.push(this.add.sprite(0, 0, 'cards', Phaser.Math.RND.pick(frames)));
    }
}

Цикл создает 8 спрайтов-карт, выбирая для каждого случайный кадр (frame) из атласа, и помещает их в массив cards. Пока все они находятся в одной точке (в верхнем левом углу мира).

Магия GridAlign: настройка сетки

Вот где происходит основное действие. Мы вызываем Phaser.Actions.GridAlign, передавая массив объектов и объект настроек.

Phaser.Actions.GridAlign(cards, {
    width: 4,
    height: 2,
    cellWidth: 150,
    cellHeight: 200,
    x: 100,
    y: 100
});

Разберем параметры конфигурации: * width: 4 и height: 2 — определяют размерность сетки (4 колонки, 2 строки). Всего 8 ячеек, что идеально соответствует нашему массиву из 8 карт. * cellWidth: 150 и cellHeight: 200 — задают размер одной ячейки сетки. Эти значения должны быть немного больше размера самого спрайта (140x190), чтобы оставался промежуток (padding). * x: 100 и y: 100 — координаты верхнего левого угла всей сетки. От этой точки начинается расчет позиций для всех объектов.

Метод проходит по массиву cards и для каждого элемента вычисляет его окончательные координаты, основываясь на его индексе в массиве и заданных параметрах сетки. Первый объект попадает в ячейку [0,0], второй — в [1,0], и так далее, построчно.

Конфигурация игры и запуск

Завершающий шаг — стандартная настройка объекта конфигурации Phaser Game и его создание.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};
const game = new Phaser.Game(config);

Эта конфигурация создает игровое поле размером 800x600 пикселей с темно-серым фоном и запускает нашу сцену Example. После запуска вы увидите 8 различных карт, аккуратно разложенных в два ряда по четыре штуки.

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

Phaser.Actions.GridAlign — это мощный и простой инструмент для организации игровых объектов. Он избавляет от рутинных расчетов и позволяет управлять layout'ом через понятные параметры. Для экспериментов попробуйте: изменить параметры width и height на 2x4; динамически добавлять новые карты в массив и заново применять GridAlign; использовать разные значения cellWidth и cellHeight для создания неравномерных сеток; или применить этот метод не к спрайтам, а к текстовым объектам для создания меню.