Методы управления вводом в больших играх

16

Существует ли стандартная методика управления вводом в больших играх. В настоящее время в моем проекте вся обработка ввода выполняется в игровом цикле, например так:

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

w4etwetewtwet
источник
С помощью менеджера событий вы можете запустить событие на входе и позволить всем остальным частям вашей игры регистрироваться на них.
Данияр
@danijar, что именно вы подразумеваете под менеджером событий, возможно ли, если бы вы могли предоставить какой-нибудь скелетный псевдокод, чтобы показать, о чем вы говорите?
w4etwetewtwet
3
связанные: gamedev.stackexchange.com/questions/48315/… gamedev.net/blog/355/…
Патрик Чачурски
1
Я написал ответ, чтобы подробно рассказать о менеджерах событий, которые являются для меня способом обработки ввода.
Данияр

Ответы:

12

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

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

Обратные вызовы могут быть std::functionобъектами, которые могут содержать лямбда-выражения. Ключи могут быть строками. Поскольку менеджер является глобальным, компоненты вашего приложения могут регистрироваться на ключи, запускаемые из других компонентов.

// in character controller
// at initialization time
Events->Register("Jump", [=]{
    // perform the movement
});

// in input controller
// inside the game loop
// note that I took the code structure from the question
case SDL_KEYDOWN:
    switch(event.key.keysym.sym) {
    case SDLK_c:
        Events->Fire("Jump");
        break;
    }
    break;

Вы даже можете расширить этот менеджер событий, чтобы разрешить передачу значений в качестве дополнительных аргументов. Шаблоны C ++ отлично подходят для этого. Вы можете использовать такую ​​систему, например, чтобы "WindowResize"событие передавало новый размер окна, чтобы прослушивающим компонентам не приходилось извлекать его самостоятельно. Это может немного уменьшить зависимости кода.

Events->Register<int>("LevelUp", [=](int NewLevel){ ... });

Я реализовал такой менеджер событий для своей игры. Если вам интересно, я выложу ссылку на код здесь.

Используя менеджер событий, вы можете легко транслировать входную информацию в вашем приложении. Более того, это дает хороший способ позволить пользователю настраивать привязки клавиш. Компоненты слушают семантические события вместо ключей напрямую ( "PlayerJump"вместо "KeyPressedSpace"). Тогда у вас может быть компонент отображения ввода, который прослушивает "KeyPressedSpace"и запускает любое действие, связанное пользователем с этим ключом.

Данияр
источник
4
Отличный ответ, спасибо. Хотя я хотел бы увидеть код, я не хочу его копировать, поэтому я не буду просить вас публиковать его, пока я не реализовал свой собственный, так как я узнаю больше таким образом.
w4etwetewtwet
Я только что подумала, могу ли я передать какую-либо функцию-член, подобную этой, или функция регистра не должна принимать AClass :: func, ограничивая ее функциями-членами одного класса
w4etwetewtwet
Это замечательная особенность лямбда-выражений в C ++, вы можете указать предложение захвата, [=]и ссылки на все локальные переменные, к которым обращается лямбда, будут скопированы. Так что вам не нужно передавать указатель this или что-то в этом роде. Но обратите внимание, что вы не можете хранить лямбды с предложением захвата в старых указателях функций Си . Тем не менее, C ++ std::functionработает нормально.
Данияр
std :: function очень медленная
TheStatehz
22

Разделите это на несколько слоев.

На самом нижнем уровне у вас есть необработанные входные события из ОС. Ввод с клавиатуры SDL, ввод с помощью мыши, ввод с помощью джойстика и т. Д. У вас может быть несколько платформ (SDL - это наименьший общий знаменатель, которому не хватает, например, нескольких форм ввода, о которых вам может потребоваться позднее).

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

Система ввода теперь имеет функцию преобразования низкоуровневого ввода в логические события высокого уровня. Игровая логика совершенно не заботится о том, чтобы пробел был нажат. Это волнует, что JUMP был нажат. Задача менеджера ввода состоит в том, чтобы собирать эти входные события низкого уровня и генерировать входные события высокого уровня. Он отвечает за знание того, что пробел и кнопка геймпада «А» соответствуют логической команде «Перейти». Он имеет дело с геймпадом, мышью и другими элементами управления. Он генерирует высокоуровневые логические события, которые настолько абстрагированы, насколько это возможно, от низкоуровневых элементов управления (здесь есть некоторые ограничения, но вы можете полностью абстрагироваться в общем случае).

Ваш контроллер персонажа затем получает эти события и обрабатывает эти высокоуровневые входные события для фактического ответа. На уровне платформы отправлено событие «Клавиша пробела». Система ввода получает это, просматривает свои таблицы / логику сопоставления и затем отправляет событие «Нажатый переход». Контроллер игровой логики / персонажа получает это событие, проверяет, действительно ли игроку разрешено прыгать, а затем испускает событие «Прыжок игрока» (или просто вызывает прыжок), которое остальная игровая логика использует для выполнения любых действий. ,

Все, что зависит от игровой логики, попадает в контроллер игрока. Все, что зависит от ОС, идет на уровне платформы. Все остальное переходит на уровень управления вводом.

Вот немного любительского искусства ASCII, чтобы описать это:

-----------------------------------------------------------------------
Platform Abstraction | Collect and forward OS input events
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
    Input Manager    | Translate OS input events into logical events
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
Character Controller | React to logical events and affect game play
-----------------------------------------------------------------------
                          | |
                          | |
                         \   /
                          \_/
-----------------------------------------------------------------------
      Game Logic     | React to player actions and provides feedback
-----------------------------------------------------------------------
Шон Миддледич
источник
Классный ASCII арт, но не то что надо, извините. Вместо этого я предлагаю использовать нумерованный список. В любом случае хороший ответ!
Данияр
1
@danijar: Эх, я экспериментировал, никогда раньше не пытался найти ответ. Больше работы, чем стоило, но гораздо меньше, чем работа с программой рисования. :)
Шон Миддледич
Хорошо, понятно :-)
Данияр
8
Лично я предпочитаю искусство ASCII больше, чем скучный нумерованный список.
Джесси Эмонд
@JesseEmond Эй, здесь для искусства?
Данияр