在C++Builder中如何使用游戏操纵杆

来源:网络时间:2011-06-27

  在Windows环境下通过编程来操纵鼠标、键盘是一件再简单不过的事了,不过大家有没有想过要尝试一下另一样我们比较常见的输入工具——游戏操纵杆呢?在某些情况下,尤其是象编制一些小型的游戏软件的时候,加入对游戏操纵杆的支持可以给使用者提供更为友好的人机界面,极大的提高游戏软件的可玩性。

   C++Builder中没有专门控制操纵杆函数(其实在常见的编程语言中基本上都没有),因此要增加对游戏操纵杆的支持,就要和Windows的MCI API函数打交道,这里我们首先介绍一些在读取操纵杆的属性、状态,位置和按钮信息时要用到的API函数、常量及数据结构。

   相关常量:
#define MM_JOY1MOVE 0x3A0 /* 用以传递操纵杆当前状态的一些消息 */

  #define MM_JOY2MOVE 0x3A1

  #define MM_JOY1ZMOVE 0x3A2

  #define MM_JOY2ZMOVE 0x3A3

  #define MM_JOY1BUTTONDOWN 0x3B5

  #define MM_JOY2BUTTONDOWN 0x3B6

  #define MM_JOY1BUTTONUP 0x3B7

  #define MM_JOY2BUTTONUP 0x3B8

  #define JOY_BUTTON1 0x0001 /* 用以表明当前操纵杆的状态 */

  #define JOY_BUTTON2 0x0002

  #define JOY_BUTTON3 0x0004

  #define JOY_BUTTON4 0x0008

  #define JOY_BUTTON1CHG 0x0100

  #define JOY_BUTTON2CHG 0x0200

  #define JOY_BUTTON3CHG 0x0400

  #define JOY_BUTTON4CHG 0x0800

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

  #define JOYERR_BASE 160

  #define JOYERR_NOERROR (0) /* 正常 */

  #define JOYERR_ParmS (JOYERR_BASE+5) /* 参数错误 */

  #define JOYERR_NOCANDO (JOYERR_BASE+6) /* 无法正常工作 */

  #define JOYERR_UNPLUGGED (JOYERR_BASE+7) /* 操纵杆未连接 */

  /* 操纵杆标识号 */

  #define JOYSTICKID1 0

  #define JOYSTICKID2 1

  

  
相关函数:

   WINMMAPI UINT WINAPI joyGetNumDevs(void);

   获取设备标识号。

   MMRESULT WINAPI joyGetDevCaps(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc);

   获取操纵杆属性信息,以结构体JoyCaps接收。

   WINMMAPI MMRESULT WINAPI joyGetPos(UINT uJoyID, LPJOYINFO pji);

   获取操纵杆位置和按钮状态,以结构体接收。

   WINMMAPI MMRESULT WINAPI joyGetThreshold(UINT uJoyID, LPUINT puThreshold);

   读取操纵杆移动阈值。

   WINMMAPI MMRESULT WINAPI joyReleaseCapture(UINT uJoyID);

   结束对操纵杆信息的接收。

   WINMMAPI MMRESULT WINAPI joySetCapture(HWND hwnd, UINT uJoyID, UINT uPeriod,

   BOOL fChanged);

   设置接收某一操纵杆的信息的窗口以及将何种频度接收。

   WINMMAPI MMRESULT WINAPI joySetThreshold(UINT uJoyID, UINT uThreshold);

   设置操纵杆移动阈值。

   相关结构体: typedef struct 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 struct 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_MOVE消息(单位为毫秒),无论操纵杆是否移动,都将以这个频度接受JM_MOVE消息。joySetCapture的四个参数允许程序当操纵杆移动一定的距离后才接受消息。该距离由joySetThreshold设置。

   joySetCapture被调用后,窗口将接受操纵杆事件。MM_JOYXMOVE(X=操纵杆号)事件已joySetCapture定义的时间间隔发生。只有当操纵杆的按钮被按下时,MM_JOYXBUTTONUP和MM_JOYXBUTTONDOWN事件才发生。操纵杆时间出发句柄,改变相应的标签状态信息。移动消息也同时通知程序在新的位置重画操纵杆标志。调用joyReleaseCapture通知Windows已结束操纵杆的调用。

   在实际编制程序时,应首先在Form1的头文件Form1.h中加入对mmsystem.h的引用,再加入一些相关的消息映射即对MM_JOYXMOVE、MM_JOYXBUTTONUP和MM_JOYXBUTTONDOWN事件的响应函数说明。
#include <mmsystem.h>

  //--------------------

  class Tform1:public TForm

  {

  __published:

  ...

  ...

  private:

  ...

  TPoint Position;//用于存储操纵杆的坐标位置。

  ...

  public:

  MESSAGE_HANDLER(MM_JOY1BUTTONDOWN,TMessage,JMButonUpdate)

  MESSAGE_HANDLER(MM_JOY1BUTTONUP,TMessage,JMButonUpdate)

  MESSAGE_HANDLER(MM_JOY1MOVE,TMessage,JMMove)

  END_MESSAGE_MAP(TForm)

  };

  在Form1的OnCreate事件中加入以下代码用以检测操纵杆。

  void __fastcall TForm1::FormCreate(TObject *Sender)

  {

   DriverCount = joyGetNumDevs();

   Connected = false;

   MMRESULT JoyResult;

   JOYINFO JoyInfo;

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

   if(DriverCount != 0)

   {

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

   //测试第一个操纵杆。

   JoyResult = joyGetPos(JOYSTICKID1,&JoyInfo);

   if(JoyResult == JOYERR_NOERROR )

   {

   Connected = true;

   JoystickID = JOYSTICKID1;

   }

   //如果发生INVALIDPARAM错误,则退出。

   else if(JoyResult == MMSYSERR_INVALPARAM)

   Application->MessageBox("An error occured while calling joyGetPos",

   "Error", MB_OK);

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

   else if((JoyResult=joyGetPos(JOYSTICKID2,&JoyInfo)) == JOYERR_NOERROR)

   {

   Connected = true;

   JoystickID = JOYSTICKID2;

   }

   }

  }

  
