Step 3: Input service |
Let's add the DigitalRune input service to the game and use it to exit the game when ESC is pressed.
… using DigitalRune.Game.Input; // NEW … namespace MyGame { public class Game1 : Microsoft.Xna.Framework.Game { … public InputManager _inputManager; // NEW … protected override void Initialize() { _inputManager = new InputManager(false); // NEW base.Initialize(); } protected override void Update(GameTime gameTime) { _inputManager.Update(gameTime.ElapsedGameTime); // NEW if (_inputManager.IsDown(Keys.Escape)) // NEW Exit(); // NEW base.Update(gameTime); } …
Here you can see a common pattern: A service/manager is created in Game.Initialize. It is updated in Game.Update. Most services/managers in the DigitalRune Engine are used like this.
If you run the game, you should be able to exit by pressing ESC on the keyboard.
Personally, I prefer to keep my Game class clean and move have any game-specific update logic and input handling (like handling the ESC key) in a separate class.
Let's add an item MyGameComponent.cs to the project and move the input handling into this class:
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; namespace MyGame { public class MyGameComponent : Microsoft.Xna.Framework.GameComponent { public MyGameComponent(Game game) : base(game) { } public override void Update(GameTime gameTime) { if (((Game1)Game)._inputManager.IsDown(Keys.Escape)) Game.Exit(); base.Update(gameTime); } } }
In Game1.cs we create an instance of this game component. The input handling is removed.
public class Game1 : Microsoft.Xna.Framework.Game { … protected override void Initialize() { _inputManager = new InputManager(false); Components.Add(new MyGameComponent(this)); // NEW base.Initialize(); } protected override void Update(GameTime gameTime) { _inputManager.Update(gameTime.ElapsedGameTime); //// Allows the game to exit // REMOVE //if (_inputManager.IsDown(Keys.Escape)) // REMOVE // Exit(); // REMOVE base.Update(gameTime); } …
The Game class is now cleaner. However, the game component uses following code to access the input manager:
if (((Game1)Game)._inputManager.IsDown(Keys.Escape))
That means, every class in the game that wants to use the input service must have a reference to the Game instance. This is fine for small project. However, in more complex projects we want to decouple classes. This makes the whole code more reusable and maintainable. A design pattern that can be used for this is the service provider pattern.
Let's use the service provider pattern in our game using the DigitalRune.ServiceLocationServiceContainer class.
Add following in Game1.cs:
… using DigitalRune.ServiceLocation; // NEW using Microsoft.Practices.ServiceLocation; // NEW … namespace MyGame { public class Game1 : Microsoft.Xna.Framework.Game { … private ServiceContainer _services; // NEW private InputManager _inputManager; // NEW: private instead of public … protected override void Initialize() { _services = new ServiceContainer(); // NEW ServiceLocator.SetLocatorProvider(() => _services); // NEW _services.Register(typeof(Microsoft.Xna.Framework.Game), null, this); // NEW _services.Register(typeof(Game1), null, this); // NEW _inputManager = new InputManager(false); _services.Register(typeof(IInputService), null, _inputManager); // NEW Components.Add(new MyGameComponent(this)); base.Initialize(); } … } }
This code creates a ServiceContainer instance and uses the ServiceLocator to make it globally available. The input service is registered in the service container. The field Game1._inputManager doesn't have to be public any more. We also register the Game instance itself in the service container.
From now on MyGameComponent and any other code in your game can access the input service like this:
using DigitalRune.Game.Input; // NEW using Microsoft.Practices.ServiceLocation; // NEW using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; namespace MyGame { public class MyGameComponent : Microsoft.Xna.Framework.GameComponent { private IInputService _inputService; // NEW public MyGameComponent(Game game) : base(game) { _inputService = ServiceLocator.Current.GetInstance<IInputService>(); // NEW } public override void Update(GameTime gameTime) { //if (((Game1)Game)._inputManager.IsDown(Keys.Escape)) // REMOVE if (_inputService.IsDown(Keys.Escape)) // NEW Game.Exit(); base.Update(gameTime); } } }
Another pattern that is used througout the DigitalRune libraries: The manager class, e.g. InputManager, implements an interface, e.g. IInputService. The Game class creates and uses the manager instances. Only the interfaces are registered in the service container. The rest of the game code uses only the interface IInputService. This interface contains everything that is needed by other game components.
The service provider pattern is very helpful – however, it is completely optional. You do not need to use it. All DigitalRune libraries will work fine without a service provider.