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

Введение
Для начала нужно разобраться, что же такое спрайт. Вот такое описание я нашел в книге Андрэ Ла Мота: 
" Знаете, есть такой газированный напиток... Снова шучу. На самом деле спрайты - это маленькие объектики, которые находятся на игровом поле и могут двигаться. Этот термин прижился с легкой руки программистов фирмы 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;
 ... 
 Wizard:=Tbitmap.Create; 
 Wizard.Loadfromfile('spr1.bmp'); 
 WizardMask:=Tbitmap.Create; 
 WizardMask.Loadfromfile('spr2.bmp'); 
 Buffer:=Tbitmap.Create; // Копируем маску в буфер 
 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); 
 ... 
// Перемещаем буфер на форму 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
Рис 1	Рис 2	Рис 3	Рис 4
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. У вас может возникнуть вопрос - почему передвижение спрайта за мышкой. Ответ: наименьшие затраты на писанину тест программы, при неплохом разнообразии движения.
Источник: http://www.delphigfx.narod.ru

                    
                    
                    
                    
                    
                    


