Playing with Phaser and Three.js: preload glTF 3d models, render them with Three then animate them with Phaser

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers