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

При разработке игр часто требуется измерять расстояние между объектами. В Phaser для этого есть целый набор методов, и один из самых интересных — 'расстояние по Манхэттену', или 'змейка-расстояние' (`Snake`). В отличие от привычного прямого расстояния, оно измеряется как сумма разностей по горизонтали и вертикали, что идеально подходит для игр с сеточным или пошаговым движением, как в классических головоломках или тактических RPG. В этой статье мы разберем готовый пример, который не только вычисляет это расстояние между спрайтами, но и наглядно рисует вокруг игрока ромб, радиус которого равен этому самому 'змейка-расстоянию'. Этот прием поможет вам визуализировать зону досягаемости или область эффекта для персонажа.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    graphic;
    ufo;
    player;
    cursors;

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

    create ()
    {
        this.cursors = this.input.keyboard.createCursorKeys();

        this.player = this.add.image(400, 300, 'eyes');

        this.ufo = this.add.image(200, 150, 'ufo');

        this.graphic = this.add.graphics({ lineStyle: { color: 0x00ffff } });
    }

    update ()
    {
        if (this.cursors.left.isDown)
        {
            this.player.x -= 5;
        }
        else if (this.cursors.right.isDown)
        {
            this.player.x += 5;
        }
        else if (this.cursors.up.isDown)
        {
            this.player.y -= 5;
        }
        else if (this.cursors.down.isDown)
        {
            this.player.y += 5;
        }

        const dist = Phaser.Math.Distance.Snake(this.player.x, this.player.y, this.ufo.x, this.ufo.y);

        this.graphic
            .clear()
            .strokePoints([
                { x: this.player.x + dist, y: this.player.y },
                { x: this.player.x, y: this.player.y + dist },
                { x: this.player.x - dist, y: this.player.y },
                { x: this.player.x , y: this.player.y - dist }
            ], true, true);
    }
}

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

const game = new Phaser.Game(config);

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

В методе preload мы загружаем два изображения с удаленного сервера. Одно будет представлять игрока, другое — статичный объект (НЛО), до которого будем считать расстояние.

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

В методе create инициализируются ключевые объекты сцены. Создается управление с клавиатуры, оба спрайта размещаются на поле, а также создается объект Graphics для последующего рисования.

create ()
{
    this.cursors = this.input.keyboard.createCursorKeys();
    this.player = this.add.image(400, 300, 'eyes');
    this.ufo = this.add.image(200, 150, 'ufo');
    this.graphic = this.add.graphics({ lineStyle: { color: 0x00ffff } });
}

Управление игроком и вычисление расстояния

Логика движения игрока обрабатывается в update. При нажатии клавиш стрелок координаты спрайта player меняются с фиксированным шагом.

if (this.cursors.left.isDown)
{
    this.player.x -= 5;
}
else if (this.cursors.right.isDown)
{
    this.player.x += 5;
}
else if (this.cursors.up.isDown)
{
    this.player.y -= 5;
}
else if (this.cursors.down.isDown)
{
    this.player.y += 5;
}

Сразу после этого вычисляется ключевая метрика — 'змейка-расстояние' между игроком и НЛО с помощью Phaser.Math.Distance.Snake. Эта функция возвращает сумму абсолютных разностей по осям X и Y.

const dist = Phaser.Math.Distance.Snake(this.player.x, this.player.y, this.ufo.x, this.ufo.y);

Визуализация расстояния в виде ромба

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

this.graphic
    .clear()
    .strokePoints([
        { x: this.player.x + dist, y: this.player.y },
        { x: this.player.x, y: this.player.y + dist },
        { x: this.player.x - dist, y: this.player.y },
        { x: this.player.x , y: this.player.y - dist }
    ], true, true);

Эти точки расположены справа, снизу, слева и сверху от центра игрока. Параметр true замыкает контур, создавая ромб. Важно понимать: в данном контексте dist — это не гипотенуза (евклидово расстояние), а сумма катетов. Поэтому все вершины ромба отстоят от центра ровно на это значение, если двигаться строго по осям, что и соответствует логике 'расстояния по Манхэттену'.

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

Стандартная конфигурация движка Phaser 3. Указывается автоматический выбор рендерера, родительский HTML-элемент и класс нашей сцены.

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

const game = new Phaser.Game(config);

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

Метод Phaser.Math.Distance.Snake — это мощный и простой инструмент для игр, где движение ограничено сеткой или осями. Визуализация в виде ромба делает это расстояние осязаемым на экране. Для экспериментов попробуйте: 1. Заменить Snake на Distance.Between и нарисовать круг, чтобы увидеть разницу между метриками. 2. Сделать так, чтобы НЛО 'убегало' от игрока, если 'змейка-расстояние' становится меньше определенного порога. 3. Использовать это расстояние для расчета урона, который уменьшается по мере отдаления цели.