woman in gray tank top looking frightened
Photo by Andrea Piacquadio on Pexels.com

Oh yes, I’m going there! I can almost hear the groans as I’m writing this, and perhaps some may be justifiable, but stick with me and hear me out…

We recently published a little game on Google Play store called Jumpy Kitty, written using C# and monogame. To be honest it had been a long time since I’d dabbled with monogame, as most of our recent coding work was just good old ASP.NET and Blazor web apps. This had made me quite used to the built in dependency injection available in ASP.NET, so going back to monogame I found myself a little irked at not having something similar there. But wait, you say, what about the GameServiceContainer! Yes, yes, however you can only use that to retrieve services via the service locator method, no constructor injection available unfortunately…

Lets get something out of the way though before we dive too deep; I’d personally say that using dependency injection in a game should only be where it’s. You need to watch out, otherwise it potentially could introduce some performance issues. So why am I talking about this then? Well, first off, for me it actually was about convenience, as I’m quite lazy (nice excuse, very professional)

close up photo of cat yawning
Photo by Fred on Pexels.com

As I was fiddling around with my various classes I was constantly having to refactor several parts if I dared to change the constructor parameters in one class somewhere. As I said above, yes I know that sounds lazy, but I’m explaining what first got me thinking about this… could I actually use DI in a game and not have it suffer performance wise? Note that I first played around with the GameServiceContainer, but I really wanted my good old friend ‘constructor injection’ that I was used to from the world of ASP.NET.

As I didn’t need or want to go too overboard, I choose to start with the Microsoft service container. Getting it setup was dead simple really. Just create a monogame project as normal, add the nuget package we’ll need Microsoft.Extensions.DependencyInjection and change the usual Game1 class to include the following code inside the Initialize method and you’ll end up with something like this:-

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace ExampleGame
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;        
        private SpriteBatch _spriteBatch;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            // Create service collection
            var serviceCollection = new ServiceCollection();

            // Add services we want to inject, in this example we're going to inject the SpriteBatch service
            // into some other classes eventually via constructor. So lets register SpriteBatch and also
            // the GraphicsDevice (which SpriteBatch needs) so we can do that later on whenever... Note that
            // we're registering Singletons as we only ever should have ONE SpriteBatch in our game!
            serviceCollection.AddSingleton(GraphicsDevice);
            serviceCollection.AddSingleton<SpriteBatch>();

            // Build the provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // Get some services now that we'll need now
            _spriteBatch = serviceProvider.GetService<SpriteBatch>();
            
            base.Initialize();
        }

        protected override void LoadContent()
        {            
            // TODO: use this.Content to load your game content here
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            
            _spriteBatch.Begin();

            // Draw something here

            _spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}

crop cheerful multiethnic colleagues celebrating victory in office
Photo by Kindel Media on Pexels.com

Ok, all done! We’ll, almost… to effectively use the DI container we need to design our classes to use constructor injection. Say for example you wanted to create a screen manager service, you’ll need to code it so that it makes full use of DI, so instead of just writing ‘new ScreenManagementService()’ we’d probably use that service as a base for containing the rest of our game. So lets part implement this class, note I’ve excluded some stuff as I just want to illustrate the DI relevant parts of this exercise for now:-

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ExampleGame
{
    internal class ScreenManagementService
    {
        private readonly SpriteBatch _spriteBatch;

        public ScreenManagementService(SpriteBatch spriteBatch)
        {
            _spriteBatch = spriteBatch;
        }

        public void Draw(GameTime gameTime)
        {
            _spriteBatch.GraphicsDevice.Clear(Color.CornflowerBlue);
            _spriteBatch.Begin();

            // Draw the current screen here
            // e.g. _currentScreen.Draw(gameTime);

            _spriteBatch.End();
        }

        public void Update(GameTime gameTime)
        {
            // Update the current screen here
            // e.g. _currentScreen.Update(gameTime);
        }
    }
}

So a nice clean easy to understand class, it doesn’t need to worry where SpriteBatch (or any other services we inject) come from, you can just concentrate on just this class and what it needs to do. However, we now need to modify the Game1 class a little to support this class. So lets change the original Game1 code we created previously so that it will work with this ScreenManagementService:-

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace ExampleGame
{
    public class Game1 : Game
    {
        private GraphicsDeviceManager _graphics;        
        private ScreenManagementService _screenManagementService;

        public Game1()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }

        protected override void Initialize()
        {
            // Create service collection
            var serviceCollection = new ServiceCollection();

            // Add services we want to inject, in this example we're going to inject the SpriteBatch service
            // into some other classes eventually via constructor. So lets register SpriteBatch and also
            // the GraphicsDevice (which SpriteBatch needs) so we can do that later on whenever... Note that
            // we're registering Singletons as we only ever should have ONE SpriteBatch in our game!
            serviceCollection.AddSingleton(GraphicsDevice);
            serviceCollection.AddSingleton<SpriteBatch>();
            serviceCollection.AddSingleton<ScreenManagementService>();

            // Build the provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // We don't need to get the SpriteBatch like we did before, we only need to get the screen management service
            // that this class requires. The SpriteBatch will be created by the container since the screen management
            // service needs it to be injected via its constructor ;-)
            _screenManagementService = serviceProvider.GetService<ScreenManagementService>();            
            
            base.Initialize();
        }

        protected override void LoadContent()
        {            
            // TODO: use this.Content to load your game content here
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            // All update logic is now handled by the screen management service
            _screenManagementService.Update(gameTime);

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            // We've moved the previous SpriteBatch begin/end bits to the screen management service
            _screenManagementService.Draw(gameTime);

            base.Draw(gameTime);
        }
    }
}

There we go, dependency inject with monogame. As I mentioned before, parts of your game may suit this but other performance sensitive parts of your game may not. For our example, the screen manager is and should only ever be a singleton, so using DI here makes sense (at least to me). I used DI for our recent mobile game and also for an upcoming car game and its helped (forced) me to design my classes better, but also just changing several constructor parameters in several class and not having to worry about changing all the other places where I’d done a ‘new SomeClass(a,b,c,etc)’ was a definite weight off my mind. I suppose this could also support some unit testing in places…

So what do you think? Let me know

P.S. The code for this is here on GitHub 🫡

Leave a Reply

Discover more from Aventius

Subscribe now to keep reading and get access to the full archive.

Continue reading