Input Class


The Input class has many properties to easily access all the most-used keyboard, mouse, and gamepad key states (see Figure 10-1). The class also provides some helper methods to manage keyboard input and typing in text, but you will probably not need most of the code in the Input class for your first game projects. It becomes more useful the bigger your game grows and the more UI code you have in it. The most important method is the Update method, which has to be called every frame (done automatically from the BaseGame class at the beginning of each frame in the Update method there).

image from book
Figure 10-1

Please note that all the mouse functionality is missing if you run XNA on your Xbox 360, and you can’t even get the mouse states when you compile for the Xbox 360 (the class is just missing). So you have to comment out all your mouse code just to make the game run on the Xbox 360. That is not funny, especially if you have hundreds of mouse calls all over the place in your project. A much better solution is to completely hide the mouse state (make it private) and only access the mouse parameters through properties in the Input class. As you can see in Figure 10-1, you have access to the Gamepad and Keyboard states directly in case you want to check another key or button yourself, but for the mouse, you can only access the properties the Input class gives you. Now all these properties return just what you would expect on the PC, but on the Xbox 360 they all return false or just the virtual mouse position. You can set and get the mouse position the same way as on the PC, but the user does not control it directly.

Now the same code for mouse, keyboard, and gamepad input can be used on both platforms. XNA had the goal to have 98% of the code behave the same way; go to 100% for your engine.

For example, the A button of the Xbox 360 gamepad or the cursor keys for the keyboard are used very often. It is far easier to write code like the following to allow navigating through the menu entries with the up/down keyboard keys. Additionally supporting the gamepad up/down cursor or even controlling the menu with the gamepad left thumb stick is easier to do with these helper properties. If you had to write all the state checking yourself every time you need to check if a key or button is currently pressed and was not pressed before, you would probably go crazy after a while.

  // Handle GamePad input, and also allow keyboard input if (Input.GamePadUpJustPressed ||   Input.KeyboardUpJustPressed) {   Sound.Play(Sound.Sounds.Highlight);   selectedButton =     (selectedButton + NumberOfButtons - 1) % NumberOfButtons; } // if (BaseGame.GamePadLeftNowPressed) else if (Input.GamePadDownJustPressed ||   Input.KeyboardDownJustPressed) {   Sound.Play(Sound.Sounds.Highlight);   selectedButton = (selectedButton + 1) % NumberOfButtons; } // else if 

For example, the GamePadUpJustPressed method does the following:

  /// <summary> /// Game pad up just pressed /// </summary> /// <returns>Bool</returns> public static bool GamePadUpJustPressed {   get   {     return (gamePadState.DPad.Up == ButtonState.Pressed &&       gamePadStateLastFrame.DPad.Up == ButtonState.Released) ||       (gamePadState.ThumbSticks.Left.Y > 0.75f &&       gamePadStateLastFrame.ThumbSticks.Left.Y < 0.75f);   } // get } // GamePadUpJustPressed 

The code uses the current gamepad state, which is assigned at the beginning of each frame, and the gamepad state from the last frame to check if any button state has changed. As you can see, if you had to write this code again and again every time you wanted to check if the gamepad up cursor was pressed, you would go crazy. Copy and paste works fine for the first one or two times, but if you use the same code over and over again, write a helper method or property to let it do the work for you. In the future when you write more UI code it will become much easier to check all input states and work with many different input devices.

The Update Method in the Input Class

