东方吧 关注:653,013贴子:17,789,191

【弹幕程序科普计划】第十篇 输入采集与键映射

只看楼主收藏回复

【输入采集与键映射】
  上次文章讲到了游戏循环中包括了输入采集、数据处理、画面显示这三个部分,尤其强调了数据处理和画面显示要分开。其实,输入采集部分也非常重要,而且输入采集和数据处理也应该分工明确。下面就来讲一下输入采集的相关问题。
  各种游戏引擎和编程工具都会提供输入采集的方法,包括键盘采集、手柄采集甚至鼠标采集的方法,这些方法在WinForm、WinAPI、DirectInput、Unity3D等等工具里各不相同,方法五花八门,这里并不打算具体去讲解某种工具的使用方法。其实这些方法本身并不复杂,参考各种工具的说明文档很快就能使用,比如Unity3D里对键盘采集的方法几乎已经到了最简的地步,可以在代码任何位置访问到键盘的键数据。完全可以在自机的控制代码中写上“如果键盘Z键按下就发弹”这样的逻辑,但是这也就是将输入采集和数据处理混在一起的做法,用我们STGer的说法,这是要糟的。至于为什么糟,我们来简单了解一下游戏中几个相关的需要实现的功能就明白了,分别是多设备输入采集功能、replay键数据存储功能以及键映射。
1. 多设备输入采集
  游戏中,常常要用到多种输入设备,比如STG中除了键盘输入还会支持手柄、摇杆,甚至某些场合要支持鼠标。单以键盘和手柄为例,这两种输入几乎是可以互相替代的关系,对于自机方向操控、射击、低速、Bomb、跳对话、暂停,这些功能可以通过键盘和手柄中任意一个实现,那么如何同时处理两种设备的输入呢?
  最笨的方法就像前面说的,在自机的控制方法里写上逻辑“如果Z键按下或者手柄3号键按下,就射击”,但是这样写是要糟的,因为少说有9个按键都要这么写,不但麻烦而且非常不利于代码维护,当然后面要讲的键映射功能也是难以实现。
  比较好的方法是定义一个按键数据包,把游戏要用到的按键信息打包传递,游戏在输入采集环节将输入数据采集的结果打包成数据包,而自机控制时只访问数据包而不直接访问输入设备,于是整体架构就变成下图所示的样子(对于单输入情况):

  这个按键数据包记录所有要用到按键的状态,并且是以按键功能的形式进行描述,比如射击键、Bomb键、低速键以及方向键。说得再细一点的话,这个按键数据包就是一串布尔量,程序里可以用结构体也可以用类来建立,这在C++和C#中都差不多,对C会有一些区别,因为在C++和C#中结构体中也支持写函数,能写函数的话就能把相关的一些按键数据处理方法和这些数据封装在一起,这样会有不少好处。
  以上的框图给出的是单输入采集的情况,那么如果是多输入的情况,这个过程就应该变成将每一路输入采集的结果打包成数据包,然后将这些包进行汇总,得出一个最终的数据包,然后传递给后面进行数据处理。说得再清楚一点,这个过程其实也就是将各数据包里的所有按键进行或运算。
键盘和手柄同时采集的框图如下所示,即使再加入其它输入设备也可以轻松扩展吧。

