| 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 | < Назад | Оглавление | Далее > |