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

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

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

Живой запуск

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

Исходный код


class SpaceShip extends Phaser.Physics.Arcade.Sprite
{

    constructor (scene, x, y)
    {
        super(scene, x, y, 'ship');

        this.play('thrust');

        //  You can either do this:
        scene.add.existing(this);
        scene.physics.add.existing(this);

        //  Or this, the end result is the same
        // scene.sys.displayList.add(this);
        // scene.sys.updateList.add(this);
        // scene.sys.arcadePhysics.world.enableBody(this, 0);

        //  Set some default physics properties
        this.setScale(2);
        this.setBounce(1, 1);
        this.setCollideWorldBounds(true);

        this.body.onWorldBounds = true;

        this.setVelocity(0, -200);
    }
}

function onWorldBounds (body)
{
    body.gameObject.toggleFlipY();
}

class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.spritesheet('ship', 'assets/games/lazer/ship.png', { frameWidth: 16, frameHeight: 24 });
    }
    create ()
    {
        this.anims.create({
            key: 'thrust',
            frames: this.anims.generateFrameNumbers('ship', { frames: [ 2, 7 ] }),
            frameRate: 20,
            repeat: -1
        });

        for (let i = 0; i < 32; i++)
        {
            new SpaceShip(this, Phaser.Math.Between(64, 736), Phaser.Math.Between(100, 500));
        }

        this.physics.world.on('worldbounds', onWorldBounds);
    }
}

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

const game = new Phaser.Game(config);

Зачем расширять Arcade.Sprite?

Стандартный способ добавить спрайт с физикой — создать объект и вручную настраивать его свойства. Однако при создании десятков однотипных объектов (например, врагов или астероидов) этот код быстро становится громоздким и трудно поддерживаемым.

Создание собственного класса, наследующего от Phaser.Physics.Arcade.Sprite, позволяет: * **Инкапсулировать логику:** Вся настройка физики, анимаций и начального состояния объекта находится в одном месте — его конструкторе. * **Упростить создание:** Новый объект создается одной строкой new SpaceShip(...), а не десятком вызовов API. * **Повторно использовать код:** Класс становится готовым строительным блоком для всей игры.

Исходный пример демонстрирует создание класса космического корабля SpaceShip, который при появлении на сцене уже обладает физикой, анимацией и начальным движением.

Анатомия кастомного класса SpaceShip

Ключевой момент — вызов super() для инициализации родительского класса. В него передаются сцена, координаты и ключ текстуры.

constructor (scene, x, y)
{
    super(scene, x, y, 'ship');

Сразу после этого можно работать с объектом. В примере запускается анимация 'thrust'.

this.play('thrust');

Далее объект необходимо добавить в системы отображения и физики сцены. В примере показаны два эквивалентных способа. Первый — рекомендуемый и лаконичный.

// Рекомендуемый способ
scene.add.existing(this);
scene.physics.add.existing(this);

// Альтернативный "ручной" способ
// scene.sys.displayList.add(this);
// scene.sys.updateList.add(this);
// scene.sys.arcadePhysics.world.enableBody(this, 0);

После интеграции со сценой настраиваются физические свойства объекта через методы, унаследованные от Arcade.Sprite.

this.setScale(2);
this.setBounce(1, 1); // Идеальный отскок от всех поверхностей
this.setCollideWorldBounds(true); // Не вылетать за границы мира

this.body.onWorldBounds = true; // Критически важно для события worldbounds

this.setVelocity(0, -200); // Начальная скорость вверх

Интеграция класса в сцену и обработка событий

Класс SpaceShip — это заготовка. Чтобы его использовать, нужно подготовить ассеты в сцене и создать экземпляры. В методе preload загружается спрайтшип, а в create создается анимация.

this.anims.create({
    key: 'thrust', // Ключ, который используется в `this.play('thrust')`
    frames: this.anims.generateFrameNumbers('ship', { frames: [ 2, 7 ] }),
    frameRate: 20,
    repeat: -1 // Бесконечное повторение
});

Создание 32 кораблей в случайных позициях становится тривиальной задачей:

for (let i = 0; i < 32; i++)
{
    new SpaceShip(this, Phaser.Math.Between(64, 736), Phaser.Math.Between(100, 500));
}

Отдельно настраивается глобальная реакция на столкновение любого физического тела с границами мира. Событие 'worldbounds' генерируется только у тел, у которых свойство body.onWorldBounds установлено в true (мы это сделали в конструкторе).

function onWorldBounds (body)
{
    body.gameObject.toggleFlipY(); // Переворачиваем спрайт по вертикали
}

// В create() сцены:
this.physics.world.on('worldbounds', onWorldBounds);

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

Конфигурация игры стандартна для использования Arcade Physics. Важный нюанс — pixelArt: true, который отключает сглаживание для пиксельной графики, и нулевая гравитация.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    pixelArt: true,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            debug: false,
            gravity: { y: 0 } // Космос, гравитации нет
        }
    },
    scene: Example
};

В результате получается сцена, где 32 корабля летают, отскакивают от границ экрана и переворачиваются при каждом таком столкновении. Вся бизнес-логика корабля изолирована в его классе, а сцена отвечает только за создание анимации, управление группами объектов и глобальные обработчики событий.

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

Расширение Phaser.Physics.Arcade.Sprite — мощный паттерн для создания сложных, самодостаточных игровых объектов. Он превращает спагетти-код в сцене в модульную и понятную архитектуру. **Идеи для экспериментов:** 1. Добавьте в класс SpaceShip метод takeDamage() и свойство health. 2. Сделайте так, чтобы корабли при создании получали случайную скорость и цветовой оттенок. 3. Перенесите обработчик onWorldBounds внутрь класса SpaceShip в виде метода и подписывайтесь на событие индивидуально для каждого тела. 4. Создайте второй класс, например, Asteroid, и настройте столкновения между ними и SpaceShip с помощью this.physics.add.collider().