netlib.narod.ru< Назад | Оглавление | Далее >

Анимация

В Windows Forms и GDI+ не хватает нескольких важных для анимации функций. В главе 8 я говорил, что GDI+ не поддерживает XOR-рисование, позволяющее стереть то, что уже нарисовано повторным рисованием. Другая проблема в том, что Windows Forms не позволяет считывать пикселы с экрана. При анимации зачастую полезно считать блок пикселов с экрана как битовую карту, нарисовать что-либо на ней и вывести результат на экран.

И все же элементарная анимация в программах Windows Forms возможна. Один из способов — покадровая анимация (frame animation) — заключается в последовательном выводе одинаковых по размеру изображений, наподобие кадров фильма. Следующая программа загружает четыре растровых изображения из ресурсов и затем с помощью события Timer выводит мигающий глаз.

Wink.cs

  //-------------------------------------
  // Wink.cs (C) 2001 by Charles Petzold
  //-------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class Wink: Form
  {
      protected Image[] aimage  = new Image[4];
      protected int     iImage = 0, iIncr = 1;

      public static void Main()
      {
          Application.Run(new Wink());
      }
      public Wink()
      {
          Text = "Wink";
          ResizeRedraw = true;
          BackColor = Color.White;

          for (int i = 0; i < 4; i++)
              aimage[i] = new Bitmap(GetType(), 
                                     "Wink.Eye" + (i + 1) + ".png");
          Timer timer = new Timer();
          timer.Interval = 100;
          timer.Tick += new EventHandler(TimerOnTick);
          timer.Enabled = true;
      }
      protected virtual void TimerOnTick(object obj, EventArgs ea)
      {
          Graphics grfx = CreateGraphics();

          grfx.DrawImage(aimage[iImage], 
                        (ClientSize.Width  - aimage[iImage].Width)  / 2,
                        (ClientSize.Height - aimage[iImage].Height) / 2,
                        aimage[iImage].Width, aimage[iImage].Height);
          grfx.Dispose();

          iImage += iIncr;

          if (iImage == 3)
              iIncr = -1;
          
          else if (iImage == 0)
              iIncr = 1;
      }
  }

Рис. 11.16.

Заметьте: при загрузке ресурсов конструктор использует имена Wink.Eye1.png, Wink.Eye2.png и т.д. Wink — это название пространства имен. Метод TimerOnTick с помощью DrawImage последовательно выводит каждое изображение по центру клиентской области. Вот программа в действии:


Рис. 11.17.

При анимации надо выводить изображения в пиксельном размере. Это позволяет предотвратить уменьшение и унеличение изображения, а также экономит время процессора.

Шутки ради я создал в следующей программе дочерний класс Wink и методом RotateFlip вывел рядом с левым глазом правый.

DualWink.cs

  //-----------------------------------------
  // DualWink.cs (C) 2001 by Charles Petzold
  //-----------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class DualWink: Wink
  {
      Image[] aimageRev = new Image[4];

      public new static void Main()
      {
          Application.Run(new DualWink());
      }
      public DualWink()
      {
          Text = "Dual " + Text;

          for(int i = 0; i < 4; i++)
          {
              aimageRev[i] = (Image) aimage[i].Clone();
              aimageRev[i].RotateFlip(RotateFlipType.RotateNoneFlipX);
          }
      }
      protected override void TimerOnTick(object obj, EventArgs ea)
      {
          Graphics grfx = CreateGraphics();

          grfx.DrawImage(aimage[iImage], 
                    ClientSize.Width / 2,
                    (ClientSize.Height - aimage[iImage].Height) / 2,
                    aimage[iImage].Width, aimage[iImage].Height);

          grfx.DrawImage(aimageRev[3 - iImage],
                    ClientSize.Width / 2 - aimageRev[3 - iImage].Width,
                    (ClientSize.Height - aimageRev[3 - iImage].Height) / 2,
                    aimageRev[3 - iImage].Width,
                    aimageRev[3 - iImage].Height);

          grfx.Dispose();

          iImage += iIncr;

          if (iImage == 3)
              iIncr = -1;

          else if (iImage == 0)
              iIncr = 1;
      }
  }

В этом проекте нужны ссылки на четыре PNG-файла, связанных с программой Wink. И помните мое предупреждение о наследовании классов, загружающих ресурсы! В проекте DualWink мне пришлось изменить название пространства имен ресурса с DualWink на Wink.


