Создание карты в игре, методом спрайтов

Часть 1 
В играх, где используется простая 2D проекция, используется наверное два способа построения игрового пространства (карт). Первый о котором и будет рассказ, заключается в построение карты из спрайтов одинакового размера. А второй из уже готовых больших изображений, но об этом в другой главе.
 Преимущества этого способа - малый объем ресурсов графики, следовательно не требовательность к ресурсам. Практически любой размер карты - он ограничен лишь массивом и т.д. К минусам можно отнести сложность подгона текстур друг к другу. 
 Этот способ построения карт применялся в таких БОЕВИКАХ ( не побоюсь этого слова ) игрового фронта как : WARCRAFT, DUNE и т.д. С помощью этого типа карт можно написать неплохую RPG или стратегия. Ну приступим. Первое что нам нужно это спрайты: 
 В примере я сделал файл Flur1.bmp там 20 спрайтов размером 32 x 32 / при создании старайтесь делать спрайты квадратные - упрощается вычисление/. Спрайты индексируются 0 ... 19;
 Что представляет из себя карта - это двух мерный массив , элементы которого - индексы спрайтов.
 Например: MAP: Array [0..100, 0..100] of integer; 
Это означает, что мы хоти создать карту размером 100 x 100 спрайтов, допустим размер спрайта 32 x 32 тогда общая длина/ширина карты в пикселях будет 3200 x 3200. Но что вам мешает создавать карты, 1000 x 1000 клеток. ( Пример в HMM2 максимальная карта 128 x 128 при размере спрайта 32 пикселя )
 Я советую создать класс для карты, вот как сделал бы я :type TFlur= class
private
 SpriteLastIndex, // Кол-во спрайтов в Resource
 SpriteWH:integer;// Размер спрайта, должен быть квадрат
 Resource:TBitmap;// Ресурсы графики
public
 Table:Tlist; // Массив карты развернутый в список
 Width, Heigth:Integer;// Ширина и высота карты в спрайтах
 Constructor Create(Const X, Y, Sprite:integer);// ширина, высота, размер спрайта
 Procedure LoadResource(FileName:String); // путь к BMP ресурсу
 Procedure RandomGenerator; // случайное заполнение Table
 Procedure Draw(DestHandle:TCanvas;X, Y:Integer;SourceRect:TRect);
 // Canvas для вывода,
 // Смещение X и Y относительно начала DestHandle
 // SourceRect - какой кусок карты Table выводить на экран, В КОЛ-ВАХ СПРАЙТОВ
 Destructor Destroy; override;
end;
По поводу Table - я использовал TList вместо Array т.к. при создании мы можем не знаем, какого размера карту будем делать. В игре создавая один TFlur можно подгружать туда новые карты динамически, при переходе на новый уровень ( зону, город ... ), без замедления.
 Рассмотрим процедуру Draw, т.к. все остальное достаточно понятно :Procedure TFlur.Draw(DestHandle:TCanvas;X, Y:Integer;SourceRect:TRect);
var
 I, J:integer;
 dx, dy:integer; // для смещений в пикселях 
 TableIndex:^integer; // указатель на элемент Table
begin
 // Делаем проверку
 if SourceRect.Left<0 then exit;
 if SourceRect.Top<0 then exit;
 if SourceRect.Left+SourceRect.Right>Heigth-1 then exit;
 if SourceRect.Top+SourceRect.Bottom>Width-1 then exit;
 dx:=0;dy:=0;// обнуляем смещение
 // зададим цикл по области карты для показа
for I:=SourceRect.Left to SourceRect.Left+SourceRect.Right do
begin
 for J:=SourceRect.Top to SourceRect.Top+SourceRect.Bottom do
 begin
 TableIndex:=(Table.Items[Width*J+I]);
 // Уже знакомой прцедурой копируем один спрайт на экран
 BitBlt(DestHandle.Handle, dx+X, dy+Y, SpriteWH,
 SpriteWh, Resource.Canvas.Handle, TableIndex^*SpriteWh, 0, SRCCopy); inc(dy, SpriteWH);// увеличиваем смещение dy на SpriteWH
 end;
 Inc(dx, SpriteWH);dy:=0;// увеличиваем смещение dx на SpriteWH, обнуляем dy
end;
 end;
