Using OmniGraffle as a level editor

This was a fun one. As we've mentioned before, we're currently working on a board game for a client. Without going into the details, the game is based on nodes on a game board that are linked together; players move between connected nodes, and claim territory.

When coding up the game, we initially generated the game boards in memory, by directly instantiating 'node' and 'link' objects. Then, when it came time to make the game load different boards on demand, we created a JSON-based data structure that defines the nodes and links. It looks a little something like this:

This structure defines two nodes, including their names and positions, as well as a relationship that links the two together. (Because the graph is nondirectional, we didn't want to define links in the node data structure itself.)

This is all well and good for simple game boards, like this:

tutorial-board

But then the client said, "cool, can you make this?"

hex-board

And we were all:

Reaction_ItalianSpiderman

Clearly, hand-coding wasn't an option, and a tool was needed. Writing one ourselves wouldn't have been the best use of our time, so we looked at our favourite Mac app ever: OmniGraffle Pro. OmniGraffle already knows about objects and connections, and it exposes all of this information through AppleScript.

So, we went searching, found this excellent Gist by Michael Bianco, and adapted it into one that extracts the information we need and generates the game board data that we care about.

Loosely put, it turns this:

omnigraffle-board

Into a level that can be loaded:

sembl-board

How it works

First, we designed the level. In this board game, there are only nodes and connections; we represented nodes as circles, and connected the nodes with lines.

omnigraffle-shot

The first thing the script does, we get access to OmniGraffle via the Scripting Bridge:

OmniGraffle exposes a list of shapes, which AppleScript can query for important information such as the origin point, and its text. You can also query and set a special "tag" property for each shape, which is useful for storing an identifier. We base the identifier on the text of the shape, if it has any; otherwise, we use a random number.

So, to generate the list of game nodes, we ask OmniGraffle for all shapes, and format the result into a hash, which we store in an array for later.

When generating the hash for a node, we can also make use of the user data dictionary that OmniGraffle Pro exposes. This lets you set custom key-value pairs for a shape, which is very useful for setting things like which player owns a node, or at what point in the game the node becomes active. This is a simple matter of merging in the userData hash.

Once all nodes have been processed, we know that all shapes have had a tag associated with them; we can then iterate over all shapes a second time, this time generating information for each connection.

Finally, we export the nodes and links as JSON:

Because this script operates on the frontmost open document in OmniGraffle and outputs to stdout, saving the JSON is as simple as a one-line command: $ ruby graffle2board.rb > MyAwesomeBoard.board

Summary

This is a pretty powerful technique, since it lets us design the game maps with a powerful (and, more importantly, pre-existing) tool and import them exactly the way we want them. We're definitely going to be using this more in the future.

You can see the script in GitHub!