Пример 06 - Создание меню

Этот пример показывает, как создать собственный элемент управления и меню.

Создание пользовательских элементов управления

Сначала, мы должны определить класс нового элемента управления, наследованныц от hgeGUIObject:

class hgeGUIMenuItem : public hgeGUIObject
{
public:
  hgeGUIMenuItem(int id, hgeFont *fnt, HEFFECT snd,
          float x, float y, float delay, char *title);

  virtual void  Render();
  virtual void  Update(float dt);

  virtual void  Enter();
  virtual void  Leave();
  virtual bool  IsDone();
  virtual void  Focus(bool bFocused);
  virtual void  MouseOver(bool bOver);

  virtual bool  MouseLButton(bool bDown);
  virtual bool  KeyClick(int key, int chr);

private:
  hgeFont       *fnt;
  HEFFECT       snd;
  float         delay;
  char          *title;

  hgeColor      scolor, dcolor, scolor2, dcolor2, color;
  hgeColor      sshadow, dshadow, shadow;
  float         soffset, doffset, offset;
  float         timer, timer2;
};

Конструктор нового элемента управления должен инициализировать члены класса hgeGUIObject: id, bStatic, bVisible, bEnabled and rect:

hgeGUIMenuItem::hgeGUIMenuItem(int _id, hgeFont *_fnt,
                HEFFECT _snd, float _x, float _y,
                float _delay, char *_title)
{
  id=_id;
  fnt=_fnt;
  snd=_snd;
  delay=_delay;
  title=_title;

  color.SetHWColor(0xFFFFE060);
  shadow.SetHWColor(0x30000000);
  offset=0.0f; timer=-1.0f; timer2=-1.0f;

  bStatic=false; bVisible=true; bEnabled=true;

  float w=fnt->GetStringWidth(title);
  rect.Set(_x-w/2, _y, _x+w/2, _y+fnt->GetHeight());
}

Метод Render, естественно, должен определить каждый элемент управления:

void hgeGUIMenuItem::Render()
{
  fnt->SetColor(shadow.GetHWColor());
  fnt->Render(rect.x1+offset+3, rect.y1+3, title);
  fnt->SetColor(color.GetHWColor());
  fnt->Render(rect.x1-offset, rect.y1-offset, title);
}

Все остальные методы необязательы и вы можете не определять их, если они не нужны.

Метод Update вызывается каждый раз, когда обновляется GUI и должен выполнять анимацию. В этом примере у нас есть два таймера и согласно им мы изменем цвет элемента управления и его позицию:

void hgeGUIMenuItem::Update(float dt)
{
  if(timer2 != -1.0f)
  {
    timer2+=dt;
    if(timer2 >= delay+0.1f)
    {
      color=scolor2+dcolor2;
      shadow=sshadow+dshadow;
      offset=0.0f;
      timer2=-1.0f;
    }
    else
    {
      if(timer2 < delay) { color=scolor2; shadow=sshadow; }
      else {
        color=scolor2+dcolor2*(timer2-delay)*10;
        shadow=sshadow+dshadow*(timer2-delay)*10;
      }
    }
  }
  else if(timer != -1.0f)
  {
    timer+=dt;
    if(timer >= 0.2f)
    {
      color=scolor+dcolor;
      offset=soffset+doffset;
      timer=-1.0f;
    }
    else
    {
      color=scolor+dcolor*timer*5;
      offset=soffset+doffset*timer*5;
    }
  }
}

Метод Enter вызывается перед началом появления GUI на экране. Конторл долден Начальную анимацю в нем:

void hgeGUIMenuItem::Enter()
{
  hgeColor tcolor2;

  scolor2.SetHWColor(0x00FFE060);
  tcolor2.SetHWColor(0xFFFFE060);
  dcolor2=tcolor2-scolor2;

  sshadow.SetHWColor(0x00000000);
  tcolor2.SetHWColor(0x30000000);
  dshadow=tcolor2-sshadow;

  timer2=0.0f;
}

Метод Leave вызывается, когда GUI начинает исчезать с экрана. Элемент управления должне начать анимацию Окончания в этой функции:

void hgeGUIMenuItem::Leave()
{
  hgeColor tcolor2;

  scolor2.SetHWColor(0xFFFFE060);
  tcolor2.SetHWColor(0x00FFE060);
  dcolor2=tcolor2-scolor2;

  sshadow.SetHWColor(0x30000000);
  tcolor2.SetHWColor(0x00000000);
  dshadow=tcolor2-sshadow;

  timer2=0.0f;
}

Метод IsDone используется для проверки того, закончил ли элемент управления свою анимацию Начала/Окончания. Когда анимация закончена, она должна вернуть значение true:

bool hgeGUIMenuItem::IsDone()
{
  if(timer2==-1.0f) return true;
  else return false;
}

Метод Focus вызывается когда элемент управления получает или теряет фокус ввода. В нашем примере мы начинаем в этом методе анимацию получения фокуса:

