Предположим, что вы, наслушавшись рассказов старших товарищей о некогда великом программисте Кармаке, вдохновились и решили написать свой шутер от первого лица. Вооружившись туториалами, вы справились созданием окна и инициализацией апи, и вот уже на экран выводится ваш первый в жизни треугольник.
Что еще нужно для полноценного шутера? Конечно же возможность вертеть головой при помощи мыши! Как это сделать? Правильный ответ на этот вопрос в наше время - используя WM_INPUT, но предположим на секунду, что вы по каким-то лишь вам одним ведомым причинам отвергли этот вариант, и решили реализовать mouselook также, как это было сделано например в Quake, потому что только в нем и живет дух старой школы.
Метод следующий:
1) Находим координаты центра экрана (окна)
2) Перемещаем указатель мыши в центр экрана
3) Периодически получаем текущие координаты указателя, считаем дельту и возвращаем указатель в центр
4) По вкусу устанавливаем ClipCursor/SetCapture, но это уже детали...
Вау! Гордясь собой, вы перемещаетесь по созданному вами миру, стремясь осмотреть созданный вами треугольник со всех сторон. Но тут вы услышали уведомление - это друг написал вам в инстаграмчике, приглашая на новый митинг Навального. Вы жмете Alt+Tab чтобы переключиться на браузер, и тут понимаете, какая с вами произошла неприятность - кровавая гэбня захватила ваш к... указатель мыши все время перемещается в центр экрана, даже когда вы работаете с другим окном!
Слегка отойдя от шока, вы понимаете, что вам нужно каким-то образом узнавать, когда пользователь переключился на игру (и тогда активировать работу с мышью) и когда он переключился на что-то другое (и тогда мышь деактивируется). Пошерстив MSDN (или как вариант - код кваки) вы узнаете о существовании сообщений WM_ACTIVATE и WM_ACTIVATEAPP. Они немного различаются, но для однооконного приложения, каким является типичная игра, разницы практически нет (хотя WM_ACTIVATE позволяет еще узнать о том, что окно свернуто).
Казалось бы - вот оно решение! Пользователь нажимает на окно - оно активируется, нажимает на другое - первое деактивируется. Все просто, не так ли?
К сожалению, нет. Вернее, не совсем...
Предположим, что вы долго работали над вашей игрой, и дошли до того момента, когда между запуском экзешника и появлением окна проходит пара секунд. Вы в очередной раз запустили вашу игру, но сразу после запуска экзешника случайно щелкнули по открытому окну папки, в которой экзешник располагался. Когда окно вашей игры наконец появилось, вы с удивлением обнаружили, что окно папки теперь находится поверх окна игры, а над ним бегает, перемещаясь в центр, указатель мыши. Конечно, это легко лечится альт-табом на окно игры, но все-таки сложно назвать это дружелюбием к пользователю...
Но постойте, ведь активация мыши завязана на WM_ACTIVATE, как же так получилось, что окно получило это сообщение, несмотря на то, что не является активным?
Все дело в том, что в многозадачной винде статус активного (active) окна имеет смысл только для треда, который это окно создал. Он ничего не говорит о том, имеет ли окно на самом деле фокус ввода в системе. Окно может быть активным в рамках своего треда, но не иметь при этом системого фокуса ввода. Самое гадкое при этом, что функции GetActiveWindow() и GetFocus() при этом будут вас убеждать что именно ваше окно, а не какое-то там другое является активным и имеет фокус.
Окно, имеющее системный фокус, называется foreground window, узнать, имеет ли ваше окно фокус можно используя функцию GetForegroundWindow(). У нее есть и собрат - функция SetForegroundWindow(). Так вот же оно - решение! Просто попросим систему дать фокус нашему окну...
К сожалению, в современных версиях винды приложение не может принудительно установить фокус ввода на свое окно. Подробнее об этом можно прочитать в документации к SetForegroundWindow.
Как же быть? Хороший вопрос...
Вам может придти в голову идея установить вашему окну стиль WS_EX_TOPMOST чтобы оно появлялось поверх всех остальных окон. Не делайте этого ни в коем случае! То, что ваше окно находится поверх всех остальных еще не говорит, что оно является foreground window! Если бы окно нашей гипотетической игры в приведенном выше примере имело этот стиль, то нажатие например Alt+F4 после появления окна игры, закрыло бы не окно игры, а то окно, которое вы нажали в последний момент перед появлением окна игры, например - чатик с Навальным...
Можно при получении WM_ACTIVATE дополнительно проверять GetForegroundWindow() но тогда при клике на окно вы не получите второй раз WM_ACTIVATE - окно-то уже активно! А имеет ли оно реальный фокус - кому какое дело...
Вообще существует два прямо противоположных взгляда на взаимодействие игры с системой:
1) Концепция старой школы предписывает игре считать себя главным и единственным куском кода в системе. Она предполагает агрессивный захват ввода, вывода и всех остальных ресурсов компа, рекомендацию закрыть все остальные приложения итд. Зависло? Ресет!
2) Более современная концепция предписывает игре считать себя таким же приложением как и все другие, уважать многозадачность и интересы пользователя. Зависло? Приносим глубочайшие извинения!
Сталкивались ли вы с приведенными выше проблемами? Как вы их решили? Есть ли в винде сообщение, которое посылается, когда приложение в действительности получает фокус ввода? Еще я заметил, что некоторые игры все-таки игнорят текущий фокус и запускаются поверх всего несмотря ни на что даже в современной винде. Как правило, это игры используещие D3D в полноэкранном режиме. Получается, что это отдельная фича в D3D, которая позволяет выкинуть такой фокус? Можно ли это как-то провернуть с обычным окном или на OpenGL?
Government-Man писал: и вот уже на экран выводится ваш первый в жизни треугольник.
А этот треугольник аппаратно-ускоренный или не очень?
Цитата:
Government-Man писал: Вы в очередной раз запустили вашу игру, но сразу после запуска экзешника случайно щелкнули по открытому окну папки, в которой экзешник располагался.
У меня был несколько иной баг. Допустим, что я запускал приложение не мышкой, а нажатием Enter, так вот движок успевал загрузиться раньше, чем я отпускал кнопку и генерировал её отпускание уже в игре. А я никак не мог вдуплить, ну почему с мышки - норм, а с клавы - такое. В итоге это приводило к забавному багу - отжатая кнопка генерировала команду, которая блуждала-блуждала, выходила в меню и запускала новую игру, там же как раз эвент на отпускание кнопки
В ксаше я выполняю оба-два действия
C++ Source Code:
SetForegroundWindow( host.hWnd );
SetFocus( host.hWnd );
Под XP проблем нету, а дышатка мне и даром не упала.
Цитата:
Government-Man писал: Концепция старой школы предписывает игре считать себя главным и единственным куском кода в системе. Она предполагает агрессивный захват ввода, вывода и всех остальных ресурсов компа
Ну я бы не сказал. Ксаш нормально отдаёт управление по альт-табу или просто по клику на другое окно. Я даже от аппаратной гаммы отказался в своё время по той же причине. А то как сделать упровление мышкой. Разве сейчас не только лишь все используют SDL2?
Я думал он уже давно дефакто стандарт для всех игровых движков.
Добавлено 11-03-2020 в 20:51:
Цитата:
ncuxonaT писал: Ты практически мою жизнь описал
Меньше всего я подозревал тебя в том, что ты хочешь сделать свою игру.
Добавлено 11-03-2020 в 21:29:
Вообще вот эта замута с мышкой, имеет под собой вполне конкретную задачу - полный контроль. Нет чёткой уверенности, что дельту надо получать синхронно с окончанием одного кадра. Иногда её надо получать несколько раз за кадр. Как WM_INPUT поможет это разрулить?
Дядя Миша писал: Вообще вот эта замута с мышкой, имеет под собой вполне конкретную задачу - полный контроль. Нет чёткой уверенности, что дельту надо получать синхронно с окончанием одного кадра. Иногда её надо получать несколько раз за кадр. Как WM_INPUT поможет это разрулить?
А зачем получать дельту несколько раз за кадр?
WM_INPUT никак не поможет это разрулить - наоборот, это пассивный способ ввода - приходится ждать пока система решит послать сообщение.