Time for Input!
Now that we have learned how to render basic 2D textures and can actually start making a game, we probably should talk about how Input works in a game. Unless your game is one where the player has to sit there and just stare at it (at which point you’re probably attempting a hypnosis game for some disturbing reason… do let me know if you got a working version though), you want to have them actually control something in it. So how does Input work in XNA Game Framework? Turns out that it’s pretty simple actually. Let’s talk Input!
Overview of how Input works
Input in XNA Framework is quite simple to use you’ll be pleasantly surprised. You can read input data from a keyboard, mouse and variety of supported game pads (Xbox 360 controllers, Guitar Hero controller, Rock Band drums, etc).
Where in my code do I read and process input? Simple: In the Update method of your Game class! To read input, you will be using one of the following 3 classes based on the device you want to read from:
- Keyboard Class: For keyboard input
- Mouse Class: For reading the Mouse input
- GamePad Class: For reading from a varitey of supported gamepads and controllers.
The way it works is that every one of these classes has a GetState() static method that you call. This method returns either a KeyboardState, MouseState or GamePadState object back to you. This object has data for the state of the input device when the GetState() method was called. So for instance, the MouseState object will have information such as mouse cursor location, the state of all the buttons on the mouse (pressed or released) and the scroll wheel value. You simply read the values and act on them.
Here’s a thought for you though. The call to the GetState() method returns a snapshot of the state of the device at the time you made the call. Since you call this in the Update() method of the game class, it gets called 60 times a second. So if for instance you are writing a shooting game and will have the player use the mouse button to shoot, you might check the MouseState.LeftButton property to see if the button is pressed or released. If it’s pressed, you will have the game shoot. The nasty surprise you’ll quickly run into though is that the game will fire at an alarming rate vs just one shot per mouse click! Why? Because you are checking on the Pressed button state in the Update loop that gets called 60 times per second. Since most players can’t click and release the button in 1/60th of a second, they probably will have it held done for longer and all those will come through as Pressed giving the wrong result of multiple shots until the button is released.
So how do you deal with that? The answer is quite simple. Let me show you via some code. Let’s say we want to check if the left mouse button was pressed or not. The code would be as follows:
First we declare a member variable in our class of type MouseState (line 5):
1: public class Game1 : Microsoft.Xna.Framework.Game
2: {
3: GraphicsDeviceManager graphics;
4: SpriteBatch spriteBatch;
5: MouseState previousMouseState = Mouse.GetState();
We initialize the variable by calling Mouse.GetState() like in the code. This grabs the state of the mouse when the game is about to start.
When we read the state of the mouse in the Update code, we want to register a mouse click when the CURRENT state of the left mouse button is Released and PREVIOUS state for it was Pressed. This way we will know that the button was pressed and just got released at the time we called GetState(). Then at the end of the Update method, we save the Current MouseState we just read as the PreviousMouseState so that we can use it next time Update is called. Here, check out the code, it will become very clear:
1: protected override void Update(GameTime gameTime)
2: {
3: // Grab the current state of the mouse
4: MouseState CurrentMouseState = Mouse.GetState();
5:
6: // Now we see if the left mouse button was clicked or not
7: if (PreviousMouseState.LeftButton == ButtonState.Pressed
8: &&
9: CurrentMouseState.LeftButton == ButtonState.Released)
10: {
11: // Left mouse button was clicked!
12: // Do something with it!
13: }
14:
15: // Record the CurrentMouseState
16: // as PreviousMouseState
17: PreviousMouseState = CurrentMouseState;
18:
19: base.Update(gameTime);
20: }
Let’s dissect the code:
- Line 4: We get the current state of the mouse and save it.
- Lines 7-9: We check to see if last time we called update the button was pressed and now it is released. This means we got a mouse button click!
- Lines 11,12: Do whatever the game is supposed to do in response to a button press (shoot something)
- Line 17: Save the CurrentMouseState in PreviousMouseState so that you can use it next time Update is called.
Simple eh? The same method applies to Keyboard and GamePad classes as well. Just save the previous state and compare it with the current state. This way you can get a clear reading of when a player has pressed a button or not. Some game play actions of course require the player to keep a button pressed for instance, these don’t need such a method, you just check if the button is pressed or not and do whatever action in response as long as the button remains pressed.
Just for completness sake (and to make this post look bigger and more important that it really is), here’s some code for how this works for both a Keyboard and a GamePad.
Keyboard
Here’s the code to check for the Left arrow key on the keyboard, I removed all non-essential parts:
1: public class Game1 : Microsoft.Xna.Framework.Game
2: {
3: GraphicsDeviceManager graphics;
4: SpriteBatch spriteBatch;
5: KeyboardState previousKS = Keyboard.GetState();
6:
7: protected override void Update(GameTime gameTime)
8: {
9: // Grab the current state of the mouse
10: KeyboardState CurrentKS = Keyboard.GetState();
11:
12: // Now we see if the left mouse button was clicked or not
13: if (previousKS.IsKeyDown(Keys.Left)
14: &&
15: CurrentKS.IsKeyUp(Keys.Left))
16: {
17: // Left arrow was pressed!
18: // Do something with it!
19: }
20:
21: // Record the CurrentMouseState
22: // as PreviousMouseState
23: previousKS = CurrentKS;
24:
25: base.Update(gameTime);
26: }
27: }
GamePad
Now let’s see how this works for a GamePad like the Xbox 360 controller:
1: public class Game1 : Microsoft.Xna.Framework.Game
2: {
3: GraphicsDeviceManager graphics;
4: SpriteBatch spriteBatch;
5: GamePadState previousGS = GamePad.GetState(PlayerIndex.One);
6:
7: protected override void Update(GameTime gameTime)
8: {
9: // Grab the current state of the mouse
10: GamePadState CurrentGS = GamePad.GetState(PlayerIndex.One);
11:
12: // Now we see if the left mouse button was clicked or not
13: if (previousGS.Buttons.A == ButtonState.Pressed
14: &&
15: CurrentGS.Buttons.A == ButtonState.Released)
16: {
17: // A button was pressed!
18: // Do something with it!
19: }
20:
21: // Record the CurrentMouseState
22: // as PreviousMouseState
23: previousGS = CurrentGS;
24:
25: base.Update(gameTime);
26: }
27: }
Check out Line 5, do you notice the argument to GamePad.GetState? That’s the PlayerIndex enumeration value you need to specify. Since an Xbox can have up to 4 controllers plugged in, you need to specify which one you want to read from. In this case, we specified PlayerIndex.One meaning controller #1. To read input from multiple controllers, you need to declare PreviousGameState variables for each PlayerIndex you want. Just do the same thing for more than one controller like you would for one.
Final thoughts
One of the things you might want to do is write an input handling class for your game. Something to encapsulate all the input handling code for your game. You can go totally wild with how you implement it. You can make it event driven so code can subscribe to certain events such as “AButtonPressed”, etc. Be creative! These classes will live with you for a long time ;)
Thanks for reading! I'd love to hear your thoughts, feel free to leave a comment below. Don't forget to subscribe to my RSS Feed!
December 19th, 2007 at 3:01 pm
Hi Nazeeh
Thanks for this article. I’ve been developing a little XNA game engine myself for learning purposes, and it’s helpful to get a good grasp on the basics.
p.s. could you modify your RSS feed so that it shows the whole thing? I hate having to visit the site just to read the whole bit; I’d much prefer to read your whole blog post inside the RSS reader.
December 19th, 2007 at 4:43 pm
Thanks Judah :)
I am not sure if I can change the way the RSS feeder works though. I’ll look into it though. The reason you see it the way you do is that I insert a “More…” link after the first paragraph usually so that my front page doesn’t display the entire post.
Good luck with your engine!! Exciting stuff eh?
December 24th, 2007 at 11:06 pm
Good news Judah!
I just fixed the RSS issue you mentioned. My RSS feed will now show the full contents of every post vs partial. My front page will still display a partial text to make it less cluttered. Works very well now :)
Thanks for pointing this out again.
Nazeeh
February 28th, 2008 at 7:01 pm
Great site nazeeh, I love your tutorials.
I have a question. I’m new to this XNA stuff. When the mouse click is detected in the update method, how would I go about displaying a small graphic on screen for a moment? I tried using a bool that I set to true if clicked and false if it wasn’t and then in the draw method I check this before drawing the sprite. however, nothing appears. I’m assuming it’s because it happens so fast right? 1/60th of a second. Any help would be great :)
Keep up the awesome work!!
February 29th, 2008 at 11:23 am
Thanks Michael :)
The easiest way would be to check for the mouse button down and as long as it’s down, you set the bool to true and hence draw the graphic. When the mouse button is not down, you set it to false and not draw the graphic. This way as long as you have the button down, the graphic will be there!
February 29th, 2008 at 8:11 pm
Ah, right that would work. Thanks! :)
When can we see more tutorials?
March 1st, 2008 at 12:49 pm
Yeah… I am working on them now actually. I’ve been sidetracked by some stuff at work but really, I should get moving on them regardless. Sorry about that :)
March 4th, 2008 at 10:24 pm
No problem. I know how hard it is to stay focused and find time for a side project when work is busy. Anyways, looking forward to the new stuff.