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 ,如需转载请自行联系原作者
相关文章
|
2天前
|
安全 测试技术 API
后端开发中的API设计原则与最佳实践
本文将深入探讨在后端开发中API(应用程序编程接口)设计的基本原则和最佳实践。通过阐述如何构建高效、可扩展且安全的API,帮助开发者提升后端系统的性能和用户体验。不同于传统的摘要,本文无需包含背景介绍,直接进入主题,为读者提供实用的指导。
18 7
|
1天前
|
SQL 缓存 安全
深入理解后端开发中的API设计原则
【9月更文挑战第32天】在数字化浪潮中,API(应用程序编程接口)作为连接不同软件组件的桥梁,其设计质量直接影响着后端系统的效能与扩展性。本文将通过浅显易懂的方式,探讨如何构建高效、安全且易于维护的API,同时提供实用的代码示例,帮助读者在后端开发实践中提升API设计的水平。
10 3
|
7天前
|
JSON 中间件 API
开发REST API3-11
开发REST API3-11
|
13天前
|
监控 API 开发工具
探索 Postman:API 开发的瑞士军刀
在现代软件开发中,API 起着关键作用,连接前后端应用及微服务架构。Postman 是一款流行的一站式 API 开发工具,支持 REST、GraphQL 和 SOAP 等协议,具备构建、测试、调试 API 的强大功能,包括请求构建器、环境变量管理、测试脚本编写、文档生成及 Mock 服务器创建等。本文详细介绍 Postman 的核心功能与进阶技巧,助你提高 API 开发效率。
|
10天前
|
物联网 C# C语言
物联网开发中C、C++和C#哪个更好用
在物联网(IoT)开发中,C、C++和C#各有优缺点,适用场景不同。C语言性能高、资源占用低,适合内存和计算能力有限的嵌入式系统,但开发复杂度高,易出错。C++支持面向对象编程,性能优秀,适用于复杂应用,但学习曲线陡峭,编译时间长。C#易于学习,与.NET框架结合紧密,适合快速开发Windows应用,但性能略低,平台支持有限。选择语言需根据具体项目需求、复杂性和团队技术栈综合考虑。
|
10天前
|
API C#
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
25 2
|
12天前
|
前端开发 API 开发者
探索后端开发中的RESTful API设计原则
【9月更文挑战第21天】在数字化时代的浪潮中,后端开发扮演着至关重要的角色。本文将深入探讨RESTful API的设计原则,旨在为开发者提供一套清晰、高效的指导方针。我们将从资源的命名与表述开始,逐步引导您理解如何通过统一接口和状态码来构建可扩展且易于维护的API。文章不仅涵盖理论知识,还将通过实际代码示例,展示如何将这些原则应用于日常开发实践中。无论您是初学者还是经验丰富的开发者,这篇文章都将为您的后端开发之旅增添宝贵的知识财富。
|
13天前
|
JavaScript NoSQL 关系型数据库
深入浅出后端开发:从零搭建RESTful API
【9月更文挑战第20天】在数字时代的浪潮中,后端开发如同一座桥梁,连接用户界面与数据世界。本文将引领你踏上一段探索之旅,从零基础开始,一步步揭开后端开发的神秘面纱。我们将以构建一个RESTful API为例,深入理解后端逻辑的核心。通过简洁的代码示例和生动的比喻,本文旨在让初学者轻松入门,同时也为有一定基础的开发者提供新的视角和思考。准备好,让我们开始这段奇妙的旅程吧!
|
15天前
|
API 网络架构 开发者
探索后端开发:RESTful API设计的艺术
【9月更文挑战第18天】在数字化时代的浪潮中,后端开发如同搭建一座座坚固的桥梁,连接用户与数据的无限可能。本文将深入浅出地探讨RESTful API设计的精髓,从理论基础到实践应用,带领读者领略API设计的艺术。我们将以代码示例为灯塔,照亮理解之路,但
|
14天前
|
SQL 开发框架 安全
并发集合与任务并行库:C#中的高效编程实践
在现代软件开发中,多核处理器普及使多线程编程成为提升性能的关键。然而,传统同步模型在高并发下易引发死锁等问题。为此,.NET Framework引入了任务并行库(TPL)和并发集合,简化并发编程并增强代码可维护性。并发集合允许多线程安全访问,如`ConcurrentQueue&lt;T&gt;`和`ConcurrentDictionary&lt;TKey, TValue&gt;`,有效避免数据不一致。TPL则通过`Task`类实现异步操作,提高开发效率。正确使用这些工具可显著提升程序性能,但也需注意任务取消和异常处理等常见问题。
26 1