13
votes

What is an efficient way to draw sprites in my 2D XNA game? To be more concrete, I have split this question up into 4 questions.


I used to declare Game1's spriteBatch static, and called SpriteBatch.Begin and .Close in every IDrawable.Draw. That didn't work well. Giving each drawable it's own SpriteBatch also didn't work well.

Q1: I assume it's best to have one SpriteBatch instance, and only call begin/close once. Is this correct?


Currently, my Game1.Draw looks something like this:

spriteBatch.Begin();

base.Draw(gameTime); // draws elements of Game.Components

// ...

spriteBatch.End();

Q2: This way, Begin is called only once. Is this the way to go? How did you guys solve this?


Q3: Also, every component has it's own IGameComponent.LoadContent method. Am I supposed to load my content there? Or is it better to load content at a parent class, such as Game1.LoadContent?


I realise that I should never load the same content twice, so I have given my drawable components both a static and a non-static Texture2D, and gave them 2 constructors:

static Texture2D defaultTexture;
public Texture2D MyTexture;

public Enemy()
    : this(defaultTexture) { }

public Enemy(Texture2D texture)
{
    MyTexture = texture;
}

protected override void LoadContent()
{
    if (MyTexture == null)
    {
        if (defaultTexture == null)
        {
            defaultTexture = Content.Load<Texture2D>("enemy");
        }

        MyTexture = defaultTexture;
    }
}

This way, the default textures of a class are only loaded the first time LoadContent is called on that class. However, when the parameter texture is specified in the constructor, I'll have to load that texture beforehand.

Q4: I think there should be better ways to do this. I'm thinking of creating a texture dictionary with their asset names as string. What would you suggest?

3
I do it in the same way. If i have not reason to use multiply sprite batch (eg. for split-screen, different effect, and so), i'm calling begin and end only once in main class of game, and draw everything between this begin and end.Thaven

3 Answers

3
votes

I used to declare Game1's spriteBatch static

You don't have to have it static. Just make a singleton. Inside Draw method

SpriteBatch spriteBatch = ScreenManager.SpriteBatch; // singletion SpriteBatch
spriteBatch.Begin();
// Draw the background
spriteBatch.Draw(background, ...
foreach (var enemy in enemys)
{
     enemy.Draw(gameTime);
}
// etc.
spriteBatch.End(); // call here end 
base.Draw(gameTime); // and then call the base class

I assume it's best to have one SpriteBatch instance, and only call begin/close once. Is this correct?

I advice you to open and end the SpriteBatch within one method. It will help you avoid conflicts with SpriteBatch that started drawing, but didn't finish.

Are you adding your elements to the global collection of components? This is a bad idea to add drawable to this collection. You cannot control the order of drawing, the components are global. See this answer.

Implement in your components IUpdatable and IDrawable and call them in your code where you need it. Get rid of the static stuff and use Dependency Injection istead.

Also, every component has it's own IGameComponent.LoadContent method. Am I supposed to load my content there? Or is it better to load content at a parent class, such as Game1.LoadContent?

You should load assets when you need it and you are responsible for it. You don'y have to load all 30 levels when user just started the game, do you? For example, when your game starts you load all assets that you need for your start screen and main menu. If you load just what you need the player is happy that game starts fast. Then player wants to start the gameplay. Here you can load the assets on a separate thread to keep the app responsive.

I think there should be better ways to do this. I'm thinking of creating a texture dictionary with their asset names as string. What would you suggest?

Content.Load is cached already, so you don't have to do it.

1
votes

as far as can be gleaned from tutorials around

calling begin and end only once is the preferred method.

0
votes

Q4: I think there should be better ways to do this. I'm thinking of creating a texture dictionary with their asset names as string. What would you suggest?


A4: I usually create a Dictionary<string, Texture2D> NameTextureDic within my TextureLib class (which handles loading png images at runtime), so I can get any texture like so:

var t2d = textureLib.NameTextureDic["tree-4"];