Рис. 11.18.

А сейчас то, чего вы все ждали, — прыгающий мяч! Программа Bounce создает квадратную битовую карту, рисует на ней красный мяч и затем выводит итоговую карту в разных частях клиентской области, имитируя отскок мяча от стен.

Bounce.cs

  //---------------------------------------
  // Bounce.cs (C) 2001 by Charles Petzold
  //---------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class Bounce: Form
  {
      const  int iTimerInterval = 25;    // В милисекундах
      const  int iBallSize = 16;         // Часть клиентской области
      const  int iMoveSize = 4;          // Часть размера мяча

      Bitmap bitmap;
      int    xCenter, yCenter;
      int    cxRadius, cyRadius, cxMove, cyMove, cxTotal, cyTotal;

      public static void Main()
      {
          Application.Run(new Bounce());
      }
      public Bounce()
      {
          Text = "Bounce";
          ResizeRedraw = true;
          BackColor = Color.White;

          Timer timer = new Timer();
          timer.Interval = iTimerInterval;
          timer.Tick += new EventHandler(TimerOnTick);
          timer.Start();

          OnResize(EventArgs.Empty);
      }
      protected override void OnResize(EventArgs ea)
      {
          Graphics grfx = CreateGraphics();
          grfx.Clear(BackColor);

          float fRadius = Math.Min(ClientSize.Width  / grfx.DpiX,
                                   ClientSize.Height / grfx.DpiY) 
                                        / iBallSize;

          cxRadius = (int) (fRadius * grfx.DpiX);
          cyRadius = (int) (fRadius * grfx.DpiY);

          grfx.Dispose();

          cxMove = Math.Max(1, cxRadius / iMoveSize);
          cyMove = Math.Max(1, cyRadius / iMoveSize);

          cxTotal = 2 * (cxRadius + cxMove);
          cyTotal = 2 * (cyRadius + cyMove);

          bitmap = new Bitmap(cxTotal, cyTotal);

          grfx = Graphics.FromImage(bitmap);
          grfx.Clear(BackColor);

          DrawBall(grfx, new Rectangle(cxMove, cyMove, 
                                       2 * cxRadius, 2 * cyRadius));
          grfx.Dispose();

          xCenter = ClientSize.Width / 2;
          yCenter = ClientSize.Height / 2;
      }
      protected virtual void DrawBall(Graphics grfx, Rectangle rect)
      {
          grfx.FillEllipse(Brushes.Red, rect);
      }
      void TimerOnTick(object obj, EventArgs ea)
      {
          Graphics grfx = CreateGraphics();

          grfx.DrawImage(bitmap, xCenter - cxTotal / 2,
                         yCenter - cyTotal / 2,
                         cxTotal, cyTotal);
          grfx.Dispose();

          xCenter += cxMove;
          yCenter += cyMove;

          if ((xCenter + cxRadius >= ClientSize.Width) || 
              (xCenter - cxRadius <= 0))
                    cxMove = -cxMove;

          if ((yCenter + cyRadius >= ClientSize.Height) || 
              (yCenter - cyRadius <= 0))
                    cyMove = -cyMove;
      }
  }

Конечно, главный вопрос — не то, как мяч выводится в клиентской области, а то, как стирается его предыдущее изображение и может ли программа реализовать это без мерцания. Решение данной проблемы в том, что размер растрового изображения больше размера мяча, и границы вокруг мяча стирают предыдущее его изображение.

Программа Bounce заново строит растровое изображение при каждом вызове метода OnResize формы. Радиус мяча равен 1/16 ширины или высоты клиентской области в зависимости от того, что из них меньше. Однако программа создает растровое изображение большего размера, оставляя с каждой стороны по 1/4 радиуса мяча (изменить оба коэффициента очень легко). Сначала растровое изображение закрашивается белым цветом, а затем на нем рисуется мяч (я поместил код рисования мяча в защищенный виртуальный метод, чтобы в одной из следующих глав нарисовать мяч, который будет выглядеть получше).

Размеры полей вокруг мяча хранятся в параметрах cxMove и cyMove. Совершенно не случайно, что значения этих параметров точно соответствуют шагу перемещения по битовой карте при вызове метода TimerOnTick.


Рис. 11.19.

В общем случае такой простой подход к анимации может и не сработать. Сделайте фоном клиентской области что-либо, отличное от сплошного цвета, и весь вышеописанный способ распадется.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz