<< Предыдущая страница

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

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

#ifndef Unit1H
#define Unit1H

#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vc1\Forms.hpp>
#include "sprite.h"
#include <vcl\Buttons.hpp>
#include "sampreg.h"
#include <vcl\Dialogs.hpp>

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

class TForm1 : public TForm
{
__published: // IDE-managed Components
    TPaintBox *PaintBox;
    TImage *DrawBox;
    TImage *Background;
    TImage *Figures;
    TTimer *Timer1;
    TPanel * Panel1;
    TSpeedButton *SpeedButton1;
    TSpeedButton *SpeedButton2;
    TSpeedButton *SpeedButton3;
    TSpinEdit *SpinEdit1;
    TOpenDialog *OpenDialog;

    void __fastcall Timer1Timer(TObject * Sender);
    void __fastcall SpinEdit1KeyUp(TObject *Sender, WORD &Key, TShiftState Shift);
    void _fastcall SpinEdit1KeyDown(TObject * Sender, WORD &Key, TShiftState Shift);
    void_fastcall SpeedButton1Click(TObject *Sender) ;
    void_fastcall SpeedButton2Click(TObject * Sender);
    void_fastcall SpeedButton3Click(TObject * Sender);

private: // User declarations

public: // User declarations
   __fastcall TForm1(TComponent* Owner);
    int W, H, w, h;
    FigureType Fig[2];
    SpriteClass Sprite[4];
};

extern TForm1 *Form1;

#endif

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

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

//--------------------------------------

#include <vcl\vcl.h>

#pragma hdrstop

#include "Unit1.h"

#pragma link "sampreg"

#pragma resource "*.dfm"

TForm1 *Form1;

//--------------------------------------

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

void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
    if (OpenDialog->Execute())
    { // Открыть файл изображения фона
       Background->Picture->LoadFromFile(OpenDialog->FileName) ;
       W = Background->Picture->Width;
       H = Background->Picture->Height;
    }

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

void __fastcall TForm1::SpeedButton2Click(TObject *Sender)
{
    if (OpenDialog->Execute())
    { // Открыть файл изображения фигур
       Figures->Picture->LoadFromFile(OpenDialog->FileName);
       w = (Figures->Picture->Width)/6;
       h = Figures->Picture->Height;
}
//-------------------------------------

void __fastcall TForm1::SpeedButton3Click(TObject *Sender)
{
// Инициализировать поля структуры обеих фигур
    Fig[0].X = W/4; Fig[1].X = 3*W/4;
    Fig[0].Y = H/4; Fig[1].Y = 3*H/4;
    Fig[0].DX = 1; Fig[1].DX = -1;
    Fig[0].DY = 1; Fig[1].DY = -1;
    Fig[0].SD = 0; Fig[1].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) ;
   Timer1->Enabled = true;
}

void __fastcall TForm1::SpinEdit1KeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
    Timerl->Interval++;
}

void __fastcall TForm1::SpinEdit1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
    Timer1->Interval--;
}
//---------------------------------------

int Shift = 0; // переменная прокрутки фона

void __fastcall TForm1::Timer1Timer(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[1].X += Fig[1].DX; Fig[1].Y += Fig[1].DY;
    if (Fig[1].X > (W-w)) Fig[1].DX = -1;
    if (Fig[1].X < (W/2+w)) Fig[1].DX = 1;
    if (Fig[1].Y > (H-h)) Fig[1].DY = -1;
    if (Fig[1].Y < 30) Fig[1].DY = 1 ;

// Оживить фигуры, переключая экземпляр спрайта
    Fig[0].SD = (Fig[0].SD == 0) ? 2 : 0;
    Fig[1].SD = (Fig[1].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->CopyMode = 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[1].SD].Draw(DrawBox->Canvas,Fig[1].X, Fig[1].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 [ 1 ] . SD для связи I-фигуры с нужным экземпляром спрайта.

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

<< Предыдущая страница