Adventures in Phaser with TypeScript — Mixing 2D and 3D Graphics using Phaser and Three.js

 

 

First off, let’s start of with a big disclaimer!  This is more of an experiment that worked than a tutorial, as I can’t actually recommend you do anything I just did!  Let’s just say, I did a few really hack worthy things to get this particular example to work.  That said, it does work and the results are cool enough I figured I’d share them.  So, there is the warning…  all the code in this example works, but it might be a really really really bad idea! 😉

 

Ok, disclaimer covered…  now on to what we are actually going to cover, using a 3D model in Phaser.  Out of the box Phaser doesn’t actually have any support for 3D, but Three.js certainly does.  So what we are going to do is use Three.js to load a model and render it to a canvas, which then can be used as the image source for a Phaser Sprite.  If you’ve got no prior experience with Three.js I previously did a two part ( part one, part two ) series on Three.js, from which this a lot of this code was copied.

 

So let’s jump right in with the code.  It’s heavily commented, but doesn’t really explain any Three.js or Phaser specifics, see earlier tutorials for that.  We are going to render a threejs scene to a canvas, and then use that canvas as a sprite.  In this example, the Threejs scene will be visible, while in real life you would set it to invisible.

 

Index.html

<!DOCTYPE html>    <html lang="en">  <head>      <meta charset="utf-8" />      <title>Model rendered in ThreeJS and used in Phaser</title>      <script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r67/three.js"></script>      <script src="phaser.js"></script>      <script src="Main.js"></script>  </head>  <body>  <h1>Model rendered in ThreeJS and used in Phaser</h1>  <table width="600">      <tr>          <th>Canvas rendered in Phaser</th>          <th>ThreeJS rendered to Canvas</th>      </tr>      <tr>          <td width="300px" align="center"><div style="width:298px;height:298px;" id="content"></div></td>          <td width="300px" align="center">              <div id="hiddenContent" style="width:298px;height:298px;visibility:visible;"></div>          </td>      </tr>  </table>  </div>  </body>  </html>  

 

Main.ts

///<reference path="./three.d.ts"/>  /// <reference path="phaser.d.ts"/>    module Test {        export class RunningState extends Phaser.State {            // Create the Threejs variables we need          renderer:THREE.WebGLRenderer;          scene:THREE.Scene;          threeJSCamera:THREE.Camera;          mesh:THREE.SkinnedMesh;            // Create the Phaser variables          sprite:Phaser.Sprite;            // This flag indicates our model is loaded and ready to be poked and proded          goTime:boolean = false;            constructor() {              super();                // ThreeJS setup              this.renderer = new THREE.WebGLRenderer({ alpha: true });                this.renderer.setSize(256, 256);              this.renderer.setClearColor(0x0000FF, 1);              // ThreeJS renders to a (normally) hidden canvas in the hiddenContent div              // In real life you'd set it's visiblity to false              document.getElementById('hiddenContent').appendChild(this.renderer.domElement);                // Create the scene and a camera              this.scene = new THREE.Scene();              this.threeJSCamera = new THREE.PerspectiveCamera(75                  , 1                  , 0.1, 1000);                this.threeJSCamera.position = new THREE.Vector3(0, 0, 1.5);                // Like framebuffers, the results are upside down once rendered.              // As I couldn't find a way to flip UVs in PIXI, I instead render the scene upside down              // by rotating the camera 180 degrees on Z axis              this.threeJSCamera.rotateZ(Math.PI);          }            create() {              // Set this because we want to display FPS details              this.game.time.advancedTiming = true;                // Load the model exported from Blender in threejs format              // Model loads async              var modelLoader = new THREE.JSONLoader();              var that = this;              modelLoader.load("model/model.jsm", (geometry, materials) => {                    // This is the callback for when our model is loaded, set everything up here                  this.mesh = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(                      materials));                    this.mesh.position.y -=1; // Center vertically about origin                    // add the loaded mesh and a light to the scene                  that.scene.add(this.mesh);                  that.scene.add(new THREE.AmbientLight(new THREE.Color(1.0, 1.0, 1.0).                      getHex()));                    // Start threejs up                  that.renderThreeJS(null);                    // For some reason, you dont get lighting for a while...                  // This is a gross hack... basically we wait a second and then create a Phaser Sprite                  // and then render the canvas to our sprite.                  setTimeout( () => {                      that.sprite = this.game.add.sprite(0, 0, null);                      that.renderThreeJS(that.sprite);                      // Set goTime flag so we know it's go time baby.                      that.goTime = true;                  },1000);              });          }            // This is the function that actually call Threejs's render          // Then we set the Sprite's texture to the canvas          // Warning, this may be horrible for performance.          renderThreeJS(target:Phaser.Sprite) {              if (this.renderer) {                  this.renderer.render(this.scene, this.threeJSCamera);                    if(target) {                      target.texture.destroy(true);                         target.setTexture(PIXI.Texture.fromCanvas(                          <HTMLCanvasElement>document.getElementById("hiddenContent")                              .children[0], PIXI.scaleModes.DEFAULT));                  }              }          }            // Let's just print out debug framerate          render(){              this.game.debug.text("FPS" + this.game.time.fps,0,20);          }            // Just so we can see that updates to the ThreeJS scene result in the Sprite being updated          // We simply rotate by 0.01 radians per frame... note this is running as fast as possible          // So will rotate differently on different machines...          update(){              if(this.goTime) {                  this.mesh.rotateY(0.01);                  this.renderThreeJS(this.sprite);              }          }            start() {          }      }        export class SimpleGame{          game:Phaser.Game;          constructor(){              // Create a game, and our RunningState, passing true to make it start right away              this.game = new Phaser.Game(256, 256, Phaser.WEBGL, 'content');              this.game.state.add("RunningState",RunningState,true);          }      }  }    // On window load, create an instance of our SimpleGame  window.onload = () => {      var three = new Test.SimpleGame();  };  

 

And the code running:

 

 

Now there are a few horrific hacks in this code.  First off, there is a “warm up” in Threejs, where for a few ms, it renders without lighting… no clue why, so I just brute force by delaying for a second after Threejs rendering starts.  The next brutal hack is to get the Phaser texture to show, I destroy the previous version… there HAS to be a better way to do this.  Interestingly, in Chrome the results to the canvas are flipped.  However, on Safari they are not!  The joy of HTML5.

 

As you can see though, mixing 3D objects in a 2D Phaser game is very much possible and actually fairly easy.

 

 

Programming


Scroll to Top