本篇是使用DirectInput进行交互(3)的续篇,当时由于身边没有游戏操纵杆,所以拖到现在才写。
使用游戏杆玩游戏
游戏杆是游戏控制的支柱,尽管游戏杆不是游戏惟一可以使用的输入设备,但它却是专门为玩游戏而设计的。将游戏杆向左推,游戏人物就会向左走,按下一个按键,游戏中的英雄就会挥舞他的剑,还有什么比这更容易的吗?
游戏杆的形状和大小千差万别,商店货架上的方向盘控制器就是一个游戏杆。如果去过街机游戏厅,就可能玩过允许(或要求)玩家站在很大的踏雪板或骑在小摩托车上来控制屏幕上的角色之类的游戏。别惊奇,这些踏雪板和摩托车甚至都可以看成是游戏杆!
游戏杆是一种轴控制器,按键很少。方向盘只有一个用于向左向右转向的控制轴,它可能还有用于刹车和控制油门的控制轴。连基本的两键游戏杆都有两个控制轴:一个用于向上和向下,另外一个用于向左和向右。
下图显示了一些游戏杆,不论外形如何,方向性、旋转型以及推动型输入是游戏杆的共同特征。
控制轴只是一个电位器(变量寄存器),它控制传送给电路的电压。传送的最小电压表示一个轴的范围(游戏杆能够被移动的最远点),而最大电压表示的是另外一个范围,所有电压就介于这两个范围之间。
电压会流向系统,多亏经过windows(或directinput)处理,因此才能使用它。游戏杆按键的工作方式几乎完全相同,就是根据电压是否施加到按键上来发出按键是否被按下的信号。
读入游戏杆数据的方式采用的是绝对值,这些绝对值都是相对于游戏杆中心的值。向左或向右推动游戏杆,都会接收到负值,负值表示远离游戏杆中心的距离。向下或向右按,就会得到正值。按键都是单个标志,这些标志指出了按键是否被按下。
各种游戏杆之间惟一较大的差别就是那些带有数字控制轴的游戏杆了,这些游戏杆就像是一些按键的组合。将游戏杆向左推就像按下一个表示向左的按键一样,无论程序员何时查询游戏杆以得到正在读取的轴,游戏杆都会返回轴的可能最低值或最高值。
使用DirectInput处理游戏杆
从某种程度上讲,游戏杆是最难处理的设备。最难的地方在于游戏杆的设置,要找到连接到系统中的游戏杆设备,必须进行枚举。在枚举的过程中,必须决定使用哪个游戏杆,然后再为游戏杆创建COM对象。
如下所示,该函数枚举并返回第一个枚举到的游戏杆。
IDirectInput8* g_di; // directinput component
IDirectInputDevice8* g_enum_joystick; // enum joystick device
IDirectInputDevice8* g_joystick; // joystick device
//--------------------------------------------------------------------------------
// Initialize joystick interface, return a joystick interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* init_joystick(HWND hwnd, IDirectInput8* di)
{
g_di->EnumDevices(DI8DEVTYPE_JOYSTICK, enum_joysticks, NULL, DIEDFL_ATTACHEDONLY);
// everything was a success, return the pointer.
return g_enum_joystick;
}
接着来看看枚举函数的实现:
//--------------------------------------------------------------------------------
// Enumerate all attached joysticks, return first enumerated joystick.
//--------------------------------------------------------------------------------
BOOL CALLBACK enum_joysticks(LPCDIDEVICEINSTANCE device_inst, LPVOID ref)
{
DIPROPRANGE prop_range;
DIPROPDWORD prop_dword;
g_enum_joystick = NULL;
// create the device object using global directinput object
if(FAILED(g_di->CreateDevice(device_inst->guidInstance, &g_enum_joystick, NULL)))
return DIENUM_CONTINUE;
// set the data format
if(FAILED(g_enum_joystick->SetDataFormat(&c_dfDIJoystick)))
goto fail;
// set the cooperative mode
if(FAILED(g_enum_joystick->SetCooperativeLevel(g_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
goto fail;
// clear out the structure first
ZeroMemory(&prop_range, sizeof(DIPROPRANGE));
prop_range.diph.dwSize = sizeof(DIPROPRANGE);
prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_range.diph.dwHow = DIJOFS_X;
prop_range.diph.dwHow = DIPH_BYOFFSET; // offset into data format
prop_range.lMin = -1024;
prop_range.lMax = 1024;
// Sets properties that define the device behavior.
// These properties include input buffer size and axis mode.
HRESULT rv = g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph);
if(FAILED(rv))
{
if(rv == DIERR_INVALIDPARAM)
MessageBox(NULL, "invalid param", NULL, MB_OK);
else if(rv == DIERR_NOTINITIALIZED)
MessageBox(NULL, "not initialize", NULL, MB_OK);
else if(rv == DIERR_OBJECTNOTFOUND)
MessageBox(NULL, "object not found", NULL, MB_OK);
else if(rv == DIERR_UNSUPPORTED)
MessageBox(NULL, "unsopported", NULL, MB_OK);
goto fail;
}
prop_range.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph)))
goto fail;
// set x deadzone to 15%
prop_dword.diph.dwSize = sizeof(DIPROPDWORD);
prop_dword.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_dword.diph.dwHow = DIPH_BYOFFSET;
prop_dword.diph.dwObj = DIJOFS_X;
prop_dword.dwData = 1500;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// set Y deadzone
prop_dword.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// acquire the device for use
if(FAILED(g_enum_joystick->Acquire()))
goto fail;
// stop enumeration
return DIENUM_STOP;
fail:
g_enum_joystick->Release();
g_enum_joystick = NULL;
return DIENUM_CONTINUE;
}
其中涉及到的结构体DIPROPRANGE定义如下:
Contains information about the range of an object within a device. This
structure is used with the DIPROP_RANGE flag set in the
IDirectInputDevice8::GetProperty and IDirectInputDevice8::SetProperty methods.
typedef struct DIPROPRANGE {
DIPROPHEADER diph;
LONG lMin;
LONG lMax;
} DIPROPRANGE, *LPDIPROPRANGE;
Members
- diph
- DIPROPHEADER structure.
- lMin
- Lower limit of the range. If the range of the device is unrestricted,
this value is DIPROPRANGE_NOMIN when the
IDirectInputDevice8::GetProperty method returns.
- lMax
- Upper limit of the range. If the range of the device is unrestricted,
this value is DIPROPRANGE_NOMAX when the
IDirectInputDevice8::GetProperty method returns.
Remarks
The diph member must be initialized as follows:
Member |
Value |
dwSize
|
sizeof(DIPROPRANGE) |
dwHeaderSize |
sizeof(DIPROPHEADER) |
dwObj
|
If the
dwHow member is DIPH_DEVICE, this member must be 0.
If
the dwHow member is DIPH_BYID, this member must be the identifier for
the object whose property setting is to be set or retrieved.
If
the dwHow member is DIPH_BYOFFSET, this member must be a data format
offset for the object whose property setting is to be set or retrieved.
For example, if the c_dfDIMouse data format is selected, it must be one
of the DIMOFS_* values.Identifier of the object whose property is being
retrieved or set.
If
the dwHow member is DIPH_BYUSAGE, the device must be a Human Interface
Device (human interface device). The device object will be identified by
the HID usage page and usage values in packed form.
|
dwHow
|
Specifies how the dwObj member should be interpreted. See the preceding
description of the dwObj member for details. |
The range values for devices whose ranges are unrestricted wraparound.
DIPROPDWORD结构体的定义如下:
Used to access DWORD properties.
typedef struct DIPROPDWORD {
DIPROPHEADER diph;
DWORD dwData;
} DIPROPDWORD, *LPDIPROPDWORD;
Members
- diph
- DIPROPHEADER structure.
- dwData
- Property-specific value being set or retrieved.
-
DIPROPHEADER的定义如下:
Serves as a header for all property structures.
typedef struct DIPROPHEADER {
DWORD dwSize;
DWORD dwHeaderSize;
DWORD dwObj;
DWORD dwHow;
} DIPROPHEADER, *LPDIPROPHEADER;
Members
- dwSize
- Size of the enclosing structure. This member must be initialized before
the structure is used.
- dwHeaderSize
- Size of the DIPROPHEADER structure.
- dwObj
- Object for which the property is to be accessed. The value set for this
member depends on the value specified in the dwHow member.
- dwHow
- Value that specifies how the dwObj member should be interpreted. This
value can be one of the following:
- DIPH_DEVICE
- The dwObj member must be 0.
- DIPH_BYOFFSET
- The dwObj member is the offset into the current data format of the
object whose property is being accessed.
- DIPH_BYUSAGE
- The dwObj member is the human interface device usage page and usage
values in packed form.
- DIPH_BYID
- The dwObj member is the object type/instance identifier. This
identifier is returned in the dwType member of the
DIDEVICEOBJECTINSTANCE structure returned from a previous call to the
IDirectInputDevice8::EnumObjects member.
如果有游戏杆被初始化,g_enum_joystick就成了指向新对象的指针,反之如果没有游戏杆被初始化,它就等于NULL。一旦设备对象被初始化,就能像前面读取键盘和鼠标的信息一样读取游戏杆的信息,但是read_joystick函数要使用DIJOYSTATE结构体。
Describes the state of a joystick device. This structure is used with the
IDirectInputDevice8::GetDeviceState method.
typedef struct DIJOYSTATE {
LONG lX;
LONG lY;
LONG lZ;
LONG lRx;
LONG lRy;
LONG lRz;
LONG rglSlider[2];
DWORD rgdwPOV[4];
BYTE rgbButtons[32];
} DIJOYSTATE, *LPDIJOYSTATE;
Members
- lX
- X-axis, usually the left-right movement of a stick.
- lY
- Y-axis, usually the forward-backward movement of a stick.
- lZ
- Z-axis, often the throttle control. If the joystick does not have this
axis, the value is 0.
- lRx
- X-axis rotation. If the joystick does not have this axis, the value is
0.
- lRy
- Y-axis rotation. If the joystick does not have this axis, the value is
0.
- lRz
- Z-axis rotation (often called the rudder). If the joystick does not have
this axis, the value is 0.
- rglSlider
- Two additional axes, formerly called the u-axis and v-axis, whose
semantics depend on the joystick. Use the IDirectInputDevice8::GetObjectInfo
method to obtain semantic information about these values.
- rgdwPOV
- Direction controllers, such as point-of-view hats. The position is
indicated in hundredths of a degree clockwise from north (away from the
user). The center position is normally reported as - 1; but see Remarks. For
indicators that have only five positions, the value for a controller is - 1,
0, 9,000, 18,000, or 27,000.
- rgbButtons
- Array of buttons. The high-order bit of the byte is set if the
corresponding button is down, and clear if the button is up or does not
exist.
Remarks
You must prepare the device for joystick-style access by calling the
IDirectInputDevice8::SetDataFormat method, passing the c_dfDIJoystick global
data format variable.
If an axis is in relative mode, the appropriate member contains the change in
position. If it is in absolute mode, the member contains the absolute axis
position.
Some drivers report the centered position of the POV indicator as 65,535.
Determine whether the indicator is centered as follows:
BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);
Note Under DirectX 7.0, sliders on
some joysticks could be assigned to the Z axis, with subsequent code retrieving
data from that member. Using DirectX 8.0 and later, those same sliders will be
assigned to the rglSlider array. This should be taken into account when porting
applications to later versions of DirectX. Make any necessary alterations to
ensure that slider data is retrieved from the rglSlider array.
来看看read_joystick的实现:
//--------------------------------------------------------------------------------
// Read joystick buffer.
//--------------------------------------------------------------------------------
BOOL read_joystick(void* buffer, long buffer_size)
{
HRESULT rv;
while(1)
{
// poll device
g_joystick->Poll();
// read in state
if(SUCCEEDED(rv = g_joystick->GetDeviceState(buffer_size, buffer)))
break;
// return when an unknown error
if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
return FALSE;
// re-acquire and try again
if(FAILED(g_joystick->Acquire()))
return FALSE;
}
return TRUE;
}
用于读取按键状态的宏在这里仍然起作用:
#define JOYSTICK_BUTTON_STATE(x) ((joy_state.rgbButtons[x] &0x80) ? TRUE :
FALSE)
完整代码示例如下:
点击下载源码和工程
/***************************************************************************************
PURPOSE:
Joystick device Demo
***************************************************************************************/
#define DIRECTINPUT_VERSION 0x0800
#include <windows.h>
#include <stdio.h>
#include <dinput.h>
#include "resource.h"
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dinput8.lib")
#pragma warning(disable : 4996)
#define Safe_Release(p) if((p)) (p)->Release();
// window handles, class and caption text.
HWND g_hwnd;
char g_class_name[] = "JoystickClass";
IDirectInput8* g_di; // directinput component
IDirectInputDevice8* g_enum_joystick; // enum joystick device
IDirectInputDevice8* g_joystick; // joystick device
BOOL CALLBACK enum_joysticks(LPCDIDEVICEINSTANCE device_inst, LPVOID ref);
//--------------------------------------------------------------------------------
// Window procedure.
//--------------------------------------------------------------------------------
long WINAPI window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return (long) DefWindowProc(hwnd, msg, wParam, lParam);
}
//--------------------------------------------------------------------------------
// Initialize joystick interface, return a joystick interface pointer.
//--------------------------------------------------------------------------------
IDirectInputDevice8* init_joystick(HWND hwnd, IDirectInput8* di)
{
g_di->EnumDevices(DI8DEVTYPE_JOYSTICK, enum_joysticks, NULL, DIEDFL_ATTACHEDONLY);
// everything was a success, return the pointer.
return g_enum_joystick;
}
//--------------------------------------------------------------------------------
// Read joystick buffer.
//--------------------------------------------------------------------------------
BOOL read_joystick(void* buffer, long buffer_size)
{
HRESULT rv;
while(1)
{
// poll device
g_joystick->Poll();
// read in state
if(SUCCEEDED(rv = g_joystick->GetDeviceState(buffer_size, buffer)))
break;
// return when an unknown error
if(rv != DIERR_INPUTLOST || rv != DIERR_NOTACQUIRED)
return FALSE;
// re-acquire and try again
if(FAILED(g_joystick->Acquire()))
return FALSE;
}
return TRUE;
}
//--------------------------------------------------------------------------------
// Enumerate all attached joysticks, return first enumerated joystick.
//--------------------------------------------------------------------------------
BOOL CALLBACK enum_joysticks(LPCDIDEVICEINSTANCE device_inst, LPVOID ref)
{
DIPROPRANGE prop_range;
DIPROPDWORD prop_dword;
g_enum_joystick = NULL;
// create the device object using global directinput object
if(FAILED(g_di->CreateDevice(device_inst->guidInstance, &g_enum_joystick, NULL)))
return DIENUM_CONTINUE;
// set the data format
if(FAILED(g_enum_joystick->SetDataFormat(&c_dfDIJoystick)))
goto fail;
// set the cooperative mode
if(FAILED(g_enum_joystick->SetCooperativeLevel(g_hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE)))
goto fail;
// clear out the structure first
ZeroMemory(&prop_range, sizeof(DIPROPRANGE));
prop_range.diph.dwSize = sizeof(DIPROPRANGE);
prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_range.diph.dwHow = DIJOFS_X;
prop_range.diph.dwHow = DIPH_BYOFFSET; // offset into data format
prop_range.lMin = -1024;
prop_range.lMax = 1024;
// Sets properties that define the device behavior.
// These properties include input buffer size and axis mode.
HRESULT rv = g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph);
if(FAILED(rv))
{
if(rv == DIERR_INVALIDPARAM)
MessageBox(NULL, "invalid param", NULL, MB_OK);
else if(rv == DIERR_NOTINITIALIZED)
MessageBox(NULL, "not initialize", NULL, MB_OK);
else if(rv == DIERR_OBJECTNOTFOUND)
MessageBox(NULL, "object not found", NULL, MB_OK);
else if(rv == DIERR_UNSUPPORTED)
MessageBox(NULL, "unsopported", NULL, MB_OK);
goto fail;
}
prop_range.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_RANGE, &prop_range.diph)))
goto fail;
// set x deadzone to 15%
prop_dword.diph.dwSize = sizeof(DIPROPDWORD);
prop_dword.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop_dword.diph.dwHow = DIPH_BYOFFSET;
prop_dword.diph.dwObj = DIJOFS_X;
prop_dword.dwData = 1500;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// set Y deadzone
prop_dword.diph.dwObj = DIJOFS_Y;
if(FAILED(g_enum_joystick->SetProperty(DIPROP_DEADZONE, &prop_dword.diph)))
goto fail;
// acquire the device for use
if(FAILED(g_enum_joystick->Acquire()))
goto fail;
// stop enumeration
return DIENUM_STOP;
fail:
g_enum_joystick->Release();
g_enum_joystick = NULL;
return DIENUM_CONTINUE;
}
//--------------------------------------------------------------------------------
// Main function, routine entry.
//--------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
WNDCLASS win_class;
MSG msg;
DIJOYSTATE joy_state;
char text[256];
LONG joystick_x = 0, joystick_y = 0;
BOOL is_first_render = TRUE;
// create window class and register it
win_class.style = CS_HREDRAW | CS_VREDRAW;
win_class.lpfnWndProc = window_proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = DLGWINDOWEXTRA;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(inst, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
win_class.lpszMenuName = NULL;
win_class.lpszClassName = g_class_name;
if(! RegisterClass(&win_class))
return FALSE;
// create the main window
g_hwnd = CreateDialog(inst, MAKEINTRESOURCE(IDD_Joystick), 0, NULL);
ShowWindow(g_hwnd, cmd_show);
UpdateWindow(g_hwnd);
// initialize directinput and get keyboard device
DirectInput8Create(inst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void **) &g_di, NULL);
// initialize mouse
g_joystick = init_joystick(g_hwnd, g_di);
if(g_joystick != NULL)
{
// start message pump, waiting for signal to quit.
ZeroMemory(&msg, sizeof(MSG));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// read in mouse and display coordinates
read_joystick(&joy_state, sizeof(DIJOYSTATE));
if(is_first_render || (joystick_x != joy_state.lX || joystick_y != joy_state.lY))
{
is_first_render = FALSE;
sprintf(text, "%ld, %ld", joy_state.lX, joy_state.lY);
SetWindowText(GetDlgItem(g_hwnd, IDC_COORDINATES), text);
}
joystick_x = joy_state.lX;
joystick_y = joy_state.lY;
}
}
else
{
MessageBox(g_hwnd, "No Joysticks!", "Error", MB_OK);
return 0;
}
// release directinput objects
g_joystick->Unacquire();
g_joystick->Release();
g_di->Release();
UnregisterClass(g_class_name, inst);
return (int) msg.wParam;
}
程序截图: