- What Is in a New Project?
- The Game Class
- Game Loop
- Components
- Summary
Components
As you can probably imagine, if you had to draw everything inside a single class, your code would quick become a mess of things to keep track of!
GameComponents
Luckily, the framework provides an easy way to encapsulate objects called game components. There are three types of game components you can use for your games: GameComponent, DrawableGameComponent, and GamerServicesComponent. The latter is discussed later, but let's dive into the others now.
First, you want to take the image-bouncing code you wrote and move it into a component, so in your main game project, right-click the project and select Add -> New Item. Choose Game Component from the list of templates (it might be easier to find if you choose the XNA Game Studio 4.0 section in the list on the left), name it BouncingImage.cs, and then click the Add button.
This adds a new file to your project with a new class deriving from GameComponent, which is close to what you want but not quite. Open up BouncingImage.cs (it should have opened when you added the component), and change it to derive from DrawableGameComponent instead:
public class BouncingImage : Microsoft.Xna.Framework.DrawableGameComponent
Now you can begin moving the code you used in your Game class to render your bouncing image to this component. Start by moving the three variables you added to the new BouncingImage class (texture, position, and velocity). Move the code initializing your two vectors into the new classes Initialize method and move the code were you modify the vectors in update to the new classes Update method. You need to do just a few things to complete your bouncing image component.
You need a way to load the texture, and DrawableGameComponent has the same virtual LoadContent method that the game has, so you can simply override it in your BouncingImage class now:
protected override void LoadContent() { texture = Game.Content.Load<Texture2D>("XnaLogo"); base.LoadContent(); }
Finally, all you need now is to draw the image. Just like LoadContent, DrawableGameComponent also has a Draw virtual method you can override:
public override void Draw(GameTime gameTime) { spriteBatch.Begin(); spriteBatch.Draw(texture, position, Color.White); spriteBatch.End(); base.Draw(gameTime); }
As you might have seen already, this won't compile. The spriteBatch variable is declared in the game, and it is a private member variable. You can create a new sprite batch for this component, but it isn't necessary. If you remember back to earlier in this chapter, we talked about the Services property on the Game class.
Go back to the game1.cs code file and to the LoadContent method. Replace the loading of the texture (which you just did in the BouncingImage class) with the following line (directly after the sprite batch has been created):
Services.AddService(typeof(SpriteBatch), spriteBatch);
This adds a "service" of type SpriteBatch to the game class and uses the just created sprite batch as the provider of that service. This enables you to use the "service" from anything that has access to the game. Back in the BouncingImage.Draw method, before the Begin call, add the following:
SpriteBatch spriteBatch = Game.Services.GetService( typeof(SpriteBatch)) as SpriteBatch;
Now that you have your drawing code in your component compiling, you can remove it from the Draw call in the game. It should have nothing but the Clear call and the base.Draw call now. With everything compiling, you can run your project and you will see absolutely nothing except a blank cornflower blue screen. This is because your component was never actually used! Back in the Initialize method in the game, add the following:
Components.Add(new BouncingImage(this));
That's it. Running the code gets you back to where you were before, but with everything much more encapsulated. It's also much easier to add new instances of the bouncing image; for example, add this to your games Update method:
if ( ((int)gameTime.TotalGameTime.TotalSeconds % 5) == 4) Components.Add(new BouncingImage(this));
Running it now adds a new set of bouncing images every 5 seconds (actually it adds quite a few because it adds one for every update that happens during that second). You can go ahead and remove that code; it is just an example of how easy it is to include more.
You might have noticed that you didn't actually have to do anything outside of add your component to the Components collection for it to start working magically. Your Initialize method is called for you, as is your LoadContent method, and the Update and Draw methods were called for each frame.
By default, components are updated and drawn in the order they were added, and they are always updated and drawn. However, these are all changeable, too. If you set the DrawOrder property, when the components are being drawn, the components with the lower DrawOrder values are drawn first. Similarly, if you use the UpdateOrder property, the components with the lower UpdateOrder value are updated first. The higher value these properties have, the later they happen in the list of components. If you want something drawn as the last possible component, you set the DrawOrder to int.MaxValue, for example. Of course, if you have more than one component with the same UpdateOrder or DrawOrder, they are called in the order they were added.
Of course, there might be times when you don't want your component to be drawn at all! If this is the case, you can simply set the Visible property to false, and your Draw override is no longer called until that property switches back to true. If you need to temporarily suspend updating for a while, you can just change the Enabled property to false!
There are also events (and virtual methods) to notify you when any of these properties change if you need to know about the changes.