XNA Framework GameEngine Development. (Part 6, Input:GameComponent)
Introduction
Welcome to Part 6 of the XNA Framework GameEngine Development series. In this article I will be introducing the Input GameComponent. This class will be changing as we add more functionality to the engine. For now, I will just implement some simple handlers to set up for Part 7 in this series. Also, I will be revisiting the TDD implementation to make some changes for ContentLoadingExceptions.
Review of articles in this series.
Preview of upcoming articles in this series.
-
Part 7, ScreenManager:GameComponent and GameScreens
-
Part 8, Multi-Threading GameComponents
-
Part 9, Scene management, scene objects, and SceneGraphManager
Input GameComponent
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
namespace RoeEngine2.GameComponents
{
public class Input : GameComponent
{
public KeyboardState CurrentKeyboardState;
public GamePadState CurrentGamePadState;
public MouseState CurrentMouseState;
public KeyboardState LastKeyboardState;
public GamePadState LastGamePadState;
public MouseState LastMouseState;
private Point _lastMouseLocation;
private Vector2 _mouseMoved;
public Vector2 MouseMoved
{
get { return _mouseMoved; }
}
public Input(Game game)
: base(game)
{
Enabled = true;
}
/// <summary>
/// Reads the latest state of the keyboard and gamepad.
/// </summary>
public void Update()
{
LastKeyboardState = CurrentKeyboardState;
LastGamePadState = CurrentGamePadState;
LastMouseState = CurrentMouseState;
CurrentKeyboardState = Keyboard.GetState();
CurrentGamePadState = GamePad.GetState(PlayerIndex.One);
CurrentMouseState = Mouse.GetState();
_mouseMoved = new Vector2(LastMouseState.X - CurrentMouseState.X, LastMouseState.Y - CurrentMouseState.Y);
_lastMouseLocation = new Point(CurrentMouseState.X, CurrentMouseState.Y);
}
/// <summary>
/// Checks for a "menu up" input action (on either keyboard or gamepad).
/// </summary>
public bool MenuUp
{
get
{
return IsNewKeyPress(Keys.Up) ||
(CurrentGamePadState.DPad.Up == ButtonState.Pressed &&
LastGamePadState.DPad.Up == ButtonState.Released) ||
(CurrentGamePadState.ThumbSticks.Left.Y > 0 &&
LastGamePadState.ThumbSticks.Left.Y <= 0);
}
}
/// <summary>
/// Checks for a "menu down" input action (on either keyboard or gamepad).
/// </summary>
public bool MenuDown
{
get
{
return IsNewKeyPress(Keys.Down) ||
(CurrentGamePadState.DPad.Down == ButtonState.Pressed &&
LastGamePadState.DPad.Down == ButtonState.Released) ||
(CurrentGamePadState.ThumbSticks.Left.Y < 0 &&
LastGamePadState.ThumbSticks.Left.Y >= 0);
}
}
/// <summary>
/// Checks for a "menu select" input action (on either keyboard or gamepad).
/// </summary>
public bool MenuSelect
{
get
{
return IsNewKeyPress(Keys.Space) ||
IsNewKeyPress(Keys.Enter) ||
(CurrentGamePadState.Buttons.A == ButtonState.Pressed &&
LastGamePadState.Buttons.A == ButtonState.Released) ||
(CurrentGamePadState.Buttons.Start == ButtonState.Pressed &&
LastGamePadState.Buttons.Start == ButtonState.Released);
}
}
/// <summary>
/// Checks for a "menu cancel" input action (on either keyboard or gamepad).
/// </summary>
public bool MenuCancel
{
get
{
return IsNewKeyPress(Keys.Escape) ||
(CurrentGamePadState.Buttons.B == ButtonState.Pressed &&
LastGamePadState.Buttons.B == ButtonState.Released) ||
(CurrentGamePadState.Buttons.Back == ButtonState.Pressed &&
LastGamePadState.Buttons.Back == ButtonState.Released);
}
}
/// <summary>
/// Checks for a "pause the game" input action (on either keyboard or gamepad).
/// </summary>
public bool PauseGame
{
get
{
return IsNewKeyPress(Keys.Escape) ||
(CurrentGamePadState.Buttons.Back == ButtonState.Pressed &&
LastGamePadState.Buttons.Back == ButtonState.Released) ||
(CurrentGamePadState.Buttons.Start == ButtonState.Pressed &&
LastGamePadState.Buttons.Start == ButtonState.Released);
}
}
/// <summary>
/// Helper for checking if a key was newly pressed during this update.
/// </summary>
bool IsNewKeyPress(Keys key)
{
return (CurrentKeyboardState.IsKeyDown(key) &&
LastKeyboardState.IsKeyUp(key));
}
}
}
Using the Input GameComponent
Add the following code to the Properties section of RoeEngine2
private static Input _input = null;
/// <summary>
/// The input helper for menus, gamepads, keyboard and mouse.
/// </summary>
public static Input Input
{
get { return _input; }
}
And in the RoeEngine2 Constructor
// Init the Input _input = new Input(this); Components.Add(_input);
Optional TDD : Required changes
I have had to make some changes to the test cases project due to exceptions when loading content. Since we really are not testing content loading I decided to Assert ExpectedExceptions in the SetupTextFixture. So your test cases should look similar to the following now.
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework;
using RoeEngine2.Shaders;
using Microsoft.Xna.Framework.Graphics;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Content;
namespace RoeEngine2UnitTests
{
[TestFixture]
public class RoeEngineTestCases
{
private EngineManager _game = null;
private const string _windowTitle = "UNITTEST";
[TestFixtureSetUp]
[ExpectedException(typeof(ContentLoadException))]
public void SetupTestFixture()
{
try
{
_game = new EngineManager(_windowTitle);
EngineManager.Game = _game;
_game.Activated += new EventHandler(game_Activated);
_game.Run();
}
catch (ContentLoadException)
{
_game.Exit();
}
}
void game_Activated(object sender, EventArgs e)
{
_game.Exit();
}
[TestFixtureTearDown]
public void TeardownTestFixture()
{
_game = null;
}
[Test]
public void GameStartup()
{
Assert.IsNotNull(_game);
Assert.IsNotNull(EngineManager.Game);
Assert.IsNotNull(EngineManager.Device);
Assert.IsNotNull(EngineManager.ContentManager);
Assert.AreEqual(_windowTitle, EngineManager.WindowTitle.ToString());
}
[Test, Category("FpsCounter")]
public void FpsCounterCreated()
{
Assert.IsNotNull(EngineManager.FpsCounter);
Assert.AreEqual("0", EngineManager.FpsCounter.FPS.ToString());
}
[Test, Category("Input")]
public void InputCreated()
{
Assert.IsNotNull(EngineManager.Input);
}
[Test, Category("ShaderManager")]
public void ShaderManagerCreated()
{
Assert.IsTrue(ShaderManager.Initialized);
Assert.IsNotNull(ShaderManager.GetShader("BasicEffect"));
Assert.AreEqual(typeof(basicEffect), ShaderManager.GetShader("BasicEffect").GetType());
}
[Test, Category("ShaderManager")]
public void ShaderManagerAddGetFailed()
{
Assert.IsNull(ShaderManager.GetShader("testshader"));
}
[Test, Category("TextureManager")]
public void TextureManagerCreated()
{
Assert.IsTrue(TextureManager.Initialized);
}
[Test, Category("TextureManager")]
public void TextureManagerAddGetFailed()
{
Assert.IsNull(TextureManager.GetTexture("testtexture"));
}
}
}
Conclusion
In this article I implemented a simple Input handler GameComponent. Obviously, this class is not complete, but should be a good basis for the next article in this series. I also revisited our TDD to make some fixes for content loading.
Please leave a comment or suggestion. I am taking feedback for some more advanced articles using this engine. Since this series is a gift to the open source community, I would enjoy your suggestions for the direction this series should take.
I want to ask a question, I’m moving sequentially with the tutorials so i didn’t see the final version of Input class, but how can I make the user to choose the input configuration instead of using the always predefined buttons ? or did you implement it in recent versions of Input class ?
I am working on a configuration tutorial to allow user defined inputs. Input mapping is a very important part of good game design. I am going to make it an add in for the engine rather than a required part of the engine.