如何在C++Builder中使用摇杆

在Windows环境下通过编程来操纵鼠标和键盘是非常简单的,但是你有没有想过尝试另一种常用的输入工具——游戏操纵杆?在某些情况下,尤其是在编译一些小游戏软件时,加入对游戏摇杆的支持,可以为用户提供更加友好的人机界面,大大提高游戏软件的可玩性。

C++Builder中没有专门的摇杆控制函数(实际上常见的编程语言中基本没有摇杆控制函数)。所以为了增加对游戏摇杆的支持,需要处理Windows的MCI API函数。在这里,我们首先介绍一些API函数,常数和数据结构,在读取游戏杆的属性,状态,位置和按钮信息时使用。

相关常数:

# define mm _ joy 1 move 0x3a 0/*用于传达一些关于操纵杆当前状态的消息*/

#define MM_JOY2MOVE 0x3A1

#define MM_JOY1ZMOVE 0x3A2

#define MM_JOY2ZMOVE 0x3A3

# define MM _ joy 1 button down 0x3b 5

#define MM_JOY2BUTTONDOWN 0x3B6

# define MM _ joy 1 button up 0x3b 7

#define MM_JOY2BUTTONUP 0x3B8

# define joy _ button 1 0x 0001/*用于指示当前操纵杆状态*/

#定义JOY_BUTTON2 0x0002

#定义JOY_BUTTON3 0x0004

#定义JOY_BUTTON4 0x0008

# define JOY _ button 1 chg 0x 0100

#定义JOY_BUTTON2CHG 0x0200

#定义JOY_BUTTON3CHG 0x0400

#定义JOY_BUTTON4CHG 0x0800

/*游戏操纵杆错误返回值*/

#define JOYERR_BASE 160

# define JOYERR _ no error(0)/* normal */

# define joyerr _ parms(joyerr _ base+5)/*参数错误*/

# Define Joyerr _ no cando(Joyerr _ base+6)/*工作不正常*/

# define joyer _ unplugged(joyer _ base+7)/*操纵杆未连接*/

/*操纵杆识别号*/

#define JOYSTICKID1 0

#define JOYSTICKID2 1

相关函数:

WINMMAPI UINT WINAPI joyGetNumDevs(void);

获取设备标识号。

mm result WINAPI joyGetDevCaps(UINT uJoyID,LPJOYCAPS pjc,UINT cbjc);

获取游戏杆属性信息,并将其作为结构游戏杆接收。

WINMMAPI mm result WINAPI joyGetPos(UINT uJoyID,LPJOYINFO pji);

获取操纵杆位置和按钮状态,并用结构接收它们。

WINMMAPI mm result WINAPI joyGetThreshold(UINT uJoyID,lpu int puThreshold);

读取操纵杆移动阈值。

WINMMAPI mm result WINAPI joyreasecacapture(UINT uJoyID);

结束操纵杆信息的接收。

WINMMAPI mm result WINAPI joyset capture(HWND HWND,UINT uJoyID,UINT uPeriod,

BOOL fChanged);

设置窗口接收操纵杆的信息以及接收的频率。

WINMMAPI mm result WINAPI joySetThreshold(UINT uJoyID,UINT uThreshold);

设置操纵杆移动阈值。

相关结构:typedef结构joyCaps{

WORD wMid/*制造商的标志*/

WORD wPid/*生产编号*/

char szPname[MAXPNAMELEN];/*产品名称*/

UINT wXmin/* X轴最小值*/

UINT wXmax/* X轴最大值*/

UINT wYmin/* Y轴的最小值*/

UINT wYmax/* Y轴的最大值*/

UINT wZmin/* Z轴最小值*/

UINT wZmax/* Z轴的最大值*/

UINT wNumButtons/*按钮数量*/

UINT wPeriodMin/*最小呼叫间隔(毫秒)*/

UINT wPeriodMax/*最大呼叫间隔(毫秒)*/

}JOYCAPS,*PJOYCAPS,NEAR *NPJOYCAPS,FAR * LPJOYCAPS

