Содержание
- Введение
- Спрайты 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 (по русски плохо
звучит). В игре он черный. 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 ... очень удобно переносить код.
Список ссылок:
|