24 августа 2011

Delphi + GlScene: Простые анимации

Всем читателям привет! Давно не обновлял журнал, давно не программировал... Дело в том, что очень много разных дел, которые занимают все свободное время. Лето - сезон, когда не упеваешь ничего, а запланировал слишком много. Но сегодня дождь за окошком, поэтому я решил немного отвлечься и написать маленькую демонстрационную программу, несущую в себе простую идею - анимацию сделать нетрудно, а игроку от нее будет радостно! Итак, я решил сделать небольшую программу, на тему "графика и анимация". Ведь движения объектов, отклик на действия игрока - это основополагающая вещь любой интерактивной программы, в том числе и игры! Поэтому давайте не забывать, что доступные элементы должны подсвечиваться, хинты для них появляться, а кнопки нажиматься!

Сегодня на растерзание возьмем три элемента:
  1.  Простая анимация полосы загрузки
  2.  Анимация крутяшки ожидания
  3.  Маленький появляющийся хинт на элементы интерфейса

Простая анимация полосы загрузки
Выглядит это чудо так:


Давайте разберемся, как сделать анимацию полоски, означающую процесс загрузки. Сам фокус здесь в том, что полосы на фоне и рамка спереди - два разных объекта:


При этом полоски смещаются с помощью изменения текстурных координат.

TextureOffset.x := TextureOffset.x + progressTime.deltaTime / 6;

Для удобства я обернул функционал в класс TLoaderBar, описание которого выглядит совсем не хитро:

  TLoaderBar = class (TGlHudSprite)
  protected
    fLoaderFront: TGlHudSprite;
    Procedure MainInit;
  public
    Procedure CoordinateChanged(Sender: TGLCustomCoordinates); override;
    Function IsHit(aPosition: TVector): Boolean;
    Procedure SetMaterials(aMatLib: TGLMaterialLibrary; aLinesName: String; aFrontName: String);
    Procedure DoProgress(const progressTime: TProgressTimes); override;
    Constructor Create(AOwner: TComponent); override;
  end;

Из основной программы достаточно выполнить всего несколько действий:

  fLoaderBar := TLoaderBar.CreateAsChild(fMainDummy);
  fLoaderBar.SetMaterials(fMatLib, 'loaderlines' , 'loaderfront');
  fLoaderBar.Position.SetPoint(280, 360, 0);

Вот и все, теперь можно порадовать пользователей анимированной полосой...

Анимация крутяшки ожидания
Иногда места для элемента, показывающего процесс загрузки совсем мало, поэтому давайте создадим запасную анимацию в виде круга. Что-то вроде такого:



Выглядит не супер, но свою функцию выполняет. Описание класса оказалось вот такое:

  TWaitAnimation = class (TGlHudSprite)
  protected
    fAnimationTime: Single;
  public
    Procedure DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean); override;
    Procedure DoProgress(const progressTime: TProgressTimes); override;
    Constructor Create(AOwner: TComponent); override;
  end;

Обращаю внимание на то, что я перекрыл DoRender() для того, чтобы внутри него отрисовывать отдельные палочки одним объектом. Получается довольно удобно. Вот сам код, наполненный магическими числами:

Procedure TWaitAnimation.DoRender(var ARci: TRenderContextInfo; ARenderSelf, ARenderChildren: Boolean);
var
  alpha: single;
  i: integer;
  pos: TVector;
begin
  pos := Position.AsVector;
  for i := 0 to 11 do
  begin
    alpha := i * pi * 2 / 12;
    Position.x := pos[0] + 15 * cos(alpha);
    Position.y := pos[1] + 15 * sin(alpha);
    Rotation := 90 - alpha * 180 / pi;
    with Material.GetActualPrimaryMaterial.FrontProperties.Diffuse do
      Alpha := cos((fAnimationTime - i)) + 1;
    inherited DoRender(ARci, true, false);
  end;
  Position.SetPoint(pos);
end;

Хоть элемент оказался довольно простым, но он все равно вносит живинку на экран... Да и начинать наполнять игру интерактивными анимированными объектами стоит с самых простых - хинты, кнопки, панельки...

Анимированный хинт на элементы интерфейса
Для простоты я "повесил" подсказку на полосу загрузки из первого пункта. Наводим курсор на полоску и появляется хинт:


Опять же для удобства я создал новый класс TSimpleImageHint:

  TSimpleImageHint = class (TGlHudSprite)
  protected
    fShowSpeed, fHideSpeed: Single;
    fAnimationTime: Single;
    fIsShow: Boolean;
    fShowPos: TVector;
    fHidePos: TVector;
  public
    property ShowSpeed: Single read fShowSpeed write fShowSpeed;
    property HideSpeed: Single read fHideSpeed write fHideSpeed;
    Procedure Show;
    Procedure Hide;
    Procedure SetPositions(aShow, aHide: TVector);
    Procedure DoProgress(const progressTime: TProgressTimes); override;
    Constructor Create(AOwner: TComponent); override;
  end;

