The whole point of a space is to hold game objects. The game objects in one space should not have any way to communicate with the game objects in another space, so spaces provide a simple means to separate different groups of game objects. In this article, you'll learn the benefits of such an architecture.
If you wish to see an example of the implementation of spaces, please see the open source game engine SEL. I myself am actively authoring SEL and am proud to present it as a fully functional resource for readers of this article.
I would like to thank Sean Middleditch for teaching me about the benefits of Spaces.
Tip: The term space in the context of this article refers to a special container of game objects. I'm not actually aware of a clearly defined official term. If you know of one please do comment!
Conventional Object Management
In a conventional game engine, game objects are usually stored in a single container. Such a container may be an allocator along with a handle manager. Sometimes the container is just a linked list. No matter what the actual implementation is, there may be just a single container holding all game objects, and every game object made is always in this container.
This is fine and totally works, but it does have some organizational issues. For example, imagine a traditional game state manager. Often, between the transition from one state to another, all currently loaded game objects are freed and new ones are read in from disk. As an optimization, game objects for the next state (or level) can be loaded on a separate thread ahead of time so that state transitions are instantaneous.
However, there is an annoying issue that commonly arises: how do we represent GUI elements for menus? Perhaps the player HUD is coded using game objects and scripts attached to these game objects. A naive implementation of state management would call for all HUD elements to be destroyed and recreated upon state transition. This means custom code will be required to handle the transition of particular objects from one state to another.
Or perhaps a game design calls for crazy background scenery where some huge battle is going on—but this battle must not interfere with the foreground (or the player) in any way.
Often, weird hacky solutions for these sort of things arise, such as representing HUD elements as being extremely far away from the rest of the gameplay in the world. Pause menus and the like are just moved into view when needed, and move away otherwise. In general, custom code is required to manage and organize game objects, as they all reside on one single factory or container.
Multiple Game Object Containers
An acceptable solution to this problem would be to make use of spaces (see the additional video description by my colleague Sean Middleditch). Since all game objects in each space have zero interaction, spaces become a natural way to approach the organization of game objects. This can greatly minimize the need for special code to maintain a separate logical container within an actual container (like mentioned in the previous section).
Let's take a quick look at an example of what a space implementation might look like in a language similar to C++:
class Space { public: GameObject CreateObject( string name ); const string GetName( void ) const; private: string m_name; // Could be some form of allocator, perhaps just a std::vector Container m_objects; };
It's often useful to be able to look up a space by name. This is especially great for scripts where a script can utilize code like so:
// Run some logic for the background local space = GetSpace( "Background" ) tornado = space.CreateObject( "Tornado" ) // Elsewhere we can do something completely isolated from the background, // like retrieving the player (who for some reason died) and playing a // death animation over top of the player local space = GetSpace( "CurrentLevel" ) player = space.GetPlayer( ) space.CreateObjectAt( "DeathAnimation", player )
What Objects Go Into Spaces?
The answer to this question is: all of them! Any type of game object will be placed into a space. If you're familiar with aggregation (or component-based design) then a game object and its associated components will all reside within the same space.
The idea is to create an architectural feature to allow a simple, effective way to group game objects together and isolate them from other groups.
Terminology
Spaces are fairly similar to some other concepts that have been floating around for a while now. It has been said that spaces are akin to the display list in Flash. If you're familiar with portals for render culling (especially important in 3D games with many interior rooms), the idea is quite similar here too.
However, there is an important distinction to make here. Spaces are not a form of spatial partitioning as is done with portals or other spacial partitioning (like the popular BSP tree) for render occlusion. Spaces are an architectural feature to allow isolation of general game objects.
Personally, I like to think of spaces like Photoshop layers: all layers can be viewed (or heard) simultaneously, but when painting on a layer, no other layers are ever directly affected; each layer is unique and isolated.
Systems and Spaces
The concept of a system, for the purposes of this article, can be described as a set of functionality (functions or methods) that operates upon the game objects of a space. In this way, a space is handed to a system to perform some action; a particular system is global to the entire game.
Imagine a graphics system that contains a void Graphics::DrawWorld( Space space )
function. This DrawWorld
function would loop over the objects within the given space that are renderable and draw them on-screen.
The idea is to now write code (systems) that operates upon a given input of game objects. No special tracking or management of game objects has to happen within such systems. A system shouldn't do anything except perform operations on game objects.
This style gives you some really nice benefits, as detailed in the next section.
Benefits of Spaces
The most immediate benefit of implementing spaces within an engine is that the handling of GUI elements or menus becomes trivial. We can construct a space dedicated to a particular menu and, whenever this menu is inactive, systems simply need not operate upon the contents of the space. When a menu is inactive it sits in memory (the game objects that comprise the memory sit within the menu space) and does nothing; it is neither updated by a logic system nor rendered by a graphics system.
When this menu becomes active again, it can be simply handed off to the appropriate systems. The menu can then be updated and rendered appropriately. Simultaneously, the gameplay going on behind the menu can cease to be updated in any way, although perhaps it still passed to the graphics system to be rendered. This trivially implements an elegant and robust pause-and-resume type of functionality that just comes implicitly due to the way spaces are defined.
Isolated Levels
Often, in RPG-style games like Pokémon, the player will enter and leave houses and huts. As far as gameplay is concerned, these different houses are usually completely isolated; small houses or caves are an ideal scenario to apply spaces. An entire space can be constructed to contain the game objects of a particular house, and these can be loaded and initialized in memory and rest until needed. Instantaneous transitions can be achieved by simply swapping out what space is being handed to the various engine systems.
A cool idea for 2D games like platformers (or even 3D games) could be to simulate actual levels and enemies from the game in the background. This might bring the world to life in a way that doesn't actually require any additional content, and hardly any additional development time. The best example I could find of this is Rayman Legends:
Actual enemies, the same as the player sees normally, could be jumping around or crawling on the walls in the distance. Transitions between these different "layers" could provide some very interesting design possibilities.
These sort of possibilities are actually quite rare to find examples of and the idea of spaces isn't really embraced by modern AAA studios or engines. However, the design is solid and the benefits are real.
Local Multiplayer
In many games with multiplayer support where both players play with the same game client, there are some limitations. Often the players cannot go to a new area without both being near each other. Sometimes the players cannot even leave each other's screen. This might be due to game design, but I have a suspicion that it is often due to architectural limitations.
With spaces, we can support two players, perhaps with split screens, travelling from one level or building to another. Each player can reside in the same space, or in two separate spaces. When each player is in a different building they might both be in two separate spaces. One they converge onto the same area, the engine can transfer one of the player's game objects into the opposing space.
Editor Support
This is definitely my favorite example of how spaces are awesome. In editors there are often sections where you can develop a new type of object. This object in creation will usually have a viewport to preview the creation as development hums along.
It can be impossible for most engines to naturally support such an editor. What if the user modifies the position and it suddenly collides with the simulation and knocks things over, or activates some AI? Custom code must be created in order to gracefully handle the object in memory somehow. Either special case isolation code, or some intermediary format, must be edited and translated from the editor to the actual simulation. Intermediary steps may be some form of serialization, or complex non-intrusive dummy "proxy object". Proxy objects can often require advanced code introspection to be implemented in a useful way. These options can be expensive or unnecessary for many projects.
However, if one has spaces at their disposal, a camera object and the object in creation can be placed into an isolated space. This space can then be handed to whatever systems necessary and handled in isolation gracefully. No special case code or additional authoring would be necessary in such a scenario.
Multiple levels can be maintained within editors with ease. Multiple viewports and simulations can be run in isolation simultaneous fashion. What if a developer wanted to split-screen the development of two levels to swap back and forth quickly? This might be a difficult software engineering task, or the editor architecture could implement some form of spaces.
Implementation Ideas
Space Management
What manages all spaces? Many game developers may have it in their practice that everything must be able to be "owned" by some manager. All spaces must be able to be managed by this single entity, right? Actually, this sort of paradigm just isn't necessary all the time.
In the SEL engine, spaces are constructed from one location, and can be looked up by name with a dictionary, but it might be best for most projects to just let spaces be managed on a case by case basis. Often, it just makes sense to create a space within some random script, hold on to it for a while, and then release the space. Other times a space is created and sits in memory the entire duration of the game's runtime.
A good recommendation would be to just let the user allocate a space and free it at will. It would likely be good to use a single allocator for this behavior. However, the management of the space instance itself, as found through experience, might be best not worried about; leave it to the user.
Note: when a space is destructed, it should clean up all the objects within! Do not confuse the lack of a manager with a lack of lifetime management.
Components and Subspaces
Components are often registered with their respective systems in a component-based engine. However, with spaces this becomes unnecessary. Instead, each space should contain what is called a subspace. A subspace can be very trivial to implement—say, as a vector of component objects. The idea is to simply contain various types of component containers within each space. Whenever a component is constructed, it requests what space it is to be associated with, and registers itself with the subspace.
A space does not necessarily have to have every single type of subspace within itself. For example, a menu space will probably not need any physical simulation, and therefore might not have an entire instance of a physics world representing a physics subspace.
Lastly, it should be noted that in a component-based architecture your game objects should have a handle or pointer to the space they reside in. In this way, the space becomes a part of the unique identifier of the game object itself.
Conclusion
Spaces are extremely simple to implement and provide a lot of important benefits. For pretty much every game and game engine in existence, the addition of spaces will be a positive one due to the ease of implementation. Use spaces, even for very small projects and very small games!
As a resource, my own implementation of spaces is open source for public viewing within the game engine SEL.