Search

Advertising

Home Blog Game Development To GameComponent or not to GameComponent
To GameComponent or not to GameComponent Print E-mail
Written by Markus Ewald   
Friday, August 13 2010 11:50

XNA provides a neat little system for you to organize your game's different parts in: GameComponents. You can use them to modularize your game's subsystems, eg. have a GuiComponent, PhysicsComponent, SceneGraphComponent etc. so you avoid having all that code in your Game class, or can use them for your actual game objects in smaller games.

However, the GameComponent and DrawableGameComponent classes provided by XNA force you to reference the Game class. This is unfortunate if you want to use those components in a WinForms-based XNA application and gets in the way when you try to write unit tests for your components because now you have to create a dummy Game instance as well (and better hope that component won't go shopping for references in the Game's Components and Services collections as well).

UML class diagram of the GameComponent and DrawableGameComponent classes

Luckily, the GameComponentCollection used by XNA to store your components manages IGameComponents and updating/drawing are based on the IUpdateable and IDrawable interfaces alone, so there's nothing preventing you from rolling your own components without referencing the Game class.

That's exactly what I did when I started writing an editor for my Island War game. I simply reimplemented XNA's GameComponent and DrawableGameComponent classes, minus the Game reference. I named them Component and DrawableComponent (cleverly removing the 'Game' from the name to show that these no longer reference the Game class):

UML class diagram of my earlier Component and DrawableComponent classes

Users of my components (like my GUI and particle system classes) wouldn't notice any difference and could use the classes like normal GameComponents, but the custom base class allowed me to use this components in a WinForms environment.

Lately, I've been tinkering around with SunBurn, Synapse Gaming's lighting and rendering framework for XNA. Here you have drawable things that do not access the GraphicsDevice. Instead, they will modify SunBurn's scene graph. Thus, carrying around the GraphicsDevice reference in my DrawableComponent became essentially dead weight for the components involved.

So I decided to make a variant of my DrawableComponent that would still do all of the uninteresting stuff managing the Visible and DrawOrder properties, firing events and so on, but not handle the GraphicsDevice for me. What I ended up with is this:

UML class diagram of my new Component, DrawableComponent and GraphicsDeviceDrawableComponent classes

In retrospect, this turns out to be a much cleaner design, too: The DrawableComponent only handles the implementation of the IDrawable interface instead of doing both that and managging the GraphicsDevice. And the GraphicsDeviceDrawableComponent (excuse the long name, haven't found anything more concise yet), deriving from DrawableComponent, only handles the management of the GraphicsDevice.

This will be a breaking change in the next Nuclex Framework release, but all you have to do is paste "GraphicsDevice" in front of your "DrawableComponent". I think this is nicer than having some DrawableComponentWithoutGraphicsDevice class :). I'd wager that in larger games (which is what my Nuclex Framework is designed for) a scene graph or something comparable is probably used in the majority of cases and direct GraphicsDevice access only happens in some corner cases.

 

Comments  

 
0 #1 psychowico 2010-08-27 07:37
thanks for this, propably u let me save a lot of time.
Quote
 
 
0 #2 programad 2010-09-23 13:06
Hi, nice article.

Can you please post three code examples for the three diagrams for better understanding?

Thanks.
Quote
 
 
0 #3 drozzy 2010-10-13 15:05
Right, so my question was why not implement the IDrawable and IUpdatable interfaces directly?
Quote
 
 
0 #4 Cygon 2010-10-13 15:55
@drozzy: Several reasons:

Because that would mean duplicating a lot of code (see en.wikipedia.org/wiki/Don't_repeat_yourself).

I also prefer small classes and this would add a lot of noise to each of them, making them less readable. My average class has ~150 lines of code.

Finally, in a "class Enemy" I'd expect to find the code that controls the behavior of an enemy - instead it would also be responsible for the game component handling (en.wikipedia.org/wiki/Single_responsibility_principle).

To deal with content loading and any kind of external dependencies, I use services (eg. IContentService, ICameraService) passed to the constructors of my components. One could also use the Game.Services container as I did earlier
(http://www.nuclex.org/articles/architecture/6-game-components-and-game-services), but I am sold on dependency injection now :)

In my current project (an RTS game), I separate logic from drawing by splitting all game entities into three parts. For a flak turret, for example, I'd have these classes:

  • FlakTurret - represents the turret, stores current orientation, elevation, remaining ammo.
  • FlakTurretRende rer - Is constructed with a reference to the FlakTurret. Obtains the model from the IContentManager and draws it (actually, maintains it in the SunBurn scene graph :D)
  • FlakTurretBehav ior - Is constructed with a reference to the FlakTurret. Turns the turret towards incoming enemies and fires if aligned, adding projectiles to the world itself.

I guess it wouldn't hurt to merge the behavior class into the turret itself, but by externalizing I avoid having the FlakTurret class store a reference back to its parent (the World class).
Quote
 
 
0 #5 drozzy 2010-10-13 19:40
It sounds great in theory, especially the way you use dependency injection and split your game entities in three parts.

Unfortunately when I start coding, I hit a brick wall. For example where do you setup the injection? What about FlakTurretBehav ior class - how does it find out the places of enemies and projectiles? About TurretRenderer - where does it get the instance of a model...

Sorry for so many questions, but my project is sooo tightly coupled (i.e. I pass Game, GraphicsDevice and an intance of my "GameWorld" everywhere), that I cannot add or change anything anymore. I am trying to rewrite it - but don't know where to start.
Quote
 
 
0 #6 Cygon 2010-10-13 21:14
Dependencies are a huge problem at all skill levels. I'm still learning new ways to avoid them every day - when I started with this game, I had a FlakTurrent class that was responsible for all three aspects (rendering itself, checking for enemies, representing the logical state) - and it turned into a mess :)

The way I try to avoid this is to keep looking for indications that the design is turning sour. For example, having parent references (eg. world has buildings, buildings have reference back to world) is usually an indication for coupling getting too tight. Especially if the parent class would allow the child to access a whole object graph (eg. to do stuff like Parent.Players[0].Units.FindAirborne().DestroyAll() - tight-coupling the building with the world, player, unitmanager and unitcollection classes in one go :P).

Another indication I use is when a class consumes too many categorically different references. If my building renderer consumes the scene graph, camera and content manager, that's okay - they're all rendering-related and necessary. If I have more than 2 such references, I usually construct either a reference container (see http://www.nuclex.org/blog/gamedev/109-code-better-reference-containers-for-change-resistant-constructors) or I consolidate functionality - for example by adding a ISceneManager.Camera property - the camera can still be represented by a different service, but it's now packaged under the scene manager. Of course the latter approach should be used with care as it introduces artifical service dependencies.

If, on the other hand, a class consumes 3+ references of different categories (say, input manager, world state and graphics device service), that's a clear indication that the class is having too many responsibilitie s and should be split. Knowing where to split and what to turn into an interface is a science of itself. I haven't yet been able to extract the rules I work by there :)

I wish I knew a formalized approach that I could simply follow, but I'm beginning to doubt there is such a thing. What I do now is to try and look at the whole problem, brainstorming different approaches with their consequences (I believe, like a good chess player, the more experience one has, the deeper one can follow this "search tree" and the earlier one can tell when a "move" isn't worth following down the tree). But I've messed up often enough. Luckily, it was mostly before I started using XNA, so one noticed because I kept my earlier code to myself most of the time XD
Quote
 

Add comment


Security code
Refresh



Joomla Template by Joomlashack