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

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

Версия 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('cannon_head', 'assets/tests/timer/cannon_head.png');
        this.load.spritesheet('ball', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });
    }

    create ()
    {
        const balls = this.physics.add.group({
            active: false,
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true,
            frame: [ 0, 1, 2, 3, 4 ],
            key: 'ball',
            quantity: 12,
            setXY: { x: 400, y: 300 }
        });

        const cannon = this.physics.add.image(400, 300, 'cannon_head')
            .setAngularVelocity(60);

        this.time.addEvent({
            delay: 100,
            startAt: 100,
            repeat: balls.getLength() - 1,
            callback: () =>
            {
                const ball = balls.getFirstDead();

                ball.setActive(true);

                this.physics.velocityFromAngle(cannon.angle, 300, ball.body.velocity);
            }
        });
    }

}

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

const game = new Phaser.Game(config);

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

В методе preload загружаются два изображения: спрайт пушки и спрайтшит для шариков-снарядов. Обратите внимание на параметры загрузки спрайтшита: указывается ширина и высота одного кадра.

this.load.image('cannon_head', 'assets/tests/timer/cannon_head.png');
this.load.spritesheet('ball', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });

В create создается физическая группа balls. Ключевые настройки: * active: false — все объекты в группе изначально неактивны. * bounceX/Y: 1 — снаряды будут идеально отскакивать от границ. * collideWorldBounds: true — включает столкновение с границами мира. * frame — массив индексов кадров из спрайтшита. * quantity: 12 — группа сразу создаст 12 физических тел. * setXY — все тела будут расположены в одной точке (400, 300).

const balls = this.physics.add.group({
    active: false,
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true,
    frame: [ 0, 1, 2, 3, 4 ],
    key: 'ball',
    quantity: 12,
    setXY: { x: 400, y: 300 }
});

Создание и вращение пушки

Пушка создается как физический спрайт с помощью this.physics.add.image. После создания ей сразу же задается постоянная угловая скорость с помощью метода .setAngularVelocity(60). Это заставляет пушку непрерывно вращаться вокруг своей центральной точки.

const cannon = this.physics.add.image(400, 300, 'cannon_head')
    .setAngularVelocity(60);

Запуск таймера и логика выстрела

Для последовательного выпуска снарядов используется this.time.addEvent. Событие настраивается так, чтобы срабатывать каждые 100 мс (delay), начиная через 100 мс после создания (startAt), и повторяться на один раз меньше, чем количество снарядов в группе. Это гарантирует, что каждый из 12 снарядов будет выпущен ровно один раз с небольшой задержкой.

Внутри коллбэка происходит основная магия: 1. balls.getFirstDead() — получаем первый неактивный ("мёртвый") снаряд из группы. 2. ball.setActive(true) — активируем его, делая видимым и подверженным физике. 3. this.physics.velocityFromAngle(cannon.angle, 300, ball.body.velocity) — ключевой вызов. Он вычисляет вектор скорости на основе текущего угла пушки (cannon.angle), заданной мощности (300 пикселей в секунду) и записывает результат в свойство velocity физического тела снаряда (ball.body).

this.time.addEvent({
    delay: 100,
    startAt: 100,
    repeat: balls.getLength() - 1,
    callback: () =>
    {
        const ball = balls.getFirstDead();
        ball.setActive(true);
        this.physics.velocityFromAngle(cannon.angle, 300, ball.body.velocity);
    }
});

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

Конфигурация игры стандартна, но важно обратить внимание на настройки физического движка Arcade. В данном примере отладка (debug) отключена. При значении true вы увидите хитбоксы (границы тел) всех физических объектов, что полезно для разработки.

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

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

Метод velocityFromAngle — это элегантный способ преобразовать угол поворота в вектор скорости, идеально подходящий для механик стрельбы. Для экспериментов попробуйте изменить угловую скорость пушки, силу выстрела или интервал между выстрелами. Можно добавить управление пушкой с клавиатуры, реализовать перезарядку или создать разные типы снарядов с уникальными свойствами отскока и скоростью.