How Do I Organise Files in A Phaser.js Project?

Among newer JavaScript developers, the manner and order in which JavaScript code is executed is often a source of confusion. Not quite grasping how your code is "seen" by the JavaScript interpreter can make it quite hard to organise your game's source files in a convenient and orderly manner, and can also cause unexpected behaviour in your code.

The official phaser examples are very helpful as quick proof-of-concepts on how to implement certain common game features with Phaser, but judging by the comments left on some of these examples many newer developers take them as gospel-truth, structure their game's source code in a similar way, and run into problems because their code then doesn't work as they expected.

In this article I'll attempt to illustrate a better way to organise your game's JavaScript source files than the basic method illustrated in the Phaser examples.

JavaScript Compared to Other Languages

If you're familiar with using strongly-typed, compiled languages such as C# and Java, you'll know that in these languages a certain amount of structure is inferred upon the program's source code by the needs of the compiler. For instance, with the necessity of the main method or with the use of import or include statements.

In JavaScript, however, code is executed from top to bottom. Code is executed in the order it is encountered by the JavaScript interpreter (your browser or the JavaScript runtime you're using). There is no entry point to the application (a main method)

The First Deadly Sin

When using <script> tags in a HTML page, many new JavaScript developers mistakenly include the <script> tags referencing their libraries in the wrong order (for instance, by adding their reference to Phaser after the reference to their source code). This would result in their game's code executing, but not knowing about Phaser yet. The code will break and throw an error similar to Phaser is not defined in the console.

This example will break:

<html>
    <head>
        <script src="localhost:3000/my-game.js"></script>
        <script src="localhost:3000/phaser.js"></script>
    </head>
    <body>
        <!-- WRONG... Will break. Phaser is not defined -->
    </body>
</html>

The following example will not

<html>
    <head>
        <script src="localhost:3000/phaser.js"></script>
        <script src="localhost:3000/my-game.js"></script>
    </head>
    <body>
        <!-- Phaser is defined by the time the game code is executed. Happy days. -->
    </body>
</html>

This way, it is easy to see the top -> bottom execution of the code.

The Second Deadly Sin

Another common source of unexpected behaviour in JavaScript is the unwitting declaration of variables in the global scope.

What is "scope"? The "scope" of a variable is the place in the code that variable can be accessed. Variables in JavaScript are function-scoped, which means that variables are private to the executing function and functions declared within it.

Scope in JavaScript is quite easy to understand. Here are two examples that will help you out.

var numberOfBottles = 1337;
function HowManyBottles() {
    numberOfBottles = 99;
    console.log("There are " + numberOfBottles + " bottles of non-alcoholic carbonated beverage on the wall");
}
HowManyBottles();

The output is There are 99 bottles of non-alcoholic carbonated beverage on the wall. That's because the numberOfBottles variable is declared in the parent scope of the HowManyBottles() function. Therefore, the HowManyBottles() function can happily access and set the numberOfBottles variable.

Now study this example.

var numberOfBottles = 99; // This is global
function HowManyBottles() {
    var numberOfBottles = 1337; // This is local
    console.log("There are " + numberOfBottles + " bottles of non-alcoholic carbonated beverage on the wall");
}
HowManyBottles();
console.log("There are " + numberOfBottles + " bottles of non-alcoholic carbonated beverage on the wall");

This is the output:

There are 1337 bottles of non-alcoholic carbonated beverage on the wall
There are 99 bottles of non-alcoholic carbonated beverage on the wall

This is because the HowManyBottles() function is now using it's own local numberOfBottles variable with is set to 1337 within the function. Therefore, the global numberOfBottles variable isn't re-set to 1337 inside the function and stays as 99.

The thing to notice here is the use of the var keyword. The use of the var keyword allowed us to re-use the numberOfBottles variable inside the function and not affect the world outsite the function.

You do not want to unwittingly pollute your global scope with all of your variables. If you add something to the global scope, it should be because you explicitly wanted it that way, not by accident.

This is how stop scope being a problem in your code:

  • Always use the var keyword to declare variables.
  • Always surround logically-seperate blocks of code in an anonymous function.

This is how you would use an anonymous function to separate logically-distinct blocks of code:

var n = 60;
 
// An anonymous function - no function name, immediately executed.
(function() {
    console.log(n); // n is undefined inside the function
    var x = 50;
})();
console.log(x); // x is undefined outside the function

Here's the real trick. You can selectively expose variables to the global scope from inside your anonymous functions.

var myGame = {};
(function(myGame) {
    console.log(typeof myGame); // object
})(myGame);

The Third Deadly Sin

Many JavaScript developers, both n00b and ninja, aren't aware that JavaScript is an object-oriented language in the highest degree.

Objects are a programming construct to relate sets of code to real-world things like Cars, Players, Lightsabers, or Flying Spaghetti Monsters.

You should be separating your game's components out into objects that have distinctly related properties and methods.

Here's a trivial example:

var game = new Phaser.Game(/*...*/);
function Car(acceleration) {
  this.sprite = game.add.sprite(game.world.centerX, game.world.centerY, 'car-sprite');
   this.acceleration = acceleration;
   this.currentSpeed = 0;
}

Car.prototype.accelerate = function() {
    this.currentSpeed += this.acceleration;
}

You can create and use instances of Car like this:

var nissanMicra = new Car(2);
var lambourghiniGallardo = new Car(900);

lambourghiniGallardo.accelerate();

The Solution

Here's how you should be organising your Phaser project's code.

  • Separate your game's code out into files with one object per file.

  • Wrap your objects in anonymous functions.

Selectively expose your objects to the global scope like this:

window.myGame = myGame || {}; // Sets myGame to a blank object if it isn't already defined
(function(myGame) {
    function Car() { /* ... */ }
    Car.prototype.accelerate = function() { /* ... */ }
    myGame.Car = Car;
})(window.myGame);

Note that, in JavaScript in your browser, everything in the "global" scope is actually attached to the window object.

  • Have a main file that initiate's all of your game's objects and serves as the "entry point" for your game.

This way you can overcome JavaScript's top-to-bottom execution and make sure things run in a controlled manner.

// Initialize the game instance

window.myGame = window.myGame || {};

(function(Phaser, myGame) {
     var game = new Phaser.Game('100%', '100%', Phaser.AUTO, '', { preload: preload, create: create, update: update });

    var road, car;
    function preload() {
        game.load.image('car', '/images/car.png');
        game.load.image('road-texture', '/images/road-texture.png');
    }

    function create() {
        road = new myGame.Starfield(myGame);
        car = new myGame.Player(myGame);
    }

    function update() {
        road.update();
        car.update();
    }

})(window.Phaser, window.myGame);

Extra Brownie Points

Use a tool like jshint to automatically detect common problems in your code. JSHint wil, importantly, alert you if you're not using scope correctly.

NB: I employ this strategy in my game Heroes of Deep Space, available on GitHub. If you'd like an example of these techniques in practice, take a look at the repository.