在确定操纵杆已正确连接之后就可以读取操纵杆的设备信息。
void TForm1::ShowDeviceInfo(void)

  {

   joyGetDevCaps(JoystickID,&JoyCaps, sizeof(JOYCAPS));

   Label1->Caption = "Number of joysticks supported by driver = " +

   IntToStr(DriverCount);

   Label2->Caption = "Current Joystick ID = " +

   IntToStr(intJoystickID);

   Label3->Caption = "Manufacturer ID = " +

   IntToStr(JoyCaps.wMid);

   Label4->Caption = "Product ID = " +

   IntToStr(JoyCaps.wPid);

   Label5->Caption = "Number of buttons = "+

   IntToStr(JoyCaps.wNumButtons);

   .

   .

   .

   // 设置当前窗口接收操纵杆信息。

   if(Connected)

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

   //计算操纵杆活动范围和屏幕范围的比率,在后面绘制操纵杆标志时会用到。

   XDivider = (JoyCaps.wXmax - JoyCaps.wXmin)/ Width;

   YDivider = (JoyCaps.wYmax - JoyCaps.wYmin)/ Height;

  }

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

  void TForm1::ShowStatusInfo(void)

  {

   if(Connected)

   {

   JOYINFO JoyInfo;

   TPoint Position;

   joyGetPos(JoystickID,&JoyInfo);

   Position.x = JoyInfo.wXpos;

   Position.y = JoyInfo.wYpos;

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

   Label6->Caption = "X Position = " + IntToStr(int(JoyInfo.wXpos));

   Label7->Caption = "Y Position = " + IntToStr(int(JoyInfo.wYpos));

   //判断某按钮是否被按下,这里只是指按钮初始的状态。

   if(JoyInfo.wButtons & JOY_BUTTON1)

   Label8->Caption = "Button 1 = Pressed";

   else

   Label8->Caption = "Button 1 = Not Pressed";

   }

  }

下面可以编写用以响应当初在头文件中定义的事件JMMove、JMButtonUpdate的代码: JMButtonUpdate的代码:

  void __fastcall TForm1::JMMove(TMessage &msg)

  {

   /*当操纵杆位置发生变化时会自动调用本函数。

在本函数中经常是根据操纵杆当前的位置来绘制操纵杆在屏幕上显示的标志,并擦 去原来的标志。这里只是简单的改变Image的坐标位置来表示操纵杆为的移动。 */

   Position.x = msg.LParamLo;

   Position.y = msg.LParamHi;

   //计算新的坐标。

   ScreenX = (Position.x-JoyCaps.wXmin)/XDivider - ImageList1->Width/2;

   ScreenY = (Position.y-JoyCaps.wYmin)/YDivider - ImageList1->Height/2;

   //显示新位置的X、Y值。

   Label6->Caption = "X Position = " + IntToStr(int(Position.x));

   Label7->Caption = "Y Position = " + IntToStr(int(Position.y));

   //移动Image的位置。

   Image1->Top=ScreenY;

   Image1->Left=ScreenX;

  }

  void __fastcall TForm1::JMButtonUpdate(TMessage &msg)

  {

   //当程序接收到JM_BUTTONDOWN和JM_BUTTONUP消息时,即某一按钮的状态发生改变时,都会调用本函数。

   if(msg.WParam & JOY_BUTTON1) //判断按钮1是否被按下

   Label8->Caption = "Button 1 = Pressed";

   else

   Label8->Caption = "Button 1 = Not Pressed";

  }

  最后在程序退出的时候要记得关闭对操纵杆的调用,即在FormDestroy事件中加入joyReleaseCapture(JoystickID)。

  void __fastcall TForm1::FormDestroy(TObject *Sender)

  {

  if(Connected)

   joyReleaseCapture(JoystickID);

  }

   以上就是使用操纵杆的常用方法,通过这些方法基本上就可以完成对操纵杆的一般操作,而且在了解了操纵杆的消息传递机制之后,我们还可以编写出一些用操纵杆模拟鼠标或是用鼠标/键盘来模拟操纵杆的程序以及通过编程让一般的手柄也象那种专用于格斗游戏的操纵杆一样具有可以记忆组合键的功能。不过要想更快速、更全面的操作操纵杆就需要用到Windows下高版本DirectX中的DirectInput技术了,鉴于篇幅以及使用比较繁琐的关系,这里就不予介绍了。

   最后说明一下,本程序在 CBuilder4/PWin98 SE 环境下通过,在 WindowsNT 下用到的API函数将会与本程序中介绍的函数有所不同,详细区别请参阅Windows API函数手册。另外在程序测试中,我仅使用了最一般的接在声卡上的那种普通4键模拟手柄。针对其他的操纵杆及新型USB手柄/操纵杆,还希望有条件的朋友自己去测试一下。

发表评论

最新评论(共0条)