Недавно добавленные исходники

•  TDictionary Custom Sort  3 226

•  Fast Watermark Sources  2 992

•  3D Designer  4 751

•  Sik Screen Capture  3 259

•  Patch Maker  3 467

•  Айболит (remote control)  3 528

•  ListBox Drag & Drop  2 904

•  Доска для игры Реверси  80 788

•  Графические эффекты  3 843

•  Рисование по маске  3 171

•  Перетаскивание изображений  2 544

•  Canvas Drawing  2 672

•  Рисование Луны  2 500

•  Поворот изображения  2 093

•  Рисование стержней  2 120

•  Paint on Shape  1 525

•  Генератор кроссвордов  2 183

•  Головоломка Paletto  1 730

•  Теорема Монжа об окружностях  2 158

•  Пазл Numbrix  1 649

•  Заборы и коммивояжеры  2 016

•  Игра HIP  1 262

•  Игра Go (Го)  1 201

•  Симулятор лифта  1 422

•  Программа укладки плитки  1 177

•  Генератор лабиринта  1 512

•  Проверка числового ввода  1 297

•  HEX View  1 466

•  Физический маятник  1 322

•  Задача коммивояжера  1 357

 
скрыть


Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Delphi Sources

Работа со спрайтами



Содержание

  • Введение
  • Спрайты c готовой маской
  • Cпрайты c программной маской Transparent
  • Использование TImageList
  • Использование Direct X
  • Список ссылок

Введение

Для начала нужно разобраться, что же такое спрайт. Вот такое описание я нашел в книге Андрэ Ла Мота:

" Знаете, есть такой газированный напиток... Снова шучу. На самом деле спрайты - это маленькие объектики, которые находятся на игровом поле и могут двигаться. Этот термин прижился с легкой руки программистов фирмы Atari и Apple в середине 70-х годов. Спрайты - это персонажи в играх для ПК, которые могут без труда перемещаться по экрану, изменять цвет и размер "

И так, спрайт это персонаж игры. Не углубляясь в дебри программирования, могу сказать что спрайт это массив из цветов - для простаты представим его как BMP файл или TBitmap, тем более что, этот формат поддерживаемый windows и не содержащий компрессии.

Что нам нужно от спрайта - заставить его появляться на экране и образовывать анимацию. Анимация это не только смена координаты спрайта, но и изменение самой картинки. Следовательно спрайт может иметь не одно изображение, а несколько. Смена их и приводит к анимации.

Как я уже говорил спрайт это матрица. При вписывании в кравдрат ( прямоугольник ) сложного объекта, например волшебника из рисунка ниже, остается свободное пространство. Его заполняют цветом, которого нет в изображении самого объекта. При простом копировании этой матрицы ( или для простоты BMP или TBitmap ) на экран выводится и волшебник и фон под ним. Но нам это не всегда, подчеркну не всегда, нужно. Если спрайт выводится на фон, то он затирает все квадратную область.

Как я уже говорил спрайт это матрица. При вписывании в кравдрат ( прямоугольник ) сложного объекта, например волшебника из рисунка ниже, остается свободное пространство. Его заполняют цветом, которого нет в изображении самого объекта. При простом копировании этой матрицы ( или для простоты BMP или TBitmap ) на экран выводится и волшебник и фон под ним. Но нам это не всегда, подчеркну не всегда, нужно. Если спрайт выводится на фон, то он затирает все квадратную область.


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

1-й способ ( маг в белом квадрате ) основан на простом копировании одной области памяти в другую.

2-й способ ( маг на фоне ) то же копирование, но интеллектуальное. Копирование происходит по следующему алгоритму: Если цвет копируемого элементы матрицы ( область памяти ) соответствует значению цвета Transparent Color, то копирования не происходит, переходим к следующему элементу.

3-й способ так же основан на копирование области памяти, но с применением логических операций - маски.

Спрайты c готовой маской

Способов вывести спрайт на поверхность экрана много. Рассмотрим один из них. Это способ, когда отдельно рисуется спрайт и отдельно маска. Для этого нам понадобится сам спрайт, его маска и буфер.


Спрайт

Маска спрайта

И спрайт и маска должны иметь одинаковый размер, в данном примере 50x50. Для чего нужна маска? Она нужна для того, чтобы при выводе спрайта не затиралось изображение, которое находится под ним. Маску можно заготовить отдельно в BMP файле - более быстрый способ, а можно рассчитать программно.Спрайт и маску помещаем в TBitmap.

Wizard := Tbitmap.Create;
Wizard.Loadfromfile('spr1.bmp'); // Bitmap для спрайта
WizardMask := Tbitmap.Create;
WizardMask.Loadfromfile('spr2.bmp'); // Bitmap для маски

Ну вот, у нас есть спрайт, маска и нам это вывести его на экран. Для этого существует функция Win32Api:

BitBlt (param_1,X1,Y1,dX1,dY1,param_2,X2,Y2,param_3);
  • Param_1 - Handle на поверхность куда выводить.
  • X1,Y1 - Смещение от начала координат.
  • dX1,dY1 - Размер выводимого изображения.
  • Param_2 - Handle откуда брать.
  • X2,Y2 - Размер выводимого изображения.
  • Param_3 - Параметры копирования.

Для нашего случая:

BitBlt(Buffer.Canvas.Handle,X,Y,50,50, WizardMask.Canvas.Handle,0,0,SrcPaint); 
BitBlt(Buffer.Canvas.Handle,X,Y,50,50, Wizard.Canvas.Handle,0,0,SrcAnd); 
  • SrcPaint - Копировать только белое.
  • SrcAnd - Копировать все кроме белого.

Сначала выводим маску с параметром SrcPaint, а затем в тоже место ( координаты X,Y) сам спрайт с параметром SrcAnd.

Осталось рассмотреть зачем же нужен буфер. При выводе одного спрайта вы не почувствуете мелькания изображения, но когда их будет 100-200 это будет заметно. По этому все спрайты копируются в буфер - это Tbitmap размером с экран или окно, короче изменяемой области. Вот как окончательно будет выглядеть фрагмент программы :

...
var
  Wizard, WizardMask, Buffer: Tbitmap;
  X, Y: integer;
begin
  ...
  Wizard := Tbitmap.Create;
  Wizard.Loadfromfile('spr1.bmp');
  WizardMask := Tbitmap.Create;
  WizardMask.Loadfromfile('spr2.bmp');
  // Копируем маску в буфер BitBlt(Buffer.Canvas.Handle,X,Y,50,50,
  Buffer := Tbitmap.Create;
  WizardMask.Canvas.Handle, 0, 0, SrcPaint);
  // Копируем спрайт в буфер
  BitBlt(Buffer.Canvas.Handle, X, Y, 50, 50, Wizard.Canvas.Handle, 0, 0, SrcAnd);
  ...
  // Перемещаем буфер на форму BitBlt(Form1.Canvas.Handle,0,0,320,240,
  // Buffer.Canvas.Handle,0,0,SrcCopy);

Флаг SrcCopy означает копирование без изменения, аналогичен простому перемещению одного участка памяти в другой.

Не нужно думать, что готовая маска это прошлое компьютерных игр. В любом случае, маска создается, только иногда это делается программно, а иногда заготавливается в виде отдельного файла. Какой вариант лучше, нужно смотреть по конкретному примеру.

Я не буду расписывать все параметры BitBlt, если интересно смотрите сами в Delphi Help. Ну вот и все. Напоследок картина творчества.


Спрайт

Cпрайты c программной маской - Transparent

Другой метод вывода спрайтов - методом программной маски. Этот способ, немного медленнее, но не требует возни с изготовлением масок. Это не значит, что маски вообще нет. Маска присутствует и создается в памяти.

Для счастливых обладателей Windows NT подойдет способ, который используется в самой ОС. Это функция MaskBlt. Судя по ее названию, она позволяет выводить растры используя битовые маски.

Привиду пример на спрайтах из игры Эпоха Империй I. Наша задача, как и во всех предыдущих примерах, вывести спрайт с Transparent Color (по русски плохо звучит). В игре он черный.


Начальный вариант спрайта

Это уже полученная маска


Вызвали MaskBLT

MaskBlt + BitBlt

var
  Sprite, Mask: TBitmap;
begin
  Sprite := TBitmap.Create;
  Sprite.LoadFromFile('G0100219.bmp');
  Mask := TBitmap.Create;
  Mask.LoadFromFile('G0100219.bmp');
  Mask.Mask(clBlack); // Создание маски
  // Преобразование в маску, после этого получится Bitmap, представленный
  // на Рис 2
  MaskBlt(Form1.Canvas.Handle, 10, 10, Sprite.Width, Sprite.Height,
    Sprite.Canvas.Handle, 0, 0, Mask.MaskHandle, 0, 0, SRCPAINT);
  // После вызова этой функции, экран выглядит как на рисунке 3.
  BitBlt(Form1.Canvas.Handle, 10, 10, Sprite.Width, Sprite.Height,
    Sprite.Canvas.Handle, 0, 0, SRCPAINT);
end;

