Running on Empty

The few things I know, I like to share.

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.

Advertisements

January 17, 2008 - Posted by | C#, GameComponent, TDD, XBOX360, XNA

2 Comments »

  1. 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 ?

    Comment by Karim Kenawy | March 25, 2008 | Reply

  2. 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.

    Comment by roecode | March 25, 2008 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: