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

简介: 回顾“被动方式”开发在C#对游戏手柄的编程开发-API篇(1)这篇文章中我们介绍了“被动方式”的开发。在此方式下,我们的程序只扮演一个消息接收者。系统会定时告诉我们某个游戏手柄当前的状态,我们的程序接收到后再按实际需要进行处理即可。

回顾“被动方式”开发

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

目录
相关文章
|
8天前
|
存储 API 数据库
后端开发中的API设计原则
【8月更文挑战第12天】在数字化时代,后端开发是构建软件系统的基石。其中,API(应用程序编程接口)的设计至关重要,它直接影响到系统的性能、可维护性以及用户体验。本文将深入探讨后端API设计的几项核心原则,帮助开发者构建更加高效和稳定的后端服务。
|
2天前
|
存储 监控 安全
后端开发中的API设计艺术
【8月更文挑战第18天】在数字时代的浪潮中,后端开发作为技术架构的基石,其重要性不言而喻。API设计,作为后端开发的关键环节,直接影响着软件系统的性能、可维护性及用户体验。本文将深入探讨API设计的基本原则、常见挑战及其应对策略,旨在为读者提供一套系统的API设计方法论,帮助开发者构建更加高效、稳定且易于扩展的后端服务。通过分析API设计的核心要素,我们将揭示如何打造优雅且强大的后端API,确保它们能够支撑起现代应用的复杂需求。
15 5
|
13天前
|
SQL 安全 前端开发
后端开发中的API设计原则
【8月更文挑战第7天】在数字化时代,后端开发成为支撑整个互联网生态的关键。其中,API设计作为后端开发的核心组成部分,其优劣直接影响着系统的稳定性、可扩展性以及用户体验。本文将深入探讨API设计中应遵循的几项基本原则,包括简洁性、一致性、安全性、版本控制以及文档化,旨在为后端开发者提供一套实用的设计指南,帮助他们构建出更加高效、安全且易于维护的API。
47 10
|
9天前
|
存储 安全 API
后端开发中的API设计哲学
【8月更文挑战第11天】在数字时代的浪潮中,后端开发如同一座桥梁,连接着用户与数据的无限可能。本文将探讨API设计的核心原则,包括其简洁性、灵活性和安全性的重要性,以及如何通过高效的API设计提升用户体验和系统性能。我们将一起探索如何在不断变化的技术环境中保持API的持续迭代和优化,确保它们能够适应未来的挑战。
21 2
|
13天前
|
安全 API 数据安全/隐私保护
后端开发中的API设计原则与实践
【8月更文挑战第7天】在数字化时代,API(应用程序接口)是连接不同软件组件和服务的桥梁。一个良好设计的API不仅能够提升开发效率,还能增强系统的可维护性和扩展性。本文将探讨一些关键的API设计原则,并结合实例分析这些原则在后端开发中的应用和重要性。
|
11天前
|
存储 API 网络架构
深入浅出:后端开发中的RESTful API设计原则
在数字化浪潮的推动下,后端开发成为构建现代网络服务不可或缺的一环。本文将引导你理解RESTful API的设计哲学,探索如何通过简洁而强大的设计来提升后端服务的可维护性与扩展性。我们将从REST的基本概念出发,逐步深入到API设计的实战技巧,最终实现高效且优雅的后端架构。
|
2天前
|
安全 C# 开发者
【C# 多线程编程陷阱揭秘】:小心!那些让你的程序瞬间崩溃的多线程数据同步异常问题,看完这篇你就能轻松应对!
【8月更文挑战第18天】多线程编程对现代软件开发至关重要,特别是在追求高性能和响应性方面。然而,它也带来了数据同步异常等挑战。本文通过一个简单的计数器示例展示了当多个线程无序地访问共享资源时可能出现的问题,并介绍了如何使用 `lock` 语句来确保线程安全。此外,还提到了其他同步工具如 `Monitor` 和 `Semaphore`,帮助开发者实现更高效的数据同步策略,以达到既保证数据一致性又维持良好性能的目标。
7 0
|
4天前
|
存储 安全 API
深入浅出:后端开发中的API设计艺术
在数字世界的构建中,后端开发扮演着基石的角色。本文将带领读者走进API设计的世界,从初学者的视角出发,逐步深入到API设计的高级概念。我们将一起探索如何打造高效、易用且安全的后端服务接口,确保数据交互的流畅与安全。通过实际案例,我们将揭示良好API设计对于提升用户体验和促进团队协作的重要性。无论你是初涉后端开发的新手,还是寻求进阶知识的资深开发者,这篇文章都将为你提供宝贵的指导和灵感。
|
9天前
|
安全 API 开发者
后端开发中的API设计哲学
【8月更文挑战第11天】在软件开发的复杂世界中,API设计不仅仅是一种技术实践,它更像是一门艺术。良好的API设计能够极大地提升软件系统的可用性、可维护性和扩展性。本文将探讨API设计的基本原则和最佳实践,以及它们如何影响后端开发的整体质量。
16 0