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

При работе с системой частиц в Phaser 3 вы можете столкнуться с неожиданным поведением зон смерти (Death Zone). В то время как зоны эмиссии (Emit Zone) используют локальные координаты эмиттера, зоны смерти оперируют глобальными координатами сцены. Эта статья поможет понять разницу и правильно позиционировать зоны смерти, избегая распространённой ошибки, когда частицы исчезают не там, где вы ожидаете.

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

Живой запуск

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

Исходный код


class ParticlesSquare extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('flare', 'assets/particles/white-flare.png');
    }

    create ()
    {
        let particles = this.add.particles(300, 300, "flare", {
            lifespan: 1500,
            alpha: { start: 0, end: 1 },
            scale: 0.2,
            quantity: 20,
            emitting: false,
        })

        // emit zone position is in local emitter space (as expected)
        let emitZone = new Phaser.Geom.Circle(0, 0, 300)
        particles.addEmitZone({
            source: emitZone,
            type: "random",
            quantity: -1,
        })

        // deathZone position is in global space
        let deathZone = new Phaser.Geom.Circle(0, 0, 200)
        // you need to manually calculate the offset to put it in the right place
        // let deathZone = new Phaser.Geom.Circle(particles.x, particles.y, 100)
        particles.addDeathZone({
            source: deathZone,
            type: "onEnter",
        })

        particles.start(1500)

    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: ParticlesSquare
};

const game = new Phaser.Game(config);

Проблема: зоны смерти работают в глобальных координатах

В примере создаётся эмиттер частиц в позиции (300, 300) сцены. Для него добавляются две зоны: зона эмиссии (Emit Zone) в форме круга и зона смерти (Death Zone), также в форме круга.

Ключевое отличие: координаты центра круга для зоны эмиссии (0, 0) интерпретируются относительно позиции самого эмиттера (300, 300). А вот координаты центра круга для зоны смерти (0, 0) берутся относительно начала координат всей сцены, то есть левого верхнего угла (0, 0). Это приводит к тому, что зона смерти оказывается не там, где вы её ожидаете увидеть.

let deathZone = new Phaser.Geom.Circle(0, 0, 200)
particles.addDeathZone({
    source: deathZone,
    type: "onEnter",
})

Решение: ручной расчёт позиции Death Zone

Чтобы зона смерти располагалась корректно относительно эмиттера, её координаты нужно задавать в глобальной системе отсчёта сцены. Поскольку эмиттер находится в точке (300, 300), центр зоны смерти тоже должен быть установлен в (300, 300).

Проще всего это сделать, создавая геометрию зоны смерти, используя позицию эмиттера (particles.x, particles.y). Это гарантирует, что зона будет находиться ровно под эмиттером.

// Корректное создание зоны смерти в глобальных координатах
let deathZone = new Phaser.Geom.Circle(particles.x, particles.y, 200)
particles.addDeathZone({
    source: deathZone,
    type: "onEnter",
})

Сравнение Emit Zone и Death Zone

Давайте чётко разграничим поведение двух типов зон, чтобы эта разница в системах координат не застала вас врасплох в будущем.

* **Emit Zone (Зона эмиссии)**: Определяет область, из которой появляются новые частицы. Координаты её геометрии (source) задаются в **локальном пространстве эмиттера**. Точка (0, 0) зоны соответствует позиции эмиттера на сцене. * **Death Zone (Зона смерти)**: Определяет область, при входе в которую частицы уничтожаются. Координаты её геометрии (source) задаются в **глобальном пространстве сцены**. Точка (0, 0) зоны соответствует левому верхнему углу холста.

// Emit Zone: локальные координаты (0,0 = позиция эмиттера)
let emitZone = new Phaser.Geom.Circle(0, 0, 300)
particles.addEmitZone({
    source: emitZone,
    type: "random",
    quantity: -1,
})

// Death Zone: глобальные координаты (0,0 = угол сцены)
let deathZone = new Phaser.Geom.Circle(particles.x, particles.y, 200)
particles.addDeathZone({
    source: deathZone,
    type: "onEnter",
})

Практический совет: выносите координаты в переменные

Чтобы код был более читаемым и устойчивым к изменениям, позицию эмиттера и радиусы зон лучше выносить в константы или переменные. Это особенно полезно, если вы планируете динамически перемещать эмиттер или менять размеры зон.

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

create ()
{
    const emitterX = 300;
    const emitterY = 300;
    const deathZoneRadius = 200;

    let particles = this.add.particles(emitterX, emitterY, "flare", {
        // ... настройки частиц
    });

    // Создаем Death Zone с явно указанными глобальными координатами
    let deathZone = new Phaser.Geom.Circle(emitterX, emitterY, deathZoneRadius);
    particles.addDeathZone({
        source: deathZone,
        type: "onEnter",
    });
}

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

Главный вывод: всегда помните о контексте системы координат при работе с addEmitZone и addDeathZone в Phaser. Для экспериментов попробуйте создать эмиттер, который следует за указателем мыши, и динамически обновляйте позицию его зоны смерти. Или реализуйте сложную систему с несколькими зонами смерти разной формы, которые включаются и выключаются по таймеру.