02 мая 2012

Gui: использование анимированных кнопок

В различных играх часто приходится видеть выбор персонажа, автомобиля или другого объекта с помощью кнопок пролистывания < >
В рамках серии сообщений о gui-элементах я решил показать, что подобную смену картинок сделать невероятно просто. За основу решил взять предыдущую статью, в которой уже присутствуют сами кнопки пролистывания. Поэтому сегодня мы продолжим дорабатывать нашу демонстрацию по использованию анимированных кнопок. По сути, у нас уже имеется заготовка, куда мы просто добавим немного интерактивности и декораций. Заодно чуть подробнее остановимся на некоторых классах нашей иерархии gui-объектов.
Итак, сегодня будем на практике применять созданные нами ранее кнопки.
Как видно, в прошлый раз мы оставили в центре экрана небольшое свободное пространство, в котором как раз и будем отображать персонажей на выбор:


Результат нашего сегодняшего труда можно посмотреть на видео:



Приготовились? Приступим к коду...
Для демонстрации и смены картинок, достаточно вот такой структуры, в которой поля fX и fAlpha нужны для красивого твининга спрайтов:

  THero = class
    fSprite: TGlHudSprite;
    fX: Single;
    fAlpha: Single;
  end;

Применять значения из полей fX и fAlpha будем в методе UpdateHeroes(), где, пробежавшись по всем персонажам, выставим им соответствующую позицию и прозрачность:

Procedure TfrmMain.UpdateHeroes;
var
  i: integer;
begin
  for i := 0 to fHeroes.Count - 1 do
    with THero(fHeroes[i]) do
    begin
      fSprite.Position.SetPoint(fX, fSprite.Position.y, 0);
      fSprite.AlphaChannel := fAlpha;
    end;
end;

Далее реализуем смену картинок, для этого заведем метод ActivateHero(), в котором будем запускать твины уходящего персонажа, а также твины на появление следующего. Для пущей красоты научим данный метод смещать картинки как в одну сторону, так и в другую (справа налево и слева направо), а регулировать направление будем аргуемнтом aDirection:

Procedure TfrmMain.ActivateHero(aIndex: integer; aDirection: integer);
begin
  if (fActiveHero >= 0) then
    with THero(fHeroes[fActiveHero]) do
    begin
      fTweener.DeletePSingle(@fX);
      fTweener.DeletePSingle(@fAlpha);
      fTweener.AddTweenPSingle(@fX, ts_ExpoEaseIn, fX, 300 + aDirection * 100, 2);
      fTweener.AddTweenPSingle(@fAlpha, ts_ExpoEaseIn, fAlpha, 0, 0.7);
    end;

  if(aIndex >= 0) then
    with THero(fHeroes[aIndex]) do
    begin
      fTweener.DeletePSingle(@fX);
      fTweener.DeletePSingle(@fAlpha);
      fTweener.AddTweenPSingle(@fX, ts_ExpoEaseIn, 300 - aDirection * 100, 300, 2, 0.5);
      fTweener.AddTweenPSingle(@fAlpha, ts_ExpoEaseIn, 0, 1, 0.7, 0.5);
    end;

  fActiveHero := aIndex;
end;

Надеюсь здесь все прозрачно, по два твина (позиция и альфа) на предыдущую и новую картинку. Также удаляем все предыдущие твины с помощью метода DeletePSingle(), для того, чтобы случайным образом не наплодить артефактов в виде промаргивания картинок при частых нажатия кнопок < >

Собственно, спрайты есть, анимация их смены тоже, осталось научиться фиксировать нажатие кнопок и запускать ActivateHero() с нужными параметрами, а метод UpdateHeroes() уже позиционирует изображения в зависимости от прогресса твинов.
Напомню, что в прошлый раз мы обошли стороной вопрос нажатия кнопок, скромно умолчав о том, что на самом деле почти все готово для того, чтобы отлавливать нажатия мыши на кнопке. Для этого перейдем в класс TGUIBaseInteractiveObject и допишем несколько коварных строк:

  TGUIBaseInteractiveObject = class(TGUIBaseAnimatedObject)
  protected
    fHitArea: THitArea;
    fMouseState: TMouseState;
    fMousePressed: Boolean;
    fOnMouseClick: TOnMouseClick;
  protected
    Procedure ApplyMouseState(aState: TMouseState);
  public
    property OnMouseClick: TOnMouseClick read fOnMouseClick write fOnMouseClick;
    Function IsHit(aPosition: TVector): Boolean;
    Procedure SetMouseState(aMouseX, aMouseY: Single; aIsButtonDown: Boolean);
    Constructor Create(AOwner: TComponent); override;
  end;

Теперь, имея свойство OnMouseClick, вызовем его при необходимости из SetMouseState() следующим образом:

    if aIsButtonDown and not fMousePressed and Assigned(fOnMouseClick) then
      fOnMouseClick(self);

Вот и все изменения в нашем классе интерактивных объектов. Осталось воспользовать волшебством и прописать обработчик события внутри основной программы:

...............
  fRightBtn.OnMouseClick := OnNextClick;
  fLeftBtn.OnMouseClick := OnPrevClick;
.....................
Procedure TfrmMain.OnNextClick(aSender: TGUIBaseInteractiveObject);
begin
  if(fActiveHero = fHeroes.Count - 1)then
    ActivateHero(0, -1)
  else
    ActivateHero(fActiveHero + 1, -1);
end;

Procedure TfrmMain.OnPrevClick(aSender: TGUIBaseInteractiveObject);
begin
  if(fActiveHero = 0)then
    ActivateHero(fHeroes.Count - 1, 1)
  else
    ActivateHero(fActiveHero - 1, 1);
end;

На каждую из кнопок свой обработчик, так проще определять, на какую кнопку нажали и запускать ActivateHero с соответствующими параметрами. Таким образом мы сможем легко отлавливать клик по кнопке и проигрывать анимацию смены спрайтов!
Как обычно, скачать демонстрацию (exe+исходники)  можно здесь, либо необходимо обновиться через svn (24ая ревизия).

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

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

5 коммент.:

  1. Я наверное повторюсь, но скажу: лучше блога по гуи на делфи я не видел :)
    Что касается замечаний - их нет, все прозрачно и доходчиво. Прям для меня :). Всегда с удовольствием читаю(жаль довольно не часто пишешь именно по этой теме). Но именно тут я вообще понял как вообще писать гуи, а то идей вообще небыло а тут уже готовые реализации и примеры, спасибо.

    ОтветитьУдалить
    Ответы
    1. спасибо за теплые слова, приятно!
      очень рад, что код читаем))
      да, писать получается нечасто, прошло более полугода с этого сообщения:
      http://lampogolovii.blogspot.com/2011/09/gui.html
      а я до сох пор третий пункт не раскрыл до конца, но надеюсь исправиться в ближайшее время!
      надеюсь примеры и код пригодятся, пользуйся на здоровье!

      Удалить
    2. Пригодятся конечно! Сейчас по тихоньку осваиваю сцену, в голове крутиться небольной проект казино, игровые автоматы и прочее(больше в учебных целях) и ваши идеи гуи очень даже подходят :), так что огромное спасибо :)
      ЗЫ: в 3d пока не лезу - 2d не до конца освоил :)))

      Удалить
  2. От себя хочу добавить, а вернее поблагодарить Вас, за Ваши статьи, в частности за статьи по GUI и Box2D!! СПАСИБО!!)

    ОтветитьУдалить
    Ответы
    1. Анонимный,
      ух ты! очень приятно читать! спасибо за добрые слова!
      удачи в разработке!

      Удалить