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

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

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
        this.move = 0;
        this.x = 0;
        this.y = 0;
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('sky', 'assets/skies/deepblue.png');
        this.load.image('ball', 'assets/demoscene/ball-tlb.png');
    }

    create ()
    {
        this.add.image(0, 0, 'sky')
            .setOrigin(0);
        this.group = this.add.group({ key: 'ball', frameQuantity: 128 });

        this.input.on('pointermove', function (pointer) {
            this.x = pointer.x;
            this.y = pointer.y;
        }, this);
    }

    update (time, delta)
    {
        this.move += delta;
        if (this.move > 6)
        {
            Phaser.Actions.ShiftPosition(this.group.getChildren(), this.x, this.y);
            this.move = 0;
        }
    }
}

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

const game = new Phaser.Game(config);

Инициализация сцены и переменных состояния

В конструкторе класса сцены Example мы инициализируем три важные переменные. Переменная this.move будет служить таймером для контроля частоты обновления позиций объектов. Переменные this.x и this.y будут хранить последние известные координаты курсора мыши.

constructor ()
{
    super();
    this.move = 0;
    this.x = 0;
    this.y = 0;
}

Создание группы объектов и подписка на движение мыши

В методе create мы сначала добавляем фоновое изображение. Затем создаем группу (this.add.group) из 128 идентичных спрайтов с ключом 'ball'. Группа в Phaser — это мощный инструмент для управления коллекциями игровых объектов.

Затем мы настраиваем обработчик события pointermove. При каждом движении мыши он обновляет внутренние переменные this.x и this.y, записывая в них текущие координаты курсора. Обратите внимание на третий аргумент this в вызове this.input.on, который определяет контекст выполнения функции-обработчика.

create ()
{
    this.add.image(0, 0, 'sky')
        .setOrigin(0);
    this.group = this.add.group({ key: 'ball', frameQuantity: 128 });

    this.input.on('pointermove', function (pointer) {
        this.x = pointer.x;
        this.y = pointer.y;
    }, this);
}

Основной игровой цикл и применение ShiftPosition

Метод update выполняется каждый кадр игры. Параметр delta представляет время, прошедшее с предыдущего кадра, в миллисекундах. Мы накапливаем это время в переменной this.move.

Когда накопленное время превышает порог в 6 миллисекунд, срабатывает основная логика. Мы вызываем Phaser.Actions.ShiftPosition. Первым аргументом передаем массив детей группы, полученный через this.group.getChildren(). Второй и третий аргументы — целевые координаты this.x и this.y. После выполнения действия сбрасываем таймер this.move в ноль.

update (time, delta)
{
    this.move += delta;
    if (this.move > 6)
    {
        Phaser.Actions.ShiftPosition(this.group.getChildren(), this.x, this.y);
        this.move = 0;
    }
}

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

Настройка и запуск игры

Конфигурационный объект config определяет основные параметры игры, такие как тип рендерера, размеры холста и основную сцену. Затем создается экземпляр игры new Phaser.Game(config), который запускает весь процесс.

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

const game = new Phaser.Game(config);

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

Использование Phaser.Actions.ShiftPosition — это отличный способ анимировать группу объектов с минимальным кодом. Для экспериментов попробуйте изменить порог в условии this.move > 6 — это повлияет на скорость реакции "роя". Также можно модифицировать логику, чтобы цель следовала не за курсором, а за другим игровым объектом, или применить другие методы из модуля Phaser.Actions, например Rotate или Scale, для создания более сложных визуальных эффектов.