University of the West of England, Bristol

Department of Computer Science and Creative Technologies


C# XNA Worksheet 2 - Mouse, Keyboard and Xbox 360 Controllers

C# 2010 - XNA Version 4


Learning Aims

This worksheet shows you how to use various control devices (mouse, keyboard and Xbox 360 controller) to move sprites around in XNA.

When you've completed this worksheet, you should be able to:

To get the most out of this worksheet you should do the following:


If You Get Interrupted

If you are unable to finish any of these worksheets in one go, then this is what you should do to make sure that you can resume it from the point you finished, without having to start from scratch:


Practical Steps- What You Have to Do

Before Starting

This worksheet assumes that you've already completed worksheet 1, and builds on the project that you have previously developed during the course of the worksheet. If you haven't finished worksheet 1 you should do so now.

Getting Started

  1. Start XNA Game Studio and reload the project you finished for worksheet 1

Tracking the Mouse Position

  1. Load the graphic image file "Crosshair.png" into the Content section of the Solution Explorer for the project
  2. Add the following new fields to the Game1.cs file:
      Texture2D mouseTexture;
      double mouseX, mouseY;
    
  3. Add a Load<Texture2D> statement to LoadContent() to load the new image texture into mouseTexture
  4. Add a spriteBatch.Draw() statement to Draw() which will draw the new image at the position given by mouseX and mouseY
  5. In the Update() method, immediately after the TODO comment, but these statements:
      // Track the mouse
      MouseState mState = Mouse.GetState();
      mouseX = mState.X;
      mouseY = mState.Y;
    

    These statements read the position of the mouse and copy it to the mouseX and mouseY fields.

  6. Run the program. Note how the crosshair sprite tracks the position of the cursor.

    What happens when you move the mouse to the edge of the game window so you can see both your sprite and the system mouse cursor?
    Does the position of the sprite seem to be offset somewhat from the position of the mouse cursor?

    The offset appears because the origin point of the system cursor is set to the tip of the arrow, whereas we haven't set an origin for the crosshair sprite. To fix this problem we need to create a Vector2 mouseOrigin which contains the origin position, and then select a version of spriteBatch.Draw() which will make use of this. We did a similar thing in the previous worksheet when setting the rotation origin for the dart sprite.

  7. Examine the code for drawing the dart that you did last time. Modify the spriteBatch.Draw() for the mouse sprite so that it matches the dart sprite. Note that the origin for the mouse should be set to new Vector2(24,24)
  8. Run the program again and ensure that the crosshair and system mouse images appear to be in sync.
  9. Check that the mouse cursor lies on top of the balloon and dart. If it doesn't then reposition the draw operation for the mouse so that it is done after all the others.

Chasing the Mouse

In this section, we're going to make the balloon sprite follow the mouse, but slowly.... As the mouse moves, so the balloon slowly tries to move to its new position. We're going to add some simple code which will be along these lines:

Follow these steps:

  1. In the Update() method, remove or comment out all code from last week which has any effect on the balloon sprite. Be sure NOT to modify the code in the Draw() method
  2. Just below the section on the mouse handler which you just added today, write statements which will force the balloon to track the mouse, as outlined immediately above
  3. Test the program. Make sure that the balloon always tries to move to the mouse's position.

    What do you notice if you keep the mouse still and allow the balloon to catch it? Are the mouse sprite and balloon sprite correctly lined up when the balloon stops? What do you conclude from this? Yes - we need to set an origin for the balloon...

  4. Modify the draw code for the balloon sprite to give it an origin value of (60, 55). This isn't actually the centre position of the sprite, but it looks right!

Keyboard Control

In this section we're going to use the arrow keys on the keyboard to control the dart sprite

Just as with the mouse, we need to get the current state of the keyboard and then we can interrogate it to see which keys are currently being pressed. Note that reading text from the keyboard is really tricky in XNA!

  1. Modify the Update() method so that you read the keyboard state just below the line where you get the mouse state:
      // TODO: Add your update logic here
      MouseState mState = Mouse.GetState();
      KeyboardState kbState = Keyboard.GetState();
    

    The current state of the keyboard is stored in a local object named kbState.

    We can interrogate this object about any key like this:

      if (kbState.IsKeyDown(Keys.Xxxxxxx))   // Xxxxxxx is the name of the key to test
    

    The names of the arrow keys are Keys.Left, Keys.Right, Keys.Up and Keys.Down

  2. In the Update() method, remove any code which changes the dart sprite's position, angle, etc. Don't modify the Draw() method.
  3. In the Update() method, just below the code which handles the balloon, put in the following statement:
      if (kbState.IsKeyDown(Keys.Left)) dartX--;
    
  4. Run the program. When you press the Left arrow on the keyboard, the dart should move to the left. Of course none of the other arrow keys will do anything so eventually the dart disappears off the left hand side of the window!
  5. Add more lines of code to move the dart sprite up, down and right when the corresponding arrow keys are pressed.
  6. Run the program once again to test it

