13

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?

Rudey
  • 4,717
  • 4
  • 42
  • 84
  • 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 Dec 20 '12 at 11:06

3 Answers3

3

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.

Community
  • 1
  • 1
Lukasz Madon
  • 14,664
  • 14
  • 64
  • 108
  • You seem to call `Draw` on enemy manually. In my game, I have added all drawables (such as walls, and enemies) to `Game1.Components` list, so `Draw` is called when calling the base class. – Rudey Dec 20 '12 at 11:17
  • +1 for pointing out that `Content.Load` handles redundant calls itself. However, is it *that* bad to use the global collection? I don't have to control drawing order. – Rudey Dec 20 '12 at 12:05
  • Also, I understand that I should not load all levels. But, for example, should I load `bullet.png` when the player shoots, and unload it when it's gone? Or should I load the image at the start of the level? – Rudey Dec 20 '12 at 12:08
  • By default you shouldn't care about unloading. `Content` will manage the caching. GameComponents is a design flaw. See http://gamedev.stackexchange.com/questions/9204/what-are-the-cons-of-using-drawablegamecomponent-for-every-instance-of-a-game-ob/9209#9209 – Lukasz Madon Dec 20 '12 at 12:14
  • But then again, should I load required sprites at level init, or when they first occur? :) – Rudey Dec 20 '12 at 12:21
  • That depends. If you have a file `player.png` that is small you can load it and user won't notice that. If you have a lot of files to load than you can make a loading screen that will load it before the init of the level. That also depends on the hardware. Is it phone or PC? – Lukasz Madon Dec 20 '12 at 12:40
  • PC. It seems I have no further questions, thanks for answering them all. :) – Rudey Dec 20 '12 at 13:01
1

as far as can be gleaned from tutorials around

calling begin and end only once is the preferred method.

Oren
  • 4,152
  • 3
  • 30
  • 37
0

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"];
user1306322
  • 8,561
  • 18
  • 61
  • 122