1

A lot of the time I have found myself wondering is there an easy way to use just one spritebatch in XNA, finding microsoft's built in component system to be a good idea but rather cryptically built.

I can only assume that a lot of the way XNA is is due either to backwards compatibility or Xbox compatibility, which aren't bad things in themselves, and I'm sure there are details that have lead things to be developed the way they are, but the main system can seem a bit cryptic.

Pharap
  • 3,826
  • 5
  • 37
  • 51

2 Answers2

0
  1. Make your own base class that replaces DrawableGameComponent (and the associated infrastructure - basically have a List<MyComponentBaseType>).
  2. Modify the Draw method of that base class to take a SpriteBatch parameter.
  3. Profit.

See discussion here, here and here for a detailed explaination of why this is a good idea (and what GameComponent and IServiceProvider are really for).

Community
  • 1
  • 1
Andrew Russell
  • 26,924
  • 7
  • 58
  • 104
-1

That's what I always thought, until I realised the true purpose and capability of game "services", and their link to boxing/unboxing and the .Net type system.

One fault I will admit Microsoft have is their lack of creative naming. At first I always thought game services were linked to system services or that services were some sort of special class or interface.

In fact, a game's services collection is nothing more than a collection of objects. What the services allow is for any game components that have been hooked up to the game to retrieve data from the game.

One example of this is the SpriteBatch. By adding a SpriteBatch to the game's services, and registering it with typeof(SpriteBatch), any component can then extract that SpriteBatch from the services by feeding typeof(SpriteBatch) to the GetService method.

Below is some sample code (not tested, just an example):

public namespace MyGame
{
  public class MainGame:Game
  {
    SpriteBatch spriteBatch;
    protected override void Initialize()
    {
      spriteBatch = new SpriteBatch(GraphicsDevice);
      Services.AddService(typeof(SpriteBatch), spriteBatch);
      base.Initialize();
    }
  }
  public class DrawableComponent : DrawableGameComponent
  {
    SpriteBatch spriteBatch;
    public override void Initialize()
    {
      spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
      if(spriteBatch == null)
      { throw new Exception("No SpriteBatch found in services"); }
    }
  }
}

My example here also demonstrates a very overlooked factor. Microsoft's default game template places the SpriteBatch creation in the load content method, leading many to believe is must remain there. In fact, you are free shift it much further forward, up as far as the initialisation method (but not the constructor), meaning it can be shifted into the services before any components are initialised, thus ensuring all components that seek a SpriteBatch from the services in their initialisation method will get one.

Make sure to note that while adding to the services will throw an exception if a given item is null, taking from them will not, it will simply return null and carry on executing, thus null checking must be performed asap. Also note that if a component is created mid game, it won't have its initialisation called, so this must be compensated for by some means.

Further examples of this technique can be found here: http://itspaulsblog.blogspot.co.uk/2011/02/xna-common-sprite-batch-as-game-service.html

Pharap
  • 3,826
  • 5
  • 37
  • 51
  • A few clarifications: There's no boxing/unboxing going on unless you're putting value types into `Services` (a very weird thing to do). And `GameComponent.Initialize` *does* get called, when that component is added to `Components`, after the game has started. – Andrew Russell Jun 14 '13 at 11:27
  • Please also note that the [documentation](http://msdn.microsoft.com/en-US/library/microsoft.xna.framework.game.loadcontent(v=xnagamestudio.40).aspx) states that **"You should not access `GraphicsDevice` until `LoadContent` is called"**. If the graphics device gets reset (eg: your game is minimised or the user's desktop is locked), and automatic resource recreation fails for whatever reason, then your `LoadContent` method (but *not* `Initialize`) gets called again to manually recreate graphics resources. Anything you create only in `Initialize` will remain invalid, and will crash if you use it. – Andrew Russell Jun 14 '13 at 11:54
  • "and their link to" the possibility of using value types is the link. I did not know that last one, I've never noticed it happen before so I'll have to look into it. As for the graphics device issue, it's not that big a deal, there are ways around it. – Pharap Jun 18 '13 at 23:09
  • I'm thinking that the "correct" way around it probably involves writing some kind of `ISpriteBatchService`, similar to [how XNA has](http://stackoverflow.com/a/6807121/165500) `IGraphicsDeviceService`, and using it in much the same way. Although I imagine a good `ISpriteBatchService` interface would be more complicated, given that `SpriteBatch` is stateful whereas `GraphicsDevice` isn't (in quite the same way, anyway). – Andrew Russell Jun 19 '13 at 05:21