基于民意测验的输入的组合键


9

因此,假设您有一个基于轮询的输入系统

void update()
{
    if( Keyboard[ 'A' ] )
        // A is down
}

假设您希望能够识别3到8个长度的按键组合(例如向下,向下,向前,向前,A代表hado-ken)

您如何最好地在轮询输入上创建通用(易于修改/可编程)的组合键输入系统?

Answers:


7

从根本上讲,您不能这样做。组合键需要排序,排序需要事件。

您可以做的是通过比较每个帧的键状态并根据差异生成事后的键上/键下事件,将轮询转换为事件。它不那么可靠,因为您丢失了“本机”事件系统提供的时间戳和其他帧内排序,但是它使您可以检测和排序每帧至少一个键。

一旦有了这些顺序事件,就可以使用有限状态机或其他工具将它们与您的移动列表进行匹配,并执行正确的事件。


说没有事件就无法做到这一点有点误导。从技术上讲,事件只是在满足某些条件时调用方法或方法列表的一种方法。从这个意义上讲,您需要事件。但是,我可以将事件一词(不适当地)应用于适合该描述的许多编程任务。我添加了一个答案,该答案以通常的方式解决了该问题,该答案没有使用大多数程序员倾向于认为的“事件”。
Olhovsky 2011年

2
您的代码完全按照我说的做,没有事件,但是忽略了警告-这是您丢失帧内排序的原因。当您从Win32消息循环中获取事件时,您将获得的事件的粒度比每帧的粒度大。当您自己计算增量时,您会丢失它。对于格斗游戏来说,玩家可能会在一个帧中输入连击的多个部分,因此您需要知道这些顺序是混乱还是混乱。

很公平。假设我们以60hz进行轮询,那么我怀疑我们是否要区分小于60Hz轮询提供的间隔为16ms的压力机(假设我们的玩家是人类)。
Olhovsky

我们的确是。熟练的格斗游戏玩家可以在不到三帧的时间内输入hadouken / shoryuken摇杆命令,这意味着我们必须对它们进行区分。

然后,您需要轮询的频率超过60hz。
2011年

3

一种方法是存储当前和先前的输入状态,并在每次轮询输入时进行比较。

对于每个可以按下的键,存储一个对象,该对象的时间戳为该键从按下状态切换到按下状态的最后时间。

通过在每次轮询中执行以下操作来更新这些对象:

void update(){
    some_key_has_been_pressed = false;
    foreach(key in keys){
        if(previous_input[key].Down && current_input[key].Up){
            keys[key].up_timestamp = current_time();
        }

        if(current_input[key].Down){
            keys[key].down_timestamp = current_time();
            some_key_has_been_pressed = true;
        }
    }
}

现在,您可以根据的内容对组合进行模式匹配keys

每个组合都有一个组合对象,并在每次轮询时对每个组合对象调用update()。

组合对象的update()方法将对组合进行模式匹配,并检查此轮询是否满足组合的所有必要条件。也就是说,到目前为止,该组合的所有键时间戳都是正确的,并且没有其他可能破坏组合的键被按下此框。对于满足的每个条件,将组合对象中的计数器增加到下一个要检查的条件。当组合中满足所有条件时,调用组合应执行的方法。如果some_key_has_been_pressed == true但尚未按下组合的下一个条件键,则将组合的满足条件计数器重置为0。

上面是我的首选方法,因为它易于实现,易于维护,高效且高度模块化。

但是,对于另一种好的方法,请查看用C#编写的XNA输入序列样本,并且该逻辑可能可以转移到您使用的语言。


在第二个if语句中,if(current_input[key].down){您是否还不想检查密钥是否在该previous_input状态下?照我所写,我认为它将不断更新一个持有的钥匙down_timestamp
webdevkit 2012年

持续更新停机时间戳是预期的行为。这使您可以比较任何密钥的up_timestamp和down_timestamp,以检查其保留时间。如果持续按住某个键,则不会更改up_timestamp,因此(down_timestamp-up_timestamp)仍会为您提供其持续时间。
Olhovsky

感谢您的澄清。我在想相反的情况,其中up​​_timestamp-down_timestamp是保持键的持续时间。
webdevkit 2012年

1

堆叠最后的X个按键事件,为每个按键事件添加一个带有按键和按下/释放时间的对象,然后检查堆栈中的最后一个成员是否与特殊模式匹配,以及是否按下这些键的速度足够快以至于算作一个组合。最后,从堆栈中删除最旧的对象。


2
您可以从中删除底部对象的堆栈实际上并不是堆栈;)
Olhovsky 2011年

一堆有序的数据实体,列表可能是正确的术语。
aaaaaaaaaaaa

deque;)O(1)enq / deq ftw。
michael.bartnett

3
将对象放在一端并从另一端移除的有序序列最恰当地称为队列

1
您可以制作一个环形缓冲区(BufferLength> =最长的命令)并向其写入密钥(位置= Number MOD BufferLength)。完全不需要添加/删除
Kromster
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.