Описание вроде прозрачное; скажу только, что с помощью метода SetPositions() задаем позиции спрятанного и показанного состояний хинта. Свойства ShowSpeed и HideSpeed отвечают за скорость анимации. Из основной программы загружаем изображение и задаем базовые свойства объекта:

  fLoadingHint := TSimpleImageHint.CreateAsChild(fMainDummy);
  with fLoadingHint do
  begin
    Material.LibMaterialName := 'loading';
    Material.MaterialLibrary := fMatLib;
    Width := Material.GetActualPrimaryTexture.Image.Width;
    Height := Material.GetActualPrimaryTexture.Image.Height;
    SetPositions(VectorMake(280, 330, 0), VectorMake(280, 360, 0));
  end;

Осталось только запускать показ и прятание хинта в соответствующие моменты:

  if fLoaderBar.IsHit(VectorMake(MPos.x, MPos.y, 0)) then
    fLoadingHint.Show
  else
    fLoadingHint.Hide;

Безумно простое использование, не находите?!
Кстати, точно такой же элемент я использовал в конкурсной работе, подсвечивая назначение тех или иных кнопок.


Итого

Для красоты я добавил еще несколько иконок животных и хинты к ним... Наводим мышкой на каждою зверюшку и наблюдаем за анимацией подсказки. В итоге все это выглядит так:


Код зверюшек:

  for i := 1 to 4 do
  begin
    ZooHint[i] := TSimpleImageHint.CreateAsChild(fMainDummy);
    with ZooHint[i] do
    begin
      Material.LibMaterialName := ZooNames[i] + '_hint';
      Material.MaterialLibrary := fMatLib;
      Width := Material.GetActualPrimaryTexture.Image.Width;
      Height := Material.GetActualPrimaryTexture.Image.Height;
      SetPositions(VectorMake(80 + (i - 1) * 150, 180, 0), VectorMake(80 + (i - 1) * 150, 120, 0));
    end;

    Zoo[i] := TGlHudSprite.CreateAsChild(fMainDummy);
    with Zoo[i] do
    begin
      Material.LibMaterialName := ZooNames[i];
      Material.MaterialLibrary := fMatLib;
      Width := Material.GetActualPrimaryTexture.Image.Width;
      Height := Material.GetActualPrimaryTexture.Image.Height;
      Position.SetPoint(80 + (i - 1) * 150, 100, 0);
    end;
  end;

А также проверка на анимацию, находящаяся в OnCadencerProgress():

    for i := 1 to 4 do
    with Zoo[i] do
      if  (MPos.x >= Position.X - Width / 2) and (MPos.y >= Position.Y - Height / 2)
      and (MPos.x <= Position.X + Width / 2) and (MPos.y <= Position.Y + Height / 2) then
        ZooHint[i].Show
      else
        ZooHint[i].Hide;

С радостью выложил бы видео, но у меня сейчас настолько медленный интернет, что я не только не могу его выложить, а зачастую не получается даже поиграть в новые хитовые флешки. Зато работается более продуктивно :)
Скачать демку с исходниками можно здесь, либо обновляемся через svn, данная демка добавилась в 10ой ревизии...

Уф... очень рад, что смог найти время и доделать демку для журнала; так что я не пропал, я просто как бы отлучился ненадолго...

Сообщения, схожие по тематике:

7 коммент.:

  1. Класс) спасибо за пример.

    ОтветитьУдалить
  2. Edward,
    благодарю за приятные слова!

    Ulop,
    очень рад! я старался! надеюсь пригодится))

    ОтветитьУдалить
    Ответы
    1. Lampogolovii, спасибо тебе большое за такие примеры и реализации.
      1. Качественно.
      2. Со вкусом(коего мало сейчас у кодеров наших)

      + я доолго искал примеры реализации контролов именно на GLScene, и только перейдя на твой блог по ссылке в подписе, понял где обитает один из лучших, не побоюсь этого слова :)
      Спасибо тебе.

      Удалить
    2. Mlex,
      спасибо за добрые слова!
      рад, если эти нехитрые, но милые гуи-элементы пригодятся!
      буду стараться и впредь, правда времени не хватает на полноценные демки((

      Удалить
  3. Ответы
    1. раньше я прогал только на Delphi 7... старая, добрая делфи))..

      Удалить