The XNA Game
class is a solid foundation to build your game on. To help you organize
your game code into manageable modules, XNA also provides a small service framework that you can
use to build reusable components that provide services to the rest of the game.
This framework comes in the form of the GameComponents
collection in the game
class and its GameServices
registry. If you don’t know how all this works yet,
don’t worry, that’s what this article is about!
Exploring GameComponents
Games typically consist of several logical modules that could ideally be reused between different games. For example, a lot of 3D games internally employ a scene graph to manage the visual entities in a game (models, decals and so on). This scene graph could be written in a general manner so that two very different games could use the same scene graph module.
This is exactly what the GameComponent
class is about. Instead of spreading your
graphics, scene graph and text rendering code all over the place, you can cleanly seperate
these parts of your games into components that you can then give to other XNA developers or
reuse in your next project. Here’s an example of which GameComponents
you might
decide to create for a simple application:
GameServices
But now the TextRenderingComponent
needs to access the
GraphicsComponent
. And the SceneGraphComponent
needs access to
the GraphicsComponent
and the EffectManagerComponent
. Was all
the separation in vain? Will all these components need to know of each other and develop
a tightly woven net of interactions that makes it impossible to take one of them and put it
in another project?
Not quite. That’s where the GameServices
collection comes into play. Basically,
a game service is just an interface under which you access a component. The built-in
GraphicsComponent
, for example, has the built-in IGraphicsDeviceService
service which allows access to the GraphicsDevice
that is managed by the component.
It works like this:
-
First, all
GameComponent
-derived classes that you added to your game are created and put into theGameComponents
collection. -
Each time a component is added to the collection, its
OnGameChanging()
method is called. Here the component will register its services to theGameServices
collection. -
After all components have been added, the second stage initialization calls the
Start()
method of all components. Here components can pick the services of other components they require.
In the end, your game will not rely on the one and only GraphicsComponent
being
there, but on some component that provides your game with the
IGraphicsDeviceService
. And that could be a different component from game to game.
Your code might even choose to just skip graphics altogether if there’s no
IGraphicsDeviceService
, but keep running neverthelesss (in a dedicated server,
for example).
GameComponent Interaction
Components that need to access each other do so only through well-defined interfaces that could be provided by a different component in another game, or not at all. See this illustration to get a grasp of the concept:
Take note of the red line. This is how the SceneGraphComponent
accesses the
GraphicsDevice
. It does not rely on the GraphicsDevice
being provided
by the GraphicsComponent
class, it only expects that the
IGraphicsDeviceService
exists in your game.
When to Create GameComponents
Well, that’s really up to you. As a rule of thumb, any time you’re writing something you’re
likely to be needing in your next game, too, create a GameComponent
from it.
Even if you need to modify it for your next game, it will give you a head start and
promote clean separation of responsibilities in your code.
There are two ways you can create your own game components:
You can create a game component by deriving a new class from either the
GameComponent
or the DrawableGameComponent
classes. These, however,
require a reference to the Game
instance in their constructor. Which means that
you can’t use them unmodified if you ever plan to use WinForms together with XNA (for example,
to create a level editor).
The other option is to just implement the IUpdateable
and optionally the
IDrawable
interfaces in your class. You can then decide yourself if you want
to depend on the Game
class, only the GameServiceContainer
to register
your services or nothing at all!
Of course it's annoying to be implementing those two interfaces over and over again. That's why
I wrote myself these two base classes which behave exactly like XNA's GameComponent
and DrawableGameComponent
classes, but don't require the Game
instance
to be constructed. See my blog post titled To GameComponent or not to GameComponent
for more info and a download of those classes.
Example Code
Need some code? Here is the source code for an example component, a
SceneGraphComponent
that publishes a service named ISceneGraphService
that can be queried for by the game class or by other components. If also shows how to use the
IGraphicsDeviceService
interface to access the game's GraphicsDevice
.
/// <summary>Interface for the scene graph service</summary>
public interface ISceneGraphService {
/// <summary>The root node of the scene tree</summary>
SceneNode Root { get; }
/// <summary>Matrix used to transform 3D to screen coordinates</summary>
Matrix Projection { get; set; }
/// <summary>Matrix that defines the viewer's location in the scene</summary>
Matrix View { get; set; }
}
/// <summary>Component to manage visuals using a scene graph&l;/summary>
/// <remarks>
/// <para>
/// A scene graph basically organizes 'nodes' in a tree, where each node's position
/// is local to the parent nodes position. If you would model a solar system, the
/// planets would be children to the sun, while the moons would be children to their
/// respective planets. Each moon would then only have to remember its position
/// relative to the planet, and wherever the planet goes, the moon will be, too.
/// It is the same for the rotation of the parent node. The orientation of all
/// child nodes is relative to the orientation of the parent nodes.
/// </para>
/// <para>
/// Scene nodes are not required to actually draw anything, you can create them
/// just for the sake of categorization or in order to chain multiple independent
/// transformation matrices without concatenating the intermediate steps. If a
/// scene node wants to draw something, it can get hold of the GraphicsDevice
/// by navigating back to the game instance through its 'SceneGraph' property.
/// </para>
/// </remarks>
public class SceneGraphComponent :
Microsoft.Xna.Framework.DrawableGameComponent, ISceneGraphService {
/// <summary>Initializes the scene graph</summary>
/// <param name="game">Game instance this scene graph belongs to</param>
public SceneGraphComponent(Game game) : base(game) {
game.Services.Add(typeof(ISceneGraphService), this);
}
/// <summary>Called when all game services have been registered</summary>
public override void Initialize() {
// Query for the graphics device service through which the graphics device
// can be accessed
IGraphicsDeviceService graphicsDeviceService =
(IGraphicsDeviceService)Game.GameServices.GetService(
typeof(IGraphicsDeviceService)
);
#if false // Example of what you could do here
// Register to the graphics device manager's events in order to get notified when
// our ResourceManagement.Manual resources need to be destroyed or recreated.
graphicsDeviceService.DeviceCreated += new EventHandler(graphicsDeviceCreated);
graphicsDeviceService.DeviceDisposing += new EventHandler(graphicsDeviceDisposing);
graphicsDeviceService.DeviceResetting += new EventHandler(graphicsDeviceResetting);
graphicsDeviceService.DeviceReset += new EventHandler(graphicsDeviceReset);
#endif
}
}