HLFX.Ru Forum
Показать все 5 сообщений этой темы на одной странице

HLFX.Ru Forum (https://hlfx.ru/forum/index.php)
- Технические вопросы (https://hlfx.ru/forum/forumdisplay.php?forumid=20)
-- WM_ACTIVATE considered harmful (https://hlfx.ru/forum/showthread.php?threadid=5462)


Отправлено Government-Man 11-03-2020 в 17:29:

WM_ACTIVATE considered harmful

Давайте проведем мысленный эксперимент.

Предположим, что вы, наслушавшись рассказов старших товарищей о некогда великом программисте Кармаке, вдохновились и решили написать свой шутер от первого лица. Вооружившись туториалами, вы справились созданием окна и инициализацией апи, и вот уже на экран выводится ваш первый в жизни треугольник.

Что еще нужно для полноценного шутера? Конечно же возможность вертеть головой при помощи мыши! Как это сделать? Правильный ответ на этот вопрос в наше время - используя 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?


Отправлено ncuxonaT 11-03-2020 в 17:39:

Ты практически мою жизнь описал


Отправлено Дядя Миша 11-03-2020 в 18:29:

Цитата:
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 поможет это разрулить?

__________________
My Projects: download page

F.A.Q по XashNT
Блог разработчика в телеграме

Цитата:

C:\DOCUME~1\C4C5~1\LOCALS~1\Temp\a33328if(72) : see declaration of 'size_t'


Отправлено Government-Man 11-03-2020 в 18:50:

Цитата:
Дядя Миша писал:
Вообще вот эта замута с мышкой, имеет под собой вполне конкретную задачу - полный контроль. Нет чёткой уверенности, что дельту надо получать синхронно с окончанием одного кадра. Иногда её надо получать несколько раз за кадр. Как WM_INPUT поможет это разрулить?


А зачем получать дельту несколько раз за кадр?

WM_INPUT никак не поможет это разрулить - наоборот, это пассивный способ ввода - приходится ждать пока система решит послать сообщение.


Отправлено Дядя Миша 11-03-2020 в 18:56:

Цитата:
Government-Man писал:
А зачем получать дельту несколько раз за кадр?

Конкретно в кваке это был такой способ борьбы с тормозами. Но уже в ку2 от него отказались.

Цитата:
Government-Man писал:
приходится ждать пока система решит послать сообщение

не нравится мне это совсем. Движок не та система, чтобы ждать подобные вещи. Вон в новом дууме для инпута вообще отдельный поток.

__________________
My Projects: download page

F.A.Q по XashNT
Блог разработчика в телеграме

Цитата:

C:\DOCUME~1\C4C5~1\LOCALS~1\Temp\a33328if(72) : see declaration of 'size_t'


Временная зона GMT. Текущее время 10:29.
Показать все 5 сообщений этой темы на одной странице

На основе vBulletin версии 2.3.0
Авторское право © Jelsoft Enterprises Limited 2000 - 2002.
Дизайн и программирование: Crystice Softworks © 2005 - 2024