Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Hello, World!
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  22:56[Войти] | [Зарегистрироваться]

DirectX для начинающих — DirectInput API

Виктор Кода
дата публикации 08-04-2002 00:00

Прежде чем начать знакомиться с примерами по DirectInput, желательно изучить общий механизм работы этого API, т .к. он одинаков в общих чертах для всех устройств ввода. Впоследствии я не буду останавливаться на повторяющихся приёмах, т. к. объясню их суть здесь.

Последовательность действий при использовании DirectInput для получения пользовательского ввода в упрощённом виде может быть представлена следующими шагами:
  • Создание объекта DirectInput (интерфейс IDirectinput8).
  • Создание устройства (получения интерфейса IDirectInputDevice8, который будет инкапсулировать свойства устройства ввода).
  • Установка режима совместного доступа для выбранного устройства.
  • Установка формата данных для выбранного устройства.
  • В случае работы с буферизированным опросом задание размера буфера для хранения данных.
  • Захват устройства
  • Получение данных методом IDirectInputDevice.GetDeviceState().
  • По окончании работы освобождение устройства и удаление объектов DirectInput.
Для возможности пользоваться методами DirectInput необходимо для начала создать главный объект DirectInput. Для версии 8 необходимо вызывать функцию DirectInput8Create() а не DirectInputCreate() или DirectInputCreateEx(), как это было в предыдущих версиях. Первым параметром необходимо передать дескриптор экземпляра программы - его можно получить, вызвав GetModuleHandle( 0 ). Второй параметр - номер версии DirectInput. Обычно передаётся DIRECTINPUT_VERSION, но для совместимости с ранними версиями можно указать и другое значение, например $0300. Параметр riidltf может принимать только одно значение - IID_IDIRECTINPUT8. Четвёртый параметр является указателем на интерфейс IDirectInput8. Последний параметр служит для агрегирования COM и не используется.

Если этот шаг одинаков для работы со всеми типами устройств ввода, то дальнейшие будут немного различаться в чертах. Для работы с каким-либо типом устройства ввода необходимо создать объект, который будет инкапсулировать в себе свойства этого устройства. Делается это методом IDirectInput.CreateDevice(). Первым параметром передаётся GUID экземпляра устройства. Этот идентификатор может быть получен, например, перечислением установленных в компьютере устройств ввода. Кроме того, можно использовать предопределённый GUID. Для каждого типа устройства определён свой GUID в файле DirectInput8.pas - для клавиатуры это GUID_SysKeyboard, для мыши - GUID_SysMouse и т. д. Вторым параметром передаётся указатель на интерфейс IDirectInputDevice8. Третий параметр не используется.

Следующий шаг не требует серьёзных раздумий и фактически является обязательным - это определение формата данных для созданного устройства методом IDirectInputDevice8.SetDataFormat(). Формат структуры даст DirectInput знать об особенностях опрашиваемого устройства ввода. Для каждого типа существует предопределённый формат - ими мы и будем пользоваться. В крайне редких случаях нужно задавать собственный формат - для нестандартных устройств ввода. Для клавиатуры воспользуемся структурой c_dfDIKeyboard - загляните в заголовочный файл - её поля определяются довольно сложным образом. Для мыши следует указать структуру c_dfDIMouse. Учтите, что в метод IDirectInputDevice8.SetDataFormat() нужно передать адрес структуры.

Следующим в списке действий является установка уровня совместного доступа - делается это методом IDirectInputDevice8.SetCooperativeLevel(). Первый параметр - дескриптор главного окна программы. Второй - комбинация флагов, которые и определяют уровень доступа. Используются следующие флаги:
  • DISCL_FOREGROUND - программа может получать данные от устройства ввода, только будучи активной.
  • DISCL_BACKGROUND - опрос будет возможен даже в неактивном состоянии.
  • DISCL_EXCLUSIVE - программа получит монопольный доступ к устройству, никакое другое приложение не сможет получить монопольный доступ, хотя совместный доступ может быть получен.
  • DISCL_NONEXCLUSIVE - программа получит совместный доступ, другие приложения также будут иметь доступ к устройству ввода.
  • DISCL_NOWINKEY - блокируется клавиша Windows.
Фактически установка уровня кооперации состоит из двух этапов: необходимо указать тип доступа к устройству ввода - активный(foreground) или фоновый (background), а также назначить режим работы с выбранным устройством - монопольный (exclusive) или совместный (nonexclusive).

