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:
- The GameWorld's local store (a dictionary)
- Properties on the GameWorld object itself
- 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
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.