О чем этот пример
Создание интерактивных персонажей с живым, отслеживающим взглядом — мощный прием для вовлечения игрока. Эта статья разбирает элегантный пример из официальных демо Phaser, где глаза следят за курсором мыши. Вы научитесь использовать геометрические объекты Phaser (`Ellipse`, `Line`, `Vector2`) для расчета реалистичного движения, ограниченного областью глазного яблока, и освоите технику управления несколькими камерами и сценами для сложных UI-элементов или персонажей.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Eyes extends Phaser.Scene {
constructor (handle, parent)
{
super(handle);
this.parent = parent;
this.left;
this.right;
this.leftTarget;
this.rightTarget;
this.leftBase;
this.rightBase;
this.mid = new Phaser.Math.Vector2();
}
create ()
{
const bg = this.add.image(0, 0, 'eyesWindow').setOrigin(0);
this.cameras.main.setViewport(this.parent.x, this.parent.y, Eyes.WIDTH, Eyes.HEIGHT);
this.left = this.add.image(46, 92, 'eye');
this.right = this.add.image(140, 92, 'eye');
this.leftTarget = new Phaser.Geom.Line(this.left.x, this.left.y, 0, 0);
this.rightTarget = new Phaser.Geom.Line(this.right.x, this.right.y, 0, 0);
this.leftBase = new Phaser.Geom.Ellipse(this.left.x, this.left.y, 24, 40);
this.rightBase = new Phaser.Geom.Ellipse(this.right.x, this.right.y, 24, 40);
}
update ()
{
this.leftTarget.x2 = this.input.activePointer.x - this.parent.x;
this.leftTarget.y2 = this.input.activePointer.y - this.parent.y;
// Within the left eye?
if (this.leftBase.contains(this.leftTarget.x2, this.leftTarget.y2))
{
this.mid.x = this.leftTarget.x2;
this.mid.y = this.leftTarget.y2;
}
else
{
Phaser.Geom.Ellipse.CircumferencePoint(this.leftBase, Phaser.Geom.Line.Angle(this.leftTarget), this.mid);
}
this.left.x = this.mid.x;
this.left.y = this.mid.y;
this.rightTarget.x2 = this.input.activePointer.x - this.parent.x;
this.rightTarget.y2 = this.input.activePointer.y - this.parent.y;
// Within the right eye?
if (this.rightBase.contains(this.rightTarget.x2, this.rightTarget.y2))
{
this.mid.x = this.rightTarget.x2;
this.mid.y = this.rightTarget.y2;
}
else
{
Phaser.Geom.Ellipse.CircumferencePoint(this.rightBase, Phaser.Geom.Line.Angle(this.rightTarget), this.mid);
}
this.right.x = this.mid.x;
this.right.y = this.mid.y;
}
refresh ()
{
this.cameras.main.setPosition(this.parent.x, this.parent.y);
this.scene.bringToTop();
}
}
Eyes.WIDTH = 183;
Eyes.HEIGHT = 162;
Архитектура сцены: вложенность и коммуникация
Класс Eyes представляет собой отдельную сцену (Phaser.Scene), которая управляет отрисовкой и логикой двух глаз. Это не самостоятельная игровая сцена, а скорее компонент, встроенный в другую, «родительскую» сцену. Такой подход полезен для изоляции сложной логики.
Конструктор сохраняет ссылку на parent — объект из основной сцены, который содержит координаты (`x,y`) окна с глазами. Это позволяет корректно позиционировать viewport камеры.
constructor (handle, parent)
{
super(handle);
this.parent = parent;
// ... объявление переменных для глаз и геометрии
}
Инициализация: создание глаз и их границ
В методе create() происходит базовая настройка. Ключевой момент — создание отдельного viewport для камеры этой сцены. Он «вырезает» прямоугольное окно в координатах родительского объекта, создавая иллюзию, что глаза нарисованы прямо в нем.
Затем создаются два спрайта глаза и геометрические объекты для них:
- Phaser.Geom.Line (leftTarget, rightTarget): Линия от центра глаза до цели (курсора). Изначально конец линии в (0,0), он будет обновляться.
- Phaser.Geom.Ellipse (leftBase, rightBase): Эллипс, описывающий область, внутри которой зрачок может двигаться свободно (белок глаза).
create ()
{
this.cameras.main.setViewport(this.parent.x, this.parent.y, Eyes.WIDTH, Eyes.HEIGHT);
this.left = this.add.image(46, 92, 'eye');
this.right = this.add.image(140, 92, 'eye');
this.leftBase = new Phaser.Geom.Ellipse(this.left.x, this.left.y, 24, 40);
// ... аналогично для правого глаза
}
Сердце логики: метод update() и расчет позиции
Вся магия происходит каждый кадр в `update()`. Алгоритм для каждого глаза одинаков:
1. **Обновление цели:** Координаты курсора (`this.input.activePointer`) переводятся в локальную систему координат сцены `Eyes` путем вычитания `this.parent.x` и `this.parent.y`.
2. **Проверка нахождения внутри области:** Метод `Ellipse.contains()` проверяет, лежит ли целевая точка внутри эллипса «белка». Если да — зрачок (спрайт `eye`) просто помещается в эту точку.
3. **Проецирование на границу:** Если точка снаружи, нужно найти точку на окружности эллипса, которая смотрит в направлении цели. Для этого используется статический метод `Phaser.Geom.Ellipse.CircumferencePoint()`. Ему передают эллипс, угол от центра глаза до цели (рассчитывается через `Phaser.Geom.Line.Angle()`), и вектор `this.mid`, куда записывается результат.
// Для левого глаза:
this.leftTarget.x2 = this.input.activePointer.x - this.parent.x;
this.leftTarget.y2 = this.input.activePointer.y - this.parent.y;
if (this.leftBase.contains(this.leftTarget.x2, this.leftTarget.y2))
{
this.mid.set(this.leftTarget.x2, this.leftTarget.y2);
}
else
{
Phaser.Geom.Ellipse.CircumferencePoint(this.leftBase, Phaser.Geom.Line.Angle(this.leftTarget), this.mid);
}
this.left.setPosition(this.mid.x, this.mid.y);
Синхронизация с родителем: метод refresh()
Поскольку позиция окна (this.parent.x, this.parent.y) может меняться извне (например, окно перетаскивается), сцена Eyes должна уметь синхронизировать свой viewport с новой позицией. Для этого служит публичный метод refresh().
Он переставляет камеру на новые координаты и, что важно, поднимает сцену наверх стека отрисовки командой this.scene.bringToTop(). Это гарантирует, что окно с глазами всегда будет поверх других элементов родительской сцены.
refresh ()
{
this.cameras.main.setPosition(this.parent.x, this.parent.y);
this.scene.bringToTop();
}
Что попробовать дальше
Разобранный подход — отличная основа для создания динамических UI-элементов или частей персонажа (не только глаз). Экспериментируйте: измените форму области с Ellipse на Circle или Rectangle, добавьте инерцию движению зрачков, используйте эту технику для прицелов или интерактивных указателей. Главное — вы теперь знаете, как использовать геометрические модули Phaser для точных и эффективных расчетов.
