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

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

Версия 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.spritesheet('diamonds', 'assets/sprites/diamonds32x5.png', { frameWidth: 64, frameHeight: 64 });
    }

    create ()
    {
        const gems = [];

        for (let i = 1; i < 64; i++)
        {
            const x = Phaser.Math.Between(100, 700);
            const y = Phaser.Math.Between(100, 500);
            const frame = Phaser.Math.Between(0, 4);

            gems.push(this.add.sprite(x, y, 'diamonds', frame));
        }

        this.add.text(16, 16, 'Click to find the first Red gem with a Scale of 1');

        const redFrame = this.textures.getFrame('diamonds', 0);

        this.input.on('pointerdown', () => {

            //  Get the first sprite with a scale of 1 that is using the Red frame
            const red = Phaser.Actions.GetFirst(gems, { scaleX: 1, frame: redFrame });

            if (red)
            {
                this.children.bringToTop(red);

                this.tweens.chain({
                    targets: red,
                    tweens: [
                        {
                            scale: 2,
                            duration: 400,
                            ease: 'Bounce.easeOut'
                        },
                        {
                            delay: 500,
                            scale: 0,
                            duration: 1000
                        }
                    ]
                });
            }

        });
    }
}

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

const game = new Phaser.Game(config);

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

В примере создается сцена, которая в методе preload загружает спрайтшит с алмазами. Ключевой этап происходит в create. Здесь мы создаем массив gems и в цикле заполняем его 63 спрайтами. Каждому спрайту случайным образом задаются координаты (`x,y) и кадр анимации (frame`) от 0 до 4.

const gems = [];
for (let i = 1; i < 64; i++) {
    const x = Phaser.Math.Between(100, 700);
    const y = Phaser.Math.Between(100, 500);
    const frame = Phaser.Math.Between(0, 4);
    gems.push(this.add.sprite(x, y, 'diamonds', frame));
}

Таким образом, мы получаем игровое поле, усыпанное разноцветными камнями в случайных местах. Массив gems становится нашей коллекцией для поиска.

Условия поиска: как задать критерии для GetFirst

Метод Phaser.Actions.GetFirst принимает два аргумента: массив элементов и объект с критериями поиска. В примере мы хотим найти первый красный алмаз (кадр 0), который еще не был масштабирован (имеет scaleX: 1).

Сначала мы получаем ссылку на нужный кадр из текстуры:

const redFrame = this.textures.getFrame('diamonds', 0);

Затем, по клику мыши, запускаем поиск:

const red = Phaser.Actions.GetFirst(gems, { scaleX: 1, frame: redFrame });

Метод переберет массив gems и вернет первый спрайт, у которого одновременно scaleX равен 1 и свойство frame строго равно объекту redFrame. Если такого спрайта нет, вернется undefined. Это гораздо удобнее ручного перебора с условиями if.

Реакция на результат: анимация и управление отображением

После того как объект найден, мы можем с ним взаимодействовать. В примере найденный красный алмаз визуально выделяется.

Сначала он перемещается на передний план с помощью метода this.children.bringToTop(). Это гарантирует, что его анимация не будет перекрыта другими спрайтами.

if (red) {
    this.children.bringToTop(red);
}

Затем для объекта запускается цепочка твинов (tweens.chain). Сначала алмаз увеличивается до двойного размера с "пружинящей" анимацией (Bounce.easeOut), а после небольшой задержки плавно исчезает, уменьшаясь до нуля.

this.tweens.chain({
    targets: red,
    tweens: [
        { scale: 2, duration: 400, ease: 'Bounce.easeOut' },
        { delay: 500, scale: 0, duration: 1000 }
    ]
});

Эта простая последовательность делает взаимодействие с объектом отзывчивым и приятным для игрока.

Гибкость критериев: что еще можно искать

Сила Phaser.Actions.GetFirst в гибкости объекта критериев. Вы можете искать объекты по любым числовым, строковым или булевым свойствам, которые у них есть. Например:

*   По положению: `{ x: 400, y: 300 }`
*   По видимости: `{ visible: true }`
*   По здоровью или другим игровым параметрам, если вы их добавили: `{ hp: 100 }`
*   По комбинации свойств: `{ scaleY: 0.5, alpha: 1 }`

Важно понимать, что сравнение идет по строгому равенству (===). Для поиска по диапазону значений (например, x > 100) этот метод не подойдет — здесь придется использовать Phaser.Actions.Call или обычный цикл. GetFirst идеален для точного, дискретного поиска.

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

Phaser.Actions.GetFirst — это элегантный инструмент для декларативного поиска объектов в массиве. Он избавляет от написания шаблонных циклов, делает код нагляднее и концентрирует внимание на условиях поиска, а не на механике перебора. Для экспериментов попробуйте: изменить критерии поиска на { scaleX: 1, frame: 2 } для поиска синего алмаза; добавить проверку на свойство alpha; или использовать найденный объект не для анимации, а для начисления очков или запуска игрового события.