52880.fb2 DirectX 8 Programming Tutorial - читать онлайн бесплатно полную версию книги . Страница 12

DirectX 8 Programming Tutorial - читать онлайн бесплатно полную версию книги . Страница 12

DirectX Tutorial 12: Keyboard and Mouse Input

Introduction

In this tutorial we will create a simple Earth object that the user can control. The user will be able to use the mouse to change the Earth's position. The user will also be able to change the Earth's scale and rotation by using the keyboard. You can download the full source code by clicking the "Download Source" link above.

DirectInput

So far we have been using Direct3D to draw 3D objects. Now we want to add some user interaction to our application. We can do this by using DirectInput. DirectInput allows us to get data from any input device: mouse, keyboard or joystick. We can then use this data to change elements in our scene. In this tutorial we will be looking at the mouse and keyboard only, we'll take a look at joysticks in a future tutorial.

In our application, we will need to do the following steps to add user interaction.

· Initialise DirectInput

· Setup the keyboard

· Setup the mouse

· For each frame, get the state of the keyboard and mouse and use it to modify the scene

· Once finished, clean up DirectInput

Include and Library files

First thing's first. To use DirectInput we need to add a new include file and two library files to our project. If you don't add these to your project you'll get compiler and linker errors. I've included the new header file in Game.h just below where I have included d3dx8.h. You can add the library files by going to Project > Settings… then on the Link tab, type the new library file names into the Object/Library Modules input box. The new files are listed below:

· dinput.h

· dinput8.lib

· dxguid.lib

The Controls

The controls for this tutorial will be as follows:

· Up arrow: Scale Earth up

· Down arrow: Scale Earth down

· Left arrow: Rotate Earth more to the left

· Right arrow: Rotate Earth more to the right

· Move mouse: Change Earth x and y position

· Left mouse button: Stop Earth rotation

· Right mouse button: Start Earth rotation at default speed and direction

Initialising DirectInput

The first thing we need to do is to initialise DirectInput. To do this, I have a new function called InitialiseDirectInput that is called from the main CGame::Initialise function. The code snippet below will create a DirectInput object that we will use to create further objects for user input.

//Create the DirectInput object

if (FAILED(DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDirectInput, NULL))) {

 LogError("<li>Unable to create DirectInput interface.");

 return false;

} else {

 LogInfo("<li>DirectInput interface created OK");

}

The first parameter to DirectInput8Create is the HINSTANCE of our application which has been passed through from our WinMain function. The second parameter is the DirectInput version, this will always be DIRECTINPUT_VERSION. The third parameter is the desired interface, this will always be IID_IDirectInput8. The fourth parameter is the important one, this is a pointer the DirectInput interface which we will use later. The fifth parameter, for our examples is always NULL.

m_pDirectInput is a member variable of CGame and is of type LPDIRECTINPUT8.

Setting up the keyboard

Once we have created DirectInput interface pointer, we are ready to setup the keyboard. Shown below is the code required to setup the keyboard, take a look at it and I'll explain it below.

//KEYBOARD =======================================================================

//Create the keyboard device object

if (FAILED(m_pDirectInput->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, NULL))) {

 CleanUpDirectInput();

 LogError("<li>Unable to create DirectInput keyboard device interface.");

 return false;

} else {

 LogInfo("<li>DirectInput keyboard device interface created OK.");

}

//Set the data format for the keyboard

if (FAILED(m_pKeyboard->SetDataFormat(&c_dfDIKeyboard))) {

 CleanUpDirectInput();

 LogError("<li>Unable to set the keyboard data format.");

 return false;

} else {

 LogInfo("<li>Set the keyboard data format OK.");

}

//Set the cooperative level for the keyboard

if (FAILED(m_pKeyboard->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE))) {

 CleanUpDirectInput();

 LogError("<li>Unable to set the keyboard cooperative level.");

 return false;

} else {

 LogInfo("<li>Set the keyboard cooperative level OK.");

}

//Acquire the keyboard

if (m_pKeyboard) {

 m_pKeyboard->Acquire();

}

First of all, we need to create a keyboard device. We do this by calling the CreateDevice method of DirectInput. The first parameter is the GUID (Globally Unique Identifier) of the device to create, in our case the system keyboard device, so we simply pass in the GUID_SysKeyboard predefined GUID. The next parameter will receive a pointer to the keyboard device, we have defined this variable as a member of CGame. The last parameter is always NULL, take a look in the SDK for further details.

The next thing to do is set the data format for the keyboard device. To do this we simply pass in the predefined keyboard format c_dfDIKeyboard.