typedef结构joyInfo{

UINT wXpos/* x轴位置*/

UINT wYpos/* y轴位置*/

UINT wZpos/* z轴位置*/

UINT wButtons/*按钮状态*/

} JOYINFO,*PJOYINFO,NEAR *NPJOYINFO,FAR * LPJOYINFO

这些定义存储在mmsystem.h文件中,因此程序应该包含这个头文件。

程序首先需要检查操纵杆的存在,这包括两个任务:检查驱动程序支持和确认操纵杆已经连接到系统。JoyGetNumDevs调用检查系统是否配置了游戏端口和驱动程序。如果返回值为零,则不支持操纵杆功能。如果joyGetNumDevs的返回值不为零,则系统支持操纵杆功能。但是,joyGetNumDevs无法确定操纵杆是否已连接。您可以通过调用来完成这些任务,并检查是否有错误。

如果有游戏端口,joyGetNumDevs的返回值通常是16。

一旦您确认操纵杆已连接,您就可以接收来自它的消息。JoySetCapture告诉Windows操纵杆消息应该发送到哪里以及发送的频率。

joySetCapture中的第一个参数告诉Windows谁将获得消息,第二个参数确定器将从该操纵杆接收消息。第三个参数表示您希望接收JM _移动消息的频率(以毫秒为单位),不管操纵杆是否移动。JoySetCapture的四个参数允许程序仅在操纵杆移动一定距离后才接受消息。这个距离由joySetThreshold设置。

调用joySetCapture后,窗口将接受操纵杆事件。MM_JOYXMOVE(X=操纵杆编号)事件以joySetCapture定义的时间间隔发生。MM_JOYXBUTTONUP和MM_JOYXBUTTONDOWN事件仅在按下操纵杆的按钮时发生。操纵杆时间偏离手柄来改变相应的标签状态信息。移动消息还通知程序在新的位置重新绘制操纵杆标志。调用joyReleaseCapture通知Windows操纵杆调用已经结束。

在实际编程中,首先要在Form1.h的头文件中添加一个对mmsystem.h的引用,然后再添加一些相关的消息映射,也就是对MM_JOYXMOVE、MM_JOYXBUTTONUP、MM_JOYXBUTTONDOWN事件的响应函数的描述。

#包含mmsystem.h

// -

类Tform1:公共Tform

{

_ _已发布:

...

...

私人:

...

t点位置;//用于存储操纵杆的坐标位置。

...

公共:

MESSAGE _ HANDLER(MM _ joy 1 button down,TMessage,JMButonUpdate)

MESSAGE _ HANDLER(MM _ joy 1 button up,TMessage,JMButonUpdate)

MESSAGE _ HANDLER(MM _ joy 1 move,TMessage,JMMove)

结束消息映射

};

将以下代码添加到Form1的OnCreate事件中,以检测操纵杆。

void _ _ fast call t form 1::form create(to object * Sender)

{

driver count = joyGetNumDevs();

连通=假;

MMRESULT JoyResult

JOYINFO JoyInfo

//检查系统是否配置了游戏端口和驱动程序。

if(DriverCount!= 0)

{

//joyGetPos仍然需要调用进行测试。如果返回JOYERR_NOERROR,则操纵杆连接正常。

//测试第一个操纵杆。

joy result = joy getpos(joystickid 1,JoyInfo);

if(JoyResult == JOYERR_NOERROR)

{

连接=真;

JoystickID = JoystickID 1;

}

//如果出现INVALIDPARAM错误,则退出。

else if(joy result = = MMSYSERR _ INVALPARAM)

应用程序-MessageBox("调用joyGetPos时出错",

“错误”,MB _ OK);

//如果连接了第一个操纵杆,检查第二个操纵杆。

else if((joy result = joy getpos(joystickid 2,JoyInfo)) == JOYERR_NOERROR)

{

连接=真;

JoystickID = JOYSTICKID2

}

}

}