The Update method does nothing really complicated, but it is the most important part of the Input class because all the last frame states are copied over from the previous states and then you get each new state for the keyboard, mouse, and gamepad input. To help out with relative mouse movement there is some extra code to handle it. Relative mouse movement is something that is not supported in XNA unlike the DirectInput classes from DirectX, which allow you to get relative mouse data instead of the absolute mouse positions like (–3.5, +7.0) instead of position (350, 802).

  /// <summary> /// Update, called from BaseGame.Update(). /// Will catch all new states for keyboard, mouse and the gamepad. /// </summary> internal static void Update() { #if XBOX360   // No mouse support on the XBox360 yet :(   mouseDetected = false; #else   // Handle mouse input variables   mouseStateLastFrame = mouseState;   mouseState = Microsoft.Xna.Framework.Input.Mouse.GetState();   // Update mouseXMovement and mouseYMovement   lastMouseXMovement += mouseState.X - mouseStateLastFrame.X;   lastMouseYMovement += mouseState.Y - mouseStateLastFrame.Y;   mouseXMovement = lastMouseXMovement / 2.0f;   mouseYMovement = lastMouseYMovement / 2.0f;   lastMouseXMovement -= lastMouseXMovement / 2.0f;   lastMouseYMovement -= lastMouseYMovement / 2.0f;   if (MouseLeftButtonPressed == false)     startDraggingPos = MousePos;   mouseWheelDelta = mouseState.ScrollWheelValue - mouseWheelValue;   mouseWheelValue = mouseState.ScrollWheelValue;   // Check if mouse was moved this frame if it is not detected yet.   // This allows us to ignore the mouse even when it is captured   // on a windows machine if just the gamepad or keyboard is used.   if (mouseDetected == false)     mouseDetected = mouseState.X != mouseStateLastFrame.X ||       mouseState.Y != mouseStateLastFrame.Y ||       mouseState.LeftButton != mouseStateLastFrame.LeftButton; #endif   // Handle keyboard input   keysPressedLastFrame = new List<Keys>(keyboardState.GetPressedKeys());   keyboardState = Microsoft.Xna.Framework.Input.Keyboard.GetState();   // And finally catch the XBox Controller input (only use 1 player)   gamePadStateLastFrame = gamePadState;   gamePadState = Microsoft.Xna.Framework.Input.GamePad.GetState(     PlayerIndex.One);   // Handle rumbling   if (leftRumble > 0 || rightRumble > 0)   {     if (leftRumble > 0)       leftRumble -= 1.5f * BaseGame.MoveFactorPerSecond;     if (rightRumble > 0)        rightRumble -= 1.5f * BaseGame.MoveFactorPerSecond;     Microsoft.Xna.Framework.Input.GamePad.SetVibration(       PlayerIndex.One, leftRumble, rightRumble);   } // if (leftRumble) } // Update() 

You might notice the code for the gamepad rumbling at the end of the Update method, which allows you to use the following method to let the gamepad rumble for a while without having to worry about it anymore. The Update method will automatically reduce the rumbled effect if GamePadRumble is not called again in the next frame. You can also use a weaker rumble effect for smaller events. Playing XNA Shooter is a lot more fun with rumbling enabled.

  /// <summary> /// Game pad rumble /// </summary> /// <param name="setLeftRumble">Set left rumble</param> /// <param name="setRightRumble">Set right rumble</param> public static void GamePadRumble(   float setLeftRumble, float setRightRumble) {   leftRumble = setLeftRumble;   rightRumble = setRightRumble; } // GamePadRumble(setLeftRumble, setRightRumble) 

Mouse Rectangles

The Input class also has some methods to help you detect if the mouse is over a UI element like a menu button. Use the following method to check if the mouse is hovering over a UI element. The method will also automatically play the highlight sound for you if you just entered this rectangle.

  /// <summary> /// Mouse in box /// </summary> /// <param name="rect">Rectangle</param> /// <returns>Bool</returns> public static bool MouseInBox(Rectangle rect) { #if XBOX360   return false; #else   bool ret = mouseState.X >= rect.X &&     mouseState.Y >= rect.Y &&     mouseState.X < rect.Right &&     mouseState.Y < rect.Bottom;   bool lastRet = mouseStateLastFrame.X >= rect.X &&     mouseStateLastFrame.Y >= rect.Y &&     mouseStateLastFrame.X < rect.Right &&     mouseStateLastFrame.Y < rect.Bottom;   // Highlight happened?   if (ret &&     lastRet == false)     Sound.Play(Sound.Sounds.Highlight);   return ret; #endif } // MouseInBox(rect) 

As an example you can use the code to allow selecting your main menu elements like in Figure 10-2. As I said before try to keep it simple. A menu should just show all important parts of the game. The background texture should also be a nice picture from your game - some artwork or custom-made from your skilled graphics artist (if you have one).

