Patrick Sletvold’s Blog

Running Phaser 3 on the server

Phaser 3.2.0 added support for headless rendering mode. Combine it with jsdom and node-canvas and you can run your game server-side.

Someone decapitated my game engine…

A game engine is made up by lots of parts, but the one you see the most of is the renderer, naturally because that’s what renders your game to the screen. What would happen if you chopped off this part of the game engine? You’d get a headless game engine, a game engine that doesn’t render anything at all. Perfect for running on computers without a display, like most servers.

Why is this useful?

There are several reasons why you might wish to run a Phaser game on a server. I’ll make a bullet list for you:

  • To show to your friends
  • To write some sort of automated tests
  • So you don’t have to trust the client, and can do everything server-side

The last point is definitely the most viable one. Although it would be possible to write automated tests for your game, I’m not sure how much you’d get from that. In that case it would also be easier to just run an entire browser headless instead.

A major principle in computer security is that you should never trust the client in a client-server relationship. This is very true in multiplayer games too, especially those were you expect that players could try to cheat. Running a full-blown Phaser instance on the server opens up a lot of possibilities, since the same collision and movement can happen both on the server and the client. The server can then correct any errors (or cheat attempts) that the client does.

The journey to headless Phaser 3

Phaser 2 has had a headless mode for a few years (though I’ve never used it), but Phaser 3 just got it recently, in the v3.2.0 update. I started playing around with it, discovered a bug when using it with some game objects (which was fixed in v3.2.1), and then I started writing the text you are currently reading.

There’s a lot to be said about making multiplayer games (like communicating efficiently over the network, reducing latency and so on), but you’ll have to find that stuff another place. I’m going to focus on the headless part of it. So let’s get started!

Setting it up

I’ll assume you have some knowledge of Node, npm and Phaser before starting. I’ll also assume you have some sort of game you wish to run on the server. If not, just grab one of the examples from the Phaser website, like I did. If you’re not using a package.json file already, create one by running npm init.

Since Node is for running javascript on a server, and not in a browser, it does not support running Phaser out of the box. We’ll need a couple of packages: jsdom and node-canvas. jsdom recreates most of the DOM javascript APIs from the browser, and lets you load HTML files and interact with them. With the correct config it also lets you run scripts from inside the HTML file. Be careful to only use this when you trust the js code, as jsdom is not meant as a proper sandbox!

The next package is node-canvas, which is an implementation of the canvas API using Cairo. This might seem like it’s overkill, but Phaser still needs access to the canvas API when running in headless mode. node-canvas is currently on v2.x, but since jsdom has nice integration with the v1.x releases, we’ll just install that for now. Or rather, we’ll install canvas-prebuilt, which is the same as the node-canvas v1.x, but with the native dependencies prebuilt and included. Let’s go ahead and install them (maybe throw Phaser into the mix if you haven’t already):

npm install canvas-prebuilt jsdom

Writing javascript

Now it’s time to start writing javascript. First, we’ll need to actually enable the headless rendering mode. Just find the place you game instance is created and set the type to Phaser.HEADLESS. Something like this:

var game = new Phaser.Game({
type: Phaser.HEADLESS,
parent: 'game',
scene: {
preload: preload,
create: create,
update: update
},
width: 800,
height: 600
});

Now we’re ready to do the node part. Let’s create an index.js file and require some modules:

const jsdom = require('jsdom')
const { JSDOM } = jsdom;

We actually only need to require the jsdom module, since it finds the canvas module automatically. Now it’s time to load the html file into the JSDOM we just created, and since I’d like to use async/await, we’ll have to wrap it all in an async IIFE.

(async function(){
global.dom = await JSDOM.fromFile('index.html', {
// To run the scripts in the html file
runScripts: "dangerously",
// Also load supported external resources
resources: "usable",
// So requestAnimatinFrame events fire
pretendToBeVisual: true
})
})()

We’re using the fromFile method of the JSDOM, and telling it to run scripts, load external resources, and pretend to be a normal, visual browser. I’m also assigning it to the global variable dom, to make it easier to access when debugging using Chrome DevTools, but there’s no reason you can’t use a normal, local variable. If you run the script now, you should see something similar to the following output in your terminal:

%c %c %c %c %c Phaser v3.2.1 (Headless | HTML5 Audio) %c https://phaser.io background: #ff0000 background: #ffff00 background: #00ff00 background: #00ffff color: #ffffff; background: #000000 background: #fff

If you inspect the node process in the Chrome DevTools, you’ll see the Phaser banner in it’s proper glory:

The Phaser bannerThe Phaser banner

The Phaser banner in it’s proper glory!

Likely you’ll also see something not as good:

Error: URL.createObjectURL is not a functionError: URL.createObjectURL is not a function

This stuff… not as good

We’ve got an error! It seems like jsdom doesn’t support the createObjectURL method. We’ve got to do something about that. As the createObjectURL returns a URL representing blob, we’ll need to do something similar, and I landed on using the datauri package to return a data URI. In that case we won’t need to do anything in the revokeObjectURL either. Just install it with npm, and we’ll fix things up. Add this to the top of your file:

const Datauri = require('datauri');
const datauri = new Datauri();

And then we’ll add the actual implementation of createObjectURL and revokeObjectURL inside the async IIFE:

dom.window.URL.createObjectURL = function (blob) {
if(blob){
return datauri.format(blob.type, blob[Object.getOwnPropertySymbols(blob)[0]]._buffer).content;
}
};
dom.window.URL.revokeObjectURL = function (objectURL) {
// Do nothing at the moment
};

You’ll see we do some slightly crazy stuff to return the right value, that’s only to get the Buffer object the implementation of Blob uses internally, as that’s what datauri wants. If you know of a better way to do this, please let me know. Restart your server, and you’ll see that everything is working fine.

Using it in a multiplayer game

When using it in your multiplayer game, you would do all this inside your server script. You’d also want to add some more code to handle the networking. I’d recommend doing this in the server version of your game, and not directly in the server script, as it would be easier that way.

If you’re using socket.io, you’d probably want to inject your socket.io instance, often assigned to the variable io, into the jsdom. Just add a single line of code, and you have the same variable accessible inside the game code running on your server.

dom.window.io = io

A basic example is to react to player movement by moving the equivalent sprite on the server. You could setup something like this inside the create function in your Phaser scene:

io.on('connection', function(socket){
socket.on('playermove', function(player){
// Do some logic to move the player
})
});

For a real multiplayer game, you’d want to check the client id, so the server knows which sprite to move. Then you’d send a message back to the client periodically, to tell it where the player (and other players) should, so the client can correct itself. Like this, inside the create function in the version of your game that you run on the client:

socket.on('move', function(move){
player.x = move.x
player.y = move.y
});

In a big production game, you’d ideally use some logic to split out everything server/client side. That way, you could use the same basic code base on both ends, and not need to maintain two separate versions. But for basic games, this will probably do fine.

Conclusion

This was not meant as a complete tutorial on how to implement a multiplayer game using headless Phaser, but more as a quick guide on how you can set stuff up. I’ll let people with more experience with multiplayer game development take over to show you the rest.

If you have any questions, feel free to reach out to me. I usually hang out in our Phaser Discord server, so join me (@16patsle#7801) and the other members there for some questions, big or small, or just a quick chat about Phaser.

This is a repost of a Medium article I wrote last year.


Avatar for Patrick Sletvold

Patrick Sletvold

Written by Patrick Sletvold who … afshjjkfhkjhsfkj sfhskjf shjksh s skjfhsj k sdfsfdsf p.

More about me

© 2024 Patrick Sletvold, Built with Gatsby