I find that I can create more separation between objects with the least amount of code possible by letting objects draw themselves. However, when I say that, I mean that while render-able objects have a Draw() method, they certainly don't know a single thing about drawing vertices or textures. Instead, they are passed an instance of an object that does all the drawing for them.
This object can be considered a Renderer
object. What does Renderer
do? It renders stuff, and it's the only class that knows the ins-and-outs of rendering vertices and textures. Now your gameObjects can just tell the renderer, "Hey, draw me. Here's my data.".
For 2D games in XNA, this is already done for you with SpriteBatch
.
You may want some sort of an interface, for example, IDrawable
, that each of your render-able objects will inherit from. This is a guarantee being made to all of your calling code that your render-able objects will contain a specific method signature. It also helps when iterating over a list of objects as you'll see in a bit.
public interface IDrawable {
void Draw( SpriteBatch batch );
}
In this case, we're saying that all objects of type IDrawable
will contain a Draw
method that takes in a SpriteBatch
object. Make sure all of your render-able objects inherit this interface, and implement that method. Your typical Draw
method might look something like:
public Draw( SpriteBatch spriteBatch ) {
spriteBatch.Draw( myTexture2D, myDestRect, Color.White );
}
I don't know how you keep a list of all your game-objects, but I tend to have some sort of base class. GameObject
tends to be the typical base that most people use. In C#, the foreach
statement makes rendering all of our render-able objects within our List<GameObject>
really simple:
List<GameObject> gameObjects; // list of all game objects in play
SpriteBatch batch; // our spritebatch instance
public void Draw() {
this.batch.Begin();
foreach( IDrawable gameObject in this.gameObjects ) {
gameObject.Draw( renderer );
}
this.batch.End();
}
If you don't want your gameObjects to have a Draw() method at all, you can simply move the calling code back to the game state to achieve the same effect:
List<GameObject> gameObjects; // list of all game objects in play
SpriteBatch batch; // our spritebatch instance
public void Draw() {
this.batch.Begin();
foreach( IDrawable gameObject in this.gameObjects ) {
this.batch.Draw(
gameObject.Texture2D,
gameObject.DestRect,
Color.White );
}
this.batch.End();
}
There are more extreme methods of separation out there (read up on Entity Component Systems), but I find them a bit too convoluted for my tastes. Game development entails a lot of coupling between data and presentation. That's just the way it is, and sometimes keeping it simple works best in the long run.