Spritesheets Revisited – Game sprites with Inkscape + Gimp

I’ve lately been doing some work with the (awesome) Unity game engine, prototyping a 2D platformer using version 4.3’s new 2D mode, and as part of that I needed to create some sprite-sheets for game-related animations and the like.

Inkscape Screen Shot
Spritesheet fodder!

As I’ve mentioned before, my tools of choice for graphical work are Inkscape and The GIMP, so I needed to  put together a workflow that would allow me to draw vectors in Inkscape and then export pixels to Gimp, where I would create suitable spritesheets for import into Unity.

I don’t want delve too deeply into the Inkscape/Gimp interaction in this post (I’m still stuck on Windows at the moment, so I just followed this post to get Inkscape’s “Save as XCF with layers” feature working – thanks to Chris Hillbig for the instructions). So, skipping over that part, what I ended up with was a GIMP image, made up of layers, each layer being an animation frame. I now needed to make this into a flat spritesheet, suitable for PNG export to Unity.

If all this sounds a bit familiar, that’s because it’s very reminiscent of this post from September 2012 (if you’ve read that post, then thanks for sticking around for this long!), when I was busy putting retroify.me together and needed to easily create spritesheets, in GIMP, from layered images. Of course, I initially just used the GIMP script I developed then to create my sheets, and while it did work, it wasn’t perfect. Why? Well, to explain that, I think a little background is in order.

The way retroify.me works, it’s easy (and efficient) for us to use very wide spritesheets with a single row of sprites, and so the script I wrote way back in 2012 does just that – it creates a sheet that’s arbitrarily wide, with the overall height being the height of a single sprite. For Unity, however, this is non-optimal. Like most game-engines, and anything else that works directly with video hardware to provide high-performance graphics, Unity prefers its assets to be sized by powers of two. While it will happily chomp away on an image that’s 5376 pixels by 256, and even handle the slicing for me, this can result in some odd visual artifacts, and will prevent proper optimisation of the graphics in the game by the engine. I really needed to revisit my old script, and add in the ability to create images with multiple rows, to allow my spritesheets to fit neatly into power-of-two-sized images.

And so, it was back to Scheme. As I mentioned in the old post, I’m not a Scheme programmer, but it’s always fun to play around in an unfamiliar language. I spent a little while hacking around, re-reading the GIMP script API, and generally having fun, and the end result is a new script that does everything I need. In the spirit of the previous post, here is the updated code:

