3
\$\begingroup\$

I have the following Sprite class (leaving out everything that doesn't pertain to my question):

public class Sprite
{
    public Texture2D Texture { get; set; }
    public Vector2 Position { get; set; }
    private SpriteBatch _spriteBatch;

    public Sprite(Texture2D texture, Vector2 position)
    {
        Texture = texture;
        Position = position;

        // what code do I put here?
        _spriteBatch = ???
    }

    public virtual void Draw()
    {
        _spriteBatch.Draw(Texture, Position, Color.White);
    }

In my Game1.cs class, I register the SpriteBatch as a service:

protected override void LoadContent()
{
    _spriteBatch = new SpriteBatch(GraphicsDevice);
    Services.AddService(_spriteBatch);
}

Problem is, I don't know what code to put in the Sprite constructor to get the SpriteBatch object from the service. It doesn't have access to the Game.Services object. How would I load the service in this case?

\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

It is completely a matter of taste.

You could pass your Game class instance into the constructor of your Sprite and access its public Services property. This is what DrawableGameComponent and GraphicsDeviceManager do.

You could pass Game.Services as an IServiceProvider into the constructor of your Sprite class. This is what the ContentManager class does. This has the advantage of not depending on an actual instance of Game (which you might not have if, say, you end up making a form-based level editor).

You could just pass an instance of SpriteBatch in the constructor of your Sprite class, but you can then only create Sprite objects after you create your SpriteBatch, which you can only do during and after LoadContent.

You could get especially lazy and create a globally accessible Game or IServiceProvider or SpriteBatch object and just use that (make it a static property on your game class). Advantage: quick and easy; Disadvantage: ugly!

You could pass a SpriteBatch instance into your Sprite.Draw method. This is pretty easy to do, and is the method I use for my gameplay classes - things that might have a Draw and an Update function (eg: a Player, or an Enemy, etc). One advantage is that it makes it both locally explicit and easy to modify the shared resources that the Draw function depends on.

But if your Sprite class is simply visual data for SpriteBatch (texture, position, etc), which is managed externally (having public setters for these properties, in your example, indicates that it might be), then I would recommend creating an extension method for SpriteBatch that does the drawing. This allows you to maintain the "feel" of the SpriteBatch object. Your extension method might look like:

public static void Draw(this SpriteBatch sb, Sprite sprite)
{
    sb.Draw(sprite.Texture, sprite.Position, Color.White);
}

Allowing you to draw it with: sb.Draw(sprite);. This method is preferable if you are not doing SpriteBatch.Begin and .End calls in your Draw method - as this allows it to work the same as SpriteBatch.

Those last two methods are the ones that I use and recommend myself.

But you have done something that strikes me as pretty unusual by making your Sprite.Draw method virtual - indicating that you will be inheriting from Sprite and using it virtually. But combining this with public setters, and not calling Begin and End, makes for a pretty confused design.

If this is really the design you intend to use, then passing an IServiceProvider to the Sprite constructor is probably the best method to use as it allows for better encapsulation. (Just fix up those other things, which don't ;)

\$\endgroup\$
6
  • \$\begingroup\$ And +1 to you also; but, what is "confused" about not calling Begin()/End()? It makes sense to me - he wants the drawing code in the sprite class, but doesn't want every sprite to be in its own batch. He presumably has a sprite-manager DrawableGameComponent which loops over all the sprites and calls Draw() on them individually, within a single sprite-batch. This is exactly how I'd do it. \$\endgroup\$ Commented Jul 11, 2011 at 8:10
  • \$\begingroup\$ @BlueRaja: For performance, it is best to batch together sprites (that have the same texture), and often performance will take precedence over a "nice" design. It is nice to say "share this SpriteBatch object, but leave it in its base (not-in-batch) state when you're done". It's also reasonably nice to say "my draw function takes a SpriteBatch parameter that is already in-batch with the correct settings". It is not so nice to say "share this SpriteBatch object, and trust us that we'll put it in the right state before calling your Draw method". \$\endgroup\$ Commented Jul 11, 2011 at 13:35
  • \$\begingroup\$ In particular, the latter design can encourage ugly hacks like: { End; Begin(my settings); DrawMyStuff; End; Begin(try to reset); } - and that is very much un-nice. And, again, architecture is a matter of taste - these are not hard-and-fast rules. You might recall that I don't like DrawableGameComponent, and one reason is that it can force you to use this less-nice architecture (which works, it's just not nice). \$\endgroup\$ Commented Jul 11, 2011 at 13:39
  • \$\begingroup\$ I still don't get it. If any classes need different SpriteBatch settings, they can create their own instance - there's nothing wrong with multiple overlapping sprite-batches. Otherwise, not forcing every class to call Begin/End is more convenient and (as I understand it; I've never profiled it) more performant. The same argument applies whether or not he's actually using GameComponents. \$\endgroup\$ Commented Jul 11, 2011 at 16:10
  • \$\begingroup\$ You are telling me that it will work and that it might be more performant (it is, if consecutive sprites share the same texture). I don't disagree with you! You suggest that it is more "convenient" - I think you mean "convenient to write". Whereas an indicator of a good architecture is that it is convenient to read. All of these factors need to be balanced. All I am saying is that, on the "nice architecture" factor alone, having a member who's state is shared and changed externally is an ugly design (especially in a class designed to be inherited from). \$\endgroup\$ Commented Jul 11, 2011 at 16:51
3
\$\begingroup\$

You have a couple of possibilities :

  • Pass it in the constructor
  • Pass the Services object in the constructor
  • Pass it to your Sprite.Draw function

The right solution depends on your overall architecture.

\$\endgroup\$
2
  • \$\begingroup\$ +1; see this answer for more information. \$\endgroup\$ Commented Jul 11, 2011 at 8:03
  • \$\begingroup\$ @BlueRaja Great answer. \$\endgroup\$
    – subb
    Commented Jul 11, 2011 at 14:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .