О чем этот пример
Хотите добавить трёхмерные эффекты или сложные шейдеры в свою 2D-игру на Phaser? Интеграция с Three.js открывает новые горизонты, позволяя совместить простоту Phaser с мощью WebGL-рендерера. В этой статье мы разберём практический пример: как встроить 3D-сцену Three.js в Phaser, управлять ей и даже применять к ней встроенные фильтры Phaser, такие как размытие. Этот подход полезен для создания гибридных игр, сложных UI-эффектов или визуализаций, где 2D и 3D должны работать вместе.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.script('threejs', 'js/three145.js');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.image('bg', 'assets/skies/chrome.png');
}
create ()
{
this.add.image(640, 360, 'bg').setDisplaySize(1280, 720);
this.init3D();
this.add.image(0, 0, 'logo').setOrigin(0, 0);
}
// This is all mostly ThreeJS code
init3D ()
{
const camera = new THREE.PerspectiveCamera(70, 1280 / 720, 1, 10000);
camera.position.z = 300;
const scene = new THREE.Scene();
const texture1 = new THREE.TextureLoader().load('assets/normal-maps/brick.jpg');
const texture2 = new THREE.TextureLoader().load('assets/textures/gold.png');
const texture3 = new THREE.TextureLoader().load('assets/textures/grass.png');
const material1 = new THREE.MeshBasicMaterial({ map: texture1 });
const material2 = new THREE.MeshBasicMaterial({ map: texture2 });
const material3 = new THREE.MeshBasicMaterial({ map: texture3 });
const geometry1 = new THREE.BoxGeometry(100, 100, 100);
const mesh1 = new THREE.Mesh(geometry1, material1);
const geometry2 = new THREE.SphereGeometry(64, 32, 16);
const mesh2 = new THREE.Mesh(geometry2, material2);
const geometry3 = new THREE.CylinderGeometry(35, 35, 80, 32);
const mesh3 = new THREE.Mesh(geometry3, material3);
mesh2.position.x = -200;
mesh3.position.x = 200;
scene.add(mesh1);
scene.add(mesh2);
scene.add(mesh3);
// Tell three to use the Phaser canvas
// Also: Notice we're using the WebGL1 Renderer here
const renderer = new THREE.WebGL1Renderer({
canvas: this.sys.game.canvas,
context: this.sys.game.context,
antialias: true,
});
// Create the Phaser Extern, tells Phaser to hand-off rendering to ThreeJS
const view = this.add.extern();
renderer.setPixelRatio(1);
renderer.setSize(1280, 720);
// You can skip this if threeJS is providing the _background_
// and all Phaser objects are on-top of it
renderer.autoClear = false;
// The Extern render function
view.render = (webGLRenderer, drawingContext, calcMatrix) => {
// This is essential to get ThreeJS to reset the GL state
renderer.resetState();
// Ensure the DrawingContext framebuffer is bound.
webGLRenderer.glWrapper.updateBindingsFramebuffer({
bindings: {
framebuffer: drawingContext.framebuffer
}
}, true);
mesh1.rotation.x += 0.005;
mesh1.rotation.y += 0.01;
mesh2.rotation.x -= 0.005;
mesh2.rotation.y += 0.02;
mesh3.rotation.x += 0.03;
mesh3.rotation.y += 0.02;
renderer.render(scene, camera);
// Call it again, after rendering, if you get graphical corruption
// renderer.resetState();
};
// Add extern to a framebuffer for Phaser filter effects.
view.enableFilters();
const blur = view.filters.external.addBlur();
// Tween the blur strength.
this.tweens.add({
targets: blur,
strength: 0,
ease: 'Sine.easeInOut',
duration: 3000,
yoyo: true,
repeat: -1
});
}
}
const config = {
type: Phaser.WEBGL,
width: 1280,
height: 720,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка: загрузка Three.js и ассетов
Первый шаг — загрузить библиотеку Three.js как внешний скрипт. Phaser позволяет это сделать с помощью метода load.script(). Важно указать корректный базовый URL и путь к файлу Three.js. Параллельно загружаются обычные 2D-изображения для фона и логотипа.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.script('threejs', 'js/three145.js');
this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
this.load.image('bg', 'assets/skies/chrome.png');
Создание 3D-сцены Three.js
В методе init3D() создаётся стандартная сцена Three.js: камера, сцена, геометрия, материалы и меши (объекты). Код создаёт три простые фигуры с разными текстурами и располагает их в пространстве. Ключевой момент — рендерер Three.js настраивается на использование существующего canvas и WebGL-контекста Phaser.
const renderer = new THREE.WebGL1Renderer({
canvas: this.sys.game.canvas,
context: this.sys.game.context,
antialias: true,
});
Настройка autoClear: false критична, если Three.js рисует фон, а Phaser-объекты должны отображаться поверх. Без этого Phaser может очищать буферы, удаляя 3D-сцену.
Мост между Phaser и Three.js: объект Extern
Phaser предоставляет специальный объект Extern для кастомного рендеринга. Создав его через this.add.extern(), мы можем переопределить его метод render(). Внутри этого метода происходит магия интеграции.
const view = this.add.extern();
view.render = (webGLRenderer, drawingContext, calcMatrix) => {
renderer.resetState();
webGLRenderer.glWrapper.updateBindingsFramebuffer({
bindings: {
framebuffer: drawingContext.framebuffer
}
}, true);
// ... анимация вращения объектов ...
renderer.render(scene, camera);
};
Вызов renderer.resetState() сбрасывает состояние WebGL, чтобы Three.js и Phaser не конфликтовали. Связывание framebuffer необходимо для корректного вывода 3D-изображения в текущий буфер Phaser. Анимация вращения объектов обновляется каждый кадр прямо в этом методе.
Применение фильтров Phaser к 3D-сцене
Одна из самых мощных возможностей — применение фильтров Phaser к рендеру Three.js. Объект Extern можно перевести в режим работы с фильтрами.
view.enableFilters();
const blur = view.filters.external.addBlur();
После этого к 3D-сцене можно добавлять стандартные фильтры, например, размытие. Его параметры можно анимировать с помощью системы Tween Phaser, создавая плавные эффекты.
this.tweens.add({
targets: blur,
strength: 0,
ease: 'Sine.easeInOut',
duration: 3000,
yoyo: true,
repeat: -1
});
Важные нюансы и отладка
Интеграция требует аккуратности. Если вы видите графические артефакты (например, чёрный экран или наложения), попробуйте вызвать renderer.resetState() дважды — до и после рендера Three.js. Убедитесь, что Three.js не пытается очистить буфер (autoClear: false), если фон отрисовывается Phaser. Также проверьте порядок отрисовки: в данном примере сначала добавляется фон Phaser, затем создаётся Extern, что гарантирует правильный z-order. Все пути к ассетам Three.js должны быть абсолютными или корректно относительными к базовому URL.
Что попробовать дальше
Интеграция Phaser и Three.js — мощный инструмент для разработчиков, желающих выйти за рамки 2D. Вы можете анимировать 3D-объекты, применять к ним постобработку Phaser и создавать гибридные сцены. Для экспериментов попробуйте: добавить взаимодействие (клики по 3D-объектам), использовать более сложные шейдеры Three.js или комбинировать несколько фильтров Phaser на одном Extern. Это открывает путь к уникальному визуальному стилю вашей игры.
