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

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

Версия 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('contra', 'assets/pics/contra3.png');
    }

    create ()
    {
        this.children.add(new EnemyRobot(this, 264, 250));
        this.children.add(new EnemyRobot(this, 464, 350));
        this.children.add(new EnemyRobot(this, 664, 450));
    }
}

class EnemyRobot extends Phaser.GameObjects.Image
{
    constructor (scene, x, y)
    {
        super(scene);

        this.setTexture('contra');
        this.setPosition(x, y);
        this.setScale(2);
    }
}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Зачем создавать свои классы объектов?

Представьте, что в вашей игре есть враг-робот, который должен всегда появляться с определённой текстурой, увеличенным размером и, возможно, заранее заданной анимацией или физическим телом. Каждый раз при создании такого робота вам пришлось бы повторять одни и те же вызовы API: setTexture, setScale, setPosition и т.д. Это ведёт к дублированию кода и усложняет его поддержку.

Создание собственного класса EnemyRobot решает эту проблему. Вы описываете конфигурацию объекта один раз в его конструкторе, а затем можете создавать экземпляры одной строкой. Это делает код сцены чище, а логику врага проще развивать — вся информация о нём сосредоточена в одном месте.

Анализ исходного кода примера

Пример состоит из двух ключевых частей: сцены Example и пользовательского класса EnemyRobot. Давайте посмотрим на сцену:

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

    create ()
    {
        this.children.add(new EnemyRobot(this, 264, 250));
        this.children.add(new EnemyRobot(this, 464, 350));
        this.children.add(new EnemyRobot(this, 664, 450));
    }
}

В методе preload задаётся базовый URL для загрузки и загружается текстура с ключом 'contra'. В методе create происходит самое интересное: создаются три экземпляра нашего кастомного робота. Обратите внимание, что для добавления объекта в систему отображения Phaser используется this.children.add(). Это стандартный способ регистрации любого игрового объекта в сцене. Конструктору EnemyRobot передаются ссылка на саму сцену (this) и координаты.

Создание класса EnemyRobot

Класс EnemyRobot наследуется от стандартного Phaser.GameObjects.Image. Это даёт ему все свойства и методы изображения.

class EnemyRobot extends Phaser.GameObjects.Image
{
    constructor (scene, x, y)
    {
        super(scene);

        this.setTexture('contra');
        this.setPosition(x, y);
        this.setScale(2);
    }
}
Разберём конструктор по шагам:
1.  `super(scene);` — Вызов конструктора родительского класса (`Image`) является обязательным. Он инициализирует объект внутри фреймворка и привязывает его к переданной сцене.
2.  `this.setTexture('contra');` — Устанавливает текстуру, которая была загружена в сцене под ключом `'contra'`. Это внутренний спрайт из примера.
3.  `this.setPosition(x, y);` — Задаёт позицию объекта на игровом поле. Координаты `x` и `y` передаются из вызова в сцене.
4.  `this.setScale(2);` — Увеличивает спрайт в 2 раза. Это демонстрация того, как можно сразу применять любые стандартные трансформации к объекту.

После выполнения конструктора объект полностью настроен и готов к отображению.

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

Завершающая часть примера — стандартная конфигурация движка Phaser и создание экземпляра игры.

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Конфиг указывает движку использовать WebGL или Canvas автоматически (Phaser.AUTO), монтировать игру в HTML-элемент с id='phaser-example' и использовать нашу сцену Example в качестве начальной. Инициализация new Phaser.Game(config) запускает весь игровой цикл.

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

Создание собственных классов игровых объектов через наследование — мощный паттерн в Phaser для структурирования кода и повторного использования логики. Вы инкапсулируете настройки и поведение объекта в одном месте, что соответствует принципам ООП. **Идеи для экспериментов:** 1. Добавьте в конструктор EnemyRobot вызов this.setFlipX(true) или this.setAngle(45), чтобы роботы появлялись отражёнными или повёрнутыми. 2. Реализуйте в классе методы preUpdate() для добавления простой анимации, например, покачивания (this.y += Math.sin(time) * 0.5). 3. Расширьте класс, добавив физическое тело через this.scene.physics.add.existing(this) в конструкторе, чтобы роботы могли сталкиваться с миром.