C - 手柄模拟鼠标控制

更新时间:2024-03-21 02:49:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

虽然Flash中不支持对游戏手柄进行编程,但我们可以换种方法,做一个辅助程序(外挂? ),将手柄中的操作事件转换为Flash中可接受的键盘与鼠标操作事件,这样不就可以使用游戏手柄来玩Flash游戏了吗?!于是,上网查了相关资料,但却发现只有C 方面的案例,而C#一个也找不,这不打紧,自己动手,丰衣足食。

(注:类似这样的功能,网络已有现成的软件,是一个日本人开发的,叫JoyToKey)

对游戏手柄进行操作,大概有两种方式:采用系统API或者使用DirectInput操作游戏手柄设备。(也许还有其它方式,但我的知识范围有限,其它方式就不得而知了)

采用系统API是一种最简单的方式,因为系统已帮我们封装好了所有细节,我们只要在程序中定时取得游戏手柄设备的状态就可以了(轮循)。 操作游戏手柄(杆)的API有以下几个: 函数名称 函数说明

joyGetNumDevs 获取当前系统支持的游戏设备数量 joyGetDevCaps 查询获取指定的游戏杆设备以确定其性能 joySetCapture 向系统申请捕获某个游戏设备并定时将该

设备的状态值通过消息发送到某个窗口 joyReleaseCapture 释放对某个游戏设备的捕获 joyGetPos 获取游戏设备的坐标位置和按钮状态 joyGetPosEx 获取游戏设备的坐标位置和按钮状态 joyGetThreshold 查询指定的游戏杆设备的当前移动阈值 joySetThreshold 设置指定的游戏杆设备的移动阈值

其中,根据调用不同的方法又可分为两种方式。 1)被动方式:

调用joySetCapture方法,向系统申请对某个游戏手柄的捕捉,如果成功申请,系统将会定时将此游戏手柄的状态信息通过消息方式通知到我们的某个窗口上。

2)主动方式:

即是根据我们自己的需要,按需调用joyGetPos或joyGetPosEx方法查询获取某个游戏手柄的当前状态。

而在本篇中,我们要讲解的只是“被动方式”。 joySetCapture方法的C#定义原型如下:

///

/// 向系统申请捕获某个游戏杆并定时将该设备的状态值通过消息发送到某个窗口

///

///

///

/// ///

/// [DllImport(\

public static extern int joySetCapture(IntPtr hWnd, int uJoyID, int uPeriod, bool fChanged);

当我们调用此方法向系统申请捕获某个游戏手柄后,如果成功,则返回JOYERR_NOERROR(值为0),否则返回其它值的话表示申请失败。并且在不再需要捕获游戏手柄时要记得调用joyReleaseCapture方法释放捕捉。

如果申请成功,系统将会定时(根据uPeriod的值决定时间的长短)将游戏手柄的状态以消息包形式发送到hWnd对应的窗口界面。所以我们必须要在程序中处理对应的消息(如重写WndProc方法进行处理)。

并且根据不同的uJoyID值,系统发送的消息号又会有所不同,如对于JOYSTICKID1系统将会分别发送以下消息包:

消息号说明MM_JOY1MOVE当手柄的位置已变动或按了某些按钮时,将会发送此消息包。MM_JOY1BUTTONDOWN当手柄的A,B,C,D四个按钮中的一个或多个正被按下时,将会发送此消息包。MM_JOY1BUTTONUP当手柄的A,B,C,D四个按钮中的一个或多个正被弹起时,将会发送此消息包。

而对于JOYSTICKID2 系统发出的消息包分别为MM_JOY2MOVE、MM_JOY2BUTTONDOWN、MM_JOY2BUTTONUP!

并且要注意!不管你有没有按游戏手柄上的按钮,系统也会定时发送MM_JOYXMOVE消息!! 怎样判断按了哪些键?

在消息包中,游戏手柄的状态信息(按钮状态)分别存储在消息包中的WParam与LParam参数。 1)WParam参数:

对于游戏手柄来说WParam存储的是除了上下左右四个方向键之外的所有按钮中当前被按下的按钮值,它的值是一个复合值。如它的值为JOY_BUTTON1 | JOY_BUTTON2时,就表明按下的按键是1号和2号按钮。

注意:对于MM_JOYXBUTTONDOWN与MM_JOYXBUTTONUP两个消息,用于判断的按钮值是不同于MM_JOYXMOVE的按钮值!! 2)LParam参数:

此参数存储的是游戏手柄的坐标参数,并且此参数的高16位存储的是Y坐标值,低16位存储的是X坐标值。

而对于游戏手柄来说,判断上下左右四个方向键有没有被按下就是通过此参数进行判断的。如果当四个方向键都没有被按下时,表示当前游戏手柄处于中心坐标中!也就是X,Y坐标都是在中心点位置上,而当某些方向键被按下时,X,Y坐标将根据所按的键向对应方向偏移。如当按了向右键,则X坐标向右偏移,Y坐标保持在中心点位置,而如果按了右、上两个方向键同时按下,则X坐标向右偏移,Y坐标向上偏移。所以我们可以根据LParam参数取得X,Y坐标的值,然后再根据其中心点来判断。参考代码如下:

///

/// 获取X,Y轴的状态 ///

///

private void GetXYButtonsStateFromLParam(int lParam, ref JoystickButtons buttons) {

//处理X,Y轴

int x = lParam & 0x0000FFFF; //低16位存储X轴坐标

int y = (int)((lParam & 0xFFFF0000) >> 16); //高16位存储Y轴坐标(不直接移位是为避免0xFFFFFF时的情况)

int m = 0x7EFF; //中心点的值

}

if (x > m) {

buttons |= JoystickButtons.Right; }

else if (x < m) {

buttons |= JoystickButtons.Left; }

if (y > m) {

buttons |= JoystickButtons.Down; }

else if (y < m) {

buttons |= JoystickButtons.UP; }

好了,对游戏手柄的“被动方式”编程就讲解完成了,剩下的就是要怎么利用游戏手柄来实现模拟键盘或鼠标的操作了??

回顾“被动方式”开发

在C#对游戏手柄的编程开发-API篇(1)这篇文章中我们介绍了“被动方式”的开发。在此方式下,我们的程序只扮演一个消息接收者。系统会定时告诉我们某个游戏手柄当前的状态,我们的程序接收到后再按实际需要进行处理即可。但如果你是一个细心的人,你会发现如果直接按消息事件处理的话会存在一个问题,如我们按下某个键(比如向上的方向键)然后放开时,对于我们“人”来说,我们按下与弹起的这两个动作应该只是说明我们只点击这个按钮一次。但对于系统来说,它只是机械地定时通知我们的程序在某个时间内游戏手柄的各个按钮的状态,而在我们按下到弹起这段时间内,系统有可能已经传递了N次的消息通知(N值根据捕捉时设置的uPeriod值与你的按键速度来决定),通知手柄有按钮处于被按下状态,而如果我们就根据消息包直接处理点击事件的话,就会导致问题出现(比如在某个游戏中,我们设计的是当点击一次手柄的右键,就将角色向前移动一步。但从我们按下按钮到弹开此

按钮这段时间,由于人的反应速度远远慢于电脑的处理速度,所以这段很短的时间内,系统可能已通知了10次以上的消息包表明游戏手柄右键已被按下,这就导致我们按一次右键,游戏中的角色却有可能已移动了十步之多,这可不是我们想要的结果)。那我们要怎样处理这个“点击”事件才可以避免重复通知呢?这就是本篇最后要重点讲解的内容了…… 在讲解这个问题的解决方法之前我们再来讲解一下上文还提到的一种开发方式。 “主动方式”的开发

主动方式即我们不需要向系统申请注册捕捉某个游戏手柄,我们只是根据自己的需要按时去获取游戏手柄的状态信息。 这时我们就要用到以下的API函数。

///

/// 获取操纵杆位置和按钮状态 /// /// [DllImport(\ public static extern int joyGetPos(int uJoyID, ref JOYINFO pji); /// /// 获取操纵杆位置和按钮状态 /// ///

/// [DllImport(\ public static extern int joyGetPosEx(int uJoyID, ref JOYINFOEX pji); 上面的两个API函数,我们可以从中任选一个,但joyGetPos函数只能取得1,2,3,4号四个按钮的状态。所以建议不用,下面只重讲解joyGetPosEx函数! JOYINFO 与 JOYINFOEX 是属于结构体,它们的定义如下:

#region 游戏手柄的位置与按钮状态 ///

