Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Графические компоненты 7.doc
Скачиваний:
2
Добавлен:
08.07.2019
Размер:
100.86 Кб
Скачать

7.5.1 Проектирование формы

Начнем проектирование приложения, перетаскивая из Палитры на форму следующие компоненты:

=> Три невидимых и пустых контейнера TImage из вкладки Additional и один видимый и пустой контейнер TPaintBox из вкладки System. Объекты DrawBox класса TImage и PaintBox класса TPaintBox совместно реализуют двойную буферизацию графики: сначала фигуры и фон рисуются на канве внеэкранного битового образа, в объекте DrawBox, а затем изображение канвы копируется на экран, в объект PaintBox.

=> Панель инструментов TPanel из вкладки Standard.

=^> Таймер TTimer из вкладки System.

=> Компоненту редактирования TSpinEdit с кнопками "а" и "-у" из вкладки Samples для управления скоростью анимации.

=> Три быстрые кнопки TSpeedButton из вкладки Additional для управления работой программы.

=^ Компоненту TOpenDialog из вкладки Dialogs диалога открытия файлов.

Рис. 7.2 показывает форму приложения, фоном которой служит изображение облачного неба из файла Clouds.bmp, а в качестве движущихся фигур выступают два самолета из файла Planes, bmp или два вертолета из файла Helicopters.bmp. Редактор изображений открыт двойным щелчком мышью в графе значений свойства Picture компонентного объекта Figures. Обратите внимание, что изображение каждой фигуры представлено тремя битовыми образами - первые два отображают возможные фазы анимации фигуры, а третий представляет собой бинарную маску внешнего контура. Обе фигуры различаются только цветом и имеют одинаковую форму, поэтому разделяют общую пару масок. Такая совокупность составляет банк данных спрайта.

Рис. 7.2. Форма приложения анимации.

7.5.2 Программный модуль

К файлу модуля Unit1.h (Листинг 7.5) добавлено описание структуры FigureType, включающей позицию (X,Y), смещение (DX,DY) фигуры и номер объекта спрайта (SD); а в секции public класса формы объявлены четыре переменные размеров изображений, две фигуры Fig [2] и соответствующие им две пары спрайтов Sprite [4]. Класс SpriteClass и его методы определены в файлах Sprite.h и Sprite.cpp, соответственно.

ttifndef UnitlH

#define UnitlH

#include <vcl\Classes.hpp> ttinclude <vcl\Controls.hpp> ftinclude <vcl\StdCtrls.hpp>

#include <vc1\Forms.hpp> ttinclude "sprite.h"

#include <vcl\Buttons.hpp> ftinclude "sampreg.h" ftinclude <vcl\Dialogs.hpp>

typedef struct { int X, Y, DX, DY, SD; } FigureType;

class TFormI : public TForm (

_published: // IDE-managed Components TPaintBox *PaintBox;

TImage *DrawBox;

TImage *Background;

TImage *Figures;

TTimer *Timerl;

TPanel * Panel 1;

TSpeedButton *SpeedButtonl;

TSpeedButton *SpeedButton2;

TSpeedButton *SpeedButton3;

TSpinEdit *SpinEditl;

TOpenDialog *OpenDialog;

void _fastcall TimerlTimer(TObject * Sender);

void _fastcall SpinEditlKeyUp(TObject *Sender, WORD &Key, TShiftState Shift);

void _fastcall SpinEditlKeyDown(TObject * Sender,

WORD &Key, TShiftState Shifts-void_fastcall SpeedButtonlClick(TObject *Sender) ;

void_fastcall SpeedButton2Click(TObject * Sender);

void_fastcall SpeedButton3Click(TObject * Sender);

private: // User declarations public: // User declarations _fastcall TFormI(TComponent* Owner);

int W, H, w, h;

FigureType Fig[2];

SpriteClass Sprite[4];

};

extern TFormI *Forml;

#endif

Листинг 7.5. Содержание файла Unii1.h.

Файл модуля Unit1.cpp (Листинг 7.6) содержит 5 обработчиков событий от нажатия кнопок управления и обработчик события таймера.

//-_---------_____________-___-_-----__-____________________-

^include <vcl\vcl.h>

#pragma hdrstop

#include "Unitl.h"

#pragma link "sampreg"

#pragma resource "*.dfm" TFormI *Forml;

//----------------——--——--—------———------—-----------

_fastcall TFormI::TFormI(TComponent* Owner) : TForm(Owner) ( // Конструктор формы устанавливает размеры по умолчанию W = Н = 400; w = h =61;

} //----------------------------------------------------------

void_fastcall TFormI::SpeedButtonlClick(TObject *Sender) { if (OpenDialog->Execute())

{ // Открыть файл изображения фона

Background->Picture->LoadFromFile(OpenDialog->FileName) ;

W = Background->Picture->Width;

H = Background->Picture->Height;

}

} //----—----------——-------—-——---—-—-—-------------

void_fastcall TFormI::SpeedButton2Click(TObject *Sender) { if (OpenDialog->Execute())

{ // Открыть файл изображения фигур

Figures->Picture->LoadFromFile(OpenDialog->FileName);

w = (Figures->Picture->Width)/6;

h = Figures->Picture->Height;

//______---------------------------------------------------

void_fastcall TFormI::SpeedButton3Click(TObject *Sender)

{ // Инициализировать поля структуры обеих фигур Fig[0].X = W/4; Fig[l].X = 3*W/4;

Fig[0].Y = H/4; Fig[l].Y = 3*H/4;

Fig[0].DX = 1; Fig[l].DX = -1;

Fig[0].DY = 1; Fig[l].DY = -1;

Fig[0].SD = 0; Fig[l].SD = 0;

// Подготовить экземпляры спрайта

Sprite[0].SetSprite(Figures->Canvas, 0,0, 4*w,0, w,h);

Sprite[1].SetSprite(Figures->Canvas, 2*w,0, 4*w,0, w,h);

Sprite[2].SetSprite(Figures->Canvas, w,0, 5*w,0, w,h);

Sprite[3].SetSprite(Figures->Canvas, 3*w,0, 5*w,0, w,h) ;

Timerl->Enabled = true;

}

void _fastcall TFormI::SpinEditlKeyUp(TObject *Sender,

WORD &Key, TShiftState Shift) { Timerl->Interval++; } void _fastcall TFormI::SpinEditlKeyDown(TObject *Sender,

WORD &Key, TShiftState Shift) { Timerl->Interval--; } //-.____-_-_-_-_.....____.__________________________________

int Shift = 0; // переменная прокрутки фона void _fastcall TFormI::TimerlTimer(TObject *Sender) ( // Сместить фигуры в пределах периметра

Fig[0].X += Fig[0].DX; Fig[0].Y += Fig[0].DY;

if (Fig[0].X > (W/2-w)) Fig[0].DX = -1;

if (Fig[0].X < 20) Fig[0].DX = 1;

if (Fig[0].Y > (H-2*h)) Fig[0].DY = -1;

if (Fig[0].Y < 20) Fig[0].DY = 1 ;

Fig[l].X += Fig[l].DX; Fig[l].Y += Pig[l].DY;

if (Fig[l].X > (W-w)) Fig[l].DX = -1;

if (Fig[l].X < (W/2+w)) Fig[l].DX = 1;

if (Fig[l].Y > (H-h)) Fig[l].DY = -1;

if (Fig[l].Y < 30) Fig[l].DY = 1 ;

// Оживить фигуры, переключая экземпляр спрайта

Fig[0].SD = (Fig[0].SD == 0) ? 2 : 0;

Fig[l].SD = (Fig[l].SD == 1) ? 3 : 1 ;

// Нарисовать фон и сдвинуть его влево

if (Shift == 0)

{ DrawBox->Canvas->CopyMode = cmSrcCopy;

DrawBox->Canvas->CopyRect(Rect(О, О, W, H),

Background->Canvas, Rect(0, 0, W, H));

}

else

{ DrawBox->Canvas->CopyMod^s = cmSrcCopy;

DrawBox->Canvas->CopyRect(Rect(0, 0, W-Shift, H),

Background->Canvas, Rect(Shift, 0, W, H) ) ;

DrawBox->Canvas->CopyRect(Rect(W-Shift, 0, W, H),

Background->Canvas, Rect(0, 0, Shift, H)) ;

}

Shift += 2;

if (Shift >= W) Shift -= W;

// Нарисовать фигуры на канве внеэкранного битового образа

Sprite[Fig[0].SD].Draw(DrawBox->Canvas,Fig[0].X, Fig[0].Y) ;

Sprite[Fig[l].SD].Draw(DrawBox->Canvas,Fig[1].X, Fig[l].Y) ;

// Скопировать изображение на экран

PaintBox->Canvas->CopyMode = cmSrcCopy;

PaintBox->Canvas->CopyRect(Rect(0, 0, W, H),

DrawBox->Canvas, Rect(0, 0, W, H)) ;

}

Листинг 7 6 Содержание файла Unit1 cpp.

Быстрые кнопки компонент TSpeedButton служат для управления программой. Первая кнопка открывает и загружает объект Background класса TImage из файла с изображением фона (размером w на H). Вторая кнопка открывает и загружает объект Figures класса TImage из файла с изображениями фигур (размером w на h каждая).

Третья кнопка устанавливает фигуры в начальные позиции и подготавливает объекты спрайта посредством обращении к методу SetSprite., а затем запускает таймер.

Нажатие кнопок компоненты редактирования TSpinEdit вызывает увеличение или уменьшение на единицу значения интервала таймера (по умолчанию устанавливается значение 40, соответствующее кадровой частоте 1/25 сек).

Ядром приложения является обработчик события OnTimer объекта Timerl. Первый блок кода реакции на прерывание от таймера отвечает за перемещение фигур, изменяя направление движения всякий раз, когда они "утыкаются" в фиксированные границы отведенного периметра. Рис. 7.3 изображает структуру канвы объектов DrawBox и PaintBox.

Рис. 7.3. Структура канвы для рисования движущихся фигур.

Следующий блок реализует прокрутку фона справа-налево, делая анимацию более реальной (можете называть этот процесс" виртуальной реальностью, если хотите). Фигуры могут сходиться и расходиться, но общее впечатление их движения слева-направо сохраняется. Для начала надо скопировать изображение фона целиком в буфер внеэкранного изображения, а затем организовать циклический сдвиг влево данных буфера на величину, установленную переменной Shift. Заключительный блок рисует, при помощи метода Draw, фигуры на канве невидимого объекта DrawBox, а затем копирует изображение канвы на экран, в видимый объект PaintBox.

7.5.3 Спрайты

Экземпляры класса SpriteClass (Листинг 7.7) обеспечивают анимацию одной маскируемой фигуры. Банк данных спрайта содержит канву невидимой компоненты TImage с битовыми образами фаз. Естественно, что все изображения банка данных спрайта должны быть одинакового размера. Изображения фаз анимации должны иметь черный фон, маска контура - белый.

class SpriteClass

private:

TCanvas *SpriteCanvas;

int ImageLeft, ImageTop;

int MaskLeft, MaskTop;

int Width, Height;

public:

void SetSprite(TCanvas *aSpriteCanvas,

int aImageLeft, int aImageTop, int aMaskLeft, int aMaskTop, int aWidth, int aHeight);

void Draw( TCanvas *aDrawCanvas, int aLeft, int aTop) ;

);

Листинг 7.7. Объявление класса спрайта в файле Spite, h.

В классе спрайта определены только два метода установки и рисования спрайта (Листинг 7.8). При вызове метода SetSprite аргументы aImageLeft и aImageTop определяют позицию спрайта на канве aSpriteCanvas, аргументы aMaskLeft и MaskTop - позицию маски, аргументы aWidth и aHeight - размеры спрайта. При вызове метода Draw аргумент aDrawCanvas задает канву рисования, а аргументы aLeft и aTop - позицию спрайта на этой канве.

void SpriteClass::SetSprite(TCanvas *aSpriteCanvas,

int aImageLeft, int aImageTop, int aMaskLeft, int aMaskTop, int aWidth, int aHeight) { SpriteCanvas = aSpriteCanvas;

ImageLeft = aImageLeft;

ImageTop = aImageTop;

MaskLeft = aMaskLeft;

MaskTop = aMaskTop;

Width = aWidth;

Height = aHeight;

} void SpriteClass::Draw(TCanvas *aDrawCanvas,

int aLeft, int aTop) { aDrawCanvas->CopyMode = cmSrcAnd;

aDrawCanvas->CopyRect(Rect(aLeft, aTop,

aLeft+Width, aTop+Height), SpriteCanvas, Rect(MaskLeft, MaskTop, MaskLeft+Width, MaskTop+Height)) ;

aDrawCanvas->CopyMode = cmSrcPaint;

aDrawCanvas->CopyRect(Rect(aLeft, aTop,

aLeft+Width, aTop+Height), SpriteCanvas,

Rect(ImageLeft, ImageTop, ImageLeft+Width.ImageTop+Height));

}

Листинг 7.8. Определение методов класса спрайта в файле Sprite.cpp.

Довольно трудно составить впечатление от работы приложения анимации по снимку одного кадра - тем более в черно-белом исполнении (Рис. 7.4). Книга не является подходящим носителем для распространения компьютерных мультфильмов. Если проблема вас заинтересовала, нет другого способа изучать ее дальше, как придумать и подготовить картинки и разработать собственное приложение, используя приведенные листинги в качестве прототипа.

Рис. 7.4. Снимок с экрана работающего приложения MOVEIT.

Сами фигуры представляют собой массив типа структуры, а не экземпляр какого-то класса. Однако не надо быть гениальным программистом, чтобы ввести логику работы с фигурами в соответствующий класс FigureClass, который будет инкапсулировать, в частности, свойство периметра и метод перемещения. Однако не стоит наследовать SpriteClass от класса фигур. Дело в том, что экземпляры этих классов имеют разную природу: спрайты - это графические объекты, а фигуры - логические конструкции. Если бы вам потребовалось произвести анимацию более, чем двух одинаковых фигур, все они по-прежнему будут разделять единственный класс спрайта, используя поле структуры Fig [ I ] . SD для связи I-фигуры с нужным экземпляром спрайта.

Взгляните на изображения фаз анимации самолета или вертолета. На второй фазе каждой фигуры пропеллер просто отсутствует. Постоянно переключая фазы, можно создать иллюзию вращения пропеллера. Зная, что программа не моделирует фактическое вращение, спросите, тем не менее, у наблюдающего за работой приложения: "В какую сторону вращается пропеллер?" - и любой уверенный ответ убедит вас в том, что время на программирование примера было потрачено не зря. Можно придумать какой-нибудь другой впечатляющий эффект, например, стреляющего пулемета. Расширяя банк данных спрайта дополнительными фазами фигур и соответствующими масками, можно добиваться различных эффектов