Do you like my tutorials?

Then consider supporting me on Ko-fi

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.