Wednesday, 19 January 2011

XNA Game Components – How Far do they go

Ok. The idea of game components is that they allow you to take logic that would normally be in the game loop out of the game loop. This promotes a certain amount of independence in a class. As an example say we have a image that must move in a game. We’ll make it nice and simple and we’ll create an image that uses Vector2.Lerp to move to a target vector within the screen co-ordinates. Firstly we’ll look at the logic of doing this in the Game loop and then we’ll look at a class that is of type DrawableGameComponent. It’s quite strange that you can only choose a GameComponent Template when you add a new component to a game. This is an oversight by Microsoft in Visual studio.


First we need some class variables.

   1: SpriteBatch spriteBatch;

   2: Texture2D movingCircle;

   3: Vector2 movingCirclePosition;

   4: Vector2 Target;

So lets set up our Image first which is in the Project Content folder and load it into the Texture2D Class Variable and Randomise the initial position

   1: protected override void LoadContent()

   2:         {

   3:             Random r = new Random();

   4:             spriteBatch = new SpriteBatch(GraphicsDevice);

   5:             movingCircle = this.Content.Load<Texture2D>("MovingCircle");

   6:             movingCirclePosition = new Vector2(r.Next(600), r.Next(400));

   7:             Target = new Vector2(r.Next(600), r.Next(400));        }

Now we need a Randomised Vector2 for our Target. We can do this in the LoadContent or the or the initialise section.

Next its on to the update method to move things along so to speak.

   1: protected override void Update(GameTime gameTime)

   2: {

   3:     movingCirclePosition = Vector2.Lerp(movingCirclePosition, Target, 0.01f);

   4:     if (Vector2.Distance(movingCirclePosition, Target) < 0.1)

   5:     {

   6:         Random r = new Random();

   7:         movingCirclePosition = Target;

   8:         Target = new Vector2(r.Next(600), r.Next(400));

   9:     }

  10:     base.Update(gameTime);

  11: }

Then we draw the moving cricle

   1: protected override void Draw(GameTime gameTime)

   2:         {

   3:             GraphicsDevice.Clear(Color.Black);

   4:             spriteBatch.Begin();

   5:             spriteBatch.Draw(movingCircle,movingCirclePosition,Color.White);

   6:             spriteBatch.End();

   7:             base.Draw(gameTime);

   8:         }

and that’s it. One moving circle.

So lets look at the component version. Firstly a component is usually implemented as a class which is a sub-class of the component class. Once again the only component available in the new class template is a game component which does not override the Draw and LoadContent Class.

   1: public class MovingCircle : Microsoft.Xna.Framework.GameComponent

   2:     {

   3:         public MovingCircle(Game game)

   4:             : base(game)

   5:         {

   6:             // TODO: Construct any child components here

   7:         }

So we change GameComponent to DrawableGameComponent and then override the Draw and LoadContent Methods. We can then transfer the variables for moving the circle, keeping positions, drawing the circle sprite into the MovingCircle Class and the code transfers into the override methods such as the LoadContent Method shown in the code Snippet below. NOTE the LoadContent method has to have the same visibility as the DrawableGameComponent LoadContent Method. The other methods have a public visibility. The override for Draw must be public.


   1: protected override void LoadContent()

   2:         {

   3:             Random r = new Random();

   4:             spriteBatch = new SpriteBatch(Game.GraphicsDevice);

   5:             movingCircle = Game.Content.Load<Texture2D>("MovingCircle");

   6:             movingCirclePosition = new Vector2(r.Next(600), r.Next(400));

   7:             Target = new Vector2(r.Next(600), r.Next(400));

   8:         }

   9: public override void Update(GameTime gameTime)

  10:         {

  11:             // TODO: Add your update code here

  12:             movingCirclePosition = Vector2.Lerp(movingCirclePosition, Target, 0.1f);

  13:             if (Vector2.Distance(movingCirclePosition, Target) < 0.2)

  14:             {

  15:                 Random r = new Random();

  16:                 movingCirclePosition = Target;

  17:                 Target = new Vector2(r.Next(600), r.Next(400));

  18:             }

  19:             base.Update(gameTime);

  20:         }


  22:         public override void Draw(GameTime gameTime)

  23:         {

  24:             Game.GraphicsDevice.Clear(Color.Black);

  25:             spriteBatch.Begin();

  26:             spriteBatch.Draw(movingCircle, movingCirclePosition, Color.Red);

  27:             spriteBatch.End();

  28:             base.Draw(gameTime);

  29:         }

Notice that the code changes so that you use Game.GraphicsDevice isntead of the GraphicsDevice variable that was use in the LoadContent function in the Game class presiously

All that remains to do then is to wire up a MovingCircle instance to the Game Loop by adding it as a component to the game class. This is best done in the Initialise class so that the DrawableGameComponent’s LoadConentFunction is called after the DrawableGameComponent is created. The total code for the Game class now looks like this

   1: public class Game1 : Microsoft.Xna.Framework.Game

   2: {

   3:     MovingCircle myMovingCircle;

   4:     GraphicsDeviceManager graphics;

   5:     public Game1()

   6:     {

   7:         graphics = new GraphicsDeviceManager(this);

   8:         Content.RootDirectory = "Content";

   9:     }

  10:     protected override void Initialize()

  11:     {

  12:         myMovingCircle = new MovingCircle(this);

  13:         this.Components.Add(myMovingCircle);

  14:         base.Initialize();

  15:     }

  16:     protected override void LoadContent()

  17:     {         }


  19:     protected override void UnloadContent()

  20:     {

  21:     }

  22:     protected override void Update(GameTime gameTime)

  23:     { base.Update(gameTime);         }


  25:     protected override void Draw(GameTime gameTime)

  26:     {          base.Draw(gameTime);         }

  27: }

When we add the DrawableGameComponent to the Component collection of the Game and this plugs it into game loop.

No comments:

Post a Comment