; Script to convert image layers to a sprite sheet.
; Copyright (c) 2012-2014 Ross Bamford.
; http://www.apache.org/licenses/LICENSE-2.0.html
(define (script-fu-unityspritesheet img sheet-size-opt)
  (let* (
      (layer-count          (car    (gimp-image-get-layers img)))
      (layer-ids            (cadr   (gimp-image-get-layers img)))
      (sprite-width         (car    (gimp-image-width img)))
      (sprite-height        (car    (gimp-image-height img)))
      (spritesheet-width    (expt 2 (- 12 sheet-size-opt)))
      (sprites-per-row      (/ spritesheet-width sprite-width))   
      (rows-needed          (ceiling(/ layer-count sprites-per-row)))

      ; Create new image with appropriate size
      (spritesheet-image (car (gimp-image-new 
                                      spritesheet-width
                                      spritesheet-width 
                                      RGB)
                          )
      )
    )

    ; Copy and move each source layer appropriately
    (let (
        (i 0)
        (y 0)
        (x 0)
      )

      (while (< i layer-count)
        (let (
            ; Copy the layer...
            (new-layer (car(gimp-layer-new-from-drawable 
                                      (aref layer-ids (- layer-count (+ i 1))) 
                                      spritesheet-image)
                        )
            )                     
          )

          ; Insert, position and make it visible
          (gimp-image-insert-layer spritesheet-image new-layer 0 -1)        
          (gimp-layer-translate new-layer x y)
          (gimp-item-set-visible new-layer TRUE)
        )

        ; Update i/x/y for next iteration...
        (set! i (+ i 1))
        (set! x (+ x sprite-width))
        
        (if (>= x spritesheet-width)
            (begin
                (set! x 0)
                (set! y (+ y sprite-height))
            )
        )
      )
    )

    ; 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-unityspritesheet"
                    "Create Unity spritesheet"
                    "Create a Unity-compatible spritesheet from current image layers"
                    "Ross Bamford"
                    "Copyright (c)2012-2014 Ross Bamford"
                    "2012-2014"
                    "*"
                    SF-IMAGE    "Image"         0
                    SF-OPTION   "Size"          '("4096" "2048" "1024" "512")
                )

(script-fu-menu-register "script-fu-unityspritesheet"
                         "<Image>/Sprites")

(Once again, thanks for the folks at ToHTML.com for the syntax highlighting 🙂 )

Advertisements

Random headache #57 – Rails 4 and Nokogiri on Windows

Facepalm

So I’m currently working on a rails-based internal app for a company I occasionally do some work for (and often, wish I didn’t), and for one reason or another I’m stuck developing on a Windows box. I won’t go into the reasons for this right now (suffice it to say that I blame Broadcom. Or Sony. I’m not sure…) but it is what it is – I’m stuck with Windows on x86-64.

The stack in use here is Rails 4.0 on Ruby 2.0, which is apparently known to be problematic on 64-bit Windows, and it seems there have been some previous problems with Nokogiri added into the mix. I’ve spent a few hours now chasing this one down, and googling around has turned up quite a few people experiencing similar issues. The problem is that I could not get Bundler to use the (already installed) fat binary Nokogiri 1.6.1. No matter what I tried at the (braindead) windows command prompt, it would always download the source gem and try to compile it. Which didn’t work. If you’ve ever tried to get a working libxml2 build going on Windows, you’ll understand why I wasn’t prepared to push it. I just wanted to use the pre-built binary provided by the nice folks at Nokogiri.

After putting up with an annoying develop/test cycle involving pushing every change over to an old (Linux) laptop, running tests, and then coming back to the Windows machine for further coding, I finally found a way to make it work on Windows, thanks to a read through the Gemfile manual. It turns out I can unpack the binary gem into a local path within my app, using the :path option. So I unpacked the binary gem into a new directory, and then modified the app’s Gemfile thusly:

# Workaround windows issue (quelle suprise) with nokogiri on win64
# Note: We don't actually require nokogiri (which is why there's no platform agnostic
# gem line for it) - it's only required by the other dependencies...
#
# Also, since we wouldn't dream of deploying production on a windows box, this is
# a development/test only dependency...
group :development, :test do
  gem 'nokogiri', '1.6.1', path: File.join('.', 'localgems', 'nokogiri-1.6.1.beta.1.mingw.1-x64-mingw32'), platforms: :x64_mingw
end

With this in place, I was finally able to run bundle install all the way to the end. I did run up against one other problem (an execjs runtime error, which pops up because Windows doesn’t support v8, and so therubyracer is incompatible too), which I patched up by following these instructions (this is for execjs 2.02 – YMMV with other versions, obviously). After that, I was able to run my specs, and even start the rails server locally.

Job done! Now I can get back to writing some actual code…

Edit: After sorting this, I did a bundle upgrade and moved to rails 4.0.1. Predictably enough, everything broke again, with all kinds of exceptions about missing timezone info. When the obvious quick-fix didn’t work, I gave up and moved back to 4.0.0. I’ve banged my head against this particular brick wall enough for one day!

One-to-many in ORMDroid – at last!

I’m pleased to announce that as of this morning, ORMDroid now has some basic support for one-to-many relationships between entities, thanks to Jacob Ferrero and as hinted at in a previous post. Jacob actually put this together a few months ago, the only hold-up has been me getting around to merging the code in, so it’s great that it’s finally done. It’s not in a release yet (later this week is the plan on that) but is available in Git on the master branch.

Jacob explains the changes better than I can, so here’s a quote directly from his original pull request:

Basic usage is like this:

Person.java

class Person extends Entity {
    public String name;

    @Column(inverse = "person")
    public ArrayList<Hobby> hobbies;
}

Hobby.java

class Hobby extends Entity {
    public String type;
    public Person person;
}

And then you would just use it like normal:

Person person = Entity.query(Person).where(eql("name", "Christ")).execute();

for(Hobby hobby : person.hobbies)
    Log.d(TAG, "Hobby found: " + hobby.type);

It’s worth noting that this change does break the API slightly, the most common case being that if you’ve previously used @Column(inverse = true) then you’ll need to migrate to the new form which names the foreign column.

In the unlikely case that you’ve implemented your own type mappings, you’ll also need to make some changes as the public API has changed slightly (there’s a new parameter, precursors, which if you’re not currently using you can safely ignore in your code) and there have been some minor changes under the hood (e.g. in EntityMapping#load) that you’ll need to update your code for.

All in all, some long-overdue positive movement in ORMDroid. We’re definitely moving toward a new release this week, and I promise to be less tardy in the future!