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; } }
Заметьте: при загрузке ресурсов конструктор использует имена Wink.Eye1.png, Wink.Eye2.png и т.д. Wink — это название пространства имен. Метод TimerOnTick с помощью DrawImage последовательно выводит каждое изображение по центру клиентской области. Вот программа в действии:
При анимации надо выводить изображения в пиксельном размере. Это позволяет предотвратить уменьшение и унеличение изображения, а также экономит время процессора.
Шутки ради я создал в следующей программе дочерний класс 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.
А сейчас то, чего вы все ждали, — прыгающий мяч! Программа 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.
В общем случае такой простой подход к анимации может и не сработать. Сделайте фоном клиентской области что-либо, отличное от сплошного цвета, и весь вышеописанный способ распадется.
netlib.narod.ru | < Назад | Оглавление | Далее > |