Then, we need to set how our input device (the keyboard) will cooperate with our application and Windows. We will need to specify the cooperation level by selecting "foreground" or "background" and "exclusive" or "nonexclusive". To do this, we need to pass in the relevant flags (DISCL_FOREGROUND, DISCL_BACKGROUND, DISCL_EXCLUSIVE and DISCL_NONEXCLUSIVE) to the SetCooperativeLevel method.

· DISCL_FOREGROUND: Input device is available when the application is in the foreground (has focus).

· DISCL_BACKGROUND: Input device is available at all times, foreground and background.

· DISCL_EXCLUSIVE: No other instance of the device can obtain exclusive access to the device while it is acquired.

· DISCL_NONEXCLUSIVE: Access to the device does not interfere with other applications that are accessing the same device.

The final thing to do is to acquire the keyboard, this means that we can now get access to the device's data. We do this by calling the Acquire method of the device.

Setting up the mouse

As with the keyboard, we need to set up the mouse before we can get access to it. Below is the code required, take a look and you'll notice that it is very similar to the keyboard set up code above.

//MOUSE =======================================================================

//Create the mouse device object

if (FAILED(m_pDirectInput->CreateDevice(GUID_SysMouse, &m_pMouse, NULL))) {

 CleanUpDirectInput();

 LogError("<li>Unable to create DirectInput mouse device interface.");

 return false;

} else {

 LogInfo("<li>DirectInput mouse device interface created OK.");

}

//Set the data format for the mouse

if (FAILED(m_pMouse->SetDataFormat(&c_dfDIMouse))) {

 CleanUpDirectInput();

 LogError("<li>Unable to set the mouse data format.");

 return false;

} else {

 LogInfo("<li>Set the mouse data format OK.");

}

//Set the cooperative level for the mouse

if (FAILED(m_pMouse->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE))) {

 CleanUpDirectInput();

 LogError("<li>Unable to set the mouse cooperative level.");

 return false;

} else {

 LogInfo("<li>Set the mouse cooperative level OK.");

}

//Acquire the mouse

if (m_pMouse) {

 m_pMouse->Acquire();

}

So, for the mouse we have the same basic steps as for the keyboard. One of the differences is that we pass in the predefined GUID for the system mouse (GUID_SysMouse) into the CreateDevice method instead of the one for the keyboard which will return a pointer to the mouse. Another difference is that we need to use a different data format, c_dfDIMouse instead of c_dfDIKeyboard. We then set the cooperation level as before and finally acquire the mouse. We are now ready to get data from the keyboard and mouse.

Buffered and Immediate Data

Now that we have setup the mouse and keyboard, we need to get data from them. There are two forms of data that we can get, buffered and immediate data. Buffered data is a record of events that are saved (buffered) until the application requires them. Immediate data is the current state of the device. In this tutorial we will look at immediate data.

Getting keyboard data

We have a new function called ProcessKeyboard which is called from our Render function. This means that ProcessKeyboard will be called once per frame. The code for ProcessKeyboard is shown below.

void CGame::ProcessKeyboard() {

 char KeyboardState[256];

 if (FAILED(m_pKeyboard->GetDeviceState(sizeof(KeyboardState),(LPVOID)&KeyboardState))) {

  return;

 }

 if (KEYDOWN(KeyboardState, DIK_ESCAPE)) {

  //Escape key pressed. Quit game.

  m_fQuit = true;

 }

 //Rotate Earth

 if (KEYDOWN(KeyboardState, DIK_RIGHT)) {

  m_rRotate –= 0.5f;

 } else if(KEYDOWN(KeyboardState, DIK_LEFT)) {

  m_rRotate += 0.5f;

 }

 //Set an upper and lower limit for the rotation factor

 if (m_rRotate < –20.0f) {

  m_rRotate = –20.0f;

 } else if(m_rRotate > 20.0f) {

  m_rRotate = 20.0f;

 }

 //Scale Earth

 if (KEYDOWN(KeyboardState, DIK_UP)) {

  m_rScale += 0.5f;

 } else if (KEYDOWN(KeyboardState, DIK_DOWN)) {

  m_rScale –= 0.5f;

 }

 //Set an upper and lower limit for the scale factor

 if (m_rScale < 1.0f) {

  m_rScale = 1.0f;

 } else if(m_rScale > 20.0f) {

  m_rScale = 20.0f;

 }

}

This function is pretty straightforward. At the start we call GetDeviceState which gets the current state of the keyboard device. The function call fills a char array with the key states of the keyboard. You can see from the function above, that once the array has been populated with key states, we can then use a simple macro called KEYDOWN to ascertain if a given key is in the down position. The KEYDOWN macro is shown below. So, based on certain key states, we manipulate some member variables that control scale and rotation.