2. replay键数据存储功能
replay功能其原理是通过记录每一帧的按键信息来实现的,这点具体我们会在之后的文章讲到。这里只是想强调一下,如果在记录键数据的时候去访问键盘或者手柄这些输入设备,这显然也是不合理的。但是有了前面所说的按键数据包就好办多了,只需要将汇总后的按键数据包转换成一定编码形式然后记录下来就好了。至于具体做法我们还是放到以后讲吧。
3. 键映射
  关于要重定义键盘按键,或者是要定义手柄按键,这种应该怎么实现呢?可能不少朋友一开始会没思路,但是我相信,看了以上的架构以后,多少都会有点想法吧。其实只要在采集输入设备到打包成数据包的过程中,建立一个映射关系就可以了,用框图表示的话就是这个样子:

  具体讲这个映射关系的话,就是按下什么键来控制射击键,按什么键来控制Bomb键,诸如此类。具体是用Z键控制射击键还是用A键或是其它键控制射击键,需要能够设置,也就说要把自定义的键码记录下来,然后将实际的按键信息与定义的键码做对照。我是将键码记录在ini文件中,当然用其他文本文件或是什么自定义格式存储都没问题。然后在游戏程序开始运行的时候,读取这些自定义按键的信息,生成映射关系,供之后输入采集时使用。
  最后,关于这个按键数据包以及输入采集进来的数据格式,稍微还有点小问题,我也索性在这里一并讲一下。通常DirectInput或是各种游戏引擎所采集进来的按键信息是按键按压状态,也就是判断一个键是否按下。而WinForm下的键盘事件能触发的事件是按键按压事件和按键释放事件,也就是说他采集的是按键的跳变信息。在游戏中,我们最长用到的还是按键的按压状态,比如方向键是否按住。所以在打包按键数据包的时候还是将其描述为按键是否按下。但是也有一些时候需要用到跳变信息,比如按方向键切换菜单时,比如按射击键看剧情对话时,都需要检测按键按下的这个动作。这就需要记录上一帧按键的状态,来算这个跳变信息。据说在弹幕风引擎中的按键数据包直接包含了按键的跳变信息,可能是为了使用方便一些。但是一方面这种形式可能会出现按键信息和跳变信息有冲突的情况,另一方面,它也不能解决全部问题,人说还有一种按键状态叫作“持续按压30帧”,所以总还是要程序根据具体情况处理的。
  顺带提一句,我在《弹幕音乐绘》里一共有三路输入方法,分别是通过WinForm事件采集的键盘输入、通过DirectInput采集的键盘输入、通过DirectInput采集的手柄输入,其中WinForm采集的这一路信息可以做键盘键映射,DirectInput采集手柄信息也可以做手柄键映射,而唯独用DirectInput采集键盘的这一路没有映射,所以才会有现在音乐绘里这个选项。这实际上是在切换键盘的采集通道。

那么本次科普就到这里。
以下广告时间:
游戏《弹幕音乐绘》登陆steam绿光,各种求支持
http://steamcommunity.com/sharedfiles/filedetails/?id=886253524
本系列过往文章将收录在弹幕音乐绘的哔哩哔哩兴趣圈中,欢迎关注
http://www.im9.com/community.html?community_id=12241


IP属地:上海1楼2017-03-25 15:07回复
    这样一想把输入预处理后打包确实各种意义上都方便


    IP属地:江苏来自Android客户端10楼2017-03-25 16:49
    回复
      2025-05-11 02:19:10
      广告
      这一系列可以帮帮我如何重制旧作了。。。


      IP属地:山东来自Android客户端13楼2017-03-25 18:26
      回复
        先拜为敬


        IP属地:福建来自Android客户端15楼2017-03-25 20:07
        回复
          GetAsyncKeyState(int)能用吗?效率如何?


          来自Android客户端18楼2017-03-26 12:44
          收起回复
            上绿光了?支持下,说不定能有喜+1?


            IP属地:北京来自手机贴吧20楼2017-03-26 13:56
            回复
              话说楼主有没有办法解决在相反的方向键按下时的逻辑
              我目前是用switch来实现的,只要同时按下两个不同的方向移动就会卡住一小段时间


              IP属地:甘肃来自Android客户端21楼2017-03-26 21:20
              收起回复
                专拍香霖,妖忌裙底,专拍皮皮虾下面,专拍zun裙底


                IP属地:河南来自Android客户端23楼2017-03-27 09:33
                收起回复
                  2025-05-11 02:13:10
                  广告
                  现在都是按帧采集了吧


                  IP属地:湖北26楼2017-03-27 11:53
                  回复
                    支持登录steam!


                    IP属地:广东来自iPhone客户端27楼2017-03-27 12:51
                    收起回复


                      来自Android客户端29楼2017-03-27 14:33
                      回复
                        支持


                        IP属地:浙江来自Android客户端30楼2017-03-27 15:52
                        回复


                          IP属地:广东来自Android客户端31楼2017-03-27 16:59
                          回复