Just thought I might toss another alternative solution in here. Cus obviously this very active topic needs even more of that, right?
So, defining some limitations… If you’re concerned about constructors growing too large as dependencies inflate on your objects, and you don’t like using a static content manager, and you don’t want to pass in a Texture2D or handle the loading outside of the class in question…
Consider using IServiceProvider. Make a new interface for the content loader, wrap the content loader, implement the interface, use it in the IServiceProvider class provided by Monogame.
In Game1, probably in initialization:
Services.AddService(typeof(IContentLoader), Content);
Then you pass ‘Services’ around to whatever class needs the services. In their constructor:
this.Content = (IContentLoader)services.GetService(typeof(IContentLoader));
True, you now are passing around something in every class constructor, same as before, but with the added advantage that you can also put other common services in there which you might have used static classes for, like input handling, graphics manager, or other things. It’s convenient, and it maintains abstraction since you’re using interfaces, so you can still do testing and mocking and all that kind of stuff.
Another alternative…
A combination of 2 other methods mentioned above. In your Initialization() or LoadContent() method on each class, include a ContentManager parameter. Pass in the manager via method injection this way. The benefit of this, over using the constructor, is that you can delay dealing with the ContentManager until you want to later. You can also implement an interface which exposes a ‘LoadContent(ContentManager)’ on every object which needs to do content loading. This would allow you to put all such objects in a large list together, and you could then iterate over the entire list at one time and run LoadContent() on them all at one time. This would be beneficial for locality of reference, and it might be a little easier to manage depending on how and when you load your entities in your game world.
Alternative.B
Also mentioned above is to include a string to the path of the texture in the constructor. You could expose that string as a public property on the object, call it TextureSource, whatever. Do the same as above with LoadContent method, but rather then passing in the ContentManager, you simply grab the TextureSource string and grab the texture outside the object (probably also in the middle of a iterator), then you can pass in the texture to the object in either a public method or property. This is a less ‘safe’ option, in the sense that you’re making the texture mutable after initialization. But that could also be a strength, possibly, if you want to play with texture swapping at runtime as a tool for doing some cool behavior.
Alternative.C
Instead of a string with the source of the texture xnb file, ensure every object gets an ID of some kind. The ID could be a simple string, a custom data class, a GUID, or just an int. Somewhere in the bowels of your game, have a repository which contains a lookup table for entities and their textures. So for example, ID “ent_lv3_critter” refers to a very specific creature you’ve defined already, possibly in a text file somewhere. In your lookup table(a simple dictionary is fine), you’d have a key value pair of <string, string>
, where the key is the ID of the entity, and the value is the filepath to its texture file.
Now when you’re loading content for all your entities, you just peek at the creatures set ID, glance at the lookup table, grab the file source, load it into the ContentManager, pass the entity its new texture file.
You could also use serialization in conjunction with this. Deserialize your critter object from file, and have the file source included in the file definitions. Then when the object is initialized in your game it already has the source file ready to be read.
This method also gives you the ability to use a default in case of an oopsie(well, any of these would allow that actually). Put the Content.Load<Texture2D>
in a try/catch statement and catch any loading errors(such as a bad file source). If this happens, you can now load a default texture for that entity. Like a big, pink and black checkerbox. Something obvious so when you get in-game and see bouncing pink boxes everywhere you know something went wrong with the texture loading.
Insane Variant:
Or if you REALLY hate passing anything into the class at all, even its own texture file, do all the rendering outside the class. Make a lookup table for Texture2D(as value) and tie it to a key(the entities ID). Grab all your entities and iterate through them, and for each you grab ‘DestinationRectangle’ from the entity, or maybe a single data struct like ‘RenderingContext’ which contains all the rendering information for that entity, and then draw the entity from outside the class entirely.
…
I’ve been awake for a really long time. I should stop typing and go to bed.