О чем этот пример
В играх неизбежны моменты, когда производительность проседает: масштабные спецэффекты, сложные вычисления или загрузка ресурсов могут вызвать фризы. Этот пример наглядно показывает, как система твинов Phaser и настройки FPS помогают сгладить визуальное восприятие таких лагов, сохраняя плавность анимации даже при блокировке главного потока. Вы научитесь настраивать параметры кадровой частоты и использовать отложенные анимации, чтобы игрок не замечал кратковременных "тормозов".
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
let shouldRun = true;
function blockCpuFor(ms) {
var now = new Date().getTime();
console.log('start blocking');
var result = 0;
while(shouldRun) {
result += Math.random() * Math.random();
if (new Date().getTime() > now +ms)
{
console.log('end blocking');
return;
}
}
}
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('chunk', 'assets/sprites/chunk.png');
}
create ()
{
let x = 0;
let y = 0;
const chunks = [];
for (let i = 0; i < 2560; i++)
{
chunks.unshift(this.add.image(x, y, 'chunk').setOrigin(0, 0));
x += 5;
if (x >= 800)
{
x = 0;
y += 5;
}
}
this.input.once('pointerdown', () => {
chunks.forEach((chunk, index) => {
this.tweens.add({
targets: chunk,
y: '+=500',
duration: 2000,
ease: 'Linear',
delay: index * 1
});
});
setTimeout(() => {
blockCpuFor(1500)
}, 1000);
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example,
fps: {
smoothStep: false,
limit: 30
}
};
const game = new Phaser.Game(config);
Суть примера: Имитация лага и сглаживание
Пример создаёт сетку из 2560 спрайтов (чанков) и запускает их одновременное падение с задержкой. Ключевой момент — искусственная блокировка главного потока на 1.5 секунды с помощью функции blockCpuFor через секунду после начала анимации. Это имитирует тяжелые вычисления в игре. Однако, благодаря настройкам FPS и тому, как Phaser обрабатывает твины, анимация не дёргается резко, а продолжает плавно "доезжать" до своих целей после завершения лага.
Основная идея: твины в Phaser работают на основе времени, а не кадров. Они вычисляют текущее состояние анимации, опираясь на прошедшее время с момента её старта. Если рендеринг на момент обновления был заблокирован, твин просто вычислит более значительный шаг прогресса, чтобы "догнать" ожидаемое состояние.
Разбор настройки FPS: `limit` и `smoothStep`
Конфигурация игры специально задаёт низкий лимит FPS и отключает сглаживающую интерполяцию. Это сделано для демонстрации, но принцип работает и при стандартных 60 FPS.
fps: {
smoothStep: false,
limit: 30
}
* limit: 30: Устанавливает целевой лимит в 30 кадров в секунду. Движок будет стараться поддерживать это значение, что может помочь в планировании ресурсов.
* smoothStep: false: Отключает функцию сглаживания (smoothStep) рендеринга между физическими обновлениями. Когда smoothStep включен, Phaser пытается интерполировать позиции объектов для более плавной отрисовки при возможных проседаниях частоты кадров. В этом примере мы видим "чистое" поведение системы твинов без дополнительной интерполяции.
Создание анимации с задержкой для каждого спрайта
Массив chunks заполняется спрайтами, выстроенными в сетку. По клику для каждого чанка создаётся отдельный твин с уникальной задержкой (delay). Это создаёт волнообразный эффект падения.
this.tweens.add({
targets: chunk, // Цель анимации — конкретный спрайт
y: '+=500', // Свойство для изменения: увеличение Y на 500 пикселей
duration: 2000, // Общая длительность анимации в миллисекундах
ease: 'Linear', // Линейная функция плавности (без ускорения)
delay: index * 1 // Задержка старта для каждого спрайта
});
Критически важный параметр — duration: 2000. Это означает, что анимация каждого спрайта должна завершиться ровно через 2 секунды после своего СТАРТА (с учётом delay). Система твинов гарантирует это, даже если между кадрами был длительный перерыв из-за лага.
Искусственный лаг: функция `blockCpuFor`
Функция blockCpuFor синхронно блокирует главный поток на указанное время, загружая процессор бессмысленными вычислениями. Это грубая, но эффективная имитация ситуации, когда игра выполняет тяжёлую задачу.
function blockCpuFor(ms) {
var now = new Date().getTime();
console.log('start blocking');
var result = 0;
while(shouldRun) {
result += Math.random() * Math.random();
if (new Date().getTime() > now + ms) {
console.log('end blocking');
return;
}
}
}
В примере лаг запускается через секунду после клика (setTimeout(() => { blockCpuFor(1500) }, 1000);). В этот момент анимация уже идёт, и браузер не может обновлять экран. Но как только выполнение кода возвращается, твины мгновенно пересчитывают позиции всех спрайтов, приводя их в состояние, соответствующее прошедшему времени, и анимация продолжается без рывков.
Что попробовать дальше
Phaser эффективно отделяет логику анимации от частоты рендеринга, используя время как основу для расчётов. Это позволяет играм "восстанавливаться" после кратковременных фризов. Для экспериментов попробуйте: изменить smoothStep на true и сравнить визуальный эффект при лаге; увеличить количество спрайтов или длительность блокировки; заменить линейный ease на 'Bounce.Out' и посмотреть, как ведёт себя сложная анимация при проседании FPS. Это поможет лучше понять, как планировать ресурсоёмкие операции, не ломая игровой опыт.
