Графика DirectX в Delphi - Михаил Краснов
Шрифт:
Интервал:
Закладка:
Создание консоли
Консоль вы часто видели и использовали в профессиональных играх и, наверняка, захотите создать и в своей игре. Пример данного раздела - проект каталога Ех09 - поможет вам в этом. Он является развитием нашей пробной игры: теперь по нажатии клавиши <Таb> на экране появляется консоль, предназначенная для ввода команд (рис. 5.9).
Рис. 5.9. Наша игра обзавелась консолью
Я предусмотрел реакцию только на одну команду, после ввода Exit приложение завершает работу, все остальные вводимые строки просто вызывают эхо в консоли.
Моя консоль вмещает три строки, инициализируемые многозначительными фразами:
rcRectConsole : TRECT; // Вспомогательный прямоугольник
ConsoleHeight : Integer =0; // Текущий размер консоли
ConsoleLive : BOOL = False; // Флаг, связанный с присутствием
TextConsolel : String = '> Initialization....OK'; // Строки вывода
TextConsole2 : String = '> Loading .......OK';
TextConsole3 : String = '>_';
Для функционирования консоли я завел отдельную поверхность, закрашиваемую при инициализации белым цветом:
ZeroMemory(@ddsd, SizeOf(ddsd));
with ddsd do begin
dwSize := SizeOf(ddsd);
dwFlags := DDSD__CAPS or DDSD_HEIGHT or DDSD_WIDTH;
ddsCaps.dwCaps := DDSCAPS_OFFSCREENPLAIN;
dwWidth := 640;
dwHeight := 100;
end;
hRet := FDD.CreateSurface(ddsd, FDDSConsole, nil);
if Failed (hRet) then ErrorOut(hRet, 'CreateSurface1);
hRet := FDDSConsole.SetPalette(FDDPal) ;
if Failed (hRet) then ErrorOut(hRet, 'SetPalette');
ZeroMemory(gddbltfx, SizeOf(ddbltfx));
ddbltfx.dwSize := SizeOf(ddbltfx);
ddbltfx.dwFillColor :=RGB (255, 255, 255);
FDDSConsole.Bit(nil, nil, nil, DDBLT COLORFILL or DDBLT WAIT, @ddbltfx);
SetRect (rcRectConsole, 0, 0, 640, 100);
Код воспроизведения монстров и пуль немного подправил, чтобы они не появлялись в области консоли. Сама же консоль воспроизводится в последнюю очередь, непосредственно перед переключением буферов:
if ConsoleLive then begin // Надо ли рисовать консоль
if (GlobalThisTickCount - GlobalLastTickCount > DelayConsole) then
begin // Плавное появление консоли
Inc (ConsoleHeight, 5);
if ConsoleHeight > 100 then ConsoleHeight := 100;
SetRect (rcRectConsole, 0, 0, 640, ConsoleHeight);
end;
// Собственно воспроизведение консоли
FDDSBack.BltFast(0, 0, FDDSConsole, @rcRectConsole, DDBLTFAST__WAIT);
end;
Текст в консоли выводится с помощью функций GDI:
procedure OutText (const X, Y : Integer; const TextCon : String);
var
DC : HOC;
begin
FDDSConsole.GetDC (DC) ;
SetBkColor(DC, RGB (255, 255, 255)); // Цвета фона и букв необходимо
SetTextColor (DC, 0); // задавать обязательно
TextOut (DC, X, Y, PChar(TextCon), length (TextCon));
FDDSConsole.ReleaseDC (DC);
end;
Немало хлопот принесла обработка нажатия клавиши <Backspace>: чтобы стереть старый текст, приходится воспроизводить ряд пробелов:
if diks [DIK_TAB] and $80 <> 0 then begin // Клавиша <Tab>
if not ConsoleLive then begin // Включение консоли
ConsoleHeight := 0; ConsoleLive := True;
end
else ConsoleLive := False; // Выключить консоль
Sleep(lOO); // Небольшая пауза
end;
if ConsoleLive then begin // Обработка клавиш для консоли
OutText (5, 10, TextConsolel); // Вывод трех строк в консоли
OutText (5, 30, TextConsole2); OutText (5, 50, TextConsole3);
if diks [DIK_RETURN] and $80 <> 0 then begin // Ввод команды
// Введена команда "Exit"; выход из программы
if (TextConsole3 = '>EXIT_') or (TextConsole3 = '> EXIT_') then Close;
// Введена другая команда, строки стираются и поднимаются наверх
TextConsolel := ' ';
OutText (5, 10, TextConsolel); // Затираем пробелами
TextConsolel := TextConsole2; // Строка сдвигается вверх
TextConsole2 := ' ' ;
OutText (5, 30, TextConsole2);
TextConsole2 := '> Command : ' + Copy (TextConsole3, 2,
length (TextConsoleS) - 2); // Реакция на все остальные команды -
// вывод эха
TextConsoleS := ' ';
OutText (5, 50, TextConsoleS);
TextConsoleS := '>_'; // Последняя строка превратилась в приглашение
Sleep(100);
end;
if diks [DIK_BACKSPACE] and $80 <> 0 then begin // Нажата клавиша
// <Backspace>
TextConsole3 := ' ';
OutText (5, 50, TextConsoleS); // Стираем последнюю строку
TextConsoleS := '>_';
OutText (5, 50, TextConsoleS);
end;
for i := DIK_Q to DIK_M do // Просматриваем буквенные клавиши
if diks [i] and $80 <> 0 then begin // Нажата какая-то клавиша с буквой
if length (TextConsoleS) < 20 then begin // Ограничение длины строки
// Перед символом подчеркивания вставляем букву нажатой клавиши
TextConsoleS := Copy (TextConsoleS, I, length (TextConsoleS) - 1) +
ScanToChar (i) +'_';
OutText (5, 50, TextConsoleS); // Вывод получившейся строки
Sleep(100);
end;
end;
end;
Поскольку обработка клавиатуры происходит необычайно быстро, с помощью процедуры sleep создаем искусственную паузу, чтобы не получить эффекта залипания клавиши. Эта пауза дает время пользователю отпустить нажатую клавишу.
Диалоговые окна
Многие приложения нуждаются в диалоговых окнах, поэтому уделим немного внимания этому вопросу. Пример данного раздела (проект каталога Ех10) представляет собой окончательную реализацию нашего хранителя экрана с плавающими рыбками. В развитие предыдущего состояния добавлена поддержка пароля для входа в систему.
Установка и запрос пароля хранителя экрана являются системными действиями, но вызов их не ограничивается одной строкой. Это означает, что диалоги установки и ввода пароля не должны реализовываться программистом, пароль назначается для всех хранителей экранов. Мы не можем самостоятельно запросить пароль, хранить его в реестре, в определенном разделе, и самостоятельно организовывать ввод пароля при нажатии клавиши или движении курсора мыши.
Следующая процедура предназначена для вызова системного диалога задания нового пароля:
procedure TfrmDD.RunSetPassword;
type // Специальный тип функции, используется только в этой ситуации
TPCPAFunc = function(A : PChar; Parent : hWnd; В, С : Integer) :
Integer; stdcall;
var
Lib : THandle; // Ссылка на DLL
PCPAFunc : TPCPAFunc; // Загружаемая функция
begin
Lib := .LoadLibrary('MPR.DLL1); // Динамическая загрузка DLL
if Lib > 32 then begin // Проверка успешности загрузки
// Получаем адрес точки входа нужной функции
@PCPAFunc := GetProcAddress(Lib, 'PwdChangePasswordA');
// Задаем пароль хранителей экрана
if @PCPAFunc о nil then PCPAFunc('SCRSAVE', StrToInt(ParamStr(2)),
0, 0);
FreeLibrary(Lib); // Выгружаем библиотеку
end;
end;
В нашей программе эта процедура вызывается, если приложение запущено с параметром /а, т. е. в ситуации, когда пользователь нажал кнопку Изменить на вкладке Заставка (см. рис. 4.3).
При нажатии клавиши или движении курсора программа должна сама определить, установлен ли пароль для хранителя экрана, и запустить системный диалог ввода пароля:
function TfrmDD.TestPassword : BOOL;
type
// Специальный тип, тоже используется только в этом, особом случае
TVSSPFunc = function(Parent : hWnd) : BOOL; stdcall;
var
Key : hKey;
D1,D2 : Integer;
Value : Integer;
Lib : THandle;
VSSPFunc : TVSSPFunc;
begin
Result := True;
// Загружаем информацию из реестра, используя функции API
if RegOpenKeyEx(hKey_Current_User, 'Control PaneiDesktop', 0,
Key_Read, Key) = Error_Success then begin
D2 := SizeOf(Value);
// Определяем, установлен ли пароль
if RegQueryValueEx(Key, 'ScreenSaveUsePassword', nil, @D1,
@Value,@D2) = Error_Success then begin if Value <> 0 then begin
// Динамически загружаем библиотеку ввода пароля
Lib := LoadLibraryf'PASSWORD.CPL');
if Lib > 32 then begin
// Получаем адрес точки входа
SVSSPFunc := GetProcAddress(Lib, 'VerifyScreenSavePwd');
// На время работы диалога включаем курсор
ShowCursor (True) ;
// Запускаем системный диалог
if @VSSPFunc <> nil then Result := VSSPFunc(Handle);
ShowCursor(False); // Это можно, в принципе, не делать
FreeLibrary(Lib); // Освобождаем память
end;
end;
end;
RegCloseKey(Key);
end;
end;
И теперь самое главное: диалоговое окно должно работать "поверх" первичной поверхности (рис. 5.10).
Чтобы пользователь увидел его, перед вызовом нашей пользовательской функции TestPassword нужно переключиться на воспроизведение в режиме GDI:
FDD.FlipTcGDISurface;
To есть в такой ситуации обязан вызываться метод главного объекта FlipToGDisurface, а перерисовка экрана не должна осуществляться. К сожалению, мне встречались хранители экрана, написанные профессионалами, авторы которых не позаботились о корректной работе системного диалога: пароль приходится вводить "вслепую", не видя окно ввода пароля, закрытое картинкой первичной поверхности. Памятуя об этом, я многократно проверял работу своего хранителя на самых разных видеокартах, и могу сказать, что не встретил ничего подобного.
Чтобы отключить клавиатуру, точнее, запретить работу комбинаций клавиш <Alt>+<Tab> и <CtrI>+<Alt>+<Del>, на время работы приложения информируем систему о том, что работает хранитель экрана, при запуске приложения выполняется следующая строка кода:
SystemParametersInfo(SPI SCREENSAVERRUNNING, 1, nil, 0);
По окончании работы надо не забыть восстановить нормальную работу комбинаций этих клавиш, для чего вызывается та же команда, но второй аргумент задается нулевым значением.
Переключение на GDI-воспроизведение, надеюсь, сделает видимым диалоговое окно у каждого читателя книги, но полноценным такое решение назвать нельзя. При перемещении окна по экрану оно оставляет следы, расчищая первичную поверхность и обнажая окно приложения (поэтому экран становится серым).
Такой способ общения с пользователем ущербен еще по той причине, что он не работает в палитровом режиме. Из-за этого я не мог рекомендовать его для применения в функции вывода сообщения об ошибке, используемой нами в предыдущих примерах.