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

В большинстве игр объекты не двигаются вечно. Будь то трение о поверхность или сопротивление воздуха, их скорость со временем падает. Phaser Arcade Physics предоставляет свойство `drag`, но оно применяется к каждой оси независимо, что не всегда реалистично. В этой статье мы разберем альтернативный подход: ручное управление сопротивлением через вектор скорости объекта. Это даст вам прямой контроль над замедлением, позволяя создавать эффекты вязкой среды, магических полей или просто более естественное трение.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    blocks;

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('block', 'assets/sprites/block.png');
    }

    create ()
    {
        this.blocks = this.physics.add.group({
            bounceX: 0.5,
            bounceY: 0.5,
            collideWorldBounds: true
        });

        this.blocks.create(100, 200, 'block').setVelocity(100, 200);
        this.blocks.create(500, 200, 'block').setVelocity(-100, -100);
        this.blocks.create(300, 400, 'block').setVelocity(60, 100);
        this.blocks.create(600, 300, 'block').setVelocity(-30, -50);

        this.physics.add.collider(this.blocks);
    }

    update ()
    {
        for (const block of this.blocks.getChildren())
        {
            const { velocity } = block.body;

            velocity.setLength(Math.floor(0.99 * velocity.length()));
        }
    }
}

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

const game = new Phaser.Game(config);

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

Всё начинается с создания группы физических тел. Вместо того чтобы настраивать свойства для каждого спрайта по отдельности, мы используем this.physics.add.group. Это эффективно и позволяет легко применовать общие правила.

Ключевые параметры конфигурации группы: * bounceX и bounceY: определяют упругость (коэффициент восстановления) при столкновениях. * collideWorldBounds: включает столкновение с границами игрового мира.

this.blocks = this.physics.add.group({
    bounceX: 0.5,
    bounceY: 0.5,
    collideWorldBounds: true
});

Создание и запуск спрайтов

После создания группы мы наполняем её спрайтами с помощью метода .create(). Каждый спрайт автоматически получает физическое тело Arcade.

Важный шаг — задание начальной скорости. Метод setVelocity(x, y) сообщает каждому блоку вектор начального движения. Это заставляет их разлетаться в разные стороны.

this.blocks.create(100, 200, 'block').setVelocity(100, 200);
this.blocks.create(500, 200, 'block').setVelocity(-100, -100);
this.blocks.create(300, 400, 'block').setVelocity(60, 100);
this.blocks.create(600, 300, 'block').setVelocity(-30, -50);

Чтобы спрайты в группе сталкивались друг с другом, мы создаём коллайдер.

this.physics.add.collider(this.blocks);

Принцип ручного сопротивления (Drag)

Стандартное свойство drag в Arcade Physics применяется к компонентам скорости X и Y по отдельности. Это работает, но может выглядеть неестественно, особенно при диагональном движении.

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

1. В цикле update мы перебираем всех детей группы через this.blocks.getChildren(). 2. Для каждого блока получаем доступ к его вектору скорости: block.body.velocity. 3. Метод velocity.length() возвращает текущую длину вектора. 4. Умножая эту длину на коэффициент (например, 0.99), мы уменьшаем её на 1%. 5. Метод velocity.setLength() устанавливает новую длину для вектора, сохраняя его направление.

update ()
{
    for (const block of this.blocks.getChildren())
    {
        const { velocity } = block.body;
        velocity.setLength(Math.floor(0.99 * velocity.length()));
    }
}

Использование Math.floor() обеспечивает целочисленное значение скорости, что может быть полезно для оптимизации, но для плавного замедления лучше использовать числа с плавающей точкой.

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

Для работы примера необходима правильная настройка игры. Ключевой момент — активация Arcade Physics и установка глобальной гравитации.

* default: 'arcade': Указывает, что используем Arcade Physics. * debug: true: Включает отладочную отрисовку контуров тел, что помогает визуализировать столкновения. * gravity: { y: 200 }: Задаёт гравитацию, направленную вниз по оси Y. В сочетании с нашим ручным сопротивлением это создаёт сложную, но предсказуемую траекторию движения объектов.

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

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

Ручное управление вектором скорости открывает тонкую настройку физики, недоступную при использовании только стандартного drag. Вы можете экспериментировать: заменить постоянный коэффициент на переменный (например, зависящий от типа поверхности под объектом), добавить ускорение против направления движения для эффекта «обратной тяги» или комбинировать этот подход с другими силами. Попробуйте применить его не к скорости, а к угловой скорости (body.angularVelocity) для создания трения при вращении.