Talking about 3D, HTML5, Javascript, Phaser and TypeScript.
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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.