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

简介:

回顾“被动方式”开发

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

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

 

“主动方式”的开发

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

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

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

 

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

JOYINFO 与 JOYINFOEX 是属于结构体,它们的定义如下:
#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

本文转自Kingthy博客园博客,原文链接:http://www.cnblogs.com/kingthy/archive/2009/03/28/1424055.html ,如需转载请自行联系原作者
相关文章
|
1月前
|
C#
24. C# 编程:用户设定敌人初始血值的实现
24. C# 编程:用户设定敌人初始血值的实现
20 0
|
3天前
|
存储 安全 网络安全
C#编程的安全性与加密技术
【4月更文挑战第21天】C#在.NET框架支持下,以其面向对象和高级特性成为安全软件开发的利器。本文探讨C#在安全加密领域的应用,包括使用System.Security.Cryptography库实现加密算法,利用SSL/TLS保障网络传输安全,进行身份验证,并强调编写安全代码的重要性。实际案例涵盖在线支付、企业应用和文件加密,展示了C#在应对安全挑战的同时,不断拓展其在该领域的潜力和未来前景。
|
3天前
|
程序员 C#
C#编程中的面向对象编程思想
【4月更文挑战第21天】本文探讨了C#中的面向对象编程,包括类、对象、封装、继承和多态。类是对象的抽象,定义属性和行为;对象是类的实例。封装隐藏内部细节,只暴露必要接口。继承允许类复用和扩展属性与行为,而多态使不同类的对象能通过相同接口调用方法。C#通过访问修饰符实现封装,使用虚方法和抽象方法实现多态。理解并应用这些概念,能提升代码的清晰度和可扩展性,助你成为更好的C#程序员。
|
4天前
|
IDE 程序员 C#
C#编程入门:从零开始的旅程
【4月更文挑战第20天】本文引导初学者入门C#编程,从环境搭建开始,推荐使用Visual Studio Community版作为IDE。接着,通过编写&quot;Hello, World!&quot;程序,介绍基本语法,包括数据类型、运算符和表达式。文章还涉及控制结构、函数和方法,以及面向对象编程概念。通过学习,读者将对C#有初步了解,并激发进一步探索编程世界的兴趣。
|
4天前
|
开发框架 .NET Java
探索 C#编程的奥秘与魅力
【4月更文挑战第20天】C#是微软开发的现代、面向对象的编程语言,以其简洁语法、强大功能和跨平台支持脱颖而出。它支持自动垃圾回收、泛型、委托、LINQ,并广泛应用于桌面、Web、移动和游戏开发。C#拥有活跃的开发者社区和丰富的资源,是Unity游戏开发的首选语言。随着.NET Core,C#可在多个操作系统上运行,持续创新,未来发展潜力巨大。
|
4天前
|
缓存 算法 测试技术
优化 C#编程性能的策略
【4月更文挑战第20天】优化C#性能策略包括:选择合适算法和数据结构,避免频繁对象创建,缓存常用数据,减少内存分配,使用异步编程,优化数据库操作(如合理查询和使用索引),利用多线程并行处理,精简代码,使用性能分析工具,硬件升级,以及进行性能测试。综合应用这些策略可提升程序性能和响应性。
|
9天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
|
12天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
21天前
|
前端开发 Java API
构建RESTful API:Java中的RESTful服务开发
【4月更文挑战第3天】本文介绍了在Java环境中构建RESTful API的重要性及方法。遵循REST原则,利用HTTP方法处理资源,实现CRUD操作。在Java中,常用框架如Spring MVC简化了RESTful服务开发,包括定义资源、设计表示层、实现CRUD、考虑安全性、文档和测试。通过Spring MVC示例展示了创建RESTful服务的步骤,强调了其在现代Web服务开发中的关键角色,有助于提升互操作性和用户体验。
构建RESTful API:Java中的RESTful服务开发
|
26天前
|
机器学习/深度学习 前端开发 API
实现以图搜货功能,淘宝API开发实战分享
实现以图搜货功能,淘宝API开发实战分享
24 0