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

В разработке игр на Phaser управление столкновениями — одна из ключевых механик. Часто мы используем коллайдеры, которые автоматически проверяют столкновения между группами объектов. Однако бывают ситуации, когда нужно более тонкое и прямое управление. В этой статье мы разберем, как использовать методы `collide` и `overlap` напрямую в цикле `update`, что дает полный контроль над логикой взаимодействий и позволяет создавать сложные игровые ситуации, например, сбор предметов или активацию ловушек.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('lemming', 'assets/sprites/lemming.png');
        this.load.image('ball', 'assets/sprites/shinyball.png');
        this.load.image('mushroom', 'assets/sprites/mushroom-32x32.png');
    }

    create ()
    {
        this.sprite = this.physics.add.image(400, 300, 'lemming');

        const outer = new Phaser.Geom.Rectangle(0, 0, 800, 600);
        const inner = new Phaser.Geom.Rectangle(350, 250, 100, 100);

        //  Create a few balls

        this.balls = this.physics.add.group({ immovable: true });

        for (let i = 0; i < 12; i++)
        {
            const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
            const ball = this.balls.create(point.x, point.y, 'ball');

            this.physics.add.existing(ball);

            ball.body.setImmovable();
        }

        //  Create a few mushrooms

        this.mushrooms = this.physics.add.group({ immovable: true });

        for (let i = 0; i < 32; i++)
        {
            const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
            const mushroom = this.mushrooms.create(point.x, point.y, 'mushroom');

            this.physics.add.existing(mushroom);

            mushroom.body.setImmovable();
        }

        this.sprite.setVelocity(100, 200).setBounce(1, 1).setCollideWorldBounds(true).setGravityY(200);
    }

    update ()
    {
        //  As long as these methods are called as part of the 'update' step
        //  they will process collisions between any physics objects and you
        //  can use the callbacks to handle the results

        this.physics.collide(this.sprite, this.balls);

        this.physics.overlap(this.sprite, this.mushrooms, (sprite, mushroom) =>
        {

            mushroom.destroy();

        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и создание объектов

В методе preload мы загружаем три спрайта: лемминга (lemming), блестящий шарик (ball) и гриб (mushroom). Эти ресурсы будут использоваться для визуализации взаимодействий.

В create создается главный спрайт лемминга как физическое тело с помощью this.physics.add.image. Он будет двигаться и сталкиваться с другими объектами.

Затем определяются две геометрические области: outer (весь экран 800x600) и inner (центральный прямоугольник 100x100). Функция Phaser.Geom.Rectangle.RandomOutside генерирует случайные точки за пределами inner, но внутри outer. Это позволяет размещать объекты по краям экрана, избегая центра.

Создаются две физические группы: this.balls и this.mushrooms. Обе группы помечаются как immovable: true, что означает, что объекты в них не будут сдвигаться при столкновениях. Каждый созданный объект также явно добавляется в физический мир через this.physics.add.existing, и его тело настраивается как неподвижное с помощью setImmovable().

this.sprite = this.physics.add.image(400, 300, 'lemming');
const outer = new Phaser.Geom.Rectangle(0, 0, 800, 600);
const inner = new Phaser.Geom.Rectangle(350, 250, 100, 100);
this.balls = this.physics.add.group({ immovable: true });
const point = Phaser.Geom.Rectangle.RandomOutside(outer, inner);
const ball = this.balls.create(point.x, point.y, 'ball');
this.physics.add.existing(ball);
ball.body.setImmovable();

Наконец, леммингу задается начальная скорость, отскок (setBounce), включение столкновений с границами мира и гравитация по оси Y. Это заставляет его постоянно двигаться и отскакивать от краев экрана.

Прямая проверка столкновений: метод collide

В методе update, который вызывается каждый кадр игры, происходит прямое управление столкновениями. Вместо использования коллайдера мы вызываем this.physics.collide для проверки столкновения между леммингом (this.sprite) и группой шариков (this.balls).

this.physics.collide(this.sprite, this.balls);

Метод collide обрабатывает физическое столкновение: объекты будут отталкиваться друг от друга согласно законам физики Arcade. Поскольку шарики помечены как immovable, при столкновении двигаться будет только лемминг. Это полезно для создания статичных препятствий, например, стен или платформ. Вызов этого метода в update гарантирует, что проверка происходит каждый кадр, обеспечивая плавную физическую симуляцию.

Обнаружение пересечений: метод overlap с callback

Следующим шагом в update используется метод this.physics.overlap для проверки пересечения лемминга с группой грибов (this.mushrooms). Ключевое отличие от collide в том, что overlap не вызывает физической реакции (отталкивания), а только фиксирует факт пересечения тел.

this.physics.overlap(this.sprite, this.mushrooms, (sprite, mushroom) => {
    mushroom.destroy();
});

Третьим аргументом передается callback-функция, которая выполняется при каждом обнаруженном пересечении. В данном случае, когда лемминг касается гриба, этот гриб уничтожается методом destroy(). Это классический пример механики сбора предметов: игрок (лемминг) собирает грибы, и они исчезают. Использование overlap идеально подходит для таких сценариев, где не требуется физическое взаимодействие, но нужно реагировать на контакт.

Настройка физического мира и запуск игры

Конфигурация игры задается в объекте config. Здесь важно настроить физический движок Arcade.

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

Включен режим отладки (debug: true), который отображает контуры физических тел — это невероятно полезно при разработке для визуальной проверки столкновений. Игра создается экземпляром Phaser.Game с этой конфигурацией. Благодаря прямым вызовам collide и overlap в update, мы получаем полный контроль над логикой столкновений, не полагаясь на автоматические коллайдеры.

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

Использование collide и overlap напрямую в update дает гибкость для реализации сложных игровых механик. Вы можете экспериментировать: добавьте разные callback-функции для начисления очков или воспроизведения звуков при столкновении, комбинируйте проверки с несколькими группами объектов или изменяйте свойства тел (например, скорость) в зависимости от типа столкновения. Это мощный инструмент для тонкой настройки физики вашей игры.