О чем этот пример
Работа с графикой — основа визуализации в играх. Часто простого рисования фигур недостаточно: нужно оживить их, заставить двигаться, вращаться и менять размер. В этой статье мы разберем, как использовать методы трансформации канваса в Phaser (translateCanvas, scaleCanvas, rotateCanvas) для создания динамических графических объектов. Вы научитесь управлять контекстом рисования, что позволит вам легко анимировать десятки и сотни элементов с минимальным кодом, не прибегая к тяжеловесным спрайтам.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
rectangles = [];
t = 0;
graphics;
create ()
{
this.graphics = this.add.graphics({x: 0, y: 0});
for (let i = 0; i < 100; ++i)
{
this.rectangles.push({
x: Math.random() * 800,
y: Math.random() * 600,
w: 50 + Math.random() * 50,
h: 50 + Math.random() * 50,
r: Math.random()
});
}
}
update ()
{
this.graphics.clear();
for (let i = 0; i < this.rectangles.length; ++i)
{
const rect = this.rectangles[i];
this.graphics.save();
this.graphics.translateCanvas(rect.x, rect.y);
this.graphics.scaleCanvas(Math.sin(rect.r), Math.sin(rect.r));
this.graphics.rotateCanvas(rect.r);
this.graphics.fillStyle(0xFFFF00, 1.0);
this.graphics.lineStyle(4.0, 0xFF0000, 1.0);
this.graphics.fillRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
this.graphics.strokeRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
this.graphics.restore();
rect.r += 0.01;
}
}
}
const config = {
type: Phaser.CANVAS,
parent: 'phaser-example',
scene: Example,
width: 800,
height: 600
};
const game = new Phaser.Game(config);
Подготовка сцены и данных
В начале кода мы создаем класс сцены, который будет хранить массив данных для наших прямоугольников и объект Graphics. В методе create() инициализируется холст для рисования и заполняется массив rectangles.
Каждый элемент массива — это объект со случайными начальными координатами (`x,y), размерами (w,h) и углом поворота (r`). Эти данные будут основой для анимации.
class Example extends Phaser.Scene
{
rectangles = [];
t = 0;
graphics;
create ()
{
this.graphics = this.add.graphics({x: 0, y: 0});
for (let i = 0; i < 100; ++i)
{
this.rectangles.push({
x: Math.random() * 800,
y: Math.random() * 600,
w: 50 + Math.random() * 50,
h: 50 + Math.random() * 50,
r: Math.random()
});
}
}
Цикл обновления и очистка
Метод update() вызывается на каждом кадре игры. Первым делом мы очищаем холст от предыдущего кадра с помощью this.graphics.clear(). Это важно, так как мы рисуем заново все элементы каждый раз, чтобы создать иллюзию плавного движения.
Затем начинается цикл по всем прямоугольникам в массиве. Для каждого из них мы будем применять трансформации и рисовать заново.
update ()
{
this.graphics.clear();
for (let i = 0; i < this.rectangles.length; ++i)
{
const rect = this.rectangles[i];
// ... трансформации и рисование ...
rect.r += 0.01;
}
}
Сохранение и восстановление контекста
Перед применением трансформаций к конкретному прямоугольнику мы вызываем this.graphics.save(). Этот метод сохраняет текущее состояние контекста рисования (все текущие трансформации, стили и т.д.) в стек.
После отрисовки прямоугольника мы вызываем this.graphics.restore(). Это восстанавливает контекст до состояния, сохраненного последним вызовом save(). Такой подход гарантирует, что трансформации, примененные к одному прямоугольнику, не повлияют на отрисовку следующих. Это ключевой паттерн при работе с множественными графическими объектами.
this.graphics.save();
// ... применяем трансформации и рисуем ...
this.graphics.restore();
Трансформации: перемещение, масштабирование, вращение
Сердце анимации — три метода трансформации контекста канваса.
1. `this.graphics.translateCanvas(rect.x, rect.y)` — перемещает начало системы координат (точку (0,0)) в заданные координаты `rect.x` и `rect.y`. Все последующие операции рисования будут отсчитываться от этой новой точки.
2. `this.graphics.scaleCanvas(Math.sin(rect.r), Math.sin(rect.r))` — масштабирует контекст по осям X и Y. В примере используется синус от угла `rect.r`, что создает пульсирующий эффект (масштаб меняется от -1 до 1).
3. `this.graphics.rotateCanvas(rect.r)` — поворачивает контекст на угол `rect.r` (в радианах).
Важно! Порядок вызова этих методов имеет значение. В данном коде сначала выполняется перемещение, потом масштабирование, затем вращение.
this.graphics.translateCanvas(rect.x, rect.y);
this.graphics.scaleCanvas(Math.sin(rect.r), Math.sin(rect.r));
this.graphics.rotateCanvas(rect.r);
Рисование с учетом нового центра
После применения трансформаций мы задаем стили и рисуем сам прямоугольник. Обратите внимание на координаты: fillRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h). Поскольку мы переместили начало координат в центр будущего прямоугольника (translateCanvas(rect.x, rect.y)), чтобы прямоугольник вращался вокруг своего центра, мы начинаем рисовать его не с (0,0), а со смещения в минус половину ширины и высоты. Это классический прием для вращения объекта вокруг его собственного центра, а не вокруг верхнего левого угла.
Методы fillRect и strokeRect рисуют залитый и обведенный прямоугольник соответственно.
this.graphics.fillStyle(0xFFFF00, 1.0);
this.graphics.lineStyle(4.0, 0xFF0000, 1.0);
this.graphics.fillRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
this.graphics.strokeRect(-rect.w / 2, -rect.h / 2, rect.w, rect.h);
Запуск конфигурации игры
В конце файла находится стандартная конфигурация игры Phaser. Указывается тип рендерера (Phaser.CANVAS), элемент-родитель на странице, класс нашей сцены (Example), а также ширина и высота игрового поля. Создание экземпляра Phaser.Game с этой конфигурацией запускает весь описанный выше цикл.
const config = {
type: Phaser.CANVAS,
parent: 'phaser-example',
scene: Example,
width: 800,
height: 600
};
const game = new Phaser.Game(config);
Что попробовать дальше
Использование методов save(), restore() и трансформаций канваса (translateCanvas, scaleCanvas, rotateCanvas) открывает мощный и производительный способ анимации графических примитивов в Phaser. Этот подход идеален для создания частиц, фоновых элементов, UI-эффектов или простых игровых объектов, где использование текстурных спрайтов было бы избыточным.
**Идеи для экспериментов:**
1. Измените формулу для scaleCanvas, чтобы масштаб зависел от времени или положения прямоугольника.
2. Добавьте в объекты прямоугольника свойство скорости и изменяйте координаты `xиyвupdate()`, чтобы они двигались по экрану.
3. Попробуйте изменить порядок вызова трансформаций (например, вращение до перемещения) и посмотрите, как изменится поведение анимации.
4. Вместо прямоугольников нарисуйте более сложные фигуры с помощью graphics.lineTo и graphics.strokePath.
