О чем этот пример
Одна из ключевых задач при разработке игр — обеспечить плавное и предсказуемое движение объектов, независимо от производительности устройства игрока. Если привязывать скорость к кадрам в секунду (FPS), на мощном компьютере объекты будут мчаться, а на слабом — еле ползти. В этой статье мы разберем, как использовать дельту времени (`delta`) и утилиту `Phaser.Math.GetSpeed()` для создания движения с постоянной скоростью в мире Phaser. Этот подход гарантирует, что ваша игра будет вести себя одинаково на любом железе.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
speed2;
speed1;
bullet2;
bullet1;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('bullet', 'assets/tests/timer/bullet-bill.png');
this.load.image('cannon', 'assets/tests/timer/cannon.png');
this.load.image('ground', 'assets/tests/timer/ground.png');
}
create ()
{
// Bullet 1 (600px in 6 seconds)
this.add.image(0, 200, 'ground').setOrigin(0);
this.bullet1 = this.add.image(64, 76, 'bullet').setOrigin(0);
this.speed1 = Phaser.Math.GetSpeed(600, 6);
this.add.image(64, 72, 'cannon').setOrigin(0);
this.add.text(64, 50, '600px / 6 secs', { fill: '#000' });
// Bullet 2 (600px in 3 seconds)
this.add.image(0, 500, 'ground').setOrigin(0);
this.bullet2 = this.add.image(64, 376, 'bullet').setOrigin(0);
this.speed2 = Phaser.Math.GetSpeed(600, 3);
this.add.image(64, 500, 'cannon').setOrigin(0, 1);
this.add.text(64, 350, '600px / 3 secs', { fill: '#000' });
}
update (time, delta)
{
this.bullet1.x += this.speed1 * delta;
if (this.bullet1.x > 864)
{
this.bullet1.x = 64;
}
this.bullet2.x += this.speed2 * delta;
if (this.bullet2.x > 864)
{
this.bullet2.x = 64;
}
}
}
const config = {
type: Phaser.CANVAS,
width: 800,
height: 600,
parent: 'phaser-example',
backgroundColor: '#9adaea',
useTicker: true,
scene: Example
};
const game = new Phaser.Game(config);
Проблема: скорость, привязанная к FPS
Наивный подход к движению — увеличивать координату объекта на фиксированное значение в каждом кадре.
// ПЛОХО: скорость зависит от частоты кадров
this.bullet.x += 5;
Если игра работает на 60 FPS, объект переместится на 300 пикселей в секунду (5 * 60). Но если FPS упадет до 30, то скорость упадет вдвое — до 150 пикселей в секунду. Это ломает игровой баланс. Нужна скорость, которая измеряется в пикселях в секунду и не зависит от FPS.
Решение: дельта времени и GetSpeed
Phaser передает в метод update параметр delta — это время, прошедшее с предыдущего кадра, в миллисекундах. Умножая скорость (в пикселях за миллисекунду) на эту дельту, мы получаем точное расстояние, которое должен пройти объект за прошедший промежуток времени.
Утилита Phaser.Math.GetSpeed(distance, time) рассчитывает именно такую скорость. Она преобразует понятные нам значения (например, «пройти 600 пикселей за 6 секунд») в значение, готовое для умножения на delta.
// Рассчитываем скорость: 600px за 6000ms (6 секунд)
this.speed1 = Phaser.Math.GetSpeed(600, 6);
// speed1 теперь содержит значение в пикселях за миллисекунду
Анализ исходного кода примера
В примере создаются две пули, которые движутся с разной скоростью, но оба движения независимы от FPS.
**1. Загрузка и создание сцены:**
preload() {
this.load.image('bullet', 'assets/tests/timer/bullet-bill.png');
// ... загрузка других изображений
}
create() {
// Создание первого снаряда и его платформы
this.add.image(0, 200, 'ground').setOrigin(0);
this.bullet1 = this.add.image(64, 76, 'bullet').setOrigin(0);
// Ключевой расчет скорости
this.speed1 = Phaser.Math.GetSpeed(600, 6);
// ... создание пули 2 с другой скоростью
}
**2. Основная логика в update:**
update (time, delta) {
// Движение пули 1: скорость * время, прошедшее с прошлого кадра
this.bullet1.x += this.speed1 * delta;
// Сброс позиции при выходе за границу экрана (864px)
if (this.bullet1.x > 864) {
this.bullet1.x = 64;
}
// Аналогично для пули 2
this.bullet2.x += this.speed2 * delta;
if (this.bullet2.x > 864) {
this.bullet2.x = 64;
}
}
Пуля 2 использует GetSpeed(600, 3), поэтому она движется в два раза быстрее первой, так как проходит то же расстояние за меньшее время. Умножение на delta делает обе скорости frame-rate independent.
Конфигурация игры: useTicker
Обратите внимание на конфиг игры. В нем явно указана опция useTicker: true. Это стандартное и часто опускаемое значение, которое означает, что Phaser будет использовать свой внутренний игровой цикл (ticker) для вызова update. Именно этот тикер и передает в метод update актуальное значение delta. Если вы создаете кастомный игровой цикл, эта опция может быть важна.
const config = {
type: Phaser.CANVAS,
width: 800,
height: 600,
parent: 'phaser-example',
backgroundColor: '#9adaea',
useTicker: true, // Гарантирует, что update будет получать параметр delta
scene: Example
};
Практическое применение и паттерны
Используйте этот паттерн для любого движения, анимации или таймеров.
* **Плавное движение врага к цели:** Рассчитайте скорость один раз в create или при появлении врага.
* **Исчезновение объекта (fade out):** Уменьшайте значение его прозрачности (alpha) на speed * delta.
* **Вращение:** Увеличивайте угол (angle) объекта на rotationSpeed * delta.
// Пример: вращение со скоростью 180 градусов в секунду
create() {
this.sprite = this.add.sprite(400, 300, 'star');
// 180 градусов / 1000 мс = 0.18 градуса/мс
this.rotationSpeed = Phaser.Math.GetSpeed(180, 1);
}
update(time, delta) {
this.sprite.angle += this.rotationSpeed * delta;
}
Всегда рассчитывайте скорость через GetSpeed для понятных единиц измерения (пиксели/секунду, градусы/секунду), а затем умножайте на delta в update.
Что попробовать дальше
Использование дельты времени (delta) — фундаментальная практика в геймдеве для создания независимого от частоты кадров движения. Утилита Phaser.Math.GetSpeed() выступает удобным переводчиком между человеко-читаемыми единицами и внутренними расчетами. Для экспериментов попробуйте
- Сделать движение по вертикали или диагонали
- Добавить ускорение, также зависящее от
delta - Создать таймер обратного отсчета для способности, уменьшая значение на
speed * delta
