First, let me say sorry if I seemed to be absent for the past months. I was quite burned out and didn’t want to do much with computers during that time :).
My “XNA Game Architecture” series was left hanging, and right when it got interesting, too. I’ll try to find the time to continue where I left off. For now, here’s a small appetizer:
Some weeks ago, Synapse Gaming offered their SunBurn Lighting and Rendering Engine for half the price. This was too good an offer to pass and so I now find myself being able to do much better lighting effects than I had ever hoped to achieve in my game.
The first thing I did was, of course, to adapt SunBurn to Ninject, a very tidy dependency injector that works on the XBox 360 and even on Windows Phone 7, into the SunBurn example application. This article gives a short overview about the overall structure and provides you with an example application if you want to give Ninject a try yourself.
The design of SunBurn was a pleasant surprise. It’s a set of modular components you can use in a game (not unlike the design principles my Nuclex Framework), without forcing you into a specific architecture. I admit that I was at first fearing to find a monolithic, tightly integrated engine that has you attach your game logic and stuff in a few predefined places – but luckily, this is not the case at all.
This made it very easy to wire it up to Ninject. If you don’t know what dependency injection is or what advantages it brings, check out my Quick Introduction to Dependency Injection or hunt for more articles on the web. The game class now effectively becomes this:
/// <summary>Sets up global services and runs the game loop</summary>
public class SunBurnTestGame : NinjectGame {
/// <summary>Initializes a new game</summary>
/// <param name="kernel">Kernel that is managing the game's services</param>
public SunBurnTestGame(IKernel kernel) :
base(kernel) {
setWindowProperties();
setupGraphicsDeviceManager();
}
/// <summary>Assigns the properties of the game's main window</summary>
private void setWindowProperties() {
Window.Title = "Ninject/SunBurn Test Game";
Window.AllowUserResizing = false;
}
/// <summary>Creates and initializes the graphics device manager</summary>
private void setupGraphicsDeviceManager() {
this.graphics = new GraphicsDeviceManager(this) {
MinimumVertexShaderProfile = ShaderProfile.VS_3_0,
MinimumPixelShaderProfile = ShaderProfile.PS_3_0,
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
}
/// <summary>Manages the graphics device</summary>
private GraphicsDeviceManager graphics;
}
All the services you normally instantiate at the top of the game class are now
automatically wired up by Ninject as needed. To tell Ninject that, for example,
an IGraphicsDeviceManager
is obtained from the
Game
class or that SunBurn’s ILightManager
comes from the SceneInterface
, I built a few
modules that you can load into Ninject like so:
/// <summary>Contains the program's startup code</summary>
static class Program {
/// <summary>The main entry point for the application</summary>
[STAThread]
static void Main(string[] args) {
using (IKernel kernel = new StandardKernel()) {
// Tell SunBurn how we're going to render
configureSceneOptions(kernel);
// Initialize the service bindings for the game
setupBindings(kernel);
// Everything is ready, run the game loop!
using (var game = kernel.GetService<IGame>()) {
game.Run();
}
}
}
/// <summary>Configures the scene rendering options for SunBurn</summary>
/// <param name="kernel">Kernel the scene options are set for</param>
private static void configureSceneOptions(IKernel kernel) {
var sceneOptions = new SceneOptions() {
UseDeferredRendering = false,
UseAvatars = false,
UsePostProcessing = false
};
kernel.Bind<SceneOptions>().ToConstant(sceneOptions);
}
/// <summary>Binds all dependencies to their implementations</summary>
/// <param name="kernel">
/// Ninject kernel to which the bindings will be added
/// </param>
private static void setupBindings(IKernel kernel) {
kernel.Bind<IGame>().To<SunBurnTestGame>().InSingletonScope();
kernel.Load<XnaModule>();
kernel.Load<SunBurnModule>();
kernel.Load<TestModule>();
}
}
From then on, just add your actual game code as GameComponents
(you could add it into the SunBurnTestGame
class as well, of course,
but I think this violates the SRP),
request any external services as parameters from your component’s constructors
and the rest will take care of itself.
By stating which services a component consumes in its constructor (instead of fishing
for those services in the Game.Services
collection), the code becomes
shorter and it is much clearer what services a component relies on. This is by far the
most important advantage, because, if you’re writing a unit test for that component, you’ll
want to know exactly which services you need to mock.
Download
You can find all SunBurn/Ninject integration code in my Subversion repository
at https://devel.nuclex.org/framework/svn/Nuclex.Ninject.Sunburn/trunk.
If you just want a quick peek, you can also browse the sources online:
Nuclex.Ninject.Xna
Nuclex.Ninject.Sunburn