回顾“被动方式”开发

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

在讲解这个问题的解决方法之前我们再来讲解一下上文还提到的一种开发方式。

“主动方式”的开发

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

这时我们就要用到以下的API函数。

/// /// 获取操纵杆位置和按钮状态/// /// /// /// [DllImport("winmm.dll")]public static extern int joyGetPos(int uJoyID, ref JOYINFO pji);/// /// 获取操纵杆位置和按钮状态/// /// /// /// [DllImport("winmm.dll")]public static extern int joyGetPosEx(int uJoyID, ref JOYINFOEX pji);

上面的两个API函数,我们可以从中任选一个,但joyGetPos函数只能取得1,2,3,4号四个按钮的状态。所以建议不用,下面只重讲解joyGetPosEx函数

JOYINFO 与 JOYINFOEX 是属于结构体,它们的定义如下:
Code#region 游戏手柄的位置与按钮状态/// <summary>/// 游戏手柄的位置与按钮状态/// </summary>            [StructLayout(LayoutKind.Sequential)]public struct JOYINFO            {public int wXpos;public int wYpos;public int wZpos;public int wButtons;            }/// <summary>/// 游戏手柄的位置与按钮状态/// </summary>            [StructLayout(LayoutKind.Sequential)]public struct JOYINFOEX            {/// <summary>/// Size, in bytes, of this structure./// </summary>            public int dwSize;/// <summary>/// Flags indicating the valid information returned in this structure. Members that do not contain valid information are set to zero./// </summary>            public int dwFlags;/// <summary>/// Current X-coordinate./// </summary>            public int dwXpos;/// <summary>/// Current Y-coordinate./// </summary>            public int dwYpos;/// <summary>/// Current Z-coordinate./// </summary>            public int dwZpos;/// <summary>/// Current position of the rudder or fourth joystick axis./// </summary>            public int dwRpos;/// <summary>/// Current fifth axis position./// </summary>            public int dwUpos;/// <summary>/// Current sixth axis position./// </summary>            public int dwVpos;/// <summary>/// 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./// </summary>            public int dwButtons;/// <summary>/// Current button number that is pressed./// </summary>            public int dwButtonNumber;/// <summary>/// 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./// </summary>            public int dwPOV;/// <summary>/// Reserved; do not use./// </summary>            public int dwReserved1;/// <summary>/// Reserved; do not use./// </summary>            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键的按键通知。这样篇头中的问题就可以完美的解决了 !!

(范例代码可参考源码中OnTimerCallback函数)

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

源码下载: /Files/kingthy/JoyKeys.Voluntary.rar

艾伟:C#对游戏手柄的编程开发-API篇(2)相关推荐

  1. C#对游戏手柄的编程开发-API篇(1)

    前段时间花38元从网上买了一对北通的USB游戏手柄,这样周末与晚上的休闲时间就可以玩玩孩儿时的SFC与街机模拟游戏了. 某日在某个网站上玩一个Flash游戏时,突然想到,如果也能使用手柄来玩Flash ...

  2. 线程编程常见API简介(中)

    2019独角兽企业重金招聘Python工程师标准>>> 一.概述 在<线程编程常见API简介(上) >中讲述了有关线程创建过程中常用的 API 的使用方法,本节继续讲述有 ...

  3. 索尼游戏手柄SP2的开发体会

    索尼游戏手柄SP2的开发体会 1.PS手柄介绍 接收器引脚输出: 通信时序: 2.代码解读 3.库文件解读 ps2手柄是索尼的PlayStation2游戏机的遥控手柄. 该款手柄的通讯协议被游戏爱好者 ...

  4. 2018python做图形界面哪个库简单_2018年常见的python编程开发库都有哪些类型

    python编程开发可以说是目前比较热门的一项编程开发语言了,而今天我们就一起来了解一下,关于python编程都有哪些常见的python库可以使用. 1.TensorFlow "Tensor ...

  5. api有哪些 javasocket_基于java的socket编程及API解析

    一.socket通讯过程 1.socket与socket编程简介: socket 被翻译为"套接字",它是计算机之间进行通信的一种约定或一种方式.通过 socket 这种约定,一台 ...

  6. arm linux udp 自发自收_嵌入式linux编程开发必备知识

    嵌入式linux是嵌入式开发必不可少的一份子,在科技高速发展的今天,嵌入式已然已经成为了最热门的技术之一了.对于想要学习好嵌入式的学员来说,现在学习好linux是很有必要的,因为这个是嵌入式的核心.那 ...

  7. linux QT 结束当前进程_嵌入式linux编程开发必备知识

    嵌入式linux是嵌入式开发必不可少的一份子,在科技高速发展的今天,嵌入式已然已经成为了最热门的技术之一了.对于想要学习好嵌入式的学员来说,现在学习好linux是很有必要的,因为这个是嵌入式的核心.那 ...

  8. python如何编程日期_python编程开发之日期操作实例分析

    本文实例讲述了python编程开发之日期操作.分享给大家供大家参考,具体如下: 在python中对日期进行操作的库有: import datetime import time 对日期格式化信息,可以参 ...

  9. Sentinel SuperPro加密锁编程开发

    本文将简要介绍Sentinel SuperPro软件加密锁的编程开发知识,可供需要进行软件加密锁开发的软件开发商快速学习掌握这种类型的加密锁的编程开发,快速保护自己的软件不受侵害,防止加密狗被破解.本 ...

最新文章

  1. 【怎样写代码】工厂三兄弟之工厂方法模式(一):问题案例
  2. 数据结构 - 如何判断两个无环单链表是否相交;如果相交,给出相交的第一个结点
  3. (摘)TMS320 DSP混合编程的方法研究
  4. SQL Server Mysql 对null值理解的不同
  5. 【python图像处理】几何图形的绘制与文字的绘制(ImageDraw类详解)
  6. php zval_copy_static_var(),zval _ 引用计数 _ 变量分离 _ 写时拷贝
  7. 如何获得github的oauth access token
  8. 配置 HTTP 与 DNS 功能
  9. java.util报错
  10. Q74:面积光源(Area Light)
  11. 体检套餐管理系统的综合版
  12. 翻译: 3.线性神经网络 概览 深入神经网络 pytorch
  13. 现在的自助建站都有什么特点。
  14. SiT9386:AEC-Q100认证汽车级差分振荡器1-220MHz任意频率
  15. 奇葩Bug频出,苹果AirPods Pro 2提醒用户换电池
  16. PostgreSQL 源码解读(216)- 实现简单的扩展函数
  17. 转【JMeter】--JMeter下载及使用
  18. [转载]C++经典书籍汇总
  19. 你究竟该如何学习Linux系统?
  20. 关于计算机方面的知识小故事,「计算机小故事」-奇妙的HelloWorld任务

热门文章

  1. 尖峰 mysql 源码方向_MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图...
  2. linux本地监听创建,linux 创建监听服务器
  3. oracle中set怎么用,sqlplus命令格式以及sqlplus中set的用法与解释-Oracle
  4. C++ ——统一初始化
  5. 李兴华html css,2014MLDN(李兴华老师视频教程)
  6. git 配置多个SSH-Key
  7. 贪吃蛇python零基础教程_自学python-tkinter项目-贪吃蛇的程序(0基础入门学习)...
  8. linux lnmp yum,yum安装LNMP
  9. 电力电子技术第五版王兆安pdf_电力电子技术笔记(考试必备)
  10. 小米 samba linux,不折腾会死:CentOS7访问小米路由(Samba服务)