Wednesday, November 05, 2008

XNA Series - Game Components

Ok, I know I said the next article would be about steering, but the next article for that topic is not ready yet, so I thought I would talk about a pretty cool class in XNA called GameComponent and its child Class DrawableGameComponent. These classes are made to add components to your game in a modular way. A component is something that needs to update with your game. DrawableGameComponent inherits GameComponent and adds functionality so that the component is also drawn at proper times as well. You could actually re-design our GameObject class as a DrawableGameComponent or at least inherit a class from DrawableGameComponent to give us the added functionality. Now from my reading, it seems that a lot of people think GameComponents are are really aimed at single game objects like a tank, but to add a TankManager class that would deal with the updating and drawing of all the tank objects in our game. But it certainly doesn't hurt to learn the type in an easy way.

We will create a UserTank class that is derived from DrawableGameObject. We just need to implement the basic constructor which takes a Game Object. So our UserTank will get all the functions (except private ones) of the DrawableGameObject class.


public class UserTank : DrawableGameObject
{
public UserTank(Game g)
: base(g)
{
}
}


To begin this is actually all we need to add this tank to our game. In our game class we would add


UserTank hero;


and in our constructor we would initialize it in the same old way


hero = new UserTank();


but we do something a little different after that


Components.Add(hero);


Since the hero tank is a UserTank which is derived from DrawableGameObject which is Derived from GameComponent, we can put it in the GameClasses list of GameComponents. These GameComponents all get called on updates and if they are drawable, they get their draw methods called.

So now even though there is nothing "in" our UserTank class, it is being updated with our game. Lets add a couple bits of info to the UserTank class that will help us out. To save time and space, I will implement the fields of the component as public rather than making them Properties. This is lazy, don't follow my lead in this.


public Vector2 location = Vector2.zero;
public Texture2D sprite;
SpriteBatch spriteBatch;


So we can store our location, sprite and a spritebatch to draw to. Then we simply override LoadContent, Update and Draw.


protected override void LoadContent()
{
spriteBatch = new SpriteBatch(this.GraphicsDevice);
sprite = this.Game.Content.Load("tank");
base.LoadContent();
}

public override void Draw(GameTime gameTime)
{
spriteBatch.Begin();
spriteBatch.Draw(sprite, location, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}

public override void Update(GameTime gameTime)
{
KeyboardState ks = Keyboard.GetState();
if (ks.IsKeyDown(Keys.Up))
{
location.Y -= 1;
} else if (ks.IsKeyDown(Keys.Down))
{
location.Y += 1;
}

if (ks.IsKeyDown(Keys.Left))
{
location.X -= 1;
} else if (ks.IsKeyDown(Keys.Right))
{
location.X += 1;
}
base.Update(gameTime);
}


So as you see we initialize the spritebatch and load it's sprite. Then we call the base classes LoadContent method. In the Draw method we Begin our spritebatch, make a Draw call and End the SpriteBatch and then call the base classes Draw method. Then finally in the Update method we look at the KeyboardState and make some updates to the location and once again call the base classes Update method. So now that you have this component we could add it and it's logic in a very simple manner.



Here are the files for this project.

2 comments:

PiRX said...

I found your blog via GameDevKicks. By coincidence I'm writing similar series of posts (only dealing with 2d space shooter), on my blog at Latvian .NET user groups site.

Just wanted to give one little advice - as for now XNA uses C# 3.0, you could use auto-implemented properties instead of fields and everybody would be happy :)

Unknown said...

A nice and simple explanation for adding Game Components. The step I'm working on requires interactions between different objects in separate drawable game components.

How can you get them to interact?
I can see how you implement methods from the game class (base), but I don't understand how to calculate if separate objects not in the game class know about each other.