С Windows NT все понятно, но как быть в других ОС? ( Хотя возможно, эта функция появится(-лась) в Windows 2000 и Windows Me). Использовать библиотеки сторонних разработчиков. Если они поставляются с исходным кодом, то вы можете перенести необходимые вам процедуры в собственный модуль.

Я нашел самую быструю библиотеку для работы с графикой - Media Library Component Version 1.93. В примере используется только часть ее. Нам понадобится только одна процедура:

DrawBitmapTransparent(param_1,X,Y,param_2,param_3); 
  • param_1 - Canvas, куда копировать
  • X,Y - Смещение
  • param_2 - TBitmap, что копировать.
  • param_3 - TColor, цвет Transparent - этот цвет не будет копироваться

Применение только данной библиотеки не принципиально. Практически в любом наборе VCL компонентов от сторониих производителей есть процедуры или функции для вывода Bitmap с использованием цвета прозрачности. Такие процедуры есть в библиотеке RXLib, LMD Tools, Cool Control и многих других.

Для нашего примера: DrawBitmapTransparent(Buffer.Canvas,WizardX,WizardY,Wizard,clRed); Спрайт должен выглядеть так:


Небольшое замечание по поводу Transparent. Цвет надо выбирать такой, которого нет на самом спрайте, иначе неизбежны "дырки" в изображении. Лучше всего такой : #00FF00 - ярко зеленый, но можно использовать черный или белый.

В предыдущей главе "Работа спрайта c готовой маской" я подвесил передвижение спрайта на таймер:

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ... // тело цикла
end.

Да cпособ хорош, но не так быстродейственен. Есть еще пара вариантов :

1. Создать поток TThread - в примере разобран именно он.
2. "Подвесить" на IDL

Рассмотрим сначала второй способ т.к. он наименее прогрессивен:) Пишем такую процедуру:

procedure TForm1.Tic(Sender: TObject; var Done: Boolean);
begin
  ...
  // Сюда заносим, что надо исполнять.
  ...
  Done := false;
end;

.... и еще немного:

procedure TForm1.FormCreate(Sender: TObject);
 begin
  ...
  Application.OnIdle := Tic;
end;

Способ быстрее в 1-2 раз чем таймер, но не лишен недостатков. Не буду объяснять почему. Первый способ самый оптимальный для игры, как самой сложной так и простой. Реализуется он с помощью потоков. В игре их можно создать несколько - один для обработки графики, другой для AI, третий для музыки и т.д. У каждого потока свой приоритет, но высший только у одного. При работе с несколькими потоками не забывайте их "прибивать" при выходе из программы.

Сначала заводим новый класс:

TGameRead = class(TThread) // класс для таймера игры
protected
  procedure Execute; override; // Запуск
  procedure Tic; // Один тик программы
end;
// Потом переменную :

var
   ...
  T1: TGameRead;
  ...

// Описываем процедуры класса :

procedure TGameRead.execute;
 begin
  repeat
    synchronize(Tic);
  until
    Terminated
end;

procedure TGameRead.Tic;
 begin
  ...
  // Тут пишем все как в TTimer - OnTime
  ...
end;

// В событии Form1.Create инициализируем поток, и задаем приоритет.
// Расписывать все приоритеты не буду, читайте Delphi Help
// ...и не забываем убрать за собой:

...
T1 := TGameRead.Create(false); // Создаем поток
T1.Priority := TpHighest; // Ставим приоритет
...

procedure TForm1.FormDestroy(Sender: TObject);
 begin
  T1.Suspend; // Приостановим
  T1.Free; // и прибьем
end;

// Ну вот и все. Ах да, вас наверное заинтересовала строчка FPS.
// Так это тоже самое, что выдает Quake на запрос "showframerate"
// или что-то такого плана - количество кадров в секунду.
// Делается это так : заводится переменная:

var
   G: integer;
  ...

// При каждом вызове потока Tic, она увеличивается на единицу:

procedure TGameRead.Tic;
 begin
  ...
  Inc(G); // Увеличиваем значение G
end;

// Создаем таймер с интервалом 1000 - это 1 секунда, и в событии
// OnTime выводим значение G в метку. В значении G будет количество
// вызовов процедуры DoSome за 1 секунду:

procedure TForm1.Timer1Timer(Sender: TObject);
 begin
  label1.caption := 'FPS :' + IntToStr(G);
  G := 0; // Обнуляем G
end;

На моем средненьком Pentium AMD 233 c Intel 740 8M - выдает 90-100 кадров в секунду, при окне 360X360. Для начала неплохо!

P.S. У вас может возникнуть вопрос - почему передвижение спрайта за мышкой. Ответ: наименьшие затраты на писанину тест программы, при неплохом разнообразии движения.