#define KEYDOWN(name, key) (name[key] & 0x80)

Getting mouse data

As with the keyboard, we have a new function called ProcessMouse which is called from our Render function. This means that ProcessMouse will also be called once per frame. The code for ProcessMouse is shown below.

void CGame::ProcessMouse() {

 DIMOUSESTATE MouseState;

 if (FAILED(m_pMouse->GetDeviceState(sizeof(MouseState),(LPVOID)&MouseState))) {

  return;

 }

 //Is the left mouse button down?

 if (MOUSEBUTTONDOWN(MouseState.rgbButtons[MOUSEBUTTON_LEFT])) {

  m_nMouseLeft = 1;

 } else {

  m_nMouseLeft = 0;

 }

 //Is the right mouse button down?

 if (MOUSEBUTTONDOWN(MouseState.rgbButtons[MOUSEBUTTON_RIGHT])) {

  m_nMouseRight = 1;

 } else {

  m_nMouseRight = 0;

 }

 m_nMouseX += MouseState.lX;

 m_nMouseY += MouseState.lY;

}

So, to get access to the mouse data we make a call to GetDeviceState which fills a DIMOUSESTATE structure with data from the mouse. The DIMOUSESTATE structure has four members:

· lX: Distance the mouse has moved along the x-axis since the last call to GetDeviceState.

· lY: Distance the mouse has moved along the y-axis since the last call to GetDeviceState.

· lZ: Distance mouse wheel has moved since the last call to GetDeviceState.

· rgbButtons: Array of mouse button states.

lX, lY and lZ return the distance moved in device units (NOT pixels) since the last call to GetDeviceState. If you want to get the current screen position in pixels, you can use the Win32 GetCursorPos function. We use the macro MOUSEBUTTONDOWN to determine if a given mouse button is down or not. This macro is shown below along with some constants used for referring to mouse buttons.

#define MOUSEBUTTONDOWN(key) (key & 0x80)

#define MOUSEBUTTON_LEFT 0

#define MOUSEBUTTON_RIGHT 1

#define MOUSEBUTTON_MIDDLE 2

Using the data

In our Render3D function, we use the data that we have retrieved from the keyboard and mouse to alter the scene. The Render3D function is shown below:

void CGame::Render3D() {

 //Render our 3D objects

 D3DXMATRIX matEarth, matScale, matRotate, matEarthRoll, matEarthMove;

 float rMouseSpeed = 20.0f;

 //If left mouse button is down, stop the earth from rotating

 if (m_nMouseLeft == 1) {

  m_rRotate = 0.0f;

 }

 //If right mouse button is down, start the earth rotating at default speed

 if (m_nMouseRight == 1) {

  m_rRotate = 2.0f;

 }

 m_rAngle += m_rRotate;

 //Create the transformation matrices

 D3DXMatrixRotationY(&matRotate, D3DXToRadian(m_rAngle));

 D3DXMatrixScaling(&matScale, m_rScale, m_rScale, m_rScale);

 D3DXMatrixRotationYawPitchRoll(&matEarthRoll, 0.0f, 0.0f, D3DXToRadian(23.44f));

 D3DXMatrixTranslation(&matEarthMove, (m_nMouseX / rMouseSpeed), –(m_nMouseY / rMouseSpeed), 0.0f);

 D3DXMatrixMultiply(&matEarth, &matScale, &matRotate);

 D3DXMatrixMultiply(&matEarth, &matEarth, &matEarthRoll);

 D3DXMatrixMultiply(&matEarth, &matEarth, &matEarthMove);

 //Render our objects

 m_pD3DDevice->SetTransform(D3DTS_WORLD, &matEarth);

 m_dwTotalPolygons += m_pSphere->Render();

}

Cleaning up

Here is a simple function that will unacquire the mouse and keyboard and clean up the Direct Input related pointers. This is called from the destructor of our CGame class as part of the games clean up process.

void CGame::CleanUpDirectInput() {

 if (m_pKeyboard) {

  m_pKeyboard->Unacquire();

 }

 if (m_pMouse) {

  m_pMouse->Unacquire();

 }

 SafeRelease(m_pMouse);

 SafeRelease(m_pKeyboard);

 SafeRelease(m_pDirectInput);

}

So now we have an Earth that you can control.

Summary

In this tutorial we've seen how to use the mouse and keyboard via DirectInput. We've used the data from these input devices to control elements in our scene. The next thing to do is add some sound and music to our application, we'll do this in the next tutorial.