Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Создание меню

Автор: Релорт
WEB-сайт: http://daddy.mirgames.ru

Введение.

Привет! Сегодня мы поговорим о создании меню игры. Ведь в последнее время красочно оформленное меню является очень важным фактором. Поставим перед собой следующие задачи:

  1. Использование динамического меню.
  2. Возможность загрузки меню из файла.
  3. Применение анимации.

Ну, думаю, пока достаточно. Если в процессе написания статьи появится что-либо еще, я обращу на это внимание.

Отступление. Кое-что о примере.

В архиве вместе с файлами 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.

Ну вот вроде бы как и все. Мы добрались до завершения статьи. Если что непонятно, необходимо пояснение или пример пишите. Отвечу всем.

Здесь можно скачать пример к статье

Проект Delphi World © Выпуск 2002 - 2024
Автор проекта: USU Software
Вы можете выкупить этот проект.