One of the coolest Android projects I’m involved with at the moment is a new strategy game, and one of the coolest things about it is it’s retro-look 8-bit style graphics. Growing up in the 80’s I obviously got a lot of exposure to the unique look of games in the 8-bit era, and I have fond memories of hacking away on the C64 and Spectrum, trying to represent real objects in a tiny number of pixels. So I’m really loving the resurgence of these techniques on current mobile devices.
Although there are a few “pixel art” graphics tools out there, we’re actually using the gimp to create our art (it’s what we know and love) and the game itself is using a layered sprite system to allow us to keep raw data to a minimum and also to make it easy for us to use different permutations of layers to create different sprites. For example, all the characters in the game are built from a small set of sprite layers which we just overlay in different combinations.
Perhaps an example is in order. Consider the rather snazzy (and somewhat surprised) looking individual on the left. He’s actually composed of a number of different layers in gimp, which are grouped and arranged in such an order that when one layer from each group is visible, they combine to create a character (in this case a mildly-shocked purple-haired gentleman in a blue sweater).
There are a number of these files, some covering people (as in this example), some with vehicles, some with background objects, and so on. And in each one, there are many layers each with a different sprite image. Not all are overlaid in this way, but they all use gimp’s layer features. Continuing the example, the image on the right shows you the gimp layers palette with the various layers that comprise the purple-haired dude.
If you look carefully you’ll see that our chap comprises a number of layers, with helpful names such as “skin_med” and “hair_02_purple”. All this works great while we’re actually drawing stuff, but once we come to actually put it into the game, we’re obviously not importing layered XCF files – instead we break up the layers into a sprite sheet which flattens all the layers out and arranges them side-by-side in a single image (traditional sprite-sheets actually use multiple rows and columns, usually to give a fixed width and height as required by the hardware – we’re not doing this at the moment, although we do support it in the graphics engine). Turns out that this is not especially well supported in gimp.
Originally, we were using the built-in filmstrip plugin to accomplish this, but this was always a stop-gap solution – filmstrip is designed for use with photos, as a visual effect. As such, it actually adds virtual sprocket holes, frame numbers, and other assorted things. All nice if you’re making a montage of your week in Barbados, but no so great if you’re trying to get an exact-sized image you can then index into by pixel from your code. It is possible to stop these effects by tweaking the settings, but they don’t get saved across sessions, and in any event it doesn’t support transparent backgrounds so there’s always an extra select by colour/delete step once the sheet is made.
Luckily, Gimp supports scripting (via it’s Script-Fu interface to the Scheme language), so it was a fairly simple matter to knock up a script to take care of the conversion from layered image to flattened spritesheet. Gimp also supports Python scripts, but given the choice between a whitespace-sensitive language and a Lisp-like dialect, I’ll take the latter every time. It’s been a while since I last wrote Scheme, making it an ideal time to brush up some skills. A definite win-win situation!
After a little hacking around the API (Script-Fu has changed a bit since I last used it, but not so much that it’s unrecognisable) I had a basic working script, which I could call at any time through a menu item (on a new ‘Sprite’ menu I added, in anticipation of further scripts and plug-ins the guys have already asked me for). Going back to our example, the script creates a spritesheet in a new image, which for the guy above looks like:
Obviously, this example is so contrived as to be pointless. The real files have many more sprite tiles than this – I don’t think I’d be too popular if I started dumping all our graphic artifacts on here – but you get the idea.
And just in case you’re doing something similar and might find this useful, here’s the code. Call it a Saturday present from me, to you.
; Script to convert image layers to a sprite sheet. ; Copyright (c) 2012 Ross Bamford. ; http://www.apache.org/licenses/LICENSE-2.0.html (define (script-fu-spritesheet img) (let* ( (layer-count (car (gimp-image-get-layers img))) (layer-ids (cadr (gimp-image-get-layers img))) (sprite-width(car (gimp-image-width img))) (spritesheet-width (* sprite-width layer-count)) ; Create new image with appropriate size (spritesheet-image (car (gimp-image-new spritesheet-width (car (gimp-image-height img)) RGB) ) ) ) ; Copy and move each source layer appropriately (let ( (i (- layer-count 1)) ) (while (> i -1) (let ( (new-layer (car(gimp-layer-new-from-drawable (aref layer-ids i) spritesheet-image) ) ) ) (gimp-image-insert-layer spritesheet-image new-layer 0 -1) (gimp-layer-translate new-layer (- spritesheet-width (* sprite-width (+ i 1))) 0) (gimp-item-set-visible new-layer TRUE) ) (set! i (- i 1)) ) ) ; Merge layers and rename (gimp-image-merge-visible-layers spritesheet-image CLIP-TO-IMAGE) (gimp-item-set-name (aref (cadr (gimp-image-get-layers spritesheet-image)) 0) "spritesheet") ; Create view for new image (gimp-display-new spritesheet-image) ) ) (script-fu-register "script-fu-spritesheet" "Create spritesheet" "Create a spritesheet from current image layers" "Ross Bamford" "Copyright (c)2012 Ross Bamford" "2012" "*" SF-IMAGE "Image" 0) (script-fu-menu-register "script-fu-spritesheet" "<Image>/Sprites")
(Caveat: I’m not primarily a scheme programmer. If you are, and you find my style abhorrent, well, tough :D)
(Postscript: Turns out that WordPress.com doesn’t support Scheme in it’s sourcecode tags! Thanks to the guys over at tohtml.com for their highlighter, even though I had to pretend this was lisp to get it highlighted anything like properly.)