Tuesday, 8 February 2011

Converting a class to a DrawableGameComponent

Given the original code for a sprite class the source of which is provide here was provided by Neil Gannon.

   1: class Sprite

   2: {

   3: public Sprite(Texture2D texture,Vector2 userPosition, int framecount)

   4:         {

   5:             spriteImage = texture;

   6:             position = userPosition;

   7:             numberOfFrames = framecount;

   8:             spriteHeight = spriteImage.Height;

   9:             spriteWidth = spriteImage.Width / framecount;


  11:         }

  12: public void UpdateAnimation(GameTime gametime) {}

  13: public void Draw(SpriteBatch spriteBatch) {}


  15: }


We need to sub class the class off DrawableGameComponent and then add an override for update and draw. These must be public overrides to match the method declarations in the the DrawableGameComponent Class. The game component needs it’s own sprite batch to draw to the graphics card and hence it needs to see the Games Graphics Device. But thankfully this is exposed in the component model for the game class. The Class becomes

   1: class Sprite : DrawableGameComponent

   2:     {

   3:         //sprite texture and position

   4:         Texture2D spriteImage;

   5:         Vector2 position;

   6:         SpriteBatch spriteBatch;

   7:         //the number of frames in the sprite sheet

   8:         //the current fram in the animation

   9:         //the time between frames

  10:         int numberOfFrames = 0;

  11:         int currentFrame = 0;

  12:         int mililsecindsBetweenFrames = 100;


  14:         //the width and height of our texture

  15:         int spriteWidth = 0;

  16:         int spriteHeight = 0;


  18:         //the source of our image within the sprite sheet to draw

  19:         Rectangle sourceRectangle;


  21:         float timer = 0f;


  23:         public Sprite(Game G, Texture2D texture,Vector2 userPosition, int framecount) :base(G)

  24:         {

  25:             spriteBatch = new SpriteBatch(G.GraphicsDevice);

  26:             spriteImage = texture;

  27:             position = userPosition;

  28:             numberOfFrames = framecount;

  29:             spriteHeight = spriteImage.Height;

  30:             spriteWidth = spriteImage.Width / framecount;


  32:         }


  34:         public override void Update(GameTime gametime)

  35:         {

  36:             timer += (float)gametime.ElapsedGameTime.Milliseconds;


  38:             //if the timer is greater then the time between frames, then animate

  39:                     if (timer > mililsecindsBetweenFrames)

  40:                     {

  41:                         //moce to the next frame

  42:                         currentFrame++;


  44:                         //if we have exceed the number of frames

  45:                         if (currentFrame > numberOfFrames – 1)

  46:                         {

  47:                             currentFrame = 0;

  48:                         }

  49:                         //reset our timer

  50:                         timer = 0f;

  51:                     }

  52:             //set the source to be the current frame in our animation

  53:                     sourceRectangle = new Rectangle(currentFrame * spriteWidth, 0, spriteWidth, spriteHeight);

  54:                     base.Update(gametime);

  55:             }




  59:         public override void Draw(GameTime gameTime)

  60:         {

  61:             //draw the sprite , specify the postion and source for the image withtin the sprite sheet

  62:             spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

  63:             spriteBatch.Draw(spriteImage, position,sourceRectangle, Color.White);

  64:             spriteBatch.End();

  65:             base.Draw(gameTime);

  66:         }

  67: }


  1. The constructor has to have a reference to the game that creates it and must pass it on to the Super class.

  2. We can use this reference in the constructor to access the Graphics device reference to create the sprite batch. We can alternatively access the publicly exposed Game property Game.GraphicsDevice and indeed you can reference the public exposed properties of the Game class from anywhere in the game components.

  3. You must remember to add the created sprites as components in the Game class when we create the sprite

   1: rollingImage = Content.Load<Texture2D>(@"Images\rolling");

   2: rolling = new Sprite(this,rollingImage, new Vector2(500, 50), 4);

   3: this.Components.Add(rolling);

Now we can subclass the Sprite Class and create two different types of Sprite game components. The player Sprite class will have an update method that will override the sprite update and deal with the input from the input device and the CGC Sprite Class will move around the screen.

We’ll look at those classes next time.


It’s worth looking at the forum discussion here about the ins and outs of using multiple sprite batches or a single sprite batch for drawing components. So it would seem from the comments made by Shaun Hargreaves in the forum discussion referenced above that although components give good modularity and structure to games, but at the cost of efficiency in committing to the graphics device as multiple sprite batches in components seem not to be a good idea from a performance perspective!! So for multiple instances a Drawable game component may not be a good idea?? So back to the drawing  board eh? It would be a good idea to change the XNA framework so that it batches sprite batch (yeah I know – a bit of a recursive definition) and have a common end() method for them. The solution suggested is to have a Sprite Manager. But I thought that’s what a sprite batch should do!!

At that stage we might consider making the Sprite Class abstract as we would probably not want to create instances of it after that subclass action.

No comments:

Post a Comment