О чем этот пример
Пример демонстрирует, как загрузить 3D-модель из OBJ-файла, наложить на неё текстуру и создать интерактивную сцену с вращением, панорамированием и зумом с помощью мыши и клавиатуры. Это полезно для разработчиков, которые хотят добавить простые, но эффектные 3D-элементы в свою 2D-игру на Phaser, не прибегая к тяжеловесным движкам. Вы научитесь управлять мешем, включать визуальную отладку его вершин и синхронизировать эти операции с камерой.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
var config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update
}
};
var game = new Phaser.Game(config);
function preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('powerups', 'assets/obj/powerups.png');
this.load.obj('skull', 'assets/obj/skull.obj');
}
function create ()
{
const mesh = this.add.mesh(400, 300, 'powerups');
mesh.addVerticesFromObj('skull', 0.1);
mesh.panZ(7);
mesh.modelRotation.y += 0.5;
this.debug = this.add.graphics().setScrollFactor(0);
this.input.keyboard.on('keydown-D', () => {
if (mesh.debugCallback)
{
mesh.setDebug();
}
else
{
mesh.setDebug(this.debug);
}
});
const rotateRate = 1;
const panRate = 1;
const zoomRate = 4;
this.input.on('pointermove', pointer => {
if (!pointer.isDown)
{
return;
}
if (!pointer.event.shiftKey)
{
mesh.modelRotation.y += pointer.velocity.x * (rotateRate / 800);
mesh.modelRotation.x += pointer.velocity.y * (rotateRate / 600);
}
else
{
mesh.panX(pointer.velocity.x * (panRate / 800));
mesh.panY(pointer.velocity.y * (panRate / 600));
}
});
this.input.on('wheel', (pointer, over, deltaX, deltaY, deltaZ) => {
mesh.panZ(deltaY * (zoomRate / 600));
});
this.mesh = mesh;
const cursors = this.input.keyboard.createCursorKeys();
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
this.t = this.add.text(10, 10).setScrollFactor(0);
window.cam = this.cameras.main;
}
function update (time, delta)
{
this.controls.update(delta);
this.debug.clear();
this.debug.lineStyle(1, 0x00ff00);
this.t.text = this.mesh.totalRendered;
}
Загрузка данных: текстура и 3D-модель
В методе preload происходит загрузка необходимых ресурсов. Ключевой момент — использование двух разных методов: для текстуры и для геометрии.
this.load.image('powerups', 'assets/obj/powerups.png');
this.load.obj('skull', 'assets/obj/skull.obj');
Метод this.load.image загружает спрайт-лист powerups.png, который будет использован как текстура для меша. Метод this.load.obj загружает 3D-геометрию из файла skull.obj (формат Wavefront OBJ). Важно, что модель и текстура загружаются отдельно, что позволяет комбинировать их произвольным образом.
Создание меша и добавление геометрии
В методе create создаётся основной объект — меш. Меш — это контейнер, который объединяет текстуру и 3D-геометрию.
const mesh = this.add.mesh(400, 300, 'powerups');
mesh.addVerticesFromObj('skull', 0.1);
mesh.panZ(7);
mesh.modelRotation.y += 0.5;
Сначала создаётся меш в центре экрана (400x300) с текстурой 'powerups'. Затем метод mesh.addVerticesFromObj добавляет к этому мешу вершины, загруженные из OBJ-файла 'skull'. Второй аргумент 0.1 — масштаб, который уменьшает модель, чтобы она уместилась в поле зрения. Далее меш отдаляется от камеры с помощью panZ(7) и сразу немного поворачивается по оси Y (modelRotation.y += 0.5), чтобы продемонстрировать его объём.
Интерактивное управление: мышь и клавиши
Код настраивает управление мешем с помощью мыши и переключение режима отладки по клавише.
this.input.on('pointermove', pointer => {
if (!pointer.isDown) return;
if (!pointer.event.shiftKey) {
mesh.modelRotation.y += pointer.velocity.x * (rotateRate / 800);
mesh.modelRotation.x += pointer.velocity.y * (rotateRate / 600);
} else {
mesh.panX(pointer.velocity.x * (panRate / 800));
mesh.panY(pointer.velocity.y * (panRate / 600));
}
});
При движении зажатой мыши (без Shift) меш вращается. Скорость вращения зависит от pointer.velocity. Если зажат Shift — меш сдвигается (panX, panY). Колесико мыши управляет приближением (panZ).
this.input.keyboard.on('keydown-D', () => {
if (mesh.debugCallback) {
mesh.setDebug();
} else {
mesh.setDebug(this.debug);
}
});
Клавиша `Dвключает или выключает режим отладки. При включении в графический контекстthis.debug` рисуется сетка вершин меша, что помогает визуализировать его структуру.
Управление камерой и отображение статистики
Помимо управления самим мешем, в сцене есть плавное управление камерой с клавиатуры.
const controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
this.controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
Создаётся экземпляр SmoothedKeyControl, который обеспечивает инерционное движение и зум камеры по стрелкам и клавишам `Q`/`E. Это позволяет независимо исследовать сцену. В методеupdateобновляется состояние этих контролов и отображается количество отрендеренных вершин (mesh.totalRendered`) в текстовом объекте, что полезно для оценки производительности.
Что попробовать дальше
Этот пример показывает гибкий подход к работе с 3D в Phaser: геометрия и текстура загружаются независимо, а меш выступает их универсальным контейнером. Для экспериментов попробуйте: заменить модель skull.obj на свою, используя тот же метод addVerticesFromObj; анимировать modelRotation или положение меша в update для создания автоматического вращения; применить разные текстуры к одной модели, чтобы изменить её внешний вид; или использовать режим отладки (setDebug), чтобы понять структуру вершин загруженной модели.
