Loading new Phaser assets without writing code
April 23, 2021
I used to write some lines of code each time i created a new asset for a Phaser game. This was a little bit annoying. But with this Webpack trick, i don't have to think about it anymore.
The goal of this tutorial is that we just have to place our assets in folder and Webpack will pick them up and then our Phaser game will know what to do with them.
Let's start by creating a folder structure in our src
folder, where Webpack will look for the assets. We will not be putting our assets directly in the dist
, pub
or public
folder. Webpack will put them there for us, randomize the name, but give us a reference we can pass to Phaser. The folder structure looks like this:
▾ src/
▾ assets/
▸ audiosprites/
▸ images/
▸ music/
▾ sprites/
▸ 13x11/
▸ 16x16/
▸ 32x32/
▸ 48x48/
▸ 4x4/
▸ tilemaps/
Depending on the folder we will use a different Phaser load function.
But we have to make sure our Webpack config is ok first. I am using Webpack 5, but it can be done with other versions as well, but you may need extra loaders. In the case of Webpack 5, we don't need any extra dependencies.
Clean your distribution folder (mine is public
) . In webpack.config.js
set clean
in output
to true
:
output: {
path: path.resolve(__dirname, 'public'),
publicPath: '',
clean: true
},
Next we need to add a rule to module.rules
also in the webpack.config.js
module: {
rules: [
// other rules
// ...
{
test: /\.(png|svg|jpg|jpeg|gif|mp3)$/i,
type: 'asset/resource'
}
]
},
Next we will use Webpack's require.context method to load everything and add it to Phaser. We will add the code in the preload
function of a scene. The this
refers to a Phaser scene.
Images
let importAllImages = (r) => {
r.keys().forEach((k) => {
let name = k.replace('./', '').replace('.png', '');
this.load.image(name, r(k));
});
};
importAllImages(require.context('../assets/images/', false, /\.(png)$/));
That was the easy one to understand.
Music
let importAllMusic = (r) => {
r.keys().forEach((k) => {
let name = k.replace('./', '').replace('.mp3', '');
this.load.audio(name, r(k));
});
};
importAllMusic(require.context('../assets/music/', false, /\.(mp3)$/));
Very similar like the first one.
Sprites
let importAllSprites = (r) => {
r.keys().forEach((k) => {
let parts = k.split('/');
let sizes = parts[1].split('x');
let w = Number(sizes[0]);
let h = Number(sizes[1]);
let name = parts[2].replace('.png', '');
this.load.spritesheet(name, r(k), { frameWidth: w, frameHeight: h });
});
};
importAllSprites(require.context('../assets/sprites/', true, /\.(png)$/));
Sprites will have a subfolder for the sizes of their frames. A sprite that is 32 pixels wide and 64 pixels high will be in the 32x64
folder.
Audiosprites
let importAllAudioSprites = (r) => {
r.keys().forEach((k) => {
let name = k.replace('./', '').replace('.mp3', '');
this.load.audio(name, r(k));
});
};
importAllAudioSprites(require.context('../assets/audiosprites/', false, /\.(mp3)$/));
let importAllAudioSpriteData = (r) => {
r.keys().forEach((k) => {
let name = k.replace('./', '').replace('.json', '');
this.cache.json.add(name, r(k));
});
};
importAllAudioSpriteData(require.context('../assets/audiosprites/', false, /\.(json)$/));
Two things to note here: The json file and the mp3 file need to have the same name and the json files will be compiled in your bundle.
Tilemaps
let importAllTilemaps = (r) => {
r.keys().forEach((k) => {
let name = k.replace('./', '').replace('.json', '');
let data = r(k);
this.cache.tilemap.add(name, {format: 1, data: data});
// extra: make a minimap image
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let map = this.make.tilemap({ key: name });
canvas.width = map.width;
canvas.height = map.height;
let layer = map.getLayer(0);
layer.data.forEach((row) => {
row.forEach((tile) => {
if (tile.properties.collideAll) {
ctx.fillStyle = "#8e918b";
ctx.fillRect(tile.x, tile.y, 1, 1);
} else if (tile.properties.swimable) {
ctx.fillStyle = "#9aadb3";
ctx.fillRect(tile.x, tile.y, 1, 1);
} else if (tile.properties.climbable) {
ctx.fillStyle = "#9ab3a1";
ctx.fillRect(tile.x, tile.y, 1, 1);
} else if (tile.properties.collideUp || tile.properties.collideSupport) {
ctx.fillStyle = "#b2b4b0";
ctx.fillRect(tile.x, tile.y, 1, 1);
} else {
ctx.fillStyle = "#dddddc";
ctx.fillRect(tile.x, tile.y, 1, 1);
}
});
});
this.textures.addBase64('map ' + name, canvas.toDataURL());
// end extra
});
};
importAllTilemaps(require.context('../assets/tilemaps/', false, /\.(json)$/));
Note: The json files will also be compiled into the main bundle js.
Final note
The assets can now be used in your scenes by the filename. If your filename is background.png
you can use it as
this.add.image(0, 0, 'background');
That's it... Hope you find it useful.
No comments yet. Be the first.