Существует ли стандартная методика управления вводом в больших играх. В настоящее время в моем проекте вся обработка ввода выполняется в игровом цикле, например так:
while(SDL_PollEvent(&event)){
switch(event.type){
case SDL_QUIT:
exit = 1;
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym){
case SDLK_c:
//do stuff
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
switch(event.button.button){
case SDL_BUTTON_MIDDLE:
//do stuff
break;
}
}
break;
}
(Я использую SDL, но я ожидаю, что основной практикой будут также библиотеки и фреймворки). Для большого проекта это не кажется лучшим решением. У меня может быть несколько объектов, желающих знать, что нажал пользователь, поэтому для этих объектов было бы больше смысла обрабатывать ввод. Однако все они не могут обрабатывать ввод, так как после получения события оно будет выталкиваться из буфера событий, поэтому другой объект не получит этот ввод. Какой метод чаще всего используется для противодействия этому?
Ответы:
Так как автор темы спросил, я подробно остановлюсь на менеджерах событий. Я думаю, что это хороший способ обработки ввода в игре.
Менеджер событий - это глобальный класс, который позволяет регистрировать функции обратного вызова для клавиш и запускать эти обратные вызовы. Менеджер событий сохраняет зарегистрированные функции в личном списке, сгруппированном по их ключу. Каждый раз, когда ключ срабатывает, все зарегистрированные обратные вызовы выполняются.
Обратные вызовы могут быть
std::function
объектами, которые могут содержать лямбда-выражения. Ключи могут быть строками. Поскольку менеджер является глобальным, компоненты вашего приложения могут регистрироваться на ключи, запускаемые из других компонентов.Вы даже можете расширить этот менеджер событий, чтобы разрешить передачу значений в качестве дополнительных аргументов. Шаблоны C ++ отлично подходят для этого. Вы можете использовать такую систему, например, чтобы
"WindowResize"
событие передавало новый размер окна, чтобы прослушивающим компонентам не приходилось извлекать его самостоятельно. Это может немного уменьшить зависимости кода.Я реализовал такой менеджер событий для своей игры. Если вам интересно, я выложу ссылку на код здесь.
Используя менеджер событий, вы можете легко транслировать входную информацию в вашем приложении. Более того, это дает хороший способ позволить пользователю настраивать привязки клавиш. Компоненты слушают семантические события вместо ключей напрямую (
"PlayerJump"
вместо"KeyPressedSpace"
). Тогда у вас может быть компонент отображения ввода, который прослушивает"KeyPressedSpace"
и запускает любое действие, связанное пользователем с этим ключом.источник
[=]
и ссылки на все локальные переменные, к которым обращается лямбда, будут скопированы. Так что вам не нужно передавать указатель this или что-то в этом роде. Но обратите внимание, что вы не можете хранить лямбды с предложением захвата в старых указателях функций Си . Тем не менее, C ++std::function
работает нормально.Разделите это на несколько слоев.
На самом нижнем уровне у вас есть необработанные входные события из ОС. Ввод с клавиатуры SDL, ввод с помощью мыши, ввод с помощью джойстика и т. Д. У вас может быть несколько платформ (SDL - это наименьший общий знаменатель, которому не хватает, например, нескольких форм ввода, о которых вам может потребоваться позднее).
Вы можете абстрагировать их с помощью пользовательского типа события очень низкого уровня, такого как «кнопка клавиатуры вниз» или тому подобное. Когда уровень вашей платформы (игровой цикл SDL) получает входные данные, он должен создать эти низкоуровневые события и затем направить их в менеджер ввода. Это можно сделать с помощью простых вызовов методов, функций обратного вызова, сложной системы событий, что бы вы ни хотели.
Система ввода теперь имеет функцию преобразования низкоуровневого ввода в логические события высокого уровня. Игровая логика совершенно не заботится о том, чтобы пробел был нажат. Это волнует, что JUMP был нажат. Задача менеджера ввода состоит в том, чтобы собирать эти входные события низкого уровня и генерировать входные события высокого уровня. Он отвечает за знание того, что пробел и кнопка геймпада «А» соответствуют логической команде «Перейти». Он имеет дело с геймпадом, мышью и другими элементами управления. Он генерирует высокоуровневые логические события, которые настолько абстрагированы, насколько это возможно, от низкоуровневых элементов управления (здесь есть некоторые ограничения, но вы можете полностью абстрагироваться в общем случае).
Ваш контроллер персонажа затем получает эти события и обрабатывает эти высокоуровневые входные события для фактического ответа. На уровне платформы отправлено событие «Клавиша пробела». Система ввода получает это, просматривает свои таблицы / логику сопоставления и затем отправляет событие «Нажатый переход». Контроллер игровой логики / персонажа получает это событие, проверяет, действительно ли игроку разрешено прыгать, а затем испускает событие «Прыжок игрока» (или просто вызывает прыжок), которое остальная игровая логика использует для выполнения любых действий. ,
Все, что зависит от игровой логики, попадает в контроллер игрока. Все, что зависит от ОС, идет на уровне платформы. Все остальное переходит на уровень управления вводом.
Вот немного любительского искусства ASCII, чтобы описать это:
источник