Toroidal Universe (Don't tell Einstein)

If you hold down any of the arrow keys for too long, the dart disappears off the edge. Holding the opposite key down eventually brings it back but this is not satisfactory. We're going to impose a new universal geometry on the dart! When the dart moves off the left hand side of the screen we'll bring it back in on the right, and vice versa. We'll impose a similar geometry on the top and bottom, too.

Before we can start, we need to know when the dart has gone off the edge. The top and left sides are easy: they correspond to Y=0 or X=0. The right and bottom edges are given by this.Window.ClientBounds.Width and this.Window.ClientBounds.Height. We can tell that a sprite has run off the left by testing if it's less than zero, if it is then we can reset its value to the screen width. The following statement will do this:

  if (dartX < 0) dartX = this.Window.ClientBounds.Width;

  1. In the Update() method, just below the part where you move the dart in response to the arrow keys, put in the line given above.
  2. Run the program and prove that the dart reappears on the right when it runs off the left of the screen.
  3. Put it three more lines to reposition the dart at the left when it runs off the right, at the top when it runs off the botton, and at the bottom when it runs off the top.
  4. Test that the program works as expected

Pointing in the Right Direction

A minor problem with the dart sprite is that it continues to point upwards no matter what direction it's moving. This is less of a problem for the balloon, but it just doesn't look right for the dart.

To solve this problem we need to use some mathematics. If you've fainted at this point, don't panic - I'll make it easy for you.

First of all, let's think about what we can do if we know the X and Y position we calculated in the previous call to Update(). If we know the dart's previous location then we can use it to decide the dart's rotation - the dart should always point away from the previous location:

This requires a piece of mathematical magic called an arctangent, but you don't need to worry about this. The practical upshot of this is that the dart's rotation is given by this neat piece of C#:

  dartRotation = Math.Atan2(dartX-oldX, oldY-dartY);

Don't worry if you don't understand why it works - just accept that it does!

Let's put this in the code:

  1. Within the Update() method, create two local variables (oldX and oldY) and assign the dart's current position to these variables. Make sure you do the assignments before you calculate new values for the dart's position:
      double oldX = dartX;
      double oldY = dartY;
    
  2. After you've calculated the new dart position, put the following line:
      dartRotation = Math.Atan2(dartX-oldX, oldY-dartY);
    

    Note: your names for the dart's position may not be as shown! Be sure to put in your names if appropriate.

  3. Provided you managed to get the dart rotating last week, you probably won't need to make any changes to the Draw() method
  4. Run the program and (all being well) the dart's rotation should now match its direction of movement

Problems, Problems

The dart points in the correct direction, but there are some issues. Look carefully and you should see the following problems:

Let's tackle these problems one at a time. For the first issue, the problem lies here: Math.Atan2(dartX-oldX, oldY-dartY);

When the dart stops, after one update the old and new values of the position become identical and the so the differences become zero. When the zeroes are plugged into Atan2(), it returns an answer of zero which makes the dart point up. The solution is to calculate the new rotation only when the dart is moving. An easy way to do is to ensure that at least one of the differences is not zero before recalculating the rotation:

  if (dartX != oldX || dartY != oldY) rotation = Math.Atan2(..as before..);

Once this code is in place, when the dart stops moving, it retains its previous rotation and the whole effect looks more realistic.

Let's put this into code:

  1. Put an if statement (as described above) before the rotation calculation so that the calculation only takes place when the dart is moving.

That has solved one problem, but we still get a short flicker when the dart jumps from one side of the screen to other - what's causing this?

When the dart jumps from one side of the screen to the other, the old X and Y positions should not be used to calculate the dart's direction; they will cause the dart to point in the opposite direction. Once again, we have to detect when this situation has occurred and avoid recalculating the dart's rotation. By the following update, the old and new positions are now on the same side of the screen and the problem has cured itself.

There doesn't seem to be a really elegant solution to this problem, so we'll try to find one which isn't too messy ;-)

Under normal circumstances, the old and new positions should be just a few pixels apart, but when the sprite jumps across the screen, we expect the different to increase to several hundred pixels. Let's use this to solve the problem.

If we remember Pythagoras' theorem (more Maths!) then we can form a triangle between the old and new points:

From Pythagoras, the square of the distance between the old and new points is now easily calculated:

  double distanceSq = (dartX-oldX)*(dartX-oldX) + (oldY-dartY)*(oldY-dartY);

This calculation is reasonably fast, provided we don't need the actual distance and can manage with just the square. If we needed the actual distance then we'd have to take the square root, which is computationally expensive.

When the dart jumps across the screen, the distance between the points is at least 100 pixels. Squaring this distance gives us 10000. So, if the distance squared is greater than 10000 we shouldn't calculate a new rotation; - switching around the logic, we see that we should only calculate the rotation if the distance squared is less than 10000. This can now be incorporated into the if statement we put in earlier!

  1. Add the distance squared calculation to the code in the Update() method
  2. Modify the if statement you previously added to check if this value is less than 10000 for the rotation calculation to go ahead.
  3. Test the program and make sure it works properly.

Xbox 360 Controller

You can plug an Xbox 360 controller into your computer using a USB interface. This has a plethora of buttons but for today we'll use the two small joystick controllers, which in the jargon are called Thumbsticks. You can choose to use either the left or the right thumbstick.

Each thumbstick returns two values: an X value and a Y value. We can use these directly to control the speed of a sprite.

Getting data from the Xbox controller is broadly similar to using the mouse or keyboard. At the start of the Update() method, we obtain a controller status object, from which we can extract the X,Y positions of either thumbstick.

Let's get started:

  1. Modify the Update() method to remove (or comment out) the statements where you use the current state of the keyboard to change the position of the dart sprite. Don't change any other part of the method (yet).
  2. At the beginning of the Update() method, just after the lines where you get the mouse and keyboard status, modify the method thus:
      // TODO: Add your update logic here
      MouseState mState = Mouse.GetState();
      KeyboardState kbState = Keyboard.GetState();
    
      GamePadState gpState = GamePad.GetState(PlayerIndex.One);
      GamePadThumbSticks thSticks = gpState.ThumbSticks;
      double xControl = thSticks.Right.X;
      double yControl = thSticks.Right.Y;
    

    The position of the right thumbstick is returned as a pair of floating point values (I've used double here). The values range from -1.0 to 0.0 to +1.0 depending on how far the user moves them. We can use these directly to control the sprite's position

  3. Within Update(), add the following lines to adjust the dart sprite's position. Be sure to put it just after the part where you assign values to oldX and oldY, and before the part where you calculate the sprite's rotation!
      dartX += 3.0 * xControl;
      dartY -= 3.0 * yControl;
    

    Note that the Y statement uses -= since the Y orientation of the screen and joystick are in opposite directions. The 3.0 multipliers specify the maximum speed of the sprite. It appears to move too slowly if these are left out.

  4. Test the program. This time the dart sprite should move in response to the right thumbstick. As before, it should point in the direction of movement and should jump from one side of the screen to the other when it runs off the edge.

Vibration Feedback

The Xbox 360 controller features adjustable vibration feedback. The unit has two vibration motors: the left motor which produces a low frequency and the right motor which produces a high frequency. Each motor can be separately adjusted to produce different amounts of vibration.

You can control each motor like this:

  double lowFreq = 0.5;   // Allowed range 0.0 to 1.0
  double highFreq = 0.9;  // Allowed range 0.0 to 1.0
  GamePad.SetVibration(PlayerIndex.One, (float) lowFreq, (float) highFreq);
  1. Use this information to modify your game so that the controller vibrates when the dart sprite is within 100 pixels of the edge of the game window. Use the left motor when it approaches the the left or right edge of the window, and the right motor when it approaches the top or bottom edge. For your first version, each motor should either be complete off (0.0) or running at full power (1.0).
  2. Test your program
  3. Now modify the program so that the power of each motor increases as the sprite gets closer to the edge. Once again, use the left motor for left-right edges and the right motor for the top-bottom edge. The effect should only start when the sprite is within 100 pixels of any edge.
  4. Test your program, and tweak it as necessary to make it most effective.

Tidying Up When You've Finished

When you've finished all the steps above, you need to close down Game Studio Express as follows:


Return to home page