О чем этот пример
Пример из официального репозитория 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 для реакции на столкновение с каждой из границ экрана.
