О чем этот пример
В этой статье мы разберём пример, который демонстрирует мощь графического API Phaser. Используя объект `Graphics`, мы нарисуем и анимируем логотип фреймворка, состоящий из нескольких цветных слоёв. Этот подход полезен для создания динамического UI, нестандартных визуальных эффектов и отрисовки геометрических фигур напрямую на канвасе, без использования растровых изображений. Вы научитесь управлять контекстом рисования: трансформациями, стилями линий и сложными путями (paths).
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
r = 0;
graphics;
create ()
{
this.graphics = this.add.graphics();
}
update ()
{
this.r += 0.02;
this.graphics.clear();
// From back to front :)
this.drawLogo(0x650a05, -380, -100, 0.76);
this.drawLogo(0xa00d05, -380, -100, 0.78);
this.drawLogo(0xcd1106, -380, -100, 0.80);
this.drawLogo(0xf53719, -380, -100, 0.82);
this.drawLogo(0xf25520, -380, -100, 0.84);
this.drawLogo(0xf26f21, -380, -100, 0.86);
this.drawLogo(0xf49214, -380, -100, 0.88);
this.drawLogo(0xf6a90a, -380, -100, 0.90);
this.drawLogo(0xfad400, -380, -100, 0.92);
this.drawLogo(0xfef700, -380, -100, 0.94);
this.drawLogo(0xffff45, -380, -100, 0.96);
this.drawLogo(0xffffc4, -380, -100, 0.98);
this.drawLogo(0xffffff, -380, -100, 1.00);
}
drawLogo (color, x, y, scale)
{
const thickness = 2;
const alpha = 1;
this.graphics.lineStyle(thickness, color, alpha);
const w = 100;
const h = 200;
const h2 = 100;
const top = y + 0;
const mid = y + 100;
const bot = y + 200;
const s = 20;
this.graphics.save();
this.graphics.translateCanvas(400, 300);
this.graphics.scaleCanvas(scale, scale);
this.graphics.rotateCanvas(this.r);
this.graphics.beginPath();
// P
this.graphics.moveTo(x, top);
this.graphics.lineTo(x + w, top);
this.graphics.lineTo(x + w, mid);
this.graphics.lineTo(x, mid);
this.graphics.lineTo(x, bot);
// H
x += w + s;
this.graphics.moveTo(x, top);
this.graphics.lineTo(x, bot);
this.graphics.moveTo(x, mid);
this.graphics.lineTo(x + w, mid);
this.graphics.moveTo(x + w, top);
this.graphics.lineTo(x + w, bot);
// A
x += w + s;
this.graphics.moveTo(x, bot);
this.graphics.lineTo(x + (w * 0.75), top);
this.graphics.lineTo(x + (w * 0.75) + (w * 0.75), bot);
// S
x += ((w * 0.75) * 2) + s;
this.graphics.moveTo(x + w, top);
this.graphics.lineTo(x, top);
this.graphics.lineTo(x, mid);
this.graphics.lineTo(x + w, mid);
this.graphics.lineTo(x + w, bot);
this.graphics.lineTo(x, bot);
// E
x += w + s;
this.graphics.moveTo(x + w, top);
this.graphics.lineTo(x, top);
this.graphics.lineTo(x, bot);
this.graphics.lineTo(x + w, bot);
this.graphics.moveTo(x, mid);
this.graphics.lineTo(x + w, mid);
// R
x += w + s;
this.graphics.moveTo(x, top);
this.graphics.lineTo(x + w, top);
this.graphics.lineTo(x + w, mid);
this.graphics.lineTo(x, mid);
this.graphics.lineTo(x, bot);
this.graphics.moveTo(x, mid);
this.graphics.lineTo(x + w, bot);
this.graphics.strokePath();
this.graphics.restore();
}
}
const config = {
width: 800,
height: 600,
type: Phaser.AUTO,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и объекта Graphics
Вся работа начинается в классе сцены. Мы создаём экземпляр графического объекта и объявляем переменную для угла вращения.
class Example extends Phaser.Scene
{
r = 0;
graphics;
create ()
{
this.graphics = this.add.graphics();
}
Метод create вызывается один раз при инициализации сцены. Здесь мы создаём объект this.graphics через this.add.graphics(). Этот объект — наш холст для векторного рисования. Переменная `r` будет хранить текущий угол поворота логотипа и инициализируется нулём.
Цикл обновления и очистка холста
Анимация происходит в методе update, который вызывается на каждом кадре. Перед отрисовкой нового кадра мы очищаем старое изображение и увеличиваем угол вращения.
update ()
{
this.r += 0.02;
this.graphics.clear();
// From back to front :)
this.drawLogo(0x650a05, -380, -100, 0.76);
this.drawLogo(0xa00d05, -380, -100, 0.78);
// ... остальные вызовы drawLogo
this.drawLogo(0xffffff, -380, -100, 1.00);
}
Ключевые моменты:
1. `this.r += 0.02;` — инкремент угла вращения, что обеспечивает плавную анимацию.
2. `this.graphics.clear();` — полностью очищает графический объект от всех ранее нарисованных линий и фигур. Без этого вызова кадры накладывались бы друг на друга.
3. Многократный вызов `this.drawLogo()` с разными цветами и масштабами создаёт эффект «толстого» объёмного логотипа. Обратите внимание на комментарий «From back to front»: сначала рисуются дальние (меньшие) слои, затем ближние, что создаёт правильное наложение цветов.
Функция отрисовки буквы: drawLogo
Основная логика рисования заключена в методе drawLogo. Он настраивает стиль линии, применяет трансформации к контексту холста и рисует путь (path), составляющий слово "PHASER".
drawLogo (color, x, y, scale)
{
const thickness = 2;
const alpha = 1;
this.graphics.lineStyle(thickness, color, alpha);
// ... объявление переменных w, h, top, mid, bot, s
this.graphics.save();
this.graphics.translateCanvas(400, 300);
this.graphics.scaleCanvas(scale, scale);
this.graphics.rotateCanvas(this.r);
// ... команды рисования пути (moveTo, lineTo)
this.graphics.strokePath();
this.graphics.restore();
}
Разберём по порядку:
- `this.graphics.lineStyle(thickness, color, alpha);` — задаёт стиль для последующих линий: толщину, цвет (в hex) и прозрачность.
- `this.graphics.save();` — сохраняет текущее состояние контекста рисования (все трансформации). Это критически важно.
- Далее применяются трансформации:
- `translateCanvas(400, 300)` — смещает точку отсчёта (0,0) в центр экрана (при размере игры 800x600).
- `scaleCanvas(scale, scale)` — масштабирует весь последующий рисунок. Параметр `scale` разный для каждого цветного слоя.
- `rotateCanvas(this.r)` — вращает холст на текущий угол `r`.
- После этого выполняется последовательность `moveTo` и `lineTo`, которая описывает контур букв.
- `this.graphics.strokePath();` — фактически выполняет отрисовку накопленного пути заданным стилем линии.
- `this.graphics.restore();` — восстанавливает состояние контекста, которое было на момент `save()`. Это сбрасывает все трансформации (смещение, масштаб, поворот) для следующего вызова `drawLogo`, чтобы они не накапливались.
Построение контура букв
Рисование каждой буквы — это последовательное соединение линий. Координаты рассчитываются относительно параметров `xиy, переданных в функцию, и базовых размеровw(ширина буквы) иh` (высота).
// P
this.graphics.moveTo(x, top);
this.graphics.lineTo(x + w, top);
this.graphics.lineTo(x + w, mid);
this.graphics.lineTo(x, mid);
this.graphics.lineTo(x, bot);
// H
x += w + s;
this.graphics.moveTo(x, top);
this.graphics.lineTo(x, bot);
this.graphics.moveTo(x, mid);
this.graphics.lineTo(x + w, mid);
this.graphics.moveTo(x + w, top);
this.graphics.lineTo(x + w, bot);
Алгоритм:
1. moveTo(x, y) — перемещает «перо» в начальную точку отрезка, не рисуя.
2. lineTo(x, y) — рисует линию из текущей позиции пера в указанные координаты, после чего перо остаётся в новой точке.
3. Для перехода к следующей букве переменная `xувеличивается на ширину буквыwплюс интервалs:x += w + s;`. Таким образом, каждая следующая буква смещается вправо.
4. Обратите внимание, что для буквы H используется несколько команд moveTo, так как её контур состоит из трёх несвязанных отрезков. Метод strokePath() нарисует их все одним стилем.
Конфигурация и запуск игры
За пределами класса сцены находится стандартная конфигурация игры Phaser 3, которая указывает размеры, тип рендерера и запускает нашу сцену.
const config = {
width: 800,
height: 600,
type: Phaser.AUTO,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Здесь нет ничего специфичного для графики:
- width и height задают размер области отрисовки.
- type: Phaser.AUTO позволяет Phaser самому выбрать WebGL или Canvas рендерер.
- parent — это ID HTML-элемента, в который будет встроен canvas.
- scene: Example — регистрирует наш класс как единственную сцену игры.
Создание экземпляра Phaser.Game с этой конфигурацией запускает игровой цикл.
Что попробовать дальше
Этот пример — отличная отправная точка для освоения низкоуровневого рисования в Phaser. Вы узнали, как использовать объект Graphics для создания динамических векторных изображений, управлять трансформациями контекста и строить сложные пути. Для экспериментов попробуйте:
1. Изменить цвета или сделать их плавно меняющимися со временем.
2. Анимировать не только вращение, но и масштаб или положение каждого слоя независимо, создавая эффект параллакса.
3. Заменить отрисовку линий на заполненные фигуры, используя beginPath() и fillPath() вместе с fillStyle().
4. Реализовать интерактивность: например, менять скорость вращения при клике.
