On the PC, numerous devices
can be used for input in applications. The most common types of input on
PCs are the keyboard and mouse because they are standard when buying or
building a PC. When developing games for the PC, developers often focus
on these two controller types, while additional controllers such as
game pads, steering wheels, and so on are often optional means of input
that the users can use if they own and connect such a device to their
machines.
There are three main ways
to detect input in a Win32 application. You can use the message pump of
the application, you can obtain the state of the device using various
Win32 functions, or you can use an API such as XInput, DirectInput, and
so forth.
DirectInput
DirectInput is an API that
is part of the DirectX SDK that is used to detect input from various
devices including the mouse, keyboard, game controllers (e.g., steering
wheels, game pads, etc.), and force-feedback devices. DirectInput has
traditionally been very beneficial to Windows-based game developers. It
has the following benefits that should be taken into consideration.
DirectInput talks directly with the hardware.
Any input device can work with DirectInput without knowledge of the specific device being used.
DirectInput allows for a wide range of devices to be used in an application, each with different features.
Outside
of using the extended features and services of a device (such as
force-feedback), it is fairly simple to use the API to detect input from
devices, although the process is more involved than it would be for the
XInput API.
One of the most
important features of a game’s input system is speed. DirectInput allows
developers to talk directly to the hardware, which allows for fast
input-state acquiring. Whenever developers make a game, input, although
seemingly minor to the inexperienced, is extremely important to get
right. Unresponsive input can really hurt player’s experience of a game.
Speed is one of the most critical aspects of many different parts of a
game, including input.
The
major benefit of using DirectInput, aside from its speed, is that
DirectInput is an API that allows developers to take advantage of an
input device’s extended features and services. Many hardware companies
make different devices, and some of these devices have features that
many other devices of the same type do not. By following the standard,
DirectInput can use devices released on the market without any change to
the API. In other words, devices released next year will still work
with DirectInput if they follow the standard set forth.
The biggest
problem with DirectInput is the difficulty accessing these extended
features and services. This includes force-feedback, additional buttons
(e.g., a five-button mouse), additional keys, and anything added to the
device that is not standard among all devices of the same type (e.g.,
displays on a keyboard). If you are using DirectInput to detect input
from a keyboard and mouse without any additions of a specific device,
then there is no benefit because, you can directly obtain the state of a keyboard and mouse device using
two Win32 functions for both the keyboard and mouse. This is a long way
from all of the setup necessary for DirectInput to do the same thing,
and since speed in detecting input exists in both cases, using
DirectInput for standard, everyday keyboards and mice has no benefit.
Also, DirectInput has
not been updated since DirectX 8.0. It might prove beneficial to use
DirectInput to access the features of a device that you cannot access or
cannot easily access otherwise, but for everything else it would be
beneficial to use the input API. The fact that there is no benefit to
using DirectInput in this case is even stated in the DirectX SDK in the
DirectInput Introduction section (“The Power of DirectInput”). This fact
most likely is one contributing factor to the API’s deprecation.
Windows Message Pump
In Win32 applications, the
programmer can use the message pump to obtain messages from the window
or from the Windows operating system. These messages include input
events such as button presses on a keyboard, button clicks on a mouse,
and so forth.
Win32 applications have a
feature known as the application loop. This loop runs endlessly until
some condition is met to cause the loop to break and the application to
quit. What this condition is depends on the specific application and the
programmers who have developed it. This condition could be as simple as
the user pressing the Esc key, clicking a menu item such as File or Exit, clicking on the Close button, and so on.
In the
application loop, as you should already know, a callback procedure is
called every time an event is passed to the application’s message pump.
The callback is a function that usually executes specific code depending
on the event that is passed to it, and it is only called by the
application, not by the user.
LRESULT CALLBACK WndProc(HWND hwnd, UINT m, WPARAM wp, LPARAM lp)
{
// Window width and height.
int width, height;
switch(m)
{
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
case WM_SIZE:
height = HIWORD(lp);
width = LOWORD(lp);
if(height == 0)
height = 1;
ResizeD3D10Window(width, height);
return 0;
break;
case WM_KEYDOWN:
switch(wp)
{
case VK_ESCAPE:
PostQuitMessage(0);
break;
default:
break;
}
break;
default:
break;
}
// Pass remaining messages to default handler.
return (DefWindowProc(hwnd, m, wp, lp));
}
The events checked for are the close (WM_CLOSE), destroy (WM_DESTROY), resize (WM_SIZE), and keyboard button presses (WM_KEYDOWN) events. As far as input is concerned, you can check for keys being pressed by checking if the event is a WM_KEYDOWN (or WM_KEYUP for released buttons) event. The WPARAM
parameter for the callback procedure stores the value of the event, so
for button presses you can check this variable for specific key presses
and respond to them accordingly. In the demos the callback procedure
checks for the Esc key (VK_ESCAPE), and when this key is pressed, the demos quit their execution.
There are 256 keys on the
standard keyboard, and a list of all of the virtual key codes for each
button can be found in the MSDN documentation at http://msdn.microsoft.com/en-us/default.aspx. The constants are also defined in windows.h.
Events are passed by
the operating system to the application. This is a problem in video
games because the time it takes for the OS to notify the application of
an event can have a serious impact on input detection and response time.
In addition, there is no guarantee of how long it will take the OS to
notify the application of an event, so input might not be as fast as
real-time applications require. Therefore, it is not recommended that
you use the callback procedure for input in your video game
applications.
Obtaining Key States in Win32
You
can obtain the states of buttons and keys on the keyboard and mouse by
using various Win32 functions that will be discussed briefly in this
section. You can use DirectInput, but this requires more work to set up,
which might not be necessary if you just want standard button and key
presses.
The first function we will examine is GetAsyncKeyState().
This function takes as a parameter the virtual key code of the keyboard
key or mouse button that you want to test for being pressed. This
function tests if the key or button is down at the time the function was
called and tests if the key was pressed during a previous call to the
function. The value returned by this function is a short integer. If the
most significant bit is set, then the key is down; if the least
significant bit is set, then the key is up. The function prototype for
the GetAsyncKeyState() function is as follows.
SHORT GetAsyncKeyState(int vKey);
An example of testing if the up arrow key is pressed by testing the most significant bit is as follows.
if(GetAsyncKeyState(VK_UP) & 0×80) /* */
An alternative to the GetAsyncKeyState() function is the GetKeyState() function, which does the same thing, with the exception that the GetKeyState()
function returns the key or button information and status that does not
reflect the interrupt-level state associated with the hardware.
Another function that can be used to get the state of keyboard keys is the GetKeyboardState()
function. This function takes an address as a parameter to a
256-element array that will store the state of all keyboard keys. If the
function succeeds in the gathering this keyboard information, the GetKeyboardState() function returns true; if not, it returns false. The function prototype for this function is as follows.
BOOL GetKeyboardState(PBYTE lpKeyState);
For the mouse, it is possible to obtain the mouse’s position by calling the GetCursorPos() function, which takes as a parameter the address to the POINT object that will store the X and Y position of the cursor and returns true or false for whether or not the function succeeded. The function prototype for the GetCursorPos() function is as follows.
BOOL GetCursorPos(LPPOINT lpPoint);
The
GetKeyState()
and
GetAsyncKeyState()
functions can be used to return the key state of keyboard keys and mouse buttons. |