如何在C++Builder中使用摇杆
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手柄/摇杆,也希望有条件的朋友可以自己测试一下。