有效的键盘输入处理

9

如何实现良好的键盘处理?在任何语言中,当我编写键盘交互程序(例如俄罗斯方块游戏)时,最终都会有一些类似于以下代码的内容:

for event in pygame.event.get():
    if event.type == KEYDOWN:
        if False: pass          #make everything an elif
        elif rotating: pass
        elif event.key == K_q:
        elif event.key == K_e:
        elif event.key == K_LEFT:
            curpiece.shift(-1, 0)
            shadowpiece = curpiece.clone(); setupshadow(shadowpiece)
        elif event.key == K_RIGHT:
            curpiece.shift(1, 0)
            shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

(缩写). 我不喜欢这个方法,因为这需要放在我的主循环中,并且它会影响程序的所有部分。这也使得不可能有一个用户配置屏幕,让他们可以更改按键映射到哪个操作。是否有好的模式可以使用某种形式的函数回调来完成?


另请参见https://dev59.com/GHVC5IYBdhLWcg3wcgud。 - S.Lott
3个回答

17

您可以创建一个字典,其中键是输入,值是处理按键的函数:

def handle_quit():
  quit()

def handle_left():
    curpiece.shift(-1, 0)
    shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

def handle_right():
    curpiece.shift(1, 0)
    shadowpiece = curpiece.clone(); setupshadow(shadowpiece)

def handle_pause():
    if not paused:
        paused = True

branch = {
  K_q: handle_quit
  K_e: handle_pause
  K_LEFT: handle_left
  K_RIGHT: handle_right
}

for event in pygame.event.get():
    if event.type == KEYDOWN:
        branch[event.key]()

那么更改键名只需要修改字典的键。


我不确定,但我认为那些函数定义必须放在分支赋值的上方,否则你会得到一个充满None的词典。很好的答案,这也是我可能会这样做的方式。 - luqui
我猜这些仍然必须在main()循环内部定义,或者可能需要传递一些参数给GameState类。 - Claudiu
维护反向字典可能更好:func2keys = { handle_quit: [K_q], handle_left: [K_LEFT, K_a], ...}。如果配置更改,则生成一个新的 branch 字典。branch = dict((k, f) for f, keys in func2keys.items() for k in keys) - jfs

3

除了superjoe30的答案,您还可以使用两个映射级别(两个字典)

  • 键 => 命令字符串
  • 命令字符串 => 函数

我认为这会使允许用户自定义映射更容易。也就是说,用户可以将其键映射到“命令”而不是“函数名称”。


你也可以从函数的文档字符串中获取命令的名称。 - Paul Fisher
你可以使用带有__call__属性的对象来代替函数。 - jfs

2

我现在所做的是创建一种输入收集的类/函数/线程,该类/函数/线程将检查预定义的键->事件绑定列表。

类似于这样:

class InputHandler:
    def __init__ (self, eventDispatcher):
        self.keys = {}
        self.eventDispatcher = eventDispatcher
    def add_key_binding (self, key, event):
        self.keys.update((key, event,))
    def gather_input (self):
        for event in pygame.event.get():
            if event.type == KEYDOWN:
                event = self.keys.get(event.key, None)
                if not event is None:
                    self.eventDispatcher.dispatch(event)

....
inputHandler = InputHandler(EventDispatcher)
inputHandler.add_key_binding(K_q, "quit_event")
...
inputHandler.gather_input()
....

这基本上就是superjoe30正在做的事情,只不过我使用了一个事件分发系统,而不是直接调用回调函数,因此任何关心按键被按下的代码都可以监听该事件。

此外,按键可以轻松地绑定到不同的事件上,这些事件可以从配置文件或其他地方读取,任何未绑定到事件的按键都会被忽略。


如果事件不是空的,看起来更好。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接