Generating Better CreateJS SpriteSheets

As any game developer should know, having an efficient designer / developer workflow is absolutely critical to any project. With CreateJS games, a big part of this workflow is about exporting spritesheets to be used for sprite animation. This step is far from trivial, which might be a bit surprising to some, espcially those who come from the flash development crowd.
This post is not intended to be a CreateJS beginner's guide. Here, we'll take a detailed look at how tools such as Flash CS6 generate code to be used for CreateJS game workflows, and, more importantly, how we can improve it, or create our own.
The basics
Quick reminder: With CreateJS, animated sprites are implemented as BitmapAnimation objects, which constructor expects a SpriteSheet object, which itself expects an object conforming to the various possible syntaxes described in the SpriteSheet class documentation
So, if you want to use such bitmap animations in your CreateJS game, you'll need some graphic tools to generate at least two files:
-
A graphic file including the various animation frames (typically a PNG file). The layout of the various animation frames over the sheet corresponds to a more or less sophisticated algorithm which is completely independant from CreateJS.
-
A file describing the spriteSheet data: frame coordinates, animation data, and the URL of the SpriteSheet PNG file.

Those files can of course define several animated objects, not just one.
SpriteSheet data is typically exported either as a JSON file or as a javascript file. However, using a javascript file has many advantages over JSON, including the fact that you don't have to waste CPU to convert JSON strings to javascript, and you won't face security issues if loading the file from a filesystem (with a file:// URI). For instance:
var playerAnimData = {
images: ["assets/runningboy.png"],
frames: {width:50, height:50},
animations: {run:[0,4], jump:[5,8,"run"]}
};
We know this data will end up being passed to the constructor of the SpriteSheet class. So, while we're at it, we can generate the code which will instantiate those SpriteSheet objects (which will eventually be used by BitmapAnimation objects).
var playerAnimSpriteSheet = new createjs.SpriteSheet(playerAnimData);
Of course, we need to avoid the using global scope for our object declarations. The easiest (but not necessary the best) way to do so is to create an iife to which we pass a unique object declared at the window level, which we'll use as a scope. So, we could end up with something like this:
if (!window.myGame) { window.myGame = {}; }
(function(scope) {
var playerAnimData = {
images: ["assets/runningboy.png"],
frames: {width:50, height:50},
animations: {run:[0,4], jump:[5,8,"run"]}
};
scope.playerAnimSpriteSheet = new SpriteSheet(playerAnimData);
}(window.myGame));
Note that this is not what the Flash CS6' default EaselJS plugin does it. It declares everything at the window level. Fortunately, as we'll see below, it's easy to change that, and I deeply encourage you to do so.
Declaring Classes
We know that those SpriteSheet instances will eventually be used by BitmapAnimation objects, so we can choose to go one step further and generate a BitmapAnimation object, or even better: a BitmapAnimation subclass which we will be able to instantiate directly in the game.
var PlayerSprite = function() {
}
PlayerSprite.prototype = new createjs.BitmapAnimation();
However, this is where things will start to get tricky. The BitmapAnimation constructor expects to be passed the corresponding SpriteSheet instance. This is how the default Flash CS6 Easeljs spriteSheet exporter implements this inheritance:
var PlayerSprite = function() {this.initialize();}
PlayerSprite._SpriteSheet = new createjs.SpriteSheet(playerAnimSpriteSheet);
var PlayerSprite_p = PlayerSprite.prototype = new createjs.BitmapAnimation();
PlayerSprite_p.BitmapAnimation_initialize = PlayerSprite_p.initialize;
PlayerSprite_p.initialize = function() {
this.BitmapAnimation_initialize(PlayerSprite._SpriteSheet);
this.paused = false;
}
Now, there are clearly other ways to do that. You could even subclass your own custom BitmapAnimation subclass here. But keep in mind that you should probably stick with the philosophy of the easelJS library where initialization happens in the initialize method rahter than in the constructor.
Of course, we can augment this generated class with various built-in methods. For instance, the default Flash CS6 exporter adds one method per animation, like this:
PlayerSprite_p.run = function(){
this.gotoAndPlay("run");
}
All this will let the game developer use a simpler syntax in its game code, like this:
var player = new PlayerSprite();
player.run();
…rather than this:
var player = new BitmapAnimation(myGame.playerAnimSpriteSheet);
player.gotoAndPlay("run");
A small improvement, but I'm sure you can see how we could quite easily use this technique to provide much more useful capabilities to your sprites.
Adapting the Flash CS6 exporter to your needs
As we've already said, Flash CS6 contains a SpriteSheet exporter which can generated EaselJS / CreateJS compatible code (not to be confused with the createJS toolkit).
I think many developers will consider the code generated by Flash is either making too many assumptions, or is ugly, does not correspond to its coding style, etc… and will want to control which class is subclassed exactly and how.
This is completely understandable and it's also why any serious graphic tool using code generation will need to expose templates at some point, or even better: let users rewrite the whole code generation logic.

Flash CS6 EaselJS code generation is exposed as a JSFL file in the application's Common/Configuration/Sprite Sheet Plugins directory. So all you have to do is duplicate that file start tweaking it.
If you've never worked with JSFL before, just remember that it's javascript with some special objects and APIs dedicated to manipulate the Flash IDE.
The first thing to do is to give a unique name and ID to your exporter. This happens in the getPluginInfo() method. For instance, this is what one of my custom exporters looks like:
function getPluginInfo(lang){
pluginInfo = new Object();
pluginInfo.id = "easeljs2";
pluginInfo.name = "easeljs2";
pluginInfo.ext = "js";
//...
The rest of the code is relatively easy to understand and you'll quickly find yourself making pretty cool stuffs quite easily: there's a beginExport() method which generates the beginning of the output, and then a frameExport() is called for each frame, and then an endExport() method called at the end where you can create the final part of the output.
For my part, I decided to create two alternative easelJS exporters: one which exports data as pure JSON, the other which just improves a bit the original exporter by adding some parameters, changing the scope and so on.
You can learn more about these projects over at my github repository, but you should consider it as a starting point for your own custom generators, not a definitive, ready-to-use code generator.
Going further: boilerplates and templates
Of course, if the tool use you gives you deeper access ()or if you created your own) you can even go even further and generate a whole lot of additional stuff, including some boilerplate code which initializes a whole ready-to-use createJS project.

That's exactly what the Flash CS6 createJS toolkit does: it generates a whole project including an HTML file (with some javascript taking care of the createJS initialization) and a JS file describing the game library. This separation of concerns allows a designer to quickly test its art in context using Cmd+Enter, but still allows a developer to only use the library file, and go with his own code for the rest of the game.
Using the same kind of tool, which in a way is a relatively minor improvement to the core SpriteSheet generation we've seen before, you can really deeply improve your designer / developer workflow.
Hopefully, I'll give you a more concrete example of such custom tools in a future blog post.
| Attachment | Size |
|---|---|
| img1.png | 85.58 KB |
| plugin-dir.png | 94.55 KB |
| toolkit.png | 42.01 KB |
| createjs-logo.png | 24.78 KB |