Использование внешних процедур для Transparent вывода спрайтов, хорошо но есть несколько минусов данного способа:

во первых эти процедуры не слишком оптимизированы - их основное предназначение вывод простеньких элементов приложения, таких как иконок, картинок кнопок и т.д. Хотя это не относится к некоторым библиотекам, код которых на 90% состоит из ассемблера.

во вторых хранить выводимое изображение нужно в bmp файле, хотя подойдет и любой другой, не применяющий компрессию с потерей ( Jpeg) . Если картинок более 1-й, а при нормальной анимации их набирается порядка 150-200 на один юнит, то сложно получать именно нужный участок файла.

Приведу пример

В bmp файле содержатся 8 картинок - 64x64 пикселя. Нужно получить доступ к 6-й картинке ( на рисунке помечена розовым квадратом)- ее координаты будут 128,64


Чтобы получить следующий кадр анимации, нужно снова ко номеру кадра считать координаты : Не совсем удобно. Все эти проблемы можно решить используя TImageList.

Использование TImageList

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

Разберем что нужно сделать, для вывода спрайта с использованием TImageList. Во первых нужно загрузить набор спрайтов TImageList, для этого лучше всего использовать команду:

TImageList.AddMasked(Image: TBitmap; MaskColor: TColor): Integer;

Первый параметр - это Bitmap, второй Transparent Color - цвет прозрачности. Если Вам не нужно использовать цвет прозрачности, то нужно использовать процедуру Add. После загрузки всех картинок, можно приступать к их выводу на экран. Для этого существует процедура:

procedure TImageList.Draw(Canvas: TCanvas; X, Y, Index: Integer);

Первый параметр Canvas на который будет произведена отрисовка, второй и третий координаты для вывода X и Y а четвертый индекс или порядковый номер выводимого изображения.

Для примера:

ImageList1.Draw(Canvas,0,0,6); 

Тот же самое, но с использованием BitBlt:

