Создание меню
Автор: Релорт
WEB-сайт: http://daddy.mirgames.ru
Введение.
Привет! Сегодня мы поговорим о создании меню игры. Ведь в последнее время красочно оформленное меню является очень важным фактором. Поставим перед собой следующие задачи:
- Использование динамического меню.
- Возможность загрузки меню из файла.
- Применение анимации.
Ну, думаю, пока достаточно. Если в процессе написания статьи появится что-либо еще, я обращу на это внимание.
Отступление. Кое-что о примере.
В архиве вместе с файлами lmenu.lii и lmenu1.lii лежат два файла с расширением «mnu». Это – тоже менюшки, причем, именно с них и были созданы lmenu.lii и lmenu1.lii. а лежат они в архиве потому, что я сразу забыл их убрать, а потом мне стало лень это сделать. Все дело в том, что как-то, создавая игру, я наткнулся на проблему постоянного изменения положения элементов меню. И тогда, я решил грузить меню из файла (причем шифрованного). Но так как исправить его каждый раз руками через процедуру декодирование – исправление – кодирование – использование накладно, то появилась на свет программа «LI_MenuEditor»(«Lost Imron Menu Editor»), выполняющая кодирование/декодирование и позволяющая создавать меню с помощью мыши, изменять положение элементов, масштабировать и т. д. Когда же я сел писать эту статью, то к созданию файлов меню (для примера) я решил приспособить сей инструмент, добавив возможность сохранения старого формата в новый («mnu» –> «lii»). Так что, к примеру я прикладываю и редактор, если он вдруг кому-то понадобится.
Часть 1. Описание базового класса.
В свете вышеизложенного во введении у меня получился следующий код:
// указатель на TDXDraw необходим для создания поверхности Surface в классе TGameMenuItem
PDXDraw = ^TDXDraw;
{ это, собственно, - элемент меню }
TMenuItem = class tick: Cardinal;
CurFrame: Word;
rc: TRect;
Caption: string;
Surface: TDirectDrawSurface;
{ процедура, вызываемая при нажатии элемента }
OnClick: TNotifyEvent;
procedure SetXY(tx, ty: Integer);
constructor Create(tdxd: PDXDraw);
destructor Destroy;
end;
{ меню }
TGameMenu = class
private
DXD: PDXDraw;
public
Items: array of TMenuItem;
ItemIndex: Integer;
Count: Integer;
function Add(FileName: string): ShortInt; overload;
function Add(tx, ty: Integer; FileName: string): ShortInt; overload;
function Add(trc: TRect; tcaption, FileName: string): ShortInt; overload;
function Delete(index: Integer): ShortInt; overload;
function Delete(tcaption: string): ShortInt; overload;
function Clear: ShortInt;
function LoadMenu(FileName: string): ShortInt;
procedure Draw(isTimer: Boolean);
procedure ProcessMouse(tx, ty: Integer);
procedure MouseClickEx(Button: array of Boolean);
procedure MouseClick(Button: array of Boolean);
constructor Create(tdxd: PDXDraw);
destructor Destroy;
end;
Полное описание всех процедур и функций приводится в файле GameMenuUnit.pas архива, прилагаемого к данной статье в качестве примера использования меню. Мы рассмотрим лишь самые интересные, на мой взгляд, моменты.
Часть 2. Отрисовка меню.
Выводом меню на поверхность занимается процедура Draw. В качестве аргумента в нее передается isTimer логического типа. Если он равен Истине(True), тогда меню использует таймер для подсчета задержки между кадрами анимации, иначе таймер (вроде как) игнорируется. Рассмотрим пример: если свойство Interval таймера установлено не в нулевое состояние, то задержка между кадрами будет равна (для нижеследующего кода) Inteval*17; если же isTimer равен False, то используется процедура GetTickCount, не зависящая от таймера приложения. Но здесь следует заметить, что вызов TGameMenu.Draw (в коде примера) происходит из DXTimer.OnTimer, что говорит о косвенном влиянии таймера и на вывод меню с isTimer’ом равным False.
Пример: Draw вызывается из DXTimer.OnTimer с Interval’ом = 23; в этом случае задержка будет 115, а не 100, как в коде.
procedure TGameMenu.Draw(isTimer: Boolean);
var
i: Integer;
mi: TMenuItem;
ttc: Cardinal;
begin
for i := 0 to Count - 1 do
begin
mi := Items[i];
{ если элемент не активен }
if ItemIndex <> i then
begin
DXD^.Surface.Draw(mi.rc.Left, mi.rc.Top,
Rect(0, 0, 192, 64), mi.Surface, True);
end
{ если элемент активен }
else
begin
{ если используется таймер для подсчета задержки между кадрами }
if isTimer = True then
begin
inc(Items[i].tick);
if Items[i].tick > 17 then
begin
Items[i].tick := 0;
inc(Items[i].CurFrame);
if Items[i].CurFrame = 5 then
Items[i].CurFrame := 1;
end;
end
{ если используется процедура GetTickCount для подсчета задержки }
{ между кадрами }
else
begin
ttc := GetTickCount;
if (ttc - Items[i].tick) > 100 then
begin
Items[i].tick := ttc;
inc(Items[i].CurFrame);
if Items[i].CurFrame = 5 then
Items[i].CurFrame := 1;
end;
end;
{ вывод соответствующего кадра анимации }
DXD^.Surface.Draw(mi.rc.Left, mi.rc.Top,
Rect(0, 64 * mi.CurFrame, 192, 64 * (mi.CurFrame + 1)),
mi.Surface, True);
end;
end;
end;
Часть 3. Отработка нажатия.
Для отработки нажатия клавиш мыши на элементах меню можно использовать два способа:
1. обработка нажатия в зависимости от, допустим, имени менюшки (в приведенном выше классе это поле Caption) в общей куче, то есть:
{ Button – массив клавиш. Button[0] = True при нажатой левой }
{ кнопке мыши, Button[1] = True – при правой и т. д. }
procedure TGameMenu.MouseClick(Button: array of Boolean);
begin
if High(Button) = 0 then
exit;
{ Если не один элемент не активен, то выходим }
if ItemIndex = -1 then
exit;
{ Если нажата менюшка с именем ‘Exit’, то закрываем приложение }
if Items[ItemIndex].Caption = 'Exit' then
Form1.Close
else if Items[ItemIndex].Caption = 'Start' then
LoadMenu('lmenu1.lii')
else if Items[ItemIndex].Caption = 'MainMenu' then
LoadMenu('lmenu.lii')
else if Items[ItemIndex].Caption = 'Game' then
MessageBox(0, 'Game menu item click.', 'Menu', MB_OK)
else if Items[ItemIndex].Caption = 'Options' then
MessageBox(0, 'Options menu item click.', 'Menu', MB_OK);
end;
2. присвоение каждому элементу своего обработчика события нажатия кнопки мыши.
procedure TGameMenu.MouseClickEx(Button: array of Boolean);
begin
if High(Button) = 0 then
exit;
if ItemIndex = -1 then
exit;
if Button[0] = True then
begin
if Assigned(Items[ItemIndex].OnClick) then
Items[ItemIndex].OnClick(Items[ItemIndex]);
end;
end;
где Assigned(Items[ItemIndex].OnClick) – проверка на доступность вызова процедуры OnClick. Если вы хотите использовать второй способ, то при добавлении элементов необходимо определить процедуру OnClick.
Ну вот вроде бы как и все. Мы добрались до завершения статьи.
Если что непонятно, необходимо пояснение или пример пишите.
Отвечу всем.
Здесь можно скачать пример к статье
|