О чем этот пример
Одна из частых задач в играх — создание орбитального движения объектов, будь то спутники вокруг планеты или враги, кружащие вокруг игрока. Простое вращение — это полдела. Настоящая магия начинается, когда вращающийся объект всегда направлен своим "лицом" к центру орбиты. В этой статье мы разберём пример из официальной коллекции Phaser, который показывает, как использовать `Phaser.Actions.RotateAroundDistance` для орбитального движения контейнера и как рассчитать угол поворота этого контейнера, чтобы он всегда был направлен на центральную точку. Этот приём полезен для создания сложных составных объектов (например, космического корабля с оружием по бокам), которые должны синхронно двигаться по орбите.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
this.container;
this.center = {x: 400, y: 300}
this.rotateSpeed = 0.02
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.spritesheet('diamonds', 'assets/sprites/diamonds32x24x5.png', { frameWidth: 32, frameHeight: 24 });
}
create ()
{
this.add.sprite(this.center.x, this.center.y, 'diamonds', 1); // center point. We will rotate around it
this.container = this.add.container(600, 300);
const text = this.add.text(-25, -50, 'Phaser');
const diamond1 = this.add.sprite(0, 0, 'diamonds', 1);
diamond1.setScale(2)
const diamond2 = this.add.sprite(15, 0, 'diamonds', 2);
diamond2.setScale(2)
const diamond3 = this.add.sprite(-15, 0, 'diamonds', 3);
diamond3.setScale(2)
this.container.add([diamond1, diamond2, diamond3, text])
// stop rotation on click
this.input.on('pointerdown', function() {
if (this.rotateSpeed > 0) {
this.rotateSpeed = 0
} else {
this.rotateSpeed = 0.02
}
}, this);
}
update ()
{
Phaser.Actions.RotateAroundDistance([this.container], this.center, this.rotateSpeed, 250);
const angleDeg = Math.atan2(this.container.y - this.center.y, this.container.x - this.center.x) * 180 / Math.PI;
this.container.angle = angleDeg+90 // container should face the center point
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание контейнера
В конструкторе класса сцены мы инициализируем переменные для хранения контейнера, центра вращения и скорости вращения. Центр вращения — это обычный объект с координатами `xиy`.
В методе create происходит основная настройка. Сначала мы создаём спрайт в центре, чтобы визуально обозначить точку вращения. Затем создаётся контейнер — специальный объект Phaser, который может содержать в себе другие игровые объекты. Мы помещаем его в начальную точку на некотором удалении от центра.
Внутрь контейнера добавляются три увеличенных спрайта-бриллианта и текстовый объект. Все они становятся детьми контейнера и будут перемещаться и вращаться вместе с ним.
this.container = this.add.container(600, 300);
const diamond1 = this.add.sprite(0, 0, 'diamonds', 1);
const text = this.add.text(-25, -50, 'Phaser');
this.container.add([diamond1, diamond2, diamond3, text]);
Также в create добавляется обработчик клика мыши, который останавливает или возобновляет вращение. Это реализовано простым переключением переменной rotateSpeed между 0 и 0.02.
Движение по орбите с помощью Actions
Ключевая часть логики движения находится в методе update, который выполняется каждый кадр. Для реализации движения по круговой орбите используется мощный метод Phaser.Actions.RotateAroundDistance. Эта функция из встроенного набора "действий" (Actions) применяет геометрическое преобразование к массиву объектов.
Метод принимает четыре аргумента:
1. Массив объектов (в нашем случае — массив с одним контейнером).
2. Точку (object c координатами `x,y`), вокруг которой идёт вращение.
3. Угол (в радианах), на который нужно повернуть объект за вызов. Мы передаём небольшую величину this.rotateSpeed (0.02 радиана).
4. Расстояние от центра вращения до объекта — радиус орбиты.
Phaser.Actions.RotateAroundDistance([this.container], this.center, this.rotateSpeed, 250);
Важно понимать, что этот метод **не вращает** сам контейнер. Он перемещает его позицию (`x,y`) по окружности заданного радиуса. Визуально контейнер и его дети начинают "бегать" по кругу вокруг центральной точки.
Расчёт угла поворота для "взгляда" на центр
После того как контейнер перемещён по орбите, его графическое представление всё ещё смотрит вверх (угол поворота по умолчанию равен 0). Чтобы он "смотрел" на центр вращения, как стрелка компаса, нужно вычислить правильный угол.
Для этого используется тригонометрия. Мы вычисляем вектор, направленный от позиции контейнера к центру вращения, и находим его угол относительно горизонтальной оси. Функция Math.atan2 идеально подходит для этого, так как корректно работает со всеми четвертями окружности. Она возвращает угол в радианах, который мы тут же переводим в градусы.
const angleDeg = Math.atan2(this.container.y - this.center.y, this.container.x - this.center.x) * 180 / Math.PI;
Однако полученный угол направлен **от** контейнера **к** центру. Если мы просто присвоим его свойству container.angle, контейнер будет смотреть своей правой стороной на центр (поскольку нулевой угол в Phaser соответствует направлению "вправо"). Чтобы контейнер смотрел на центр своей "передней" частью (которая изначально направлена вверх), к вычисленному углу нужно прибавить 90 градусов.
this.container.angle = angleDeg + 90;
Таким образом, каждый кадр контейнер сначала перемещается по орбите, а затем разворачивается, чтобы быть направленным на центр.
Конфигурация игры и запуск
Код завершается стандартной для Phaser 3 конфигурацией игры. Мы указываем тип рендерера, размеры холста, цвет фона, элемент на странице, куда будет встроена игра, и класс нашей сцены.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Инициализация экземпляра Phaser.Game с этой конфигурацией запускает игровой цикл, и сцена начинает свою работу.
Что попробовать дальше
Комбинация Phaser.Actions.RotateAroundDistance для перемещения по орбите и ручного расчёта угла через Math.atan2 даёт мощный и гибкий инструмент для создания сложного орбитального движения. Этот паттерн можно применять не только к контейнерам, но и к любым другим игровым объектам.
**Идеи для экспериментов:**
1. Попробуйте изменить радиус орбиты или скорость вращения в зависимости от расстояния до игрока.
2. Добавьте внутрь контейнера спрайт оружия и реализуйте стрельбу в направлении центра (угол для выстрела уже известен!).
3. Используйте не одну, а несколько точек вращения, чтобы создать сложную траекторию (например, цветок).
4. Привяжите центр вращения к спрайту игрока, чтобы создать эффект спутника или охраняющего дрона.
