Subscribe

RSS Feed (xml)

Powered By

Powered by Blogger

Google
 
xnahelp.blogspot.com

Kamis, 03 April 2008

Working with keyboard Devices

We will be populating the stub we created for handling our input. We are going to create
a stationary camera and a first person. Both of these cameras will need to respond to user
input. We need to learn how to handle input devices and utilize them to move our
camera and fully see the worlds we create.
Keyboard
The first input device we will talk about is the keyboard. XNA provides us with helper
classes that allow us to easily determine the state of our input devices. We determine the
state of our keyboard by declaring a variable to store our keyboard state and then calling
the GetState method on the Keyboard helper object. We can then query that variable to
determine which key (or keys) is being pressed or released. We can start by adding a
private member field to store our keyboard state. We do this via the following code inside
of our InputHandler game component:
private KeyboardState keyboardState;
Then we can find the Update method that was stubbed out for us when the InputHandler
game component was created.
keyboardState = Keyboard.GetState();
if (keyboardState.IsKeyDown(Keys.Escape))
Game.Exit();
We can put this at the very beginning of the Update method. We are simply storing the
state of the keyboard and then checking to see if the Escape key was pressed. If so, we
simply exit the program. It doesn’t get much simpler than that! So now when we run the
program we can exit by pressing the Escape key (instead of having to close the window
with our mouse).
Although being able to exit our game easily is important, we still haven’t done anything
exciting. Let’s set up our camera so we swivel back and forth if the left or right keys are
pressed. To do this we need to add and initialize a new private member field called
cameraReference inside of our Camera game component:
private Vector3 cameraReference = new Vector3(0.0f, 0.0f, -1.0f);
This camera reference direction will not change throughout the game, but we will be
passing it in as a reference so we cannot declare it as a readonly variable. Typically this
value will either be (0,0,1) or (0,0,-1). We chose to have a negative z value so we can
continue to have our camera face the same way.
Now that we have our camera reference direction set up, we need to apply any movement
and rotation to our view of the world. So if the player wants to look left and right we can
adjust our view matrix accordingly so that our view of the world is from a certain angle.
To look left and right we need to rotate around our y axis. We can add the following code
to our Update method right before our call out to the base object’s Update method (we are
still in our Camera class):
Working with Input Devices 91
5
Matrix rotationMatrix;
Matrix.CreateRotationY(MathHelper.ToRadians(cameraYaw), out rotationMatrix);
// Create a vector pointing the direction the camera is facing.
Vector3 transformedReference;
Vector3.Transform(ref cameraReference, ref rotationMatrix,
out transformedReference);
// Calculate the position the camera is looking at.
Vector3.Add(ref cameraPosition, ref transformedReference, out cameraTarget);
Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector,
out view);
Because we know we will be rotating, we will create a matrix to hold our rotation. With a
helper function from XNA we will create a matrix with the appropriate values to rotate
our camera on the y axis by a certain number of degrees. We are storing that value in a
variable that we will set with our input devices. We will see how we set that value with
our keyboard soon. Once we have our matrix with all of the translations, scaling, and
rotations that we need (in this case we are only rotating) we can create a transform vector
that will allow us to change the target of the camera. We get this transformed vector by
using another XNA helper method, Vector3.Transform. We then add the transformed
camera reference to our camera position, which will give us our new camera target. To do
this, we could have used the plus (+) operator like the following code:
cameraTarget = cameraPosition + transformedReference;
However, it is much more efficient to use the built-in static Add method of the Vector3
struct because it allows us to pass our vectors by reference instead of having to copy the
values to memory. Finally, we reset our view with our new camera target. We also need to
set the following private member field that we used in the code:
private float cameraYaw = 0.0f;
Now we are to the point where we can compile the code, but our newly added code does
not do anything for us. This is because we are never changing our rotation angle. It stays
at 0 on every frame. Let’s change that now with our keyboard. Inside of our InputHandler
class, we need to update our IInputHandler interface to expose the keyboard state we
retrieved earlier. Let’s add the following code inside of our interface:
KeyboardState KeyboardState { get; }
Now, we need to implement that property inside of the class. An easy way to do this is to
right-click IInputHandler where we derived from it and select Implement Interface. We
will be doing this a couple of times and each time it will create a region so we will have
to clean up the code and remove the extra regions the IDE provides for us. Now we need
to change the get value to retrieve our internal keyboardState value as follows:
92 CHAPTER 5 Input Devices and Cameras
public KeyboardState KeyboardState
{
get { return(keyboardState); }
}
Now that we have exposed our keyboard state we can utilize it inside of our Camera
object. We need to add the following code to our Update method right above the previous
code inside of our camera object:
if (input.KeyboardState.IsKeyDown(Keys.Left))
cameraYaw += spinRate;
if (input.KeyboardState.IsKeyDown(Keys.Right))
cameraYaw -= spinRate;
if (cameraYaw > 360)
cameraYaw -= 360;
else if (cameraYaw < 0)
cameraYaw += 360;
We also need to make sure camera is using the XNA Framework’s Input namespace
because we are accessing the Keys enumeration:
using Microsoft.Xna.Framework.Input;
Finally, we add this constant to the top of our camera class:
private const float spinRate = 2.0f;
In our update code we utilized the current keyboard state we already captured and
checked to see if either the left arrow or right arrow on the keyboard was pressed. If the
player wants to rotate to the left then we add our spin rate constant to our current
camera yaw angle. (Yaw, pitch, and roll are terms borrowed from flight dynamics. Because
we are using a right-handed coordinate system, this means that yaw is rotation around
the y axis, pitch is rotation around the x axis, and roll is rotation around the z axis.) If
the player wants to rotate to the right then we subtract our spin rate constant from our
current camera yaw angle. Finally we just check to make sure that we do not have an
invalid rotation angle.
There is one last thing we need to do before we can run our code again. We need to actually
add our input handler game component to our game’s collection of components. We
can declare our member field as follows:
private InputHandler input;
Now we can actually initialize that variable and add it to the collection inside of our
constructor with this code:
input = new InputHandler(this);
Components.Add(input);
Working with Input Devices 93
5
We can compile and run the code and move knowing that our left and right arrows rotate
the camera. When running we can see that our objects are just flying by. It almost seems
they are blinking we are turning so fast. This is because we are calling our Update statement
as fast as possible. We can modify the game code where we are initializing our fps
variable to use a fixed time step:
fps = new FPS(this, false, true);
For the preceding code to work we need to add another constructor to our FPS code. We
are doing this is so we don’t need to actually pass in our target elapsed time value if we
want it to be the default.
public FPS(Game game, bool synchWithVerticalRetrace, bool isFixedTimeStep)
: this(game, synchWithVerticalRetrace, isFixedTimeStep,
game.TargetElapsedTime) { }
Now when we run the code we can see the objects move by at a consistent and slower
rate. This is because the Update method is now only getting called 60 times a second
instead of whatever rate our machine was running at.
We will notice, however, that as it renders the rectangles that our screen is “choppy.” The
reason is that we are not letting XNA only draw during our monitor’s vertical refresh. We
could do this by setting our second parameter to true and we would see the screen rotate
at a nice even pace with the screen drawing nicely. However, a better way to handle this
is by utilizing the elapsed time between calls to our methods. We need to retrieve the
elapsed time since the last time our Update method was called and then multiply our
spinRate by this delta of the time between calls. We will change the camera code snippet
to match the following:
float timeDelta = (float)gameTime.ElapsedGameTime.TotalSeconds;
if (input.KeyboardState.IsKeyDown(Keys.Left))
cameraYaw += (spinRate * timeDelta);
if (input.KeyboardState.IsKeyDown(Keys.Right))
cameraYaw -= (spinRate * timeDelta);
We can modify our game code to call the default constructor again:
fps = new FPS(this);
Now we are just creeping along. This is because our spin rate is so low. We had it low
because we were relying on the update method to be called 60 times per frame, so we
were basically rotating our camera 120 degrees per second. To get the same effect we
simply set our spinRate to 120. The reason is we are now multiplying it by the time
difference between calls. At this point we can safely set our units and know they will be
used on a per-second basis. Now that we have our spinRate utilizing the delta of the time
between calls we are safe to run at any frame rate and have our objects drawn correctly
based on the amount of time that has elapsed.
94 CHAPTER 5 Input Devices and Cameras
We can have it always run at 60 fps in release mode and run as fast as possible in debug
mode by modifying our game code as follows:
#if DEBUG
fps = new FPS(this);
#else
fps = new FPS(this, true, false);
#endif
This allows us to run as fast as we can in debug mode while consistently moving our
objects no matter the frame rate. It allows us to force XNA to only update the screen
during the monitor’s vertical retrace, which would drop us to 60 fps or whatever rate the
monitor is refreshing at.
Making a game update itself consistently regardless of the frame rate can make the game
more complex. We need to calculate the elapsed time (time delta) since the last frame and
use that value in all of our calculations. With the fixed step mode that XNA provides we
could cut down on development time and rely on the fact that the update code will be
called 60 times a second. This is not sufficient if we are writing a library that can be
plugged into games, as those games might not run at a consistent frame rate.

1 komentar:

Blogger mengatakan...

Are you trying to make cash from your websites or blogs by using popunder advertisments?
In case you are, have you ever used PropellerAds?