Tuesday, November 18, 2014

Combining Phaser and Three.js to make 2.5D games.

I wanted to incorporate 3D elements into a phaser game and since phaser does not support 3D I started looking at Three.js. Phaser and Three are both great game libraries on their own but individually neither one of them did everything that I wanted so I thought, why not combine them?

To see the demo in action click here.
here is the demo source code

Maybe I should take a step back for a moment. For those of you who don't know, Phaser is a great javascript game library for writing HTML5 games. It is based on PIXI.js as supports seamless canvas and webGL rendering of 2D games as well as tweening, tilemaps, sprites, collision detection, and physics. Three.js is another great javascript game library but unlike Phaser which only supports 2D, Three.js is designed to abstract away the complexities of working with 3D rendering in webGL.

The last library that I should mention is ds.oop. This is not a game library at all. It is a very lightweight abstraction layer for creating highly reusable classes in javascript. ds.oop streamlines object oriented programming in javascript by hiding all of the ugliness of inheritance and class creation in javascript and leaving you with a simple easy to maintain class object. Most importantly it just works. I highly recommend it for any javascript project.

Required Libraries:

There are likely many ways to combine phaser and three in a project but for my purposes, I wanted to use phaser primarily and just use three for incorporating some 3D elements into the game so I decided to keep the typical phaser game structure. I'm using the normal phaser rendering pipeline. Initially I thought about rendering everything from phaser onto a textured plane in three but I decided to do it the other way around and just render some elements in three and then grab the bitmap data and render it in phaser like any other sprite. This turned out to be easier than I thought.

The Phaser.Three class

This class allows you to create a layer in your phaser game that consists of a scene rendered by three.js. The class is fairly minimal. The init method starts an instance of THREE.WebGLRenderer with the same dimensions as the currently running phaser game. Then it creates a sprite from the off screen canvas to use as an layer. The sprite is attached to the phaser camera. Finally this method setup up the three.js scene and camera based on the current aspect ratio.
ds.make.class({
    type: "Phaser.Three",
    constructor: function () {
        throw 'Do no instantiate this class. Inherit it and call the init method from your constructor!';
    },
    init: function (game) {
        this.game = game;

        // renderer
        this.renderer = new THREE.WebGLRenderer({ alpha: true });
        this.renderer.setSize(game.width, game.height);

        // setup canvas to be used as a sprite texture
        this.canvas = this.renderer.domElement; 
        this.context = this.canvas.getContext('2d');
        this.baseTexture = new PIXI.BaseTexture(this.canvas);
        this.texture = new PIXI.Texture(this.baseTexture);
        this.textureFrame = new Phaser.Frame(0, 0, 0, this.game.width, this.game.height, 'debug', game.rnd.uuid());
        this.sprite = this.game.add.sprite(0, 0, this.texture, this.textureFrame);
        this.sprite.fixedToCamera = true;

        // camera
        var fov = 45;
        var camera = new THREE.PerspectiveCamera(fov, game.width / game.height, 1, 10000);
        var dist = game.height / 2 / Math.tan(Math.PI * fov / 360);
        camera.position.z = dist;
        //camera.position.y = 200;
        camera.lookAt(new THREE.Vector3(0, 0, 0));
        this.camera = camera;
        this.maxHeight = dist;

        // scene
        var scene = new THREE.Scene();
        this.scene = scene;

        var ambientlight = new THREE.AmbientLight(0xF0F0F0); // soft white light
        scene.add(ambientlight);
    },
    update: function () {

        this.scene.position.x = -this.game.camera.position.x * 2;
        this.scene.position.y = -this.game.camera.position.y * 2;

        // render
        this.renderer.render(this.scene, this.camera);

        PIXI.updateWebGLTexture(this.baseTexture, this.game.renderer.gl);
    }
});

So this is the base class that takes care of setting up the camera for you. The update function in this class will lock the three camera to the phaser camera position so when you move game.camera to a new position the scene will also render that position.

Using this class in easy. Just inherit it and start adding some meshes or whatever you need to render in Three.js
ds.make.class({
    type: 'Game.Layer3D',
    inherits: Phaser.Three,
    constructor: function (game) {
        this.init(game);

        // make a box
        var geometry = new THREE.BoxGeometry(100, 100, 100);
        mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());           
        mesh.position.z = 50;
        mesh.position.x = 300;
        mesh.position.y = 300;
        this.scene.add(mesh);
    }
});

Now to see it in phaser you have to create the Layer3D object in the phaser create function:
this.layer3d = new Game.Layer3D(game);

Finally, don't forget to call update in your update function. This will update the camera position in the three.js scene and render the scene to the viewport.
this.layer3d.update();

And That's really all there is to it. Special thanks to Rich on the Phaser forums for posting an example of rendering into Phaser from an off-screen canvas and also to everyone reading this, I hope this short tutorial was helpful for you to get started with 3D mix-ins in phaser. 

14 comments:

  1. this is rad! you could turn this into a pretty addictive game, for example by making the zombies come in waves and putting a time limit on each wave. thanks so much for sharing!

    ReplyDelete
  2. Do you have any working very simple source code example that includes the above box code?

    I tried this but am getting an error on this line:
    "this.layer3d = new Game.Layer3D(game);"

    The error is "Uncaught ReferenceError: Game is not defined"

    Do I need to create an instance of Game first?

    cheers,
    Paul

    ReplyDelete
    Replies
    1. I should have the demo code. I will check when i get home and update the article. thanks.

      Delete
    2. Thanks mate, that would be great :)

      Delete
    3. Just wondering if you had found the demo code? If so, could it be included as a download?
      Thanks for your time :)

      Delete
    4. Sorry about the delay. I was in the middle of a move so things were kind of crazy for a while. I uploaded the source code for the car demo here . http://camos052585-001-site1.myasp.net/src/cartest.zip
      Feel free to use this for your project just throw me a shout out in your credits. thanks.

      When I have more time I would like to move this off of ds.oop and into typescript. Ive started using typescript alot more since I started this and its really nice especially for phaser projects.

      Delete
    5. Thanks so much for your time, and no worries about the delay mate...I quite understand that life gets in the way of our play sometimes :D

      cheers,
      Paul

      Delete
  3. Hey, would it be possible to reupload the source files? all the links are dead. thanks for this!

    ReplyDelete
    Replies
    1. Yeah actually, i put the project up on github so you can fork it.
      https://github.com/digital-synapse/zombiegta2-demo

      Delete