void hgeGUIMenuItem::Focus(bool bFocused)
{
  hgeColor tcolor;

  if(bFocused)
  {
    hge->Effect_Play(snd);
    scolor.SetHWColor(0xFFFFE060);
    tcolor.SetHWColor(0xFFFFFFFF);
    soffset=0;
    doffset=4;
  }
  else
  {
    scolor.SetHWColor(0xFFFFFFFF);
    tcolor.SetHWColor(0xFFFFE060);
    soffset=4;
    doffset=-4;
  }

  dcolor=tcolor-scolor;
  timer=0.0f;
}

Метод MouseOver вызывается, чтобы оповестить элемент управления о том, что курсор мыши вошел или вышел из области элемента управления. Здесь мы просто устанавливаем фокус ввода на наш элемент управления, когда курсор проходит над ним:

void hgeGUIMenuItem::MouseOver(bool bOver)
{
  if(bOver) gui->SetFocus(id);
}

Метод MouseLButton оповещает элемент управления о том, что состояние левой кнопки мыши изменилось. Если элемент управления меняет свое состояние и хочет оповестить об этом вызвавшего, он должен вернуть значение true:

bool hgeGUIMenuItem::MouseLButton(bool bDown)
{
  if(!bDown)
  {
    offset=4;
    return true;
  }
  else
  {
    hge->Effect_Play(snd);
    offset=0;
    return false;
  }
}

Метод KeyClick вызывается, чтобы оповестить элемент управления о том что была нажата клавиша. Если элемент управления меняет свое состояние и хочет оповестить вызывавшего, он должен вернуть значение true:

bool hgeGUIMenuItem::KeyClick(int key, int chr)
{
  if(key==HGEK_ENTER || key==HGEK_SPACE)
  {
    MouseLButton(true);
    return MouseLButton(false);
  }

  return false;
}

Отлично, теперь нам надо определить поведение нашего нового элемента управления.

Использование GUI

Это легкая часть. Сначала нам нужны некоторые идентификаторы ресурсов:

HEFFECT    snd;
HTEXTURE   tex;

hgeGUI     *gui;
hgeFont    *fnt;
hgeSprite  *spr;

В функции WinMain, во время инициализации нам нужно загрузить требуемые ресурсы:

  snd=hge->Effect_Load("menu.wav");
  tex=hge->Texture_Load("cursor.png");

  fnt=new hgeFont("font1.fnt");
  spr=new hgeSprite(tex,0,0,32,32);

Теперь мы можем создать GUI и добавить наши пункты меню туда. Элементы управления GUI управляются изнутри библиотеки и можно не сохранять указатели на них:

  gui=new hgeGUI();

  gui->AddCtrl(new hgeGUIMenuItem(
               1,fnt,snd,400,200,0.0f,"Play"));
  gui->AddCtrl(new hgeGUIMenuItem(
               2,fnt,snd,400,240,0.1f,"Options"));
  gui->AddCtrl(new hgeGUIMenuItem(
               3,fnt,snd,400,280,0.2f,"Instructions"));
  gui->AddCtrl(new hgeGUIMenuItem(
               4,fnt,snd,400,320,0.3f,"Credits"));
  gui->AddCtrl(new hgeGUIMenuItem(
               5,fnt,snd,400,360,0.4f,"Exit"));

Теперь установим метод навигации GUI, изображение курсора мыши и фокус ввода по умолчанию, затем запустим Начальную анимацию:

  gui->SetNavMode(HGEGUI_UPDOWN | HGEGUI_CYCLED);
  gui->SetCursor(spr);
  gui->SetFocus(1);
  gui->Enter();

Сейчас давайте посмотрим, как мы обновляем наше меню и получаем нотификации. В нашей функции кадра (FrameFunc) мы вызываем метод hgeGUI::Update для обновления анимации и обработки ввода пользователя. Если элемент управления изменил свое состояние, этот метод вернет идентификатор элемента управления. Если все элементы закончат свои анимации Окончания, она вернет значение -1. Если неслучится ничего интересного, вернется значение 0.

  int id;
  static int lastid=0;
  float dt=hge->Timer_GetDelta();

  id=gui->Update(dt);
  if(id == -1)
  {
    switch(lastid)
    {
      case 1:
      case 2:
      case 3:
      case 4:
        gui->SetFocus(1);
        gui->Enter();
        break;

      case 5: return true;
    }
  }
  else if(id) { lastid=id; gui->Leave(); }

Для того, чтобы нарисовать меню просто вызовем метод hgeGUI::Render:

  hge->Gfx_BeginScene();
  gui->Render();
  hge->Gfx_EndScene();

Итак, меню запущено и работает. Теперь вернемся к функции WinMain. В конце мы должны удалить GUI и освободить ресурсы:

  delete gui;
  delete fnt;
  delete spr;
  hge->Texture_Free(tex);
  hge->Effect_Free(snd);

Полный исходный код с детальными комментариями для этого примера можно найти в директории tutorials\tutorial06. Требуемые медиа фалы можно найти в директории tutorials\precompiled.