/// 游戏手柄的位置与按钮状态 /// [StructLayout(LayoutKind.Sequential)] public struct JOYINFO { public int wXpos; public int wYpos; public int wZpos; public int wButtons; } /// /// 游戏手柄的位置与按钮状态 /// [StructLayout(LayoutKind.Sequential)] public struct JOYINFOEX { /// /// Size, in bytes, of this structure. /// public int dwSize; /// /// Flags indicating the valid information returned in this structure. Members that do not contain valid information are set to zero. /// public int dwFlags; /// /// Current X-coordinate. /// public int dwXpos; /// /// Current Y-coordinate. /// public int dwYpos; /// /// Current Z-coordinate. /// public int dwZpos; /// /// Current position of the rudder or fourth joystick axis. /// public int dwRpos; /// /// Current fifth axis position. /// public int dwUpos; /// /// Current sixth axis position. /// public int dwVpos; /// /// Current state of the 32 joystick buttons. The value of this member can be set to any combination of JOY_BUTTONn flags, where n is a value in the range of 1 through 32 corresponding to the button that is pressed. /// public int dwButtons; /// /// Current button number that is pressed. /// public int dwButtonNumber; /// /// Current position of the point-of-view control. Values for this member are in the range 0 through 35,900. These values represent the angle, in degrees, of each view multiplied by 100. /// public int dwPOV; /// /// Reserved; do not use. /// public int dwReserved1; /// /// Reserved; do not use. /// public int dwReserved2; } #endregion 如我们使用joyGetPosEx获取游戏设备的状态时,必须先初始化JOYINFOEX结构实例,并要设置dwSize参数的值,也即是JOYINFOEX结构体所占用的内存空间大小(其值可通过Marshal.SizeOf求得)。而如果要取得游戏设备的其它参数,则还必须要设置dwFlags参数的值!否则只能获取坐标值(dwXPos)。如对游戏手柄来说我们需要获取其它按钮的状

态,则设置dwFlags的值为JOY_RETURNBUTTONS,用于指示我们需要返回所有按钮的状态。 示例代码:

JoystickAPI.JOYINFOEX infoEx = new JoystickAPI.JOYINFOEX(); infoEx.dwSize = Marshal.SizeOf(typeof(JoystickAPI.JOYINFOEX)); infoEx.dwFlags = (int)JoystickAPI.JOY_RETURNBUTTONS; int result = JoystickAPI.joyGetPosEx(this.Id, ref infoEx); 如果joyGetPosEx函数获取手柄状态数据成功,则返回JOYERR_NOERROR(值为0),否则返回其它值的话表示获取失败。

当数据获取成功后,对应的游戏手柄的状态数据都已存储在JOYINFOEX结构实例中了。如要判断是否按下了方向键,则可判断dwXPos与dwYPos的值;而判断是否按了其它按钮,则可判断dwButtons的值。判断方法在上一章中有讲,这里就不再细说,或者也可以看后面提供的源码。

因为“主动方式”的“时效性”只有一次,所以为了能够随时监视到游戏手柄的按键事件,就必须进行“轮循”获取,当监视到游戏手柄有按键发生时就进行事件通知(噫?好像“被动方式”?嗯,其实当我们向系统申请捕捉某个游戏手柄时,系统最后也是在帮我们进行“轮循”操作!)。而实现“轮循”的方式则可以有多种方式,比如采用独立的线程进行一个死循环;或者采用Timer进行定时执行。

但当我们的操作进入“轮循”后,如果也是直接joyGetPostEx就处理的话也一样会碰到篇头所说的那个糟糕问题 !因为不管是“主动方式”还是“被动方式”都是一样只能得到游戏手柄按钮当前的状态(按下或未按下)。那怎么解决呢? 解决按钮重复状态的问题

解决这个问题,如果理清了思路,其实也是很简单的方法。

我们通过API得到的是游戏手柄按钮当前的状态(被按下或未按下)。因此我们可以在“轮循”里,每当监视到游戏手柄在某次时间有某些按钮是处于“按下”状态时,就记录此次被按下的按钮号,这样当下一次“轮循”操作时,如果也监视到有按钮按下,则通过与上一次按下的按钮对比,如果还是相同的按钮,则表明本次按钮还是继续上次的按下状态,那就不再需要向程序里发出消息通知了。而如果不相同,则发出新的按钮按键通知,并记录本次按下的按钮号。 伪代码如下:

previousButtons = 无; //死循环,进入轮循 while(true){ if(joyGetPosEx(手柄号,ref joyInfo) == 成功){ JoyButtons buttons = 取得当前按下的按钮(joyInfo); if(buttons != 无){ if(buttons != previousButtons){ //本次按下的按钮不同于上次按下的按钮.所以进行通知 OnClick(buttons); //记录本次按下的按钮 previousButtons = buttons; } } } 暂停uPeriod毫秒; } 经过这样的处理后,每按一次手柄的按钮我们的程序也只收到一次按键通知,看来我们的目的似乎达到了 。但在平常玩游戏中,我们同时按下的键不单单只有一个,比如边走边砍杀敌人,就有可能按住右方向键不放,然后拼命的按A或B键,那这样的话又会出现怎样的情况呢?这样的话,在我们的“轮循”中就有可能出现以下的情况(“->”表示先后顺序): 取得当前按下的是“右方向键”(1) –> 取得当前按下的是“右方向键”(2) –> 取得当前按下的是“右方向键”与A键(3) –> 取得当前按下的是“右方向键”(4) –> 取得当前按下的是“右方向键”与B键(5)–> 取得当前按下的是“右方向键”(6) ……

在上面中,(1)与(2)可通过上面的解决办法合并为一次,但到第3步时,因为当前按下的键有两个,而前一次按下的按钮只有一个,所以因(2)按键的不同,又重新发出一次按键通知。如此类推,从(1)到(6)步,程序就认为“右方向键”共按了5次!但对于我们“人”来说,这不是我们想要的结果,因为我们只是一直按住“右方向键”不放,所以应该只算按一次。那看来上面的解决方法并不完美 。

让我们再仔细再看一下上面的那个流程中的(2)与(3)中的差别,明眼的你应该看出来了,它们之间只是多了一个A键。而如果“右方向键”在第一步时已发出了按键消息通知,那么在(3)步时,如果我们只发出“A键”的按键消息通知,也就说每次只发出本次按下的按键集合与上一次按下的按键集合的差的按键消息通知的话,那么在上面的流程中,发出的消息通知就只有:在(1)步时发出“右方向键”的按键通知、(3)步时发出A键的按键通知、(5)步时发出B键的按键通知。这样篇头中的问题就可以完美的解决了 !!

到此,“C#对游戏手柄的编程开发”的文章就讲解完了,下一篇我们会讲解一下怎么去实现第一篇中说的“用游戏手柄模拟键盘或鼠标”的软件 。很简单的说,有兴趣的朋友希望能回贴支持一下我

对游戏手柄的编程开发在上两篇中,已讲解完,在此篇中将讲解对键盘的模拟。 对键盘的模拟,系统已提供了非常多的API函数,在这里就不一一讲解了,只讲解其中一个最简单的API函数 。

///

/// 模拟键盘事件 /// /// /// [DllImport(\ public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); 其中bVk可直接与System.Windows.Forms.Keys的值对应;dwFlags可为以下值的组合:

常数

KEYEVENTF_KEYDOWN KEYEVENTF_EXTENDEDKEY KEYEVENTF_KEYUP

说明

KeyDown事件

表明bScan为扩展键码。 KeyUp事件

为了方便对此API再进一步封装:

///

/// 键盘事件 /// [Flags] public enum KeyboardEvents { None = 0x0, KeyDown = 0x1, KeyUp = 0x2 } /// /// 发送键盘事件 /// /// public static void SendKeyEvent(Keys key, KeyboardEvents events) { if ((events & KeyboardEvents.KeyDown) == KeyboardEvents.KeyDown) keybd_event((byte)key, 0, KEYEVENTF_KEYDOWN, 0); if ((events & KeyboardEvents.KeyUp) == KeyboardEvents.KeyUp) keybd_event((byte)key, 0, KEYEVENTF_KEYUP, 0); } 比如我们要模拟按下A键则直接调用:SendKeyEvent(Keys.A, KeyboardEvents.KeyDown)。而要模拟按下A键并弹起则为:SendKeyEvent(Keys.A, KeyboardEvents.KeyDown | KeyboardEvents.KeyUp)

到此,对键盘的模拟就基本完成了,配合第2篇中的游戏手柄的KeyDown,KeyUp事件(请参考源码),第1篇中所说的那个功能也就能实现出来了。 下面是实现出来的软件界面截图:

使用方法:分别设置好每个手柄的按钮需要模拟的键盘键码(将光标移动到对应的按钮下面,直接点击键盘键即可),点击“启动1/2号手柄模拟”按钮。最小化程序后将鼠标焦点移动对您要玩的游戏上,您就可以使用手柄玩游戏了。

本文来源:https://www.bwwdw.com/article/izw8.html

Top