Entries in code (4)

Friday
Sep302011

Blackboard storage for your game's component architecture

In this article I've outlined a couple of ways this kind of data storage can be used alongside component-based stores for global state and shared data. You can find examples of this implemented in our latest component prototypes.

 

Background and context to date

To date we'd been sharing global data using one of the following methods:

  1. The GameWorld's local store (a dictionary)
  2. Properties on the GameWorld object itself
  3. In-component referencing / lookups

These have all got problems -- the first requires proper management of lookups, is slow for common lookups (each lookup is a key search) and is clunky for access outside of the game world (i.e. by controller interfaces; the second requires modification of the GameWorld class -- this feels very wrong and makes the GameWorld code completely unportable, and; the third leads down the road of interdependency and presents similar problems to traditional OO hierarchies (inflexibility, obfuscation and bottlenecks).

No perfect solution

A component-oriented design really should be as component centric as possible, but there are cases where there is no clear "component" solutions, for example:

  • GameWorld <> Application <> OS communication
  • Controller interfaces and attachment
  • Game state
  • Caching

The blackboard architecture helps with all of the above cases.

It also helps with more debatable components -- things like map managers -- where there's a strong case for externalising the functionality of a component but also a desire to have belong in the game world (especially for the purposes of messaging, updates etc). Other components can acccess said map managers directly as if they were singletons or external systems, but they can also participate in the gameworld's messaging system or update loops as required.

How to implement a component-friendly blackboard data store

We're simply creating a blackboard class and hosting an instance of it on the GameWorld object itself. This means that:

  • Specific data stores are exposed through properties
  • As a result data access is type safe
  • The blackboard can house it's own LoadData function to populate from persistant storage if required
  • The blackboard class itself is project-specific but it's hosting in the GameWorld is generic (keeping your game world, objects and components generic utilities)
  • Access is direct for all components (e.g. component -> object -> world -> blackboard)

Smarter game components and behaviours

Abstracting away shared storage has a couple of other benefits at the component level as well.

A great example of this is, in the iOS/Cocos2d space, sharing sprite batches between instances of the same component. Our simple BCharSprite is instanced in a number a different types of game objects and hence is difficult to build factory and shared methods for -- it's a true component in it's isolated behaviour. However, we want to be able to re-use sprite batches when the sprite sheets themselves have already been loaded by another component. This is accomplished very easily using a blackboard using a single NSMutableDictionary accessed as follows:

 

Should I use a blackboard for storing data in my game's component architecture?

As with anything component-oriented (and really any software design) this does require some forethought and consideration -- you need to understand why you are implementing this kind of storage, how your comopnents are independent and (as they inevitably will be in some way) interdependent.

There is no perfect solution, but there are lots of great ones. This seems to be working for us so far.

More information:

Wednesday
Oct132010

How to use Illustrator, SVG, TouchXML and XPath for simple level design, meshes and other game data

In the run up to implementing some relatively complicated mesh management (including collision detection, triggers and game mechnics) for our main title in development (Dead West) we wanted to try out a few methods in our component architecture-based framework. There'll be posts on this later - suffice to say component-based development is kicking the ass of the bloated-and-object-oriented alternatives.

Our entry in the Ludum Dare October Challenge, Ludum Racer (soon to likely be called Dwarf Derby) was the perfect platform for this - it's a simple top-down racer with relatively basic physics and flat collision data. Most importantly, it's based on the same component architecture that Dead West is based on.

So, that's something of a round-about away of getting to the point of this post -- we wanted to try using Illustrator as a level/track design tool. Whilst we could've easily used Vertex Helper or similar for the Dwarf Derby project, Dead West has a complicated, layered set of co-ordinate-based game data (that Illustrator is well suited to managing).

The plan (and thus far successful execution) was this:

  1. Create nav mesh (just triangles for the purpose of this) in Adobe Illustrator
  2. Export from Illustrator to SVG
  3. Load that SVG in-game using TouchXML
  4. Build whatever data structures we need in memory by querying the XML/SVG document with XPath statements

This required some specific methods and tweaks, so I've brought those together below in a deliberately basic how-to. You'll need to embelish this with your own data and outputs, but it should provide a reasonably solid process to build upon.

Illustrator - setting up layers and creating shapes

Setup a couple of layers - you'll likely want a background reference layer upon which you'll draw the require geometery.

In the example above, we've got a "reference art" layer, that contains a flattened version (from a Photoshop original) version of the level art and a "triangles" layer, that contains all the geometry we want to use.

It's important that you place the shapes that generate your in-game data on dedicated layers. When we get to the SVG stage you'll see why.

Create your polygons (we're using triangles here, but any polygon would work in effectively the same way) using the line tool. With the tool selected:

  • Click and drag to make the first edge (then release the mouse)
  • Hover the mouse of the end point of that edge so you see "anchor" appear
  • Click and drag the second edge (then release)
  • Connect the triangle by clicking from the end anchor of the second edge to the start anchor of the first edge

So far we just have a collection of edges, and as you draw out more polygons a direct export to SVG will result in a confusing mess of unconnected co-ordinates.

In order to distinguish the polygons, we must group their edges - select the 3 (or however many if you're not using triangles) edges and then hit CMD+G (or right click, Group).

Exporting to SVG

In Illustrator, use save-as and select "SVG (svg)" - not "SVG Compressed".

In the save options dialog for SVG, check you're using v1.1 and switch image location to "Link" - unfortunately you have to do this every time.

If you take a look at the SVG document, you'll see why we grouped the edges of our polygons, and why we collected all relevant shapes on a single layer -- most everything useful in SVG is a "g" element. Later using TouchXML and some XPath queries, we can use this structure to get at exactly what we need.

Loading the SVG in your iOS project

You need to get TouchXML running in your project first - I'd recommend this excellent TouchXML installation guide at foobarpig.com. Note that you'll need to download TouchXML itself from github, not the old Google code location.

Test you can compile once TouchXML's included in your project, then add an #import "TouchXML.h" to the relevant class and load up your XML -- in the example below we've a simple method that loads an SVG file, sets up the SVG namespace and executes a test query (specifically populates an NSArray with all of the triangles from our "triangles" layer).

At this point it's over to you - we'll be stepping over that array and building an in-memory array of our geometry for some simple collision detection. 

A couple of important points to note:

  • Illustrator's origin is top-left, so you need to transpose any coordinates accordingly.
  • If you are just wanting the polygon nodes, then you only need to pay attention to the x1 and y1 attributes (see SVG sample above) as the x2 and y2 coordinates of the first edge will be the same as the x1 and y1 of the second edge.

A couple of less important but otherwise useful points:

  • There's nothing stopping you from pre processing the SVG (XSLT would be a great way to do this), or to transpose it to another data format (for Box2d for example).
  • You can easily add additional data (particularly for debugging) to you data by simply adding Illustrator text elements to the polygon groups. They will get output into the SVG in "text" elements, which would be ignored when querying for "g" elements with the XPath above.
  • XSLT and XPath are wonderful tools if you're working with XML a lot - I can thoroughly recommend Doug Tidwell's XSLT book if you want to learn more - I've had a copy beside my desk for longer than I care to remember. 

Update: TouchXML is now at https://github.com/TouchCode/TouchXML

Tuesday
Sep282010

Texture tiling on the iPad - scrolling painted backgrounds > max OpenGL ES texture size of 2048x2048px

Dead West, an iPad game which we recently introduced here uses a forced-perspective top-down view of the game world - with, what we originally thought trivial - painted background.

Because the goal of the technical design was to provide for a high quality painted background that will spread beyond the maximum texture capacity supported by the iPad (2048x2048), we needed to design a rendering engine that will be able to take multiple tiles and draw them as one seamless, scrollable background on the iPad screen. After a holy-crap-this-is-harder-than-we-thought stage, we ended up with a design that meets most of our needs, with additional possibility for optimization if needed.

The process we are currently using (Texture Tiling) shouldn't be confused with Tile Mapping.  Tile maps are normally created from a texture atlas where you can draw many objects by reusing elements on the same atlas. This allows  you to draw many objects on the screen using one batch (i.e. no texture swapping).

The Texture Tiling engine’s job is to take tiles from the disk and make them available in VRam.  Once in VRam, we use those textures to build a bigger texture using an Frame Buffer Object (FBO).  An FBO is a render-to-texture technique often used for special effects. In our context, we use it to cache previously loaded textures, so they can be unloaded when not required. The first time we create the FBO it’s a bit time consuming because we need to get all of the tiles (currently 256x256px) from disk and render them one after the other into the FBO.  But further updates are much faster since we can reuse the FBO texture itself and only add to it the new textures that are coming into view.

In the above outline, the green tiles will be rendered as the visible part of the play surface moves up, and the red ones ultimately unloaded once they move out of the visible view.

This techniques is working well when we don’t have too many textures to load from disk after an update. The game plays in landscape orientation, so, for example, we can render and scroll an 8000x768px play surface with relative ease using this technique. Every time we need to load a new zone (textures that are not yet loaded from disk), we load a maximum of 3 textures (3x256==768px==iPad screen height in landscape orientation). Similarly, a 1024x8000px play surface results in a maximum load of 4 textures.

A square play surface of 2048x2048px is the natural size of a fully loaded FBO and doesn’t incur any cost at all. This is by far the best bang for the buck size vs performance.

Maps over 2048x2048px cause some performance headaches for the engine. The algorithm we’re currently using loads a full FBO width and/or height on zone change. This results in a load of between 8 and 16 textures. Whilst we’re not doing so currently, we’ll be optimizing this to only update the visible play surface area, reducing load count of textures to between 4 and 8.

Another possible optimization would be to cache loaded sections instead of querying the map for loaded sections based on the current area (we’re currently assuming this isn’t a bottleneck though).

Beyond that, we’ll assess performance and if necessary look to optimize further by asynchronously loading textures from disk when getting close to an unloaded zone. If we spit the FBO in 4 quadrants, such that if we’re viewing the top left quadrant we should start loading textures that will be on the right and bottom edges. When moving the player(s) around, this should result in an almost unnoticeable load sequence. However, we do not expect to see a noticable improvement when moving around by scrolling (which we also support) as it’s likely the user will scroll through sections too quickly. If required, we could compensate for this by not allowing scrolling outside of loaded/navigated zones or by adding fog-of-war-like effects for zones that are still loading.

We’ll report back with some performance data as and when we can -- as well as a more technical description of the engine itself once it’s matured a little.

The design and engine above was all done by our rendering/gfx/misc coder, Marc Hebert -- who can be also be found at his own blog http://shadhex.blogspot.com or more frequently on Twitter at http://twitter.com/shadhex.

Sunday
Jul252010

Seeing SIGABRT on [UIButton buttonWithType: initWithFrame:] in iOS 4.0?

It's likely because you shouldn't have been calling it the first place.

For whatever reason (and I honestly don't know why), we were using the following call in an originally iPhone 3.x compiled app:

UIButton *btn=[[UIButton buttonWithType:UIButtonTypeRoundedRect] 
   initWithFrame:CGRectMake(x, y, w, h)];

Turns out this worked in 3.x (and still works on apps compiled against a 3.x base SDK), but when compiled against a 4.x base SDK causes a [NSException raise:format:arguments:] exception on init-ing the button.

Anyway, fix is simple enough - init the UIButton as it was meant to be (SDK class refererence):

UIButton *btn=[UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(x, y, w, h)];

Order restored, compiling fine against iOS 4.0 (well, 4.0.1 now) base SDK. No idea why the combined init worked before, but simple enough to resolve.