DirectInputIntroductionIn this part of the tutorial, we will cover:
Initialising DirectInputAdd DINPUT.H to your header file includes: #include <dinput.h>
and add DINPUT.LIB to your project. If you're using DirectX 8, you may need to add the line #define DINPUT_VERSION 0x0700We will need a new function to initialise DirectInput. This function will first need to call DirectInputCreateEx to create an IDirectInput7 object. void InitDirectInput() { DirectInputCreateEx(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput7, (void **)&pDI, NULL); We now have an object that we can use to access the various input devices in the system. We could enumerate all the devices in the system and use them as necessary; however, we know (or at least are going to assume) there is a keyboard present, so we will just go ahead and create a keyboard device object. pDI->CreateDeviceEx(GUID_SysKeyboard, IID_IDirectInputDevice7,
(void **)&pKeyboard, NULL);
Now, we want to tell the device what format we expect data to be in. DirectInput defines a standard data format for the keyboard, so we will use that. pKeyboard->SetDataFormat(&c_dfDIKeyboard); Next, we must again set the co-operative level of the application. The co-operative level defines how much access we need to the keyboard hardware. We want non-exclusive, foreground access to the keyboard: pKeyboard->SetCooperativeLevel(hWndMain, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); Finally, we want to get this device for our own. Unlike DirectDraw, when we had exclusive access as soon as we called SetCooperativeLevel, we need to call Acquire: pKeyboard->Acquire(); We're done. We now have a pointer to the keyboard object, which we can use to get its state at any time. All we need is a cleanup function: void ExitDirectInput()
{
pKeyboard->Unacquire();
pKeyboard->Release();
pDI->Release();
pDI = NULL;
}
You can download this here: DirectInputBones.cpp Using the KeyboardNow we have a keyboard device object, we can get the state of the keyboard: BYTE state[256];
pKeyboard->GetDeviceState(sizeof(state), &state);
Each of the elements of the buffer 'state' corresponds to a key on the keyboard. They do not correspond to the ASCII codes of the character. If you hold down Shift and press Q, you will find that the state array tells you the Shift key is down and the Q key is down - not that you have an uppercase 'Q'. This is not so great for typing, but absolutely wonderful for a game. For now, we will simply use the up and down arrow keys to drive one of our sprites around: static int y = 0; if (state[DIK_UP] & 0x80) y--; if (state[DIK_DOWN] & 0x80) y++; r.left = 128; r.top = 0; r.right = 192; r.bottom = 64; lpBackBuffer->BltFast(288, 208 + y, lpSprites, &r, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); That was easy, wasn't it? You may notice that if you hold down lots of keys at once, it will only notice about 3 of them. This is a limitation of the keyboard hardware, not DirectInput, and not a fault in your program. Download the keyboard-driven sprite program: DirectInputKeys.cpp Using the MouseWe've already seen the use of the mouse outside of DirectInput, using GetCursorPos and drawing a mouse cursor. However, that's not a terribly useful method if you're going to use a mouse to move or look around rather than as a pointer. Ultima IX is a case in point. This game achieves this by moving the mouse cursor back to the centre of the screen every time you move it. It measures how much you're going to move by the distance you moved it from the centre before it moves it back. It's a tricky, unreliable and jerky method, and it's not what we're going to use. A far better method would be to get the mouse data at a lower level. After all, the mouse doesn't know where you are on the screen - it only tells the computer 'I've moved an inch to the left'. DirectInput thus gives us a method to get this relative motion data from the mouse. We will replace the movement of our mouse cursor with a mouse-driven sprite. First, we need a mouse object: pDI->CreateDeviceEx(GUID_SysMouse, IID_IDirectInputDevice7, (void **)&pMouse, NULL); pMouse->SetDataFormat(&c_dfDIMouse2); // Set the cooperative level pMouse->SetCooperativeLevel(hWndMain, DISCL_FOREGROUND | DISCL_EXCLUSIVE); pMouse->Acquire(); This time, we're specifying an exclusive co-operative level so that the Windows cursor disappears. We can now do GetDeviceState, as before, to get the movement of the mouse since the last call: DIMOUSESTATE2 mouse;
pMouse->GetDeviceState(sizeof(mouse), &mouse);
We will move the sprite according to the movement of the mouse: static int xMouse = 0, yMouse = 0; xMouse += mouse.lX; yMouse += mouse.lY; r.left = 64; r.right = 128; lpBackBuffer->BltFast(xMouse + 400, yMouse + 200, lpSprites, &r, DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); So what? Nothing we couldn't have done before - except we can now alter the sensitivity of the mouse: xMouse += mouse.lX / 4; yMouse += mouse.lY / 4; The sprite will now move at a quarter of the speed. We're not limited now to the edges of the screen - we can move our sprite off the edge of the screen and it will disappear, but it will keep going. DirectInput has freed our mouse cursor from the limits of the screen. Get your extra-sluggish sprite here: DirectInputMouse.cpp. Using the JoystickNow we've successfully used the keyboard and mouse, let's turn our attention to the most problematic of input devices: the joystick. If you don't have a joystick connected, you can skip this section for now. The trouble with joysticks is simply that they come in so many forms: gamepads, sticks, flight yokes, steering wheels... some have hat switches, some have 9 buttons, some 2, some have force feedback, some have rudders, throttles, pedals... the list goes on. You can also have multiple independent joysticks attached to a system. It's up to you what to do with these - you might have a split-screen mode or a team play mode where two different joysticks act at once, or you might want to make the user select a joystick and then use only that. It's bad practice to just arbitrarily select a joystick - the user might want to use his joystick for the platform game, not the gamepad. However, we have to start somewhere, so we will just pick a device for now. If you want to improve this, you could either allow all the sticks to be used at once, or allow the user to choose. We will first need to find a joystick device: DIDEVICEINSTANCE dev; pDI->EnumDevices(DIDEVTYPE_JOYSTICK, DIDevCallback, &dev, DIEDFL_ATTACHEDONLY); Note that we're only looking for attached devices. We will need a function that will select which joystick we want. This example function will select the first gamepad, or the last other joystick. BOOL CALLBACK DIDevCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) { DIDEVICEINSTANCE *p = (DIDEVICEINSTANCE *)pvRef; *p = *lpddi; if (lpddi->dwDevType & DIDEVTYPEJOYSTICK_GAMEPAD) return DIENUM_STOP; return DIENUM_CONTINUE; } Now we've found our joystick device, we can create it and set co-operative levels as before. pDI->CreateDeviceEx(dev.guidInstance, IID_IDirectInputDevice7, (void **)&pStick, NULL); pStick->SetDataFormat(&c_dfDIJoystick2); // Set the cooperative level pStick->SetCooperativeLevel(hWndMain, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); One more thing remains. Although we now have the joystick object, we need to be able to set its range in each direction. This is done with SetProperty. DIPROPRANGE range; range.diph.dwSize = sizeof(DIPROPRANGE); range.diph.dwHeaderSize = sizeof(DIPROPHEADER); range.diph.dwHow = DIPH_BYOFFSET; range.diph.dwObj = DIJOFS_X; range.lMax = 1000; range.lMin = -1000; pStick->SetProperty(DIPROP_RANGE, &range.diph); range.diph.dwObj = DIJOFS_Y; pStick->SetProperty(DIPROP_RANGE, &range.diph); Joysticks are absolute devices (i.e. they have a definite position, unlike a mouse). We will therefore need to set the device to absolute mode: DIPROPDWORD dw; dw.dwData = DIPROPAXISMODE_ABS; dw.diph.dwSize = sizeof(DIPROPDWORD); dw.diph.dwHeaderSize = sizeof(DIPROPHEADER); dw.diph.dwHow = DIPH_DEVICE; dw.diph.dwObj = 0; pStick->SetProperty(DIPROP_AXISMODE, &dw.diph); Let's get some data from the stick. Not all sticks tell DirectInput when their state has changed, so we must call Poll to make sure our data is current. DIJOYSTATE2 stick; pStick->Poll(); pStick->GetDeviceState(sizeof(stick), &stick); Now, we'll add our joystick to the mouse movement: xMouse += stick.lX / 100; yMouse += stick.lY / 100; We have a joystick-controlled sprite! You can get this code here: DirectInputStick.cpp. |
Copyright © David McCabe, 1998 - 2001. All rights reserved. You will need to download and install the m-math control to display any equations on this Web site. Without this control, you will not see most of the equations. Please do not e-mail me asking why the equations do not display! |