Графика DirectX в Delphi - Михаил Краснов
Шрифт:
Интервал:
Закладка:
Прежде всего, необходимо указать, задается ли исключительный доступ к устройству или нет (флаги DISCL_EXCLUSIVE и DISCL_NONEXCLUSIVE). В этом примере устанавливаю неисключительный доступ. Для стандартного устройства разница между ними невелика, библиотека Directlnput не может позволить никакому приложению захватить клавиатуру монопольно. Просто эксклюзивный доступ может привести к помехам в работе с устройством других приложений.
Помимо эксклюзивности обязательно необходимо задать активность режима (указать один из флагов DISCL^BACKGROUND или DISCL_FOREGROOND). Первый флаг соответствует режиму, когда приложение имеет доступ к устройству ввода всегда, даже когда не имеет активности. Если вы запустите две копии этой программы, то обе они будут реагировать на нажатие клавиш, и по нажатии клавиши <Esc> завершат работу обе копии.
Следующие действия при инициализации связаны с выбранной схемой получения доступа к данным. Можно использовать данные двух видов: непосредственные (immediate) и буферизованные (buffered).
При работе с клавиатурой по первой схеме приложение периодически опрашивает клавиши, получая данные о каждой из них: нажата она или нет. Вторая схема состоит в том, что приложение считывает буфер, в котором хранятся данные о произошедших со времени последнего опроса событиях связанных с устройством: какие клавиши были нажаты, какие были отпущены.
Наш пример позволяет применить обе схемы, но первоначально настроен на вторую, буферизованную, схему. Для нее надо задать размер буфера, и поэтому используется вспомогательная структура, передающаяся аргументом метода setProperty. Размер буфера мы задаем равным значению константы проекта:
const
SAMPLE_BUFFER_SIZE = 8;
Запомните, что для схемы непосредственного опроса эти действия не нужны.
Заканчивается код инициализации захватом устройства, получением доступа к нему, вызовом метода Acquire объекта, связанного с устройством. Теперь мы можем получать данные с устройства, если оно доступно и все подготовительные шаги были успешны.
Вызывается код инициализации при создании формы, в случае неудачи выводится сообщение:
procedure TfrmDX.FormCreate(Sender: TObject);
var
hRet : HRESULT;
begin
hRet := OnCreateDevice; // Инициализация устройства
if Failed (hRet) then MessageDlg(DIErrorString(Error), mtError,
[mbAbort], 0);
end;
Ошибки возможны при неверном указании параметров, также они появляются при занятости устройства. Если сейчас запущено приложение, имеющее исключительный доступ к клавиатуре, то у нас могут возникнуть проблемы с захватом устройства. В этой ситуации следует вызывать метод Acquire до тех пор, пока не будет установлена связь.
В нашем примере, после установления связи с устройством происходит беспрерывный вызов функции чтения буферизованных данных:
function TfrmDX.ReadBufferedData : HRESULT;
var
didod : Array [0..SAMPLE_BUFFER_SIZE - 1] of TDIDEVICEOBJECTDATA;
dwElements : DWORD;
i : DWORD;
hRet : HRESULT;
s : String;
begin
if DIKeyboard = nil then begin
Result := DI_OK;
Exit
end;
// Считываем данные из буфера
hRet := DIKeyboard.GetDeviceData (SizeOf(TDIDEVICEOBJECTDATA),
@didod, dwElements, 0);
if Failed (hRet) then begin // Восстанавливаем связь
hRet := DIKeyboard.Acquire;
while hRet = DIERR_INPUTLOST do
hRet := DIKeyboard.Acquire;
end;
// Буфер не пустой
if dwElements <> 0 then
for i := 0 to dwElements - 1 do begin
if didod[i].dwData and $80 <> 0 // Клавиша нажата
then s := 'D'
else s := 'U';
Memol.Lines.Add (Format ('Ox%02x%s', [didod[i].dwOfs, s] ) ) ;
if didod[i] .dwOfs = DIK__ESCAPE then Close;
end;
Result := DI_OK; // Нулевое значение, признак успешности
end;
Метод GetDeviceData объекта, ассоциированного с устройством, позволяет осуществить собственно считывание данных из буфера. Смысл первого аргумента прозрачен: это размер структуры, предназначенной для хранения. Второй аргумент - указатель на массив элементов данной структуры. В качестве значения третьего аргумента устанавливается количество считанных из буфера данных. Последний аргумент может быть нулем или константой DIGDD_PEEK (во втором случае буфер не будет очищаться после считывания данных).
Если функция возвращает ненулевое значение, то, скорее всего, потеряна связь с устройством. Тогда необходимо снова установить эту связь, вызвав метод Acquire. В библиотеке Directlnput отсутствуют какие-либо специальные методы восстановления, а устанавливать связь можно сколько угодно раз, т. к. лишние вызовы этой функции игнорируются.
Скан-коды клавиш содержатся в поле dwOfs структуры TDIDEVICEOBJECTDATA, значение поля dwData позволяет узнать, какое событие произошло, нажата ли клавиша или отпущена. Если это значение равно 128, то клавиша опущена. В нашем примере к коду клавиши в этом случае приписывается буква "D", иначе - "U".
Вам не обязательно помнить наизусть коды всех клавиш, можете пользоваться символическими константами. Для примера я показал, как выделить нажатие клавиши <Esc>.
После завершения работы освобождаем устройство и память, занятую объектами:
procedure TfrmDX.FormDestroy(Sender: TObject);
begin oif Assigned (DIKeyboard) then DIKeyboard.Unacquire; // Завершить диалог
if Assigned (DIKeyboard) then DIKeyboard := nil;
if Assigned (DInput) then DInput := nil;
end;
Поработайте с примером и обратите внимание, что можно отследить состояние максимум четырех клавиш одновременно.
Непосредственная схема работы с клавиатурой используется чаще, чем буферизованная, напоминаю, что состоит она в том, что в необходимые моменты происходит опрос всех клавиш. Удалите из кода обработчика onidle вызов процедуры буферного опроса клавиатуры и снимите комментарий со следующей далее строки. В коде инициализации удалите все, связанное с заданием размера буфера. Запустите проект и нажмите несколько клавиш (тоже максимум четыре) одновременно, в Memo выведутся коды всех нажатых клавиш:
function TfrmDX.ReadlinmediateData : HRESULT;
var
hRet : HRESULT;
diks : Array [0..255] of BYTE; // Массив состояния клавиатуры
i : Integer;
sMulti : String;
begin
if DIKeyboard = nil then begin
Result := DI_OK;
Exit
end;
ZeroMemory(@diks, SizeOf(diks)); // Подготавливаем массив
hRet := DIKeyboard.GetDeviceState(SizeOf(diks), Sdiks); // Заполняем
if Failed (hRet) then begin // Требуется восстановить связь
hRet := DIKeyboard.Acquire;
while hRet = DIERR_INPUTLOST do
hRet := DIKeyboard.Acquire;
end;
sMulti := '';
for i := 0 to 255 do // Вывод кодов нажатых клавиш
if diks[i] and $80 <> 0
then sMulti := sMulti + ' ' + Format ('Ox%02x', [i]);
Memol.Lines.Add (sMulti);
Result := DI_OK;
end;
Непосредственная схема основана на использовании метода GetDeviceState, по вызову которого массив заполняется данными о состоянии клавиш, точно также здесь возможны значения 0 и 128.
В примере происходит опрос состояния всех клавиш, что не обязательно делать, если вас интересуют только некоторые из них. Например, если требуется осуществить выход по нажатии клавиши <Esc>, можно не пробегать в цикле по всем элементам массива, а обратиться только к единственному:
if diks [DIK^ESCAPE] = 128 then Close;
С помощью непосредственной схемы доступа легко обрабатывать нажатие нескольких клавиш одновременно, чем и пользуются часто в играх, как, например, в очередном примере - проекте каталога Ех06. От предыдущего варианта нашей тестовой игры пример отличается только тем, что здесь для управления используется библиотека Directlnput. Скорость ввода стала чрезвычайно стремительной, воин теперь резво передвигается по нажатии клавиш, буквально быстрее пули. Хотя шаг его нисколько не увеличился. Одним махом происходит обработка нескольких клавиш, и можно, например, стрелять вверх и вбок одновременно, или двигаться вправо и стрелять вверх. Пули тоже вылетают на порядок быстрее, и ограничение в сотню расходуется в считанные секунды.
Принципиально обработка ввода ничем не отличается от первого примера на основе библиотеки Directlnput. Здесь используется непосредственная схема доступа, только уровень доступа устанавливается в комбинацию
DISCL_FOKEGROUND or DISCL_EXCLUSIVE. Несмотря на негласное соглашение,
что неактивное приложение не будет считывать данные с клавиатуры, при запуске двух копий программы обе они сильно замедлятся.
Работа с мышью
После знакомства с возможностями ввода с клавиатуры нам будет легко научиться работать с мышью, принципы обработки ввода здесь точно такие же. Непосредственную схему доступа изучим на конкретном примере - проекте каталога Ех07. Это еще один вариант создания эффекта лупы, но более эффектный, чем предыдущие, поскольку здесь добавлены сферические искажения пикселов (рис. 5.7).
Представленные ниже константы и переменные связаны с параметрами искажений:
const
Diameter = 180; // Задает максимальный размер лупы
Scale =35; // Вспомогательный коэффициент
var
Radius : Integer = Diameter div 2; // Текущий размер лупы
SqrRad : Integer; // Вспомогательные величины
Sphere : Integer;
Вспомогательные переменные заполняются первоначально при создании формы:
SqrRad := Radius * Radius; // Квадрат радиуса
Sphere := (Radius * Radius) - (Scale * Scale); // Искажение
Во время перерисовки кадра накладываем фон, а искажения вносим сразу на поверхность заднего буфера: