键盘输入系统处理


13

注意:由于API限制(SFML),我必须轮询而不是回调。对于缺少“体面”的头衔,我也表示歉意。

我想我有两个问题。如何注册我收到的输入,以及如何处理。

处理输入

我在说的是您已经注册了例如已经按下“ A”键的事实,以及如何从那里开始操作。

我看过整个键盘的排列,例如:

bool keyboard[256]; //And each input loop check the state of every key on the keyboard

但这似乎效率很低。例如,您不仅将键“ A”耦合到“玩家向左移动”,而且还会每秒检查30-60次每个键。

然后,我尝试了另一个仅查找所需密钥的系统。

std::map< unsigned char, Key> keyMap; //Key stores the keycode, and whether it's been pressed. Then, I declare a load of const unsigned char called 'Quit' or 'PlayerLeft'.
input->BindKey(Keys::PlayerLeft, KeyCode::A); //so now you can check if PlayerLeft, rather than if A.

但是,与此有关的问题是,例如,现在我不必绑定每个键就无法键入名称。

然后,我有第二个问题,我真的不能想到一个好的解决方案:

发送输入

我现在知道按下了A键,或者playerLeft为true。但是我怎么从这里走?

我考虑过只检查一下, if(input->IsKeyDown(Key::PlayerLeft) { player.MoveLeft(); }
这将输入与实体紧密结合在一起,我发现它很混乱。我希望玩家在更新时处理自己的动作。我以为某种事件系统可以工作,但是我不知道如何使用它。(我听说信号和插槽对这种工作很有用,但是它看起来非常慢,我看不出它的适合程度)。

谢谢。

Answers:


7

我要做的是使用观察者模式,并具有一个输入类,该类维护回调或输入处理对象的列表。其他对象可以在输入系统中注册自己,以便在发生某些事情时得到通知。根据观察者希望收到的输入事件的类型,您可以注册不同类型的回调。这可能像“键x的向下/向上”一样低,也可能与游戏相关,例如“发生了跳跃/射击/移动事件”。

通常,游戏对象具有输入处理组件,该组件负责向输入类注册自身并提供处理感兴趣事件的方法。对于运动,我有一个具有move(x,y)方法的输入移动组件。它在输入系统中注册自己,并收到有关移动事件的通知(其中x和y为-1和0表示向左移动),然后移动器组件根据移动方向和对象速度更改其父游戏对象坐标。

我不知道这是否是一个很好的解决方案,但到目前为止对我来说仍然有效。特别是,它允许我提供映射到相同逻辑游戏事件的不同类型的输入系统,从而使游戏手柄按钮x和键盘空间都生成一个跳转事件(比这要复杂得多,允许用户重新分配键映射)。


这绝对使我感兴趣。输入组件会为某些事件(例如playerInput寄存器“ left”,“ right”等)注册自己吗?还是每个注册的输入组件都会收到所有消息?
共产党鸭子

这实际上取决于您的要求以及您希望如何实现。就我而言,侦听器为特定事件注册自己并实现适当的输入处理程序接口。让每个输入组件都能接收所有消息,但是消息格式需要比我现有的格式更通用。
Firas Assaad 2010年

6

我认为OP的讨论将很多事情融合在一起,并且可以通过将其分解一些来大大简化该问题。

我的建议:

保留键状态数组。是否没有API调用可立即获取整个键盘状态?(我的大部分工作在控制台上,甚至在Windows上,对于已连接的控制器,您都可以立即获得整个控制器状态。)您的键状态是键盘键上的“硬”映射。未翻译是最好的,但是从API中可以最轻松地获得它。

然后保留几个并行数组,分别指示密钥是否在此帧上移,另一个并行数组指示密钥在该帧上移,第三个数组简单地指示密钥在“向下”。也许抛出一个计时器,以便您可以跟踪密钥处于其当前状态已有多长时间。

接下来,创建一种方法来创建键到动作的映射。您将需要执行几次。一次输入UI内容(请注意查询Windows映射,因为当您按“ A”时您无法假定扫描代码将在用户计算机上给出A)。每个游戏中的“模式”的另一个。这些是键码到操作的映射。您可以通过两种方法来执行此操作,一种方法是保留接收系统使用的枚举,另一种方法是函数指针,该指针指示在状态更改时要调用的函数。甚至可能是两者的混合,这取决于您的目标和需求。使用这些“模式”,您可以将它们压入并弹出到控制器模式堆栈中,并带有允许忽略输入的标志,使它们继续沿堆栈向下移动。

最后,以某种方式处理这些关键操作。对于运动,您可能想做一些棘手的事情,将“ W”翻译为“前进”,但这并不一定是二进制的解决方案;您可以将“ W降低”表示“将速度提高X直到最大Y”,将“ W升高”设为“将速度降低Z直到达到零”。一般来说,您希望“游戏系统中的控制器处理界面”非常狭窄;在一个地方进行所有关键翻译,然后将其结果用于其他地方。这与直接检查是否在代码中的任意位置处按空格键形成对比,因为如果它在某个随机位置上,则可能会在您不希望的某个随机时间被击中,而您只是不想这样做。不想处理...

我真的很讨厌对模式的设计,而且我认为组件带来的开发成本多于好处,所以这就是为什么我都没有提到。如果将模式注定要成为目标,那么模式就会出现,而组件也会注定出现,但是从一开始就着手做任何事情只会使您的生活复杂化。


您不认为将事件绑定到图形框架有点脏吗?我认为应该分开。
乔·

我的意思是,我知道,在几乎所有情况下,播放器输入按钮的速度都比图形卡发出帧的速度慢,但实际上它们应该是独立的,并且实际上它们适用于其他事件类型,例如从网络中收集的事件类型。 。
乔苏因此

确实; 没有理由不能以与显示屏不同的速率来运行键盘(或其他输入设备)的轮询。实际上,如果您以100Hz的频率轮询输入,则无论视频硬件更新频率如何,都可以提供真正一致的用户控制体验。您将不得不更聪明地处理数据(关于锁存等),但这将使手势之类的东西更容易保持一致。
dash-tom-bang

3

在SFML中,您具有按按键顺序与KeyPressed事件类型的键顺序。您可以在while(getNextEvent())中处理每个键。

因此,如果按下的键在“键”->“动作”映射中,请运行相应的动作,如果不是,则将其转发到任何可能需要它的小部件/材料。

关于您的第二个问题,建议您保持这样,因为这样可以更轻松地调整游戏玩法。如果使用信号等,则必须为“ RunKeyDown”创建一个插槽,为“ JumpKeySlot”创建一个插槽。但是,如果在您的游戏中同时按下这两个按钮时执行特殊操作会怎样?

如果要取消关联输入和实体,则可以将输入设为状态(RunKey = true,FireKey = false,...),然后将其发送给播放器,就像将任何其他事件发送给AI一样。


我不是使用sf :: Event来处理键盘事件,而是使用sf :: Input来处理键盘事件,因为它是实时检测IIRC。我试图使其尽可能与API无关。:P(问题是)
共产党鸭子

1
但是我认为Calvin1602提出的意思是,您在“我必须轮询,而不是因为API限制(SFML)进行回调”这一问题上的原始说法是不正确的。您有事件,因此可以在处理事件时发出回调。
Kylotan 2010年

3

正确的解决方案通常是您讨论的两种方法的组合:

对于具有预定义键集的游戏功能,轮询每个帧是完全合适的,并且您只应轮询实际绑定到某物的键。实际上,这与您在控制台游戏中查询控制器输入的方式类似,它将完全轮询,尤其是对于模拟输入设备。在一般的游戏过程中,您可能想忽略按键事件,而只使用轮询。

在输入诸如名称之类的输入内容时,您应该将游戏切换到其他输入处理模式。例如,一旦出现“输入您的名字”提示,您就可以修改输入代码的内容。它可以通过某些回调界面监听按键事件,也可以根据需要开始轮询每个按键。事实证明,通常在键入事件期间,您对性能的关心并不大,因此轮询不会那么糟糕。

因此,您想对游戏输入使用选择性轮询,对名称输入使用事件处理(或在事件处理不可用时使用所有键盘轮询)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.