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 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:
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.
Texture2D mouseTexture; double mouseX, mouseY;
Load<Texture2D> statement to LoadContent() to load
the new image texture into mouseTexturespriteBatch.Draw() statement to Draw() which will draw the new
image at the position given by mouseX and mouseYUpdate() 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.
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.
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)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:
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() methodWhat 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...
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!
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
Update() method, remove any code which changes the dart sprite's position, angle, etc.
Don't modify the Draw() method.Update() method, just below the code which handles the balloon, put in the following
statement:
if (kbState.IsKeyDown(Keys.Left)) dartX--;
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;
Update() method, just below the part where you move the dart in response to the arrow keys, put in the line given above.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:
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;
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.
Draw() methodThe 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:
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!
Update() methodif statement you previously added to check if this value is less
than 10000 for the rotation calculation to go ahead.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:
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).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
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.
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);
When you've finished all the steps above, you need to close down Game Studio Express as follows: