Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

XNA Game Development (Decentralize)

by Ioannis Panagopoulos

In the previous post, we have talked about drawing and moving 2D textures on the XNA window. In this post, we will explore the basic architecture that XNA uses in order to separate the game logic from the sprite logic. So prior to reading this post, it is suggested to go through the previous one.

The main code smell there, was the fact that we had to put all the game logic for moving the Ivan sprite to the Update and Draw methods of the game loop. Imagine how messed up those too methods would look if we placed more sprites on the game scene. Therefore our first tendency is to take all this logic that moves and draws the Ivan sprite and draws the background and place it in on two separate classes.

Here is the code for the CrazyIvanSprite.cs file that has the CrazyIvanSprite class.

public class CrazyIvanSprite
{
    private Game _game;
    private Texture2D _ivan;
    private Vector2 _ivanPosition;

    public CrazyIvanSprite(Game game){
        _ivanPosition = new Vector2(400, 300);
        _game = game;
    }
    public void LoadContent(){
        _ivan = _game.Content.Load<Texture2D>("Ivan");
    }
    public void UnloadContent(){
        _ivan.Dispose();
    }
    public void Update(Vector2 MouseVector){
        Vector2 IvanToMouseVector = -(_ivanPosition - MouseVector);
        IvanToMouseVector.Normalize();
        _ivanPosition += IvanToMouseVector;
    }
    public void Draw(SpriteBatch spriteBatch){
        spriteBatch.Draw(_ivan, _ivanPosition, Color.White);
    }
}

 

In other words we have recreated in the CrazyIvanSprite class the same methods that the Game loop had and placed in there the logic that moves Ivan. The same is done for the background image. The main game loop in the main class is transformed as follows:

private CrazyIvanSprite _ivan;
private BackgroundSprite _background;

protected override void LoadContent(){
    spriteBatch = new SpriteBatch(GraphicsDevice);
    _ivan = new CrazyIvanSprite(this);
    _background = new BackgroundSprite(this);
    _ivan.LoadContent();
    _background.LoadContent();
}
protected override void UnloadContent(){
    _ivan.UnloadContent();
    _background.UnloadContent();
}
protected override void Update(GameTime gameTime){
    _ivan.Update();
    base.Update(gameTime);
}
protected override void Draw(GameTime gameTime){
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();
    _background.Draw(spriteBatch);
    _ivan.Draw(spriteBatch);
    spriteBatch.End();
    base.Draw(gameTime);
}
 

Note that the game's methods just call methods with the same same in the two classes we have just created (the project that follows this approach can be downloaded here). Now the beautiful thing is the fact that XNA has thought of this approach before we did and actually has taken it one step furher. The approach is as follows:

"If we create our sprite classes to inherit from DrawableGameComponent or GameComponent and add them in the main game loop to the Components collection of the Game class, then XNA will execute automatically our sprite's Update and Draw methods within its own Update and Draw methods"

Let's see how this works for the CrazyIvan sprite. The new sprite's class is as follows:

public class CrazyIvanSprite:DrawableGameComponent
{
    private Game _game;
    private Texture2D _ivan;
    private Vector2 _ivanPosition;
    private SpriteBatch _spriteBatch;

    public CrazyIvanSprite(Game game):base(game){
        _ivanPosition = new Vector2(400, 300);
        _game = game;
        _ivan = _game.Content.Load<Texture2D>("Ivan");
        _spriteBatch = (SpriteBatch)game.Services.GetService(typeof(SpriteBatch));
    }
    protected override void Dispose(bool disposing){
        _ivan.Dispose();
        base.Dispose(disposing);
    }
    public override void Update(GameTime gameTime){
        MouseState MState=Mouse.GetState();
        Vector2 MouseVector = new Vector2(MState.X, MState.Y);
        Vector2 IvanToMouseVector = -(_ivanPosition - MouseVector);
        IvanToMouseVector.Normalize();
        _ivanPosition += IvanToMouseVector;
        base.Update(gameTime);
    }
    public override void Draw(GameTime gameTime){
        _spriteBatch.Draw(_ivan, _ivanPosition, Color.White);
        base.Draw(gameTime);
    }
}
 

As you can see, the updated CrazyIvanSprite class now overrides the typical Update , Dispose and Draw methods of the DrawableGameComponent class. The real magic happens in the main game loop's methods:

private BackGround _back;
private CrazyIvanSprite _ivan;

protected override void LoadContent(){
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Services.AddService(typeof(SpriteBatch), spriteBatch);
    _back = new BackGround(this);
    _ivan = new CrazyIvanSprite(this);
    this.Components.Add(_back);
    this.Components.Add(_ivan);
}
protected override void UnloadContent()
{}
protected override void Update(GameTime gameTime){
    base.Update(gameTime);
}
protected override void Draw(GameTime gameTime){
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin();
    base.Draw(gameTime);
    spriteBatch.End();
}

Now that the sprites inherit from DrawableGameComponent, all we have to do is just create them and add them in the Components collection of the Game class. The rest is taken care by XNA and that is why Update and Draw methods of the sprite are not explicitely called in the corresponding Game loop's methods. Those will be executed when base.Update() and base.Draw() will be called.

Therefore by inheriting from DrawableGameComponent and GameComponent classes we manage to separate the sprites' logic from the game logic allowing sprite reuse between game and the distribution of the implementation of the sprites among many developers in a team.

Two things to note here:

  • The main difference between DrawableGameComponent and GameComponent is that the first implements a Draw() and Update() method while the second only implements the Update() method for logic that does not need drawing to the window.
  • If in a specific point we want to tell our game not to call the Update() method of a sprite we can set its Enabled property to false.
  • If in a specific point we want to tell our game not to call the Draw() methods of a sprite we can set its Visible property to true

This is it for now. In the next post we will talk about Game Services and Scene management. You can download the project of the final code here. Happy coding.

 

Shout it

kick it on DotNetKicks.com
blog comments powered by Disqus
hire me