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

В стандартном режиме движок Arcade Physics обновляется автоматически на каждом кадре. Но бывают случаи, когда логика игры требует нестандартной синхронизации или ручного контроля. Пример демонстрирует, как включить кастомное обновление физики (customUpdate) и вручную запускать расчёты столкновений и движения. Это полезно для создания пошаговых игр, синхронизации с внешними сервисами или тонкой настройки производительности.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super({
            physics: {
                arcade: {
                    customUpdate: true,
                    debug: true,
                    fixedStep: false,
                    fps: 60,
                    gravity: { y: 200 }
                }
            }
        });
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('block', 'assets/sprites/block.png');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
        this.load.image('red', 'assets/particles/red.png');
        this.load.image('sky', 'assets/skies/space2.png');
    }

    create ()
    {
        this.add.image(400, 300, 'sky');

        const group = this.physics.add.group({
            bounceX: 1,
            bounceY: 1,
            collideWorldBounds: true
        });

        group.create(100, 200, 'block').setVelocity(100, 200);
        group.create(500, 200, 'block').setVelocity(-100, -100);
        group.create(300, 400, 'block').setVelocity(60, 100);
        group.create(600, 300, 'block').setVelocity(-30, -50);

        this.physics.add.collider(group);

        const particles = this.add.particles(0, 0, 'red', {
            speed: 100,
            scale: { start: 1, end: 0 },
            blendMode: 'ADD'
        });

        const logo = this.physics.add.image(400, 100, 'logo');

        logo.setVelocity(100, 200);
        logo.setBounce(1, 1);
        logo.setCollideWorldBounds(true);

        particles.startFollow(logo);

        const gui = new dat.GUI({ width: 400 });

        gui.add(this.physics, 'enableUpdate');
        gui.add(this.physics, 'disableUpdate');

        const { world } = this.physics;

        gui.add({ update: () => { world.update(0, world._frameTimeMS); } }, 'update');
    }
}

const gameConfig = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(gameConfig);

Включение кастомного обновления

Ключевой параметр customUpdate: true настраивается в конструкторе сцены внутри конфигурации физики. Это отключает встроенный цикл обновления Arcade Physics.

constructor ()
{
    super({
        physics: {
            arcade: {
                customUpdate: true, // Отключаем автообновление физики
                debug: true,
                fixedStep: false,
                fps: 60,
                gravity: { y: 200 }
            }
        }
    });
}

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

Создание физических тел и группы

В методе create() мы создаём обычные физические объекты, но они не будут двигаться, пока физика не обновится. Группа с collideWorldBounds и разными скоростями готовится к работе.

const group = this.physics.add.group({
    bounceX: 1,
    bounceY: 1,
    collideWorldBounds: true
});

group.create(100, 200, 'block').setVelocity(100, 200);
group.create(500, 200, 'block').setVelocity(-100, -100);

Коллайдер для группы создаётся стандартно, но проверка столкновений также зависит от ручного вызова update().

this.physics.add.collider(group);

Ручное управление обновлением через GUI

Для демонстрации в интерфейс добавлены три кнопки, управляющие обновлением. Они работают с публичными методами и свойствами движка.

const gui = new dat.GUI({ width: 400 });

gui.add(this.physics, 'enableUpdate');
gui.add(this.physics, 'disableUpdate');

Методы enableUpdate и disableUpdate включают и выключают стандартный автоматический цикл обновления физики, но только если customUpdate равно false. В нашем примере они не будут иметь видимого эффекта, так как автообновление уже отключено на уровне конфигурации.

Самая важная кнопка вызывает world.update() вручную, передавая время, прошедшее с последнего кадра.

const { world } = this.physics;

gui.add({ update: () => { world.update(0, world._frameTimeMS); } }, 'update');

Параметры world.update(0, world._frameTimeMS) означают: первый аргумент (delta) здесь установлен в 0, а второй (time) использует внутреннее время кадра движка. Каждый клик по этой кнопке продвинет физическую симуляцию вперёд на один шаг.

Практическое применение и нюансы

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

Важно помнить: * При customUpdate: true вы полностью отвечаете за вызов world.update(). * Внутреннее свойство world._frameTimeMS хранит время последнего кадра. Для нестандартных интервалов можно передавать своё значение дельты времени. * Все физические тела, созданные через this.physics.add, зависят от этого обновления.

Без вызовов update() тела будут висеть в воздухе, не реагируя на гравитацию, скорость и столкновения, даже если они заданы.

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

Режим customUpdate — мощный инструмент для нестандартных игровых механик, где автоматический цикл обновления не подходит. Вы получаете рычаги для управления производительностью и временем в симуляции. Поэкспериментируйте: привяжите вызов world.update() к событию мыши, таймеру или сетевому пакету. Попробуйте создать пошаговый физический паззл или синхронизировать движение объектов с аудиодорожкой.