Достанем элемент из списка с координатами I, J
Так, в принципе класс написан, это даже не класс а некое подобие ENGINE :) Что можно добавить, изменить и т.д. Все зависит от конкретной задачи:
1. Если вы хотите сделать что-то подобное Color Lines, шахмат, морского боя и т.д. вам совсем не обязательно вводить Table - поле всегда имеет одинаковые размеры.
2. При выполнении Flur.Draw неплохо ввести проверку на значения SourceRect ( я сделал ее внутри процедуры обработки клавиатуры).
3. Все объекты карты - дома, леса, юниты, технику и д.р. желательно привязывать к координатам пиксельным ( да и вообще скроллинг карты надо делать то же пиксельным - но об зтом позже ).
4. Дописать загрузку Table из файла.
5. При выводе графики на в окно происходит мерцание курсора. Это связано с тем, что обновление курсора намного медленнее, чем вывод графики. Исправить это можно следующим способом. С формы убирается курсор ( Form1.Cursor:=crNone; ). Координаты курсора запоминаем в двух переменных. При от рисовки делаем так : создаем буфер, рисуем Flur в буфер, далее туда пихаем картинку из ImageList'а. И выводим буфер на экран.Плюсы этого метода: можно сделать курсор любой формы и менять его по ходу игры, ну и избавимся от дрожания.
 Вроде все, исходник тут. 
 Хранить спрайты можно не только в BMP, но и в TImageList. При этом появляется множество удобств: 
1. Можно спрайты не вытягивать в линейку, а рисовать на прямоугольнике - допустим 640x480. При загрузке достаточно указать размер спрайта, а ImageList сам их разобьет.
2. Удобно добиваться прозрачности. Это может понадобится для спрайтов не квадратной формы - ромб. Они пригодятся для изометрии.
3. Скорость вывода на экран потрясающая. Смотрите пример.
Итак, что я переписал в TFlur : коструктор и Draw 
 constructor TFlur.Create(ConstMapX, MapY, SpriteW, SpriteH:integer;ResFileName:String);
// MapX, MapY - размер карты в спрайтах
 // SpriteW, SpriteH - размер спрайта
 // ResFileNama - путь к BMP
var
 Resource:Tbitmap; // BMP с ресурсами
begin
 Resource:=Tbitmap.Create; // Грузим ресурсы из ResFileName
 Resource.LoadFromFile(ResFileName);
 ImageList:=TImageList.CreateSize(SpriteW, SpriteH); // Создаем лист под спрайты
 ImageList.DrawingStyle :=dsNormal; // Устанавливаем параметры
 ImageList.Masked := False;
 ImageList.Add(Resource, Resource);
 ....
end; 
И изменения в TFlur.Draw : ....
for J:=SourceRect.Top to SourceRect.Top+SourceRect.Bottom do
begin
 TableIndex:=(Table.Items[Width*J+I]);
 ImageList.Draw(DestCanvas, dx+X, dy+Y, TableIndex^);
 Inc(dy, 32);
end;
 Inc(dx, 32);
 ....
При компиляции использовал компонент THeadedTimer ( инсталируйте его из директории THeadedTimer, как установить компоненты Вы надеюсь знаете ). Этот компонент аналог стандартного TTimer, но использованы потоки - высокая скорость работы. И последнее - если вы будете совместно использовать Flur и спрайты то копируйте все в БУФЕР. Т.е. карту в буфер, спрайты и курсор в буфер, буфер на экран. Тут исходник.
 Если есть у кого ссылки на ресурсы с графикой от игр ( лучше стратегий и RPG ) или вообще текстуры, но текстуры к ИГРАМ, а не web ( формат BMP, DIB, PSD) то прошу присылать ссылки. Кто напишет что что-нибудь дельное используя эти методы обязательно размещу. Если ВЫ знаете другие методы построения карт в играх прошу присылать идеи. 
Часть 2 
Прежде чем читать данную статью, настоятельно рекомендую прочесть ее первую часть.
 В первой части я показал как можно создать карту из набора спрайтов и вывести ее на любой TCanvas. При этом карта создавалась как таблица из наборов номеров спрайтов, этот способ удобен там, где все объекты привязаны к координатам спрайтов. Чтобы было более понятно, приведу простой пример: 
 Игра шахматы. Все фигурки привязаны к координатам доски ( у нас к координатам карты), и их передвижение возможно только в этих координатах. Но при переносе фигуры появляются другие координаты - промежуточные. И если ограничиваться координатами доски не возможно создать плавного перемещения, анимации. 
 Для достижения плавного скроллинга карты, анимации объектов приходится вводить координаты пиксельные.
Источник: http://www.delphigfx.narod.ru

                    
                    
                    
                    
                    
                    


