Tuesday, May 28, 2013

Avoiding Name Collision for your Sprites

In Cocos2D when sprite sheets are imported the sprites within them come in as - for example - "alarm-clock.png" where this was the original name of the image, before it was composited into the sheet.

I believe Unity and other programs that use TexturePacker work a similar way.

So where is the problem?

Well, what if you have two different alarm clocks?  Or two different images for the same clock?

Collision avoidance!
Even if you have different sprite sheets, all images go into the same name space and because you are not using any kind of directory structure - that was all lost when it was imported into Texture Packer - then you have an issue with name collision.

You have to start prefixing the alarm clock with "blue-alarm-clock.png" or "alarm-clock-1.png" but then it becomes a tussle to figure out what the original clock was, maybe still just named without any prefix.

It becomes very easy for one sprite in your game to wind up being substituted in for another without you realising it and unless you test every branch of the game, you might not notice until its too late and the game has shipped already!

Here's a quick way to disambiguate your images for your sprites, that uses the command line on the mac: embed the sha1sum signature of the image into the filename.  That way as long as the image is different it will have a different file name from any other image sprite file in your game guaranteed.

The key to it is the shasum command which is installed in Mac by default.  If your version of MacOS doesnt' have "shasum" you will probably find that an equivalent tool is installed that you can use - try md5sum or similar.  Same if you're on Windows using Cygwin or minGW-sys.

Here's the code we're going to use, laid out on different lines:

for f in sprites/*.png; do 
    g=$(shasum $f)
    h=${g:0:7};
    b=$(basename $f .png) 
    d=$(dirname $f)
    echo mv -v $f $d/$b-$h.png
done

What this is doing is getting the variable "f" to take on the string value of every file name in your sprites directory that ends with ".png", each time through the "for" loop.  Inside the loop, we store into variable "g" the sha1sum of the file which is a long string that looks like this:

~/Documents/Artwork/seperations-game-ready/main-room sez$ shasum sprites/alarm-clock.png 
1dcd66fcb214b1875ebd28cff83dedf83960707  sprites/alarm-clock.png

Since we only need a few characters from the long sha1 string, the next line is getting a substring of our g variable taking the first 7 characters only.  That also conveniently ignores the trailing filename which is repeated.

Finally we use the basename and dirname built-in bash functions to get the parts of the file name that we need, so we can stitch it back together in the move command.

Note that as per my usual practice I use "echo" so that instead of the move actually executing it just prints out what it would do.

Now we can run this command by opening a Terminal session, changing directory to the directory containing our sprites directory, then run it all on one line like this:

for f in sprites/*.png; do g=$(shasum $f); h=${g:0:7}; b=$(basename $f .png); d=$(dirname $f); echo mv -v $f $d/$b-$h.png; done

You should be able to copy the above line from your browser and paste it into your Terminal session as one single line, with no wrapping.  It's for that reason that I have used the (otherwise horrible) 1-letter variable names.

Where I have "sprites" as the directory name substitute in your own directory name if needed.

Carefully inspect the output of the echo'ed out move commands and if necessary, up-arrow and edit the command until it works the way you like.

Then finally up-arrow, arrow back and remove the "echo" and press enter to execute it for real.

If the command works, then you should see the "mv -v" spit out a line for each file looking like this:

sprites/alarm-clock.png -> sprites/alarm-clock-1dcd66f.png

If you had 10 sprites you should see 10 lines like that you should now be able to check your sprites directory and find a nice list of completely distinctively named files, guaranteed not to collide.

Happy spriting!