image from book
Figure 10-2

To get this to work you would use some code similar to the following code snippet:

  //Render background game.RenderMenuBackground(); //Show all buttons int buttonNum =0; MenuButton [] menuButtons ==new MenuButton []   {     MenuButton.Missions,     MenuButton.Highscore,     MenuButton.Credits,     MenuButton.Exit,     MenuButton.Back,   }; foreach (MenuButton button in menuButtons)   // Don’t render the back button   if (button !=MenuButton.Back)   {     if (game.RenderMenuButton(button,buttonLocations [buttonNum ]))     {       if (button ==MenuButton.Missions)         game.AddGameScreen(new Mission());       else if (button ==MenuButton.Highscore)         game.AddGameScreen(new Highscores());       else if (button ==MenuButton.Credits)         game.AddGameScreen(new Credits());       else if (button ==MenuButton.Exit)         quit =true;       }//if       buttonNum++;       if (buttonNum >=buttonLocations.Length)         break;       }//if    //Hotkeys,M=Mission,H=Highscores,C=Credits,Esc=Quit    if (Input.KeyboardKeyJustPressed(Keys.M))      game.AddGameScreen(new Mission());    else if (Input.KeyboardKeyJustPressed(Keys.H))      game.AddGameScreen(new Highscores());    else if (Input.KeyboardKeyJustPressed(Keys.C))      game.AddGameScreen(new Credits());    else if (Input.KeyboardEscapeJustPressed)      quit = true;    //If pressing XBox controller up/down change selection    if (Input.GamePadDownJustPressed)    {      xInputMenuSelection =        (xInputMenuSelection + 1) % buttonLocations.Length;      SelectMenuItemForXInput();    }//if (Input.GamePad)    else if (Input.GamePadUpJustPressed)    {      if (xInputMenuSelection <= 0)        xInputMenuSelection = buttonLocations.Length;      xInputMenuSelection -;      SelectMenuItemForXInput();    }//if (Input.GamePad)  

If you have more controls like in your options screen to enable or disable checkboxes, handle slide bars, or edit boxes, the code will get a little bit more complicated and before you have to copy and paste the same code over and over again you should introduce a UI class to handle all the UI code for you. If you just have to use an edit box once, you could also write the code directly into the Options class like I did for Rocket Commander XNA. But the code should be refactored if you use it multiple times. Handling text boxes is also a little bit more complicated in XNA because you have to implement your own keyboard input methods for text.

Entering Text in XNA

You might also have noticed that there is no keyboardStateLastFrame variable in the Input class, because unlike the MouseState and GamePadState you cannot use the KeyboardState struct again the next frame; the data is no longer valid. The reason this is happening is because the KeyboardState struct uses an internal list for all the keys, which is used in the next frame again and overwritten to all the new keyboard key states when you call Keyboard.GetState. To fix this issue you just keep your own list of keys, which is cloned every time you assign it from the last frame keys. Using the constructor of the generic List class will automatically clone all elements for you.

Using this list works almost the same way as using the keyboard state directly. As an example, take a look at the KeyboardSpaceJustPressed property of the Input class:

  /// <summary> /// Keyboard space just pressed? /// </summary> /// <returns>Bool</returns> public static bool KeyboardSpaceJustPressed {   get   {     return keyboardState.IsKeyDown(Keys.Space) &&       keysPressedLastFrame.Contains(Keys.Space) == false;   } // get } // KeyboardSpaceJustPressed 

The rest of the Input class is very similar to all the code you already saw except for the keyboard input text methods I mentioned a minute ago. To get the keyboard input for text boxes the same way as you would with a normal Windows text box, the following unit test was written:

  /// <summary> /// Test keyboard chat input /// </summary> public static void TestKeyboardChatInput() {   // Better version with help of Input.HandleKeyboardInput!   string chatText = "";   TestGame.Start("TestKeyboardChatInput",     null,     delegate     {       TextureFont.WriteText(100, 100,         "Your chat text: " + chatText +         // Add blinking |         ((int)(BaseGame.TotalTime / 0.35f) % 2 == 0 ? "|" : ""));       Input.HandleKeyboardInput(ref chatText);     },     null); } // TestKeyboardChatInput() 