确保游戏杆连接正确后,您可以读取游戏杆的设备信息。

void t form 1::ShowDeviceInfo(void)

{

joyGetDevCaps(JoystickID,JoyCaps,sizeof(joy caps));

Label1-Caption = "驱动程序支持的操纵杆数量= " +

IntToStr(driver count);

Label2-Caption = "当前操纵杆ID = " +

IntToStr(intJoystickID);

Label3-Caption = "制造商ID = " +

IntToStr(joy caps . wmid);

Label4-Caption = "产品ID = " +

IntToStr(joy caps . wpid);

Label5-Caption = "按钮数量= "+

IntToStr(joy caps . wnumbuttons);

//设置当前窗口接收游戏杆信息。

如果(已连接)

joySetCapture(Handle,JoystickID,2*JoyCaps.wPeriodMin,FALSE);

//计算操纵杆活动范围与屏幕范围的比值,后面画操纵杆logo的时候会用到。

xdrivider =(joy caps . wx max-joy caps . wx min)/Width;

YDivider =(joy caps . wymax-joy caps . wymin)/Height;

}

读取操纵杆位置信息和按钮状态:

void t form 1::ShowStatusInfo(void)

{

如果(已连接)

{

JOYINFO JoyInfo

t点位置;

joyGetPos(JoystickID,JoyInfo);

position . x = joyinfo . wxpos;

position . y = joyinfo . wypos;

//显示操纵杆的X和Y轴位置。

label 6-Caption = " X Position = "+IntToStr(int(joyinfo . wxpos));

label 7-Caption = " Y Position = "+IntToStr(int(joyinfo . wypos));

//判断一个按钮是否被按下,仅指按钮的初始状态。

if(JoyInfo.wButtons

JOY_BUTTON1)

Label8-Caption = "Button 1 =已按下";

其他

Label8-Caption = "Button 1 =未按下";

}

}

可以编写以下代码来响应头文件中定义的事件JMMove和JMButtonUpdate:JMButtonUpdate代码:

void _ _ fast call t form 1::JMMove(t message msg)

{

/*当操纵杆位置改变时,将自动调用该功能。

在该功能中,经常会根据操纵杆的当前位置来绘制操纵杆屏幕上显示的徽标,并擦除原来的徽标。在这里,只需改变图像的坐标位置来指示操纵杆的移动。*/

Position.x = msg。LParamLo

Position.y =消息。LParamHi

//计算新坐标。

ScreenX =(position . x-joy caps . wx min)/xdrivider-imagelist 1-Width/2;

ScreenY =(position . y-joy caps . wymin)/YDivider-imagelist 1-Height/2;

//显示新位置的x和y值。

label 6-Caption = " X Position = "+IntToStr(int(Position . X));

label 7-Caption = " Y Position = "+IntToStr(int(Position . Y));

//移动图像的位置。

image 1-Top = ScreenY;

image 1-Left = ScreenX;

}

void _ _ fast call t form 1::JMButtonUpdate(t message msg)

{

//当程序接收到JM_BUTTONDOWN和JM_BUTTONUP消息时,也就是按钮的状态发生变化时,就会调用这个函数。

如果(消息。WParam

JOY_BUTTON1) //判断按钮1是否被按下。

Label8-Caption = "Button 1 =已按下";

其他

Label8-Caption = "Button 1 =未按下";

}

最后,当程序退出时,记得关闭对游戏杆的调用,即在FormDestroy事件中添加joyreasecacapture(JoystickID)。

void _ _ fast call t form 1::form destroy(to object * Sender)

{

如果(已连接)

joyreasecacapture(JoystickID);

}

最后这个程序在CBuilder4/PWin98 SE环境下通过,WindowsNT中使用的API函数会和这个程序中介绍的不一样。详情请参考Windows API函数手册。另外,在程序测试中,我只使用了连接声卡的最常见的4键模拟手柄。对于其他的摇杆和新的USB手柄/摇杆,也希望有条件的朋友可以自己测试一下。