Loading Assets in Phaser 3

Part 1 - Introduction

While it's certainly possible to create a game without using any external graphics at all, before long you're going to want to load some assets and to do that you need to use the Phaser Loader Plugin.

If you've done any work in Phaser before you've likely encountered the Loader, perhaps without even realizing it. Take the following code, something you've probably seen numerous times in the Phaser examples and tutorials:

function preload ()
{
    this.load.image('chtholly', 'assets/pics/sukasuka-chtholly.png');
}

It's worth explaining what is actually happening in the background, to give you a better idea how the Loader operates and some of its more advanced features.

By default, every Scene in Phaser 3 has an instance of the Loader Plugin. You can set a Scene to not create a Loader by modifying its Default Plugins config, but that is beyond the scope of this tutorial and is instead covered in the Scenes tutorial.

The Loader Plugin is created by the Scene Systems class and mapped to the local property load. It's also injected into the Scene itself, meaning if you do this.load or this.sys.load from within a Scene, you're referencing the same Loader. Again, this is configurable, but those are the defaults. This means you should never need to actually create a new Loader() object in your game, as every Scene has access to its own instance already.

In Phaser 2 there was only one Loader, it belonged to the Game itself and every state shared it. In Phaser 3 every Scene in your game has its own Loader instance and they are not shared. This means that you could have two Scenes running in parallel, both loading assets at the same time, should you wish.

From the Loader to the Cache

It's important to remember though that while the Loaders themselves are independent, they all feed into a central cache:

[ insert picture showing several loaders putting files into the central cache ]

Asset keys in Phaser 3 are strictly exclusive among their own types. This means that if two different Scenes try to load an image using the same key, one of them will fail, because the key is already in use in the cache. You could have an image and an xml file using the same key, because they're stored in different caches, but two xml files using the same key would fail.

The Preload Process

When the Scene Manager starts your Scene it will check to see if it has a method called preload. This is a reserved method name in Phaser and allows you to control all of your asset loading up front, before the Scene create method is run. This way, any assets included in the preload method are guaranteed to be available for use by the time create is called.

If a preload method is found it is run. Typically, you'd populate it with several load requests, such as:

function preload ()
{
    this.load.atlas('boing', 'assets/boing.png', 'assets/boing.json');
    this.load.spritesheet('juggler', 'assets/juggler.png', { frameWidth: 128, frameHeight: 184 });
    this.load.image('star', 'assets/star2.png');
}

After running preload the Scene Manager will check to see if there are now any files in the load queue. If you have called this.load at least once, then there will be, and it will now automatically tell the Loader to start. This is why all you need to do in a preload method is call methods like load.image or load.atlas, you never need to actually start the Loader running yourself, as it happens automatically.

When the Loader has completed it will fire an event and the Scene Manager will run create.

The Load Queue

One of the most important things to understand about the Loader is that it works on a queue basis. When you call a command, such as load.image, that image is not loaded immediately. Instead, what you're actually doing is adding a new entry into the load queue. Only when the Loader is started is the queue processed and the browser actually starts making requests. If you are using a preload method then the Loader is started automatically. If you are invoking the Loader manually, it's up to you to call its start method.

When the Loader is started it will look at its local queue of files, take a batch of them and move them to what's known as the 'in flight' list. The in-flight list is a list of all the files that are actively being loaded by the browser. The number of files moved to the in-flight list is controlled by the game configuration property maxParallelDownloads and the default value for this is 32. That means the browser will try to load 32 files simultaneously. Most modern browsers can easily cope with this, especially thanks to HTTP/2, but should you need to change it you can do so like this:

var config = {
    loader: {
        maxParallelDownloads: 4
    }
};

var game = new Phaser.Game(config);

Using the above config the Loader will never put more than 4 files into the in-flight queue at once. This may be useful for legacy or embedded systems. While modern browsers can handle multiple loads, it absolutely puts a strain on both the device and the network connection to do it. This isn't something you often need worry about on desktop but on mobile it pays to be aware of it, especially during testing over a real network instead of locally.

When a file completes loading, either successfully or by throwing an error, it is moved out of the in-flight list and another file is taken from the load queue and moved to in-flight in its place. If there are no more files left, and it was the last one being processed, then the load is over and the Loader begins to wrap things up.

It's possible to dynamically add files to the load queue, even during load, and this is covered in a later section. Suffice to say though, once the queue is empty and the files have finished, the load is considered as being over.

File Types

The Loader works based on the concept of File Types. You can find the source code for these in the loader/filetypes folder. There is a core File class, which most of the different file types extend. For example, there is a JSONFile class, which as you may guess from its name, is responsible for providing support for loading JSON files.

All file types use the same approach when it comes to loading them. You can either pass in the arguments directly, or you can provide a configuration object, or an array of configuration objects. We'll use the Sprite Sheet File Type as an example of these three different ways.

Function Arguments

this.load.spritesheet('balls', 'assets/sprites/balls.png', { frameWidth: 17, frameHeight: 17 });

To load a Sprite Sheet into Phaser 3 you use the spritesheet function. This takes 4 arguments.

At the time of writing there are 25 different file types bundled with Phaser 3. Every one of them is fully documented.

File Class Structure

MultiFile

File Type Injection

Because all of the file types adhere to the same interface, the Loader itself doesn't need to know anything about them. This is why you'll not see any file type specific code anywhere in the Loader class, as it's the job of the file type to handle itself.

So if that's the case, how is it you're able to call a method such as load.xml directly on the Loader? It's because there is a central FileTypesManager. When a new file type is added to Phaser, it registers itself with the FileTypesManager, which maintains a central list of all the file types available. When a new Scene starts and the Loader Plugin is created, it passes itself to the FileTypesManager which then injects all of the various methods directly into the Loader. Therefore, the XML File for example, will inject an xml method into the Loader, which when called just references a function within the XMLFile.

In this way, the Loader is dynamically populated with all of the various file types it supports. In general practice, adding properties to objects at run-time is typically frowned upon, because you run the risk of the JavaScript compiler being unable to create an internal class from it. This is why it's something we never do for really hot areas of code, or frequently accessed objects such as Sprites, where you absolutely want the compiler to be able to optimize them. The Loader, however, doesn't fall into this category. It's typically only invoked once per game, it does it's job while little else is happening, other than perhaps showing a loading bar, and then it's over. Even in multi-part loading games it's still not something you need worry about the internal compiled performance of. For that reason alone, we allow it to be dynamically altered in this fashion.

The life cycle of a single file

Loader Base URL + Path + Automatic File names

Loader Prefix

Loader Events

Loader Configuration Options

Dynamically adding files to the Loader during Load

Load Packs

Creating a Progress Bar

Displaying Images as they're loaded