Things are getting interesting using Phaser with Three.js: earlier this week I published the latest step of the tutorial about building a HTML5 Stairs game using Phaser and Three, but if you want to render something more complicated than base geometries, today I am showing you how to import glTF files.
From Wikipedia: glTF is a standard file format for three-dimensional scenes and models. A glTF file uses one of two possible file extensions: .gltf (JSON/ASCII) or .glb (binary). Both .gltf and .glb files may reference external binary and texture resources. Alternatively, both formats may be self-contained by directly embedding binary data buffers (as base64-encoded strings in .gltf files or as raw byte arrays in .glb files).
We’ll see the case about .gltf extension with base64-encoded strings.
First, we need a glTF model, which I downloaded from Kay Lousberg’s itch page.
The model has been imported into a Phaser HTML5 canvas which runs the particle example you can find at this link.
Look how I mixed everything:
Look: there’s a 3D rotating knight and a 2D particle effect in the background, everything on the same canvas.
Let’s have a look at the commented source code: we have one HTML file, one CSS file and three TypeScript files.
index.html
The web page which hosts the game, to be run inside thegame element.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="style.css">
<script src="main.js"></script>
</head>
<body>
<div id = "thegame"></div>
</body>
</html>
style.css
The cascading style sheets of the main web page.
* {
padding : 0;
margin : 0;
}
body {
background-color: #000000;
}
canvas {
touch-action : none;
-ms-touch-action : none;
}
main.ts
This is where the game is created, with all Phaser related options.
// MAIN GAME FILE
// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame } from './playGame';
// object to initialize the Scale Manager
const scaleObject : Phaser.Types.Core.ScaleConfig = {
mode : Phaser.Scale.FIT,
autoCenter : Phaser.Scale.CENTER_BOTH,
parent : 'thegame',
width : 500,
height : 500
}
// game configuration object
const configObject : Phaser.Types.Core.GameConfig = {
type : Phaser.AUTO,
backgroundColor : 0x000000,
scale : scaleObject,
scene : [PreloadAssets, PlayGame]
}
// the game itself
new Phaser.Game(configObject);
preloadAssets.ts
Here we preload all assets to be used in the game, such as the sprite atlas with the particles and the knight object.
// CLASS TO PRELOAD ASSETS
// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {
// constructor
constructor() {
super({
key : 'PreloadAssets'
});
}
// method to be execute during class preloading
preload(): void {
// this is how we preload a generic text, in our case the GLTF JSON of the knight model
this.load.text('knight', 'assets/objects/character_knight.gltf');
// this is how we preload a sprite atlas
this.load.atlas('flares', 'assets/sprites/flares.png', 'assets/sprites/flares.json');
}
// method to be called once the instance has been created
create(): void {
// call PlayGame class
this.scene.start('PlayGame');
}
}
playGame.ts
Main game file, all game logic and Three integration is stored here.
// THE GAME ITSELF
import * as THREE from 'three';
import {GLTFLoader} from 'THREE/examples/jsm/loaders/GLTFLoader.js'
// this class extends Scene class
export class PlayGame extends Phaser.Scene {
// the GLTF model is a Three group
knight : THREE.Group;
constructor() {
super({
key: 'PlayGame'
});
}
// method to be executed when the scene has been created
create() : void {
// this is an adaptation of the demo found at
// https://phaser.io/examples/v3/view/game-objects/particle-emitter/random-emit-zone
var shape1 : Phaser.Geom.Circle = new Phaser.Geom.Circle(0, 0, 220);
var shape2 : Phaser.Geom.Circle = new Phaser.Geom.Circle(0, 0, 240);
var particles : Phaser.GameObjects.Particles.ParticleEmitterManager = this.add.particles('flares');
particles.createEmitter({
frame : 'red',
x : 250,
y : 250,
lifespan : 2000,
quantity : 4,
scale : 0.2,
alpha : {
start : 1,
end : 0
},
blendMode : 'ADD',
emitZone : {
type : 'random',
source : shape1 as Phaser.Types.GameObjects.Particles.RandomZoneSource
}
});
particles.createEmitter({
frame : 'yellow',
x : 250,
y : 250,
speed : 0,
lifespan : 1000,
quantity : 1,
scale : {
start : 0.4,
end : 0
},
blendMode : 'ADD',
emitZone : {
type : 'edge',
source : shape2,
quantity : 48,
yoyo : false
}
});
// INTERESTING PART BEGINS HERE
// create a new THREE scene
const threeScene : THREE.Scene = new THREE.Scene();
// initialize the Three group
this.knight = new THREE.Group();
// create the renderer
const renderer : THREE.WebGLRenderer = new THREE.WebGLRenderer({
canvas : this.sys.game.canvas,
context : this.sys.game.context as WebGLRenderingContext,
antialias : true
});
renderer.autoClear = false;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// set up the GLTF loader, load JSON model, then add it to the scene
const gltfLoader = new GLTFLoader();
gltfLoader.parse(this.cache.text.get('knight'), '', (gltf) => {
this.knight = gltf.scene;
this.knight.position.set(0, 0, 0);
this.knight.scale.set(20, 20, 20)
threeScene.add(this.knight);
})
// add a camera
const camera : THREE.PerspectiveCamera = new THREE.PerspectiveCamera();
camera.position.set(0, 17, 50);
camera.lookAt(0, 17, 0);
// add an ambient light
const ambientLight : THREE.AmbientLight = new THREE.AmbientLight(0xffffff, 0.8);
threeScene.add(ambientLight);
// add a spotlight
const spotLight : THREE.SpotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 0, 50);
spotLight.target.position.set(0, 0, 0);
threeScene.add(spotLight);
threeScene.add(spotLight.target);
// create an Extern Phaser game object
const view : Phaser.GameObjects.Extern = this.add.extern();
// custom renderer
// next line is needed to avoid TypeScript errors
// @ts-expect-error
view.render = () => {
renderer.state.reset();
renderer.render(threeScene, camera);
};
}
update() : void {
this.knight.rotateY(0.02)
}
}
Loading a complex 3D model in Phaser using Three.js was easy, and this opens endless possibilities to game development. Let’s see how will this evolve, meanwhile download the source code.