天行健 君子当自强而不息

使用DirectInput进行交互(4)

 

本篇是使用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;
}
 

程序截图:

 

 

posted on 2007-09-19 00:39 lovedday 阅读(1324) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


公告

导航

统计

常用链接

随笔分类(178)

3D游戏编程相关链接

搜索

最新评论