BitBlt(Canvas.Handle,0,0,64,64,Bitmap_Mask.Canvas.Handle,128,64,SrcPaint); - маска 
BitBlt(Canvas.Handle,0,0,64,64,Bitmap.Canvas.Handle,128,64,SrcAnd; - спрайт 

ImageList1.Draw(Canvas,0,0,6); 

Тот же самое, но с использованием BitBlt:

BitBlt(Canvas.Handle,0,0,64,64,Bitmap_Mask.Canvas.Handle,128,64,SrcPaint); - маска 
BitBlt(Canvas.Handle,0,0,64,64,Bitmap.Canvas.Handle,128,64,SrcAnd; - спрайт 

Думаю пояснять нет нужды, что использовать TImageList лучше, и проще. Пример работы с TImageList описан в файле. Там показана анимация персонажа из игры WarCraft и Warlord III. Я так и не разобрался как работает механизм отрисовки в TImageList. Мои раскопки привели к такой функции :

function ImageList_Draw(ImageList: HImageList; Index: Integer; Dest: HDC;
  X, Y: Integer; Style: UINT): Bool; stdcall;

и

function ImageList_DrawEx(ImageList: HImageList; Index: Integer; Dest: HDC;
  X, Y, DX, DY: Integer; Bk, Fg: TColorRef;
  Style: Cardinal): Bool; stdcall; 

HImageList - Handle на TImageList.

Так как вызывается экспортируемая процедура, находящаяся в библиотеке Comctl32.dll то остается не понятным, какие алгоритмы используются при выводе изображения. Могу только сказать, что при добавлении нового изображения, добавляется как изображение так и маска.

Я заинтересовался данным вопросом и продолжал копать стандартные библиотеки Windows и компоненты. Возможно информация по данным вопросам содержится во многочисленных SDK, выпускаемых Microsoft. В компоненте TFastDIB я наткнулся на процедуру Draw:

procedure TFastDIB.MaskDraw(fDC,x,y:Integer;c:TFColor); 
begin 
  TransBlt(fDC,x,y,Width,Height,hDC,0,0,Width,Height,PDWord(@c)^); 
end; 

Естественно меня заинтересовала процедура TransBlt и вот что я нашел:

function TransBlt(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11:DWord):BOOL; stdcall; 
... 
function CreateDIB; external 'gdi32.dll' name 'CreateDIBSection'; 
function TransBlt; external 'msimg32.dll' name 'TransparentBlt'; 
function AlphaBlt; external 'msimg32.dll' name 'AlphaBlend'; 

Мне захотелось посмотреть, а что еще может библиотека 'msimg32.dll' и вот полный список:

AlphaBlend 
GradientFill 
TransparentBlt 
DllInitialize 
vSetDdrawflag 

Все, хватит, а то некоторые читатели и так ничего не поняли. Но для интересующихся скажу - не все процедуры и функции описаны в Delphi, многое не документировано.

Использование DirectX

Чем плохи рассмотренные выше методы вывода спрайтов - они медленные. Хочу подчеркнуть, что для каждой программы нужно выбирать свои методы написания. Конкретное задание требует своих средств исполнения. То что Microsoft написал библиотеку Direct X не значит что тут же нужно писать всю графику используя ее.

Приведу пример. Самая популярная игра для Windows - Quake II, Warcraft, Diablo - нет САПЕР и ПАСЬЯНС. Можете не верить, но это факт. В первую категорию играют ограниченный контингент людей в последнюю играли ВСЕ. Я это говорю к тому, что если вы пишите графическое приложение, то нужно ориентироваться на его потребности и выбирать соответствующие технологию зависимости от них. Какие это потребности:

необходимость вывода большого количества часто сменяющихся изображений
большой объем графической информации
аппаратная поддержка
максимальное быстродействие

Используя Direct X можно получит все вышеперечисленное. Набор этих библиотек, изначально разрабатывался как средство для работы с графикой. Что было, когда писали под DOS: строили в участке памяти ( back buffer ) какое то изображение или копировали туда спрайты, а потом перемещали этот back buffer в область "экранной" памяти. Сразу отрисовывался весь экран. С приходом Windows, переместить участок памяти в экранную область не возможно. Приходится использовать Canvas, Handle.

DirectX позволяет решить все эти проблемы. Вы можете подготавливать изображение на так называемой поверхности, и потом FLIP и вся поверхность становится видимой - копируется в экранную область видеопамяти. Должен заметить, что алгоритм работы ничуть не меняется.

С появлением DirectX появились и аппаратные поддержки таких необходимых вещей как: Trancparent Color и Bit blitting.

Термин бит-блиттинг означает процесс перемещения группы битов (образа) из одного места экрана в другое или памяти. В играх на ПК нас интересует перемещение образа из области хранения вне экрана в область видеобуфера. Кто интересуется аппаратными возможностями своей видео карты, то их можно узнать достав Microsoft DirectX CPL. В ней можно просмотреть, какие функции в видео карте реализуются аппаратно, а какие програмно.

Итак процесс работы таков, загружаете спрайты на поверхность (ISurface) затем нужно вызвать процедуру BLT или BLTFAST, потом поменять буферную и видимую поверхность командой FLIP и все.

В начале раздела я написал Direct X, но я несколько обманул Вас. Я расскажу как выводить спрайты с помощью Direct X, но с использованием набора VCL компонентов DelphiX . Я это делаю по той простой причине, что если я напишу пример используя стандартные модули DirectX то их некоторые не поймут, отчаяться и бросят программировать вообще :) Согласитесь не все сразу поймут, что делает данная процедура, хотя она всего лишь меняет поверхности.

var
  hRet: HRESULT;
begin
  Result := False;
  while True do
  begin
    hRet := FDDSPrimary.Flip(nil, 0);
    if hRet = DD_OK then
      Break
    else
      if hRet = DDERR_SURFACELOST then
      begin
        hRet := RestoreAll;
        if hRet <> DD_OK then
          Exit;
      end
      else if hRet <> DDERR_WASSTILLDRAWING then
        Exit;
  end;
  Result := True;
end;

По этому я и решил использовать DelphiX. Писать с помошью него очень просто. Нам потребуется всего два компонента. Первый TDXDraw - если объяснить коротко, то это аналог TCanvas. Еще один компонент это TDXImageList - прямой аналог TImageList, единственно все элементы являются TDIB и не содержат ни каких масок.

Что нужно сделать чтобы успешно создать и анимировать спрайт. Как и с TImageList нужно загрузить BMP файл в элемент TDXImageList. Элемент TImageList предварительно нужно создать в программе или создать из Object Inspector.

DXImageList1.Items[0].picture.LoadFromFile('golem_start.bmp');

// Для вывода нужно использовать процедуру: 
DXImageList1.Items[0].draw(DXDraw1.Surface,0,0,6); 

Вот и все. Прямая аналогия с TImageList ... очень удобно переносить код.


Список ссылок:





Похожие по теме исходники

Nstruct (работа с DBF)

Работа с принтером

fwZIP - Работа с ZIP архивами




Copyright © 2004-2024 "Delphi Sources" by BrokenByte Software. Delphi World FAQ

Группа ВКонтакте