Перейти к содержимому

Фотография

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

- - - - -

  • Авторизуйтесь для ответа в теме

#1
daimond

Отправлено 15 ���� 2008 - 05:17

daimond

    Свояк

  • Пользователи
  • 232 сообщений
Введение Для начала нужно разобраться, что же такое спрайт. Вот такое описание я нашел в книге Андрэ Ла Мота: " Знаете, есть такой газированный напиток... Снова шучу. На самом деле спрайты - это маленькие объектики, которые находятся на игровом поле и могут двигаться. Этот термин прижился с легкой руки программистов фирмы 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'); // Копируем маску в буфер 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 Рис 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. У вас может возникнуть вопрос - почему передвижение спрайта за мышкой. Ответ: наименьшие затраты на писанину тест программы, при неплохом разнообразии движения. Использование внешних процедур для Transparent вывода спрайтов, хорошо но есть несколько минусов данного способа: во первых эти процедуры не слишком оптимизированы - их основное предназначение вывод простеньких элементов приложения, таких как иконок, картинок кнопок и т.д. Хотя это не относится к некоторым библиотекам, код которых на 90% состоит из ассемблера. во вторых хранить выводимое изображение нужно в bmp файле, хотя подойдет и любой другой, не применяющий компрессию с потерей ( Jpeg) . Если картинок более 1-й, а при нормальной анимации их набирается порядка 150-200 на один юнит, то сложно получать именно нужный участок файла.