О чем этот пример
Динамический игровой фон — это не просто украшение, а способ создать настроение и вовлечь игрока с первых секунд. В этой статье мы разберем, как с помощью системы твинов Phaser 3 и простого спрайта создать сложную, живую анимацию фона, где каждый элемент вращается независимо и с разной задержкой. Этот прием отлично подойдет для меню, загрузочных экранов или космических симуляторов, добавляя глубину и профессиональный вид вашей игре без значительных затрат ресурсов.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('block', 'assets/sprites/checkerboard.png');
}
create ()
{
const blocks = this.add.group({ key: 'block', repeat: 107 });
Phaser.Actions.GridAlign(blocks.getChildren(), {
width: 12,
height: 9,
cellWidth: 64,
cellHeight: 64
});
const a = [ 0, 90, 180, 270 ];
blocks.children.forEach(child => {
child.angle = Phaser.Math.RND.pick(a);
this.tweens.add({
targets: child,
ease: 'Power1',
duration: 250,
delay: (Math.random() * 6000),
repeatDelay: 3000 + (Math.random() * 6000),
repeat: -1,
angle: {
getEnd: (target, key, value) =>
{
var a = 90;
if (Math.random() > 0.5)
{
a = 180;
}
if (Math.random() > 0.5)
{
return target.angle + a;
}
else
{
return target.angle - a;
}
},
getStart: (target, key, value) =>
{
return target.angle;
}
}
});
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание сетки спрайтов
Первым делом мы загружаем текстуру и создаем из нее множество одинаковых спрайтов, выстроенных в аккуратную сетку. Это основа нашего анимированного фона.
В методе preload мы загружаем одно изображение — плитку в виде шахматной доски. Ключевой момент происходит в create: мы создаем группу (Group), которая сразу генерирует 108 спрайтов (1 исходный + 107 повторений) на основе загруженного изображения 'block'.
const blocks = this.add.group({ key: 'block', repeat: 107 });
Далее мы используем хелпер Phaser.Actions.GridAlign, чтобы расставить всех детей этой группы по сетке 12x9 с заданным размером ячейки. Это превращает хаотичную кучу спрайтов в упорядоченное поле.
Phaser.Actions.GridAlign(blocks.getChildren(), {
width: 12,
height: 9,
cellWidth: 64,
cellHeight: 64
});
Запуск анимации: основы твинов
Твин (tween) — это инструмент для плавного изменения свойств объекта с течением времени. В нашем случае мы будем изменять свойство angle каждого спрайта, чтобы он вращался.
Для начала мы задаем каждому блоку случайный начальный угол, выбранный из массива [0, 90, 180, 270]. Это создает визуально разнообразную стартовую позицию.
blocks.children.forEach(child => {
child.angle = Phaser.Math.RND.pick(a);
Затем для каждого спрайта создается отдельный твин с помощью this.tweens.add(). Основные параметры:
* targets: объект (наш спрайт), свойства которого будут анимироваться.
* duration: длительность одного цикла анимации (250 мс — быстрое вращение).
* delay: начальная задержка перед первым запуском твина (случайная, до 6 секунд). Это ключевой параметр для разнесения анимаций во времени.
* repeatDelay: пауза между повторениями анимации (от 3 до 9 секунд).
* repeat: -1: твин будет повторяться бесконечно.
this.tweens.add({
targets: child,
ease: 'Power1',
duration: 250,
delay: (Math.random() * 6000),
repeatDelay: 3000 + (Math.random() * 6000),
repeat: -1,
// ... свойство angle будет описано ниже
});
Динамическое вычисление углов: getStart и getEnd
Стандартный твин плавно меняет свойство от значения from к значению to. Но нам нужно не просто вращение на фиксированный угол, а логика: "повернись от текущего угла на +90 или +180 градусов в случайном направлении". Для такой нестандартной логины Phaser позволяет использовать функции-геттеры getStart и getEnd.
Функция getStart вызывается в начале каждого цикла твина и просто возвращает текущий угол спрайта. Это отправная точка для анимации.
getStart: (target, key, value) => {
return target.angle;
}
Функция getEnd определяет целевой угол. Ее логика:
1. С вероятностью 50% цель — поворот на 90 градусов, иначе — на 180.
2. С вероятностью 50% поворот будет по часовой стрелке (прибавить угол), иначе — против (вычесть угол).
Таким образом, каждый блок в случайный момент времени совершает резкий, но плавный поворот на 90 или 180 градусов в случайном направлении, создавая сложную, "живую" картину.
getEnd: (target, key, value) => {
var a = 90;
if (Math.random() > 0.5) {
a = 180;
}
if (Math.random() > 0.5) {
return target.angle + a;
} else {
return target.angle - a;
}
}
Итоговая картина и настройка
В результате мы получаем сцену, где 108 плиток, выстроенных в сетку, периодически и независимо друг от друга совершают резкие повороты. Разброс в начальной задержке (delay) и задержке между повторами (repeatDelay) гарантирует, что анимация никогда не синхронизируется и выглядит хаотично и бесконечно разнообразно.
Конфигурация игры стандартна: мы указываем размеры холста, цвет фона и передаем наш класс сцены.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что попробовать дальше
Использование твинов с функциями getStart и getEnd открывает огромные возможности для создания сложного поведения на основе простых правил. Вы можете экспериментировать: заменить angle на scale для пульсирующего фона, использовать alpha для мерцания или менять `x,y` для эффекта "плывущих" платформ. Попробуйте привязать логику вычисления конечного значения не к случайности, а, например, к положению мыши или времени, чтобы создать интерактивный фон.