The unit test just displays the chatText string and a blinking | sign behind it. Then the HandleKeyboardInput method is used to allow the user to type in new chat text. Take a look at the HandleKeyboardInput method:

  /// <<summary> /// Handle keyboard input helper method to catch keyboard input /// for an input text. Only used to enter the player name in the /// game. /// </summary> /// <param name="inputText">Input text</param> public static void HandleKeyboardInput(ref string inputText) {   // Is a shift key pressed (we have to check both, left and right)   bool isShiftPressed =     keyboardState.IsKeyDown(Keys.LeftShift) ||     keyboardState.IsKeyDown(Keys.RightShift);   // Go through all pressed keys   foreach (Keys pressedKey in keyboardState.GetPressedKeys())     // Only process if it was not pressed last frame     if (keysPressedLastFrame.Contains(pressedKey) == false)     {       // No special key?       if (IsSpecialKey(pressedKey) == false &&         // Max. allow 32 chars         inputText.Length < 32)       {         // Then add the letter to our inputText.         // Check also the shift state!         inputText += KeyToChar(pressedKey, isShiftPressed);       } // if (IsSpecialKey)       else if (pressedKey == Keys.Back &&         inputText.Length > 0)       {         // Remove 1 character at end         inputText = inputText.Substring(0, inputText.Length - 1);       } // else if     } // foreach if (WasKeyPressedLastFrame) } // HandleKeyboardInput(inputText) 

The method uses two other helper methods in the Input class. First, IsSpecialKey is used to see if the key can be used for the chat text or if it was just an F1, cursor, Delete, Enter, and so on key, which will not be added directly to the chat text. Then you handle the special case for the Backspace key. You could also extend the code to handle the Enter or Delete keys in your text boxes if you need that in your game.

The next problem is that you can’t just add keys to your input text; the first problem is that the A key would add A to the input text string regardless of whether the user pressed the Shift key. Then there are special keys like +, -, {, and so on, which would show up as “Plus”, “Minus”, “OemOpenBrackets” in your input text. To help you out with that it would be nice if XNA would provide you with the real key meaning, but it does not; you have to do it yourself with the help of the KeyToChar helper method in the Input class:

  /// <summary> /// Keys to char helper conversion method. /// Note: If the keys are mapped other than on a default QWERTY /// keyboard, this method will not work properly. Most keyboards /// will return the same for A-Z and 0-9, but the special keys /// might be different. Sorry, no easy way to fix this with XNA ... /// For a game with chat (windows) you should implement the /// Windows events for catching keyboard input, which are much better! /// </summary> /// <param name="key">Keys</param> /// <returns>Char</returns> public static char KeyToChar(Keys key, bool shiftPressed) {   // If key will not be found, just return space   char ret = ' ';   int keyNum = (int)key;   if (keyNum >= (int)Keys.A && keyNum <= (int)Keys.Z)   {     if (shiftPressed)       ret = key.ToString()[0];     else       ret = key.ToString().ToLower()[0];   } // if (keyNum)   else if (keyNum >= (int)Keys.D0 && keyNum <= (int)Keys.D9 &&     shiftPressed == false)   {     ret = (char)((int)'0' + (keyNum - Keys.D0));   } // else if   else if (key == Keys.D1 && shiftPressed)     ret = '!';   else if (key == Keys.D2 && shiftPressed)     ret = '@'; [etc. about 20 more special key checks]   // Return result   return ret; } // KeyToChar(key) 

With that method the HandleKeyboardInput method works now the way you expect it to and your unit test can be started and tested. There are even more unit tests in the Input class; check them out to learn more about the available properties and methods.




Professional XNA Game Programming
Professional XNA Programming: Building Games for Xbox 360 and Windows with XNA Game Studio 2.0
ISBN: 0470261285
EAN: 2147483647
Year: 2007
Pages: 138

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net