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

Разработка игр часто требует повторного использования однотипных объектов с уникальным поведением. Копирование кода для каждого врага или бонуса приводит к беспорядку. В этой статье мы рассмотрим, как создать собственный класс игрового объекта, унаследовав его от `Phaser.GameObjects.Sprite`, и зарегистрировать фабричный метод для удобного создания через `this.add`. Этот подход делает код чище, логику — изолированной, а объекты — легко переиспользуемыми.

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

Живой запуск

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

Исходный код


class EnemyRobot extends Phaser.GameObjects.Sprite {

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

        this.setTexture('contra');
        this.setScale(2);
    }

    preUpdate (time, delta)
    {
        super.preUpdate(time, delta);

        this.rotation += 0.01;
    }

}

let config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    pixelArt: true,
    scene: {
        init: init,
        preload: preload,
        create: create
    }
};

let game = new Phaser.Game(config);

function init ()
{
    Phaser.GameObjects.GameObjectFactory.register('robot', function (x, y)
    {
        let sprite = new EnemyRobot(this.scene, x, y);

        this.displayList.add(sprite);
        this.updateList.add(sprite);

        return sprite;
    });
}

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

function create ()
{
    this.add.robot(200, 200);
    this.add.robot(400, 300);
    this.add.robot(600, 400);
}

Наследование от базового класса Sprite

В Phaser все визуальные объекты наследуются от Phaser.GameObjects.GameObject. Для спрайтов базовым классом является Phaser.GameObjects.Sprite. Создав свой класс, мы можем инкапсулировать его внешний вид и поведение.

Ключевые моменты конструктора: - Вызов super(scene, x, y) обязателен для инициализации родительского класса. - Методы setTexture и setScale задают изображение и масштаб объекта.

class EnemyRobot extends Phaser.GameObjects.Sprite {
    constructor (scene, x, y) {
        super(scene, x, y);
        this.setTexture('contra');
        this.setScale(2);
    }
}

Добавление пользовательской логики в preUpdate

Для добавления кастомного поведения, которое должно выполняться каждый кадр, используется метод preUpdate. В нашем примере объект вращается.

Важно всегда вызывать super.preUpdate(time, delta), чтобы родительский класс мог выполнить свою внутреннюю логику обновления.

preUpdate (time, delta) {
    super.preUpdate(time, delta);
    this.rotation += 0.01;
}

Регистрация фабричного метода

Чтобы создавать наш кастомный объект так же удобно, как стандартные (this.add.image), нужно расширить GameObjectFactory. Это делается в функции init сцены.

Метод register принимает ключ (имя метода, например, 'robot') и фабричную функцию. Внутри функции создаётся экземпляр нашего класса, добавляется в списки отображения и обновления сцены и возвращается.

function init () {
    Phaser.GameObjects.GameObjectFactory.register('robot', function (x, y) {
        let sprite = new EnemyRobot(this.scene, x, y);
        this.displayList.add(sprite);
        this.updateList.add(sprite);
        return sprite;
    });
}

Использование кастомного объекта

После регистрации метод this.add.robot становится доступен в сцене. Создание экземпляров ничем не отличается от работы со встроенными типами объектов.

function create () {
    this.add.robot(200, 200);
    this.add.robot(400, 300);
    this.add.robot(600, 400);
}

Каждый созданный робот будет автоматически обновляться (preUpdate) и вращаться благодаря логике, описанной в классе.

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

Создание собственных классов игровых объектов — мощный паттерн в Phaser для структурирования кода. Вы можете экспериментировать: добавить в класс EnemyRobot физическое тело через this.scene.physics.add.existing(this), реализовать ИИ для патрулирования или создать иерархию классов для разных типов врагов (летающий робот, стреляющий робот), наследуя их от базового EnemyRobot.