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

Пример из официального репозитория Phaser наглядно демонстрирует работу Arcade Physics с отскоками (bounce) и границами мира. Это не просто анимация падающего спрайта, а полноценный симулятор, который отслеживает и визуализирует ключевые параметры движения. Понимание этих принципов критически важно для создания реалистичных платформеров, пинболов, аркадных игр с мячом или любых сцен, где объекты взаимодействуют с окружением. Статья разберет код по косточкам и покажет, как можно адаптировать эту механику под свои нужды.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    logo;
    text;
    prevDirection;
    direction;

    maxY = 0;
    minY = 600;
    lastY = 0;
    duration = 0;
    prevDuration = 0;




    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('logo', 'assets/sprites/phaser3-logo.png');
        this.load.image('marker', 'assets/sprites/longarrow.png');
    }

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

        this.logo.setOrigin(0.5, 0);
        this.logo.setVelocity(0, 60);
        this.logo.setBounce(1, 1);
        this.logo.setCollideWorldBounds(true);

        this.lastY = this.logo.y;

        this.text = this.add.text(10, 10, '', { font: '16px Courier', fill: '#00ff00' });

        // this.physics.world.timeScale = 0.1;

        // this.sys.events.on('postupdate', update, this);
    }

    update (time, delta)
    {
        this.text.setText([
            `steps: ${this.physics.world._lastCount}`,
            `this.duration: ${this.prevDuration}`,
            `last y: ${this.lastY}`,
            `min y: ${this.minY}`,
            `max y: ${this.maxY}`
        ]);

        if (Phaser.Math.Fuzzy.LessThan(this.logo.body.velocity.y, 0, 0.1))
        {
            this.direction = 'up';
        }
        else
        {
            this.direction = 'down';
        }

        if (this.prevDirection !== this.direction && this.prevDirection === 'up')
        {
            const marker = this.add.sprite(0, this.logo.y + 18, 'marker');

            marker.setOrigin(0, 1);

            this.lastY = this.logo.y;

            this.prevDuration = this.duration;
            this.duration = 0;
        }

        this.prevDirection = this.direction;
        this.duration += delta;

        this.minY = Math.min(this.minY, this.logo.y);
        this.maxY = Math.max(this.minY, this.maxY, this.lastY);
    }

}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 150 }
        }
    },
    scene: Example
};

const game = new Phaser.Game(config);


Настройка сцены и физики

Вся магия начинается с конфигурации игры. Ключевой момент — активация Arcade Physics с гравитацией.

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    parent: 'phaser-example',
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 150 }
        }
    },
    scene: Example
};

В методе create() создается физический спрайт — логотип Phaser. Ему сразу задаются ключевые свойства: скорость по оси Y, упругость (bounce) и привязка к границам мира (world bounds). Значение `1` для отскока означает 100% упругость — энергия не теряется.

create ()
{
    this.logo = this.physics.add.image(400, 100, 'logo');
    this.logo.setOrigin(0.5, 0);
    this.logo.setVelocity(0, 60);
    this.logo.setBounce(1, 1);
    this.logo.setCollideWorldBounds(true);
    // ... инициализация текста и переменных
}

Следим за движением: логика в update()

Сердце примера — метод update(). Здесь происходит три важные вещи: обновление отладочной информации, определение направления движения и фиксация момента смены этого направления (т.е., достижения верхней точки прыжка).

Определение направления использует Phaser.Math.Fuzzy.LessThan для сравнения вертикальной скорости с нулем с небольшой погрешностью (0.1). Это помогает избежать ложных срабатываний из-за микрофлуктуаций скорости.

if (Phaser.Math.Fuzzy.LessThan(this.logo.body.velocity.y, 0, 0.1))
{
    this.direction = 'up';
}
else
{
    this.direction = 'down';
}

Когда направление меняется с 'up' на 'down', объект достигает апогея. В этот момент создается маркер-стрелка, сбрасывается таймер полета (duration) и сохраняется предыдущее время (prevDuration).

if (this.prevDirection !== this.direction && this.prevDirection === 'up')
{
    const marker = this.add.sprite(0, this.logo.y + 18, 'marker');
    marker.setOrigin(0, 1);
    this.lastY = this.logo.y;
    this.prevDuration = this.duration;
    this.duration = 0;
}

Переменная delta — это время в миллисекундах, прошедшее с последнего кадра. Накопление duration позволяет измерить время между двумя последовательными верхними точками.

this.duration += delta;

Визуализация и отладка данных

Пример не просто двигает спрайт, но и предоставляет богатую текстовую отладку. Вся статистика выводится с помощью объекта this.text.

this.text.setText([
    `steps: ${this.physics.world._lastCount}`,
    `this.duration: ${this.prevDuration}`,
    `last y: ${this.lastY}`,
    `min y: ${this.minY}`,
    `max y: ${this.maxY}`
]);

Что значат эти параметры? * steps: количество шагов физического движка за последний кадр (полезно для профилирования). * this.duration: время (в мс), за которое объект совершил предыдущий полет от нижней точки до верхней. * last y, min y, max y: отслеживание экстремумов по вертикали для анализа амплитуды.

Строка this.physics.world.timeScale = 0.1; в коде закомментирована, но её раскомментирование — отличный способ замедлить симуляцию физики и детально рассмотреть процесс.

Практические применения и адаптация

Этот код — готовый каркас для множества игровых механик.

**Реалистичный отскок:** Измените значение setBounce на число меньше 1 (например, 0.7), чтобы энергия терялась, и объект со временем остановился.

this.logo.setBounce(0.7, 0.7);

**Платформер:** Вместо отслеживания верхней точки (direction) ловите момент касания земли (body.touching.down), чтобы разрешить прыжок.

**Игра с мячом:** Добавьте несколько спрайтов с отскоком и столкновения между ними.

this.physics.add.collider(ball1, ball2);

**Триггеры событий:** Создание маркера в апогее — это уже пример реакции на событие. Эту логику можно вынести в отдельный метод и использовать для начисления очков, воспроизведения звука или активации спецэффектов.

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

Пример "bounce test" — это мини-лаборатория по физике Phaser. Он учит не только настраивать отскоки, но и глубоко анализировать поведение объектов, что бесценно при отладке сложных взаимодействий. Для экспериментов попробуйте: изменить гравитацию на отрицательную (объект улетит вверх), добавить горизонтальное движение и отскок от боковых границ, или привязать создание маркеров и логику к событию worldbounds для реакции на столкновение с каждой из границ экрана.