Пара флагов DISCL_FOREGROUND и DISCL_BACKGROUND взаимно исключают друг друга. То же самое относится и паре DISCL_EXCLUSIVE и DISCL_NONEXCLUSIVE. Невозможно установить сразу оба флага из каждой пары, но один из них обязательно должен быть установлен.

Невозможно установить уровень кооперации для мыши и клавиатуры, включающий одновременно флаги DISCL_EXCLUSIVE и DISCL_BACKGROUND, т. к. такая комбинация полностью лишает саму Windows доступа к этим устройствам.

Последний флаг, DISCL_NOWINKEY, может применяться совместно с DISCL_NONEXCLUSIVE, в режиме DISCL_EXCLUSIVE клавиша Windows блокируется автоматически.

Для успешного получения данных от устройства ввода необходимо "захватить" его методом IDirectinputDevice8.Acquire().

Для получения собственно данных необходимо вызвать метод IDirectInputDevice8.GetDeviceState() или IDirectInputDevice8.GetDeviceData().

Мы рассмотрели все шаги, необходимые для успешной организации опроса различных устройств с помощью DirectInput API. Теперь можно рассмотреть конкретную реализацию механизма опроса в каждом из примеров.

Keyboard

Этот пример демонстрирует работу DirectInput для опроса клавиатуры. Для наглядности я включил в программу возможность выбора метода опроса клавиатуры - методом DirectInput и стандартным Windows Messaging. Вы сразу почувствуете разницу в скорости работы и убедитесь в том, что методы, пригодные для набора текста с клавиатуры, мало приспособлены для управления в быстро меняющейся обстановке - например, в играх. Причина том, что сообщение WM_KEYDOWN обрабатывается наравне с десятками других, которыми операционная система заваливает ваше окно, и не только ваше, но и все другие - а их очень много даже тогда, когда на экране и в панели задач не видно ни одного. К тому же Windows Messaging не в состоянии определить нажатие двух и более клавиш одновременно - и это тоже большой недостаток. Правды ради нужно сказать, что в Windows существует стандартная функция GetKeyboardState() - она общается напрямую с драйвером клавиатуры и работает не медленнее, чем DirectInput, чем немного умаляет достоинства этой компоненты. Впрочем, при создании DirectInput все усилия разработчиков были направлены на поддержку игровых контроллеров самых разных типов и других нестандартных устройств ввода - и здесь возможности Windows Messaging весьма скромны.

После создания объекта DirectInput8, объекта для работы с клавиатурой и т. д. всё готово для опроса клавиатуры - он производится из процедуры TForm1.Idle() постоянным вызовом функции UpdateKeyboardState(). Рассмотрим её подробнее.

Первым делом получаем данные уже упоминавшимся методом IDirectInputDevice8.GetDeviceState(). Второй параметр зависит от типа опрашиваемого устройства и в данном случае для клавиатры необходимо передать адрес массива из 256 элементов типа Byte. Первый параметр всегда является размером структуры, передаваемой вторым параметром.

Заметьте, что я не проверил возвращённое значение макросом FAILED() - ошибочный результат может быть связан не только с неправильной инициализацией объектов DirectInput, но и в случае с так называемой "потерей" устройства ввода. Конечно, само устройство никуда не теряется, а остаётся на столе или в руках пользователя - но вот получение данных от него прекращается. Трудно сказать, почему это происходит, но это так. В этом случае необходимо сравнить возвращенное значение с DIERR_INPUTLOST и если это так, то снова захватить устройство. Если при повторном опросе снова возникает ошибка, то прекращаем работу - может быть, устройство отсоединили от компьютера или вообще выключили.

Как же узнать состояние клавиш клавиатуры? После удачного опроса массив из элементов типа Byte оказывается заполненным в соответствии с кодами нажатых клавиш. Элемент массива со значением $080 соответствует нажатой клавише, иначе - не нажатой. Чтобы упростить проверку массива, в заголовочном файле определены мнемонические константы для всех клавиш клавиатуры, например DIK_ESCAPE соответствует клавише Escape. Вообще, массив значений предоставляет возможность одновременного получения информации о нескольких нажатых клавишах клавиатуры или даже обо всех клавишах сразу (если вы искусно прижмёте её большой подушкой :-) ).

Вот и все действия для работы в клавиатурой средствами DirectInput! Думаю, с удалением объектов вы разберётесь с самостоятельно

Mouse

Этот пример демонстрирует возможность буферизированного опроса мыши с помощью DirectInput. Буферизированный опрос подразумевает получение данных от мыши из специального буфера, информация в котором хранится последовательно в хронологичном порядке возникновения событий.

