Эффективная обработка ввода с клавиатуры

Каков хороший способ реализации обработки клавиатуры? На любом языке, где я пишу интерактивную программу с клавиатурой (например, игру в тетрис), я получаю код, который выглядит примерно так:

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)

(сокращенно). Мне это не нравится, так как это должно выполняться в моем основном цикле, и это портит все части программы. Это также делает невозможным наличие экрана конфигурации пользователя, где они могут изменить, какая клавиша соответствует какому действию. Есть ли хороший шаблон для этого с использованием какой-либо формы обратных вызовов функций?


person Claudiu    schedule 23.11.2008    source источник
comment
Также см. stackoverflow.com/questions/292095/   -  person S.Lott    schedule 23.11.2008


Ответы (3)


Вы можете создать словарь, в котором ключи являются вводом, а значение — функцией, которая обрабатывает нажатие клавиши:

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]()

Тогда изменение ключей — это вопрос изменения ключей словаря.

person andrewrk    schedule 23.11.2008
comment
Я не уверен, но я считаю, что эти определения функций должны идти выше назначения ветвей, иначе вы просто получите словарь, полный None. Хороший ответ, хотя, вероятно, я бы тоже так поступил. - person luqui; 23.11.2008
comment
я предполагаю, что они все равно должны быть определены внутри цикла main() - или, возможно, передать какой-то аргумент классу GameState - person Claudiu; 24.11.2008
comment
Возможно, лучше поддерживать обратный словарь: 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) - person jfs; 30.11.2008

в дополнение к ответу superjoe30 вы можете использовать два уровня сопоставления (два словаря)

  • ключ => командная строка
  • командная строка => функция

Я думаю, что это упростит разрешение пользовательских сопоставлений. то есть, чтобы пользователи могли сопоставлять свои клавиши с «командами», а не с «именем функции».

person hasen    schedule 23.11.2008
comment
Вы также можете получить имя команды из строки документации функции. - person Paul Fisher; 23.11.2008
comment
Вы можете использовать объекты с атрибутом __call__ вместо функций. - person jfs; 30.11.2008

Что я делаю в настоящее время, так это имею какой-то класс/функцию/поток сбора входных данных, который будет проверять список предопределенных привязок key-> event.

Что-то вроде этого:

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, за исключением того, что вместо прямого вызова обратных вызовов я добавляю еще один уровень разделения, используя систему диспетчеризации событий, так что любой код, который заботится о нажатых клавишах, просто прослушивает это событие.

Кроме того, ключи могут быть легко привязаны к различным событиям, которые можно прочитать из файла конфигурации или чего-то еще, и любой ключ, который не привязан к событию, просто игнорируется.

person Community    schedule 24.11.2008
comment
if event is not None выглядит лучше. - person jfs; 30.11.2008