Я не буду подробно описывать работу этого примера, остановлюсь лишь на самых важных деталях. Для получения буферизированных данных необходимо задать размер буфера. Для этого служит метод IDirectInputDevice8.SetProperty(). Первым параметром необходимо передать тип устанавливаемого свойства - воспользуемся константой DIPROP_BUFFERSIZE, тем самым указав, что мы желаем установить размер буфера для данных. Вторым параметром передаём адрес структуры TDIPROPDWORD. Как всегда, её нужно обнулить и заполнить поля diph.dwSize и diph.dwHeaderSize размерами структур TDIPROPDWORD и TDIPROPHEADER соответственно. Размер буфера задаётся в поле dwData.

Интересен механизм получения данных. Т. к. мы будем получать информацию из буфера, опрос необходимо организовать в виде цикла while to, где условием прекращения цикла будет отсутствие неизвлечённых данных из буфера. За один цикл из буфера извлекается один элемент (например, ось X, ось Y, клавиша 1 и т. д.). Опрос производится методом IDirectInputDevice8.GetDeviceData(). Этот метод используется только в случае работы с буфером. Первый параметр представляет собой размер структуры TDIDEVICEOBJECTDATA, адрес которой передаётся вторым параметром. Эта структура и позволит нам узнать о состоянии элемента мыши. Третий параметр - переменная типа DWORD, передаваемая по ссылке, после вызова метода содержит количество оставшихся в буфере неизвлечённых элементов. Когда все элементы извлечены, переменная равна 0. Именно её мы используем в качестве условия в цикле while to. Последнему параметру метода нужно передать 0.

Узнать, какой элемент извлечён из буфера, позволяет поле dwOfs структуры TDIDEVICEOBJECTDATA. Его значение меняется в каждом новом цикле и сравнивается с предопределёнными константами из файла DirectInput8.pas - например DIMOFS_BUTTON0 свидетельствует о том, что извлечён элемент данных, отвечающий за состояние первой кнопки мыши. Информация о состоянии того или иного объекта устройства вода содержится в поле dwData и тоже меняется с каждым новым циклом.

Заметьте, интерфейс функции UpdateMouseState() состоит из двух переменных типа DWORD, передаваемых по ссылке. В них записываются величины смещения координат мыши относительно прежних координат - например -5, +13. Получив данные о смещении, необходимо вычислить абсолютные координаты - делается это уже в процедуре TForm1.Idle().

А вот как получить данные от колёсика, я не знаю :-(

Mouse2

Этот пример демонстрирует работу с мышью путём получения непосредственных данных. Всё осталось на прежних местах, изменился лишь алгоритм опроса. Теперь мы снова используем метод IDirectInputDevice8.GetDeviceState(), передавая уже размер и адрес структуры TDIMOUSESTATE.

Если вы немного поигрались с мышкой в предыдущем примере, то наверняка заметили, что стрелка двигалась как-то немного неестественно, не так, как это происходит в Windows. К тому же я по своему опыту знаю, что и во многих серьёзных играх наблюдается та же проблема - курсор передвигается не совсем привычным образом. Секрет в том, что в Windows применяется одна хитрость - курсор перемещается не с фиксированной скоростью, а тем быстрее, чем на более большее расстояние вы сместили мышь за один условный промежуток времени. Включите отображение шлейфа для курсора в свойствах апплета Мышь Панели управления и понаблюдайте за ним - ну как?

Я попытался реализовать похожий механизм в таком алгоритме:
if ms.lX < 0 then ms.lX := Round( ms.lX * CURSOR_SPEED ) else if ms.lX > 0 then ms.lX := Round( ms.lX * CURSOR_SPEED );
if ms.lY < 0 then ms.lY := Round( ms.lY * CURSOR_SPEED ) else if ms.lY > 0 then ms.lY := Round( ms.lY * CURSOR_SPEED );
Алгоритм не универсален и не очень гибок, предлагаю вам усовершенствовать его. Если же эти строки закомментировать, то различий в работе между этим и предыдущим примером вы не увидите.

Joystick

Ну, все мы знаем, что такое джойстик и ему подобные игровые устройства управления. Главным образом они используются в играх для управления персонажем или например, самолётом. Есть такие игры, в которых управление с клавиатуры противопоказано (например, файтинги и авиасимуляторы). Поэтому часто игроманы покупают всякие штурвалы, джойстики, рули и геймпады для получения полного наслаждения от игры. Чтобы вы имели хоть какое-то представление о том, как выглядят современные игровые контроллеры, я собрал несколько фотографий с их изображением папке Show.

Win32 API содержит базовый набор мультимедиа-функций для работы с такими устройствами, но их просто недостаточно для поддержки всех современных технологий в этом направлении игровой индустрии - например, отсутствует поддержка force-feedback.

Реализовать опрос игровых контроллеров ненамного сложнее, чем например, опрос клавиатуры. Основным отличием является то, что сперва контроллер надо найти, а потом уже получать от него данные - ведь он не обязательный атрибут ПК. Откройте проект и найдите функцию InitDirectInput(). Сперва мы создаём главный объект DirecInput8 - если вы изучали примеры последовательно, то для вас уже должно быть всё понятно. Затем нужно методом IDirectInput8.EnumDevices() перечислить установленные устройства ввода. Первый параметр - константа, определяющая, какие устройства нужно перечислить. При указании DI8DEVCLASS_GAMECTRL будут перечислены только устройства, относящиеся к классу игровых контроллеров. Указав, например, DI8DEVCLASS_ALL, добъёмся перечисления ВСЕХ устройств ввода. Второй параметр представляет собой адрес функции обратного вызова, которая будет вызываться для для каждого найденного в системе устройства. Формат функции будет рассмотрен ниже. Третий параметр pvRef позволяет передать в функцию какие-то данные, например, структуру или строку. В SDK 8 в одном из примеров через неё передаётся дескриптор главного окна. Последний флаг позволяет фильтровать перечисляемые устройства. В данном случае необходимо указать константу DIEDFL_ATTACHEDONLY, что позволит перечислить только подключенные устройства ввода. Константа DIEDFL_ALLDEVICES позволит найти все устройства, для которых установлены соответствующие драйверы, даже если получить данные от них будет невозможно.

Ну а теперь вернёмся ко второму параметру рассматриваемого метода. Чтобы произвести перечисление устройств ввода, в программе должна быть определена функция с произвольным именем, но имеющая следующий прототип:
TDIEnumDevicesCallbackA = function (var lpddi : TDIDeviceInstanceA; pvRef : Pointer) : Integer; stdcall; 
Наибольший интерес представляет структура TDIDEVICEINSTANCE. Она содержит разнообразную информацию об устройстве ввода. Как только будет найдено первое, метод IDirectInput8.EnumDevices() вызовет указанную callback-функцию и эта структура будет инициализирована соответствующим образом. Т. к. для простоты примера используется первое найденное устройство, то тут же и создадим объект для работы с ним методом IDirectInputDevice8.CreateDevice(). В данном случае необходимо указать конкретный GUID устройства - он находится в поле guidInstance структуры TDIDEVICEINSTANCE. Для наглядности я осуществил определение типа найденного устройства - это делается макросом GET_DIDEVICE_TYPE() - единственным параметром нужно указать поле dwDevType давешней структуры TDIDEVICEINSTANCE. Ещё определяется количество кнопок и осей устройства - думаю, с этим в разберётесь самостоятельно.

Если нас устроил найденный контроллер, функция обратного вызова должна вернуть DIENUM_STOP, иначе - DIENUM_CONTINUE, чтобы продолжить перечисление.

Вернёмся в функцию InitDirectInput() - следующими шагами будет установка формата данных и уровня кооперации. Заметьте, что в случае с игровым контроллером можно вести себя подобно хищнику - свободно устанавливаются флаги DISCL_BACKGROUND и DISCL_EXCLUSIVE.

Следующим действием необходимо указать область диапазона координат, в которых будут представляться данные от осей. Дело в том, что крайние значения положения рукоятки штурвала или Pad-a геймпада могут представляться разными значениями. Если явно не указать этот интервал, то, например, крайнее левое положение рукоятки будет представлено значением 65535, а крайнее правое - 0. Центральное положение будет представлено значением 32768. Это не всегда удобно, хотя для минимизации кодирования можно и не задавать интервал. Если же вы решите сделать это, то есть два способа - перечислить оси и для каждой найденной установить интервал, или самостоятельно установить инервал для каждой оси. В SDK 8 представлен пример только с перечислением осей. Поступим так и мы.

Перечисление каких-либо элементов устройства подобно перечислению самих устройств. Производится оно методом IDirectInput8.EnumObject(). Первым параметром необходимо передать функцию обратного вызова, второй служит для передачи каких-либо данных в эту функцию, третий отвечает за тип перечисляемых объектов. Константа DIDFT_AXIS позволит перечислить все оси, DIDFT_BUTTON - кнопки всех типов и т. д.

Для изменение какого-либо свойства составного элемента предназначен метод IDirectinputDevice8.SetProperty(). Первый параметр - тип устанавливаемого свойства. Константа DIPROP_RANGE предназначена для установки области диапазона координат. Втрой параметр - поле diph структуры TDIPROPRANGE. Именно эта структура служит для задания области координат. Думаю, вы разберётесь с её инициализацией сами с помощью Help'а SDK. Замечу, что если поля lMin и lMax имеют по модулю одинаковое значение, то центральное положение рукоятки (и ей подобных элементов) будет представлено значением 0. Поле lMin не может быть больше lMax.

После этого приводится в соответствие GUI в окне - делаются доступными только те метки, для которых обнаружена соответствующая ось или ползунок. Узнать, какая ось перечислена, можно, сравнив поле guidType структуры TDIDeviceObjectInstanceA с одним из предопределённых значений. К сожалению, текущая реализация языка Pascal не позволяет сравнить обе структуры за один "присест" в выражении вида

if  lpddoi.guidType = GUID_XAxis then...
поэтому пришлось прибегнуть к отдельной процедуре, сравнивающей оба GUID поэлементно. Хорошо ещё, что не пришлось сравнивать структуры, например, из 256 элементов.

Осталось только "захватить" устройство и начать получать от него данные - делается это методом IDirectInputDevice8.GetDeviceData(). Только теперь используется структура TDIJOYSTATE. Обратите внимание, что дополнительно нужно вызвать метод IDirectInputDevice8.Poll(). Это необходимо сделать для большинства устройств, но не для всех.

Joystick2

Этот пример подобен предыдущему, но здесь я добавил возможность применения калибровки игровых контроллеров. По-видимому, такая калибровка производится DirectInput по-умолчанию, но можно и самому поработать над ней.

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

Обычно у многих джойстиков возникают паразитные колебания в тот момент, когда рукоятка находится в центральном положении и вы даже не касаетесь её рукой. К тому же, часто для поворота персонажа в игре приходится отклонять ручку управления в крайние положения с большим давлением, что может привести к быстрому износу контактов. Чтобы избежать этих неудобств, применяются так называемые области покоя (deadzone) и насыщения (saturation).

Область покоя для оси - это область координат, в пределах которой положение рукоятки воспринимается как центральное. Даже если вы отклонили её, но текущие координаты находятся в области покоя, DirectInput будет считать, что рукоятка не сдвинута. Размер области должен быть представлен значением от 0 до 10000. Например, при значении 5000 областью покоя будет считаться половина пространства оси. При значении 10000, вне зависимости от перемещения рукоятки вдоль этой оси, будет считаться что она находится в центральном положении. При нулевом значении могут возникать паразитные колебания.

Облать насыщения обычно состоит из двух регионов, в пределах которых положение рукоятки джойстика воспринимается как крайнее.Эти два региона располагаются на противоположных концах оси и предназначены для облегчения достижения крайних положений рукоятки. Размер области насыщения также задаётся в пределах от 0 до 10000. Если установить размер зоны насыщения раным 90000, будет считаться, что ручка джойстика уже достигла своего крайнего положения, пройдя только 90% расстояния от центральной позиции до края. Значение 5000 приведёт к тому, что отклонять джойстик нужно будет лишь слегка. Если облать насыщения равна 0 и область покоя также равна 0, то паразитные колебания могут привести к тому, что координаты положения ручки начнут "гулять" по краям зоны координат и вы с изумлением будете наблюдать, как ваш персонаж в игре совершенно произвольно бегает из стороны в сторону.

Я не буду описывать действия, необходимые для калибровки джойстика - думаю, вы разберётесь с ними самостоятельно. В крайнем случае пользуйтесь Help'ом.

Запустите проект и подвигайте ползунки TrackBar'ов. В моём случае при нулевом значении deadzone и среднем saturation возникали паразитные колебания. При смещении верхнего ползунка на одно деление вправо колебания исчезали. Конечно, для разных устройств нужны разные настройки. К сожалению, "прочувствовать" изменение области насыщения мне как-то не удалось, потому что при тестировании я пользовался геймпадом - у этих устройств вместо рукоятки небольшой "диск", или Pad, и двигать его нужно на очень небольшие расстояния. Но для джойстиков всё дожно быть заметно очень сильно.

Виктор Кода




Смотрите также материалы по темам:
[DirectInput]

 Обсуждение материала [ 31-05-2013 08:14 ] 1 сообщение
  
Время на сайте: GMT минус 5 часов

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

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования