Изометрия - 2,5 мерное пространство

Сейчас, с появлением полностью 3х мерных технологий, этот способ представления игрового поля называется изометрический, а раньше он назывался 2, 5 мерный. Но смысл остался один и тот же. Существует плоскость расположенная под неким углом к неподвижной камере. Эффект перемещения достигается скроллингом карты. 
На рисунке показан вид сбоку, где
N - нормаль к поверхности пространства
a - угол обзора камеры.
 Для начала стоит упомянуть где использовался этот способ представления : Diablo, Fallout, Gorky 17 ( хотя несколько видоизменено ). С примерами игр, я конечно могу и ошибаться. 
 Начнем с разработки спрайтов. В отличии от простого 2D представления, они имеют более сложную форму : 
 Размер выбирайте : ... 64x32, 60x30 ... 2x1. / В моем примере 60x30 /. Советую сделать спрайтов поверхности штук 30-40 на каждый тип ландшафта. 
 Наступило время все эти спрайты разместить. Для начала определимся, что карта хранится в массиве, а если точнее то в массиве хранятся индексы этих спрайтов. Сами спрайты будем хранить в TImageList. При написании, я использовал динамический массив составленный из списка. В примере это класс PTable и его описание находится в файле Table.pas. Там все просто: 
Procedure Create(Const X, Y:integer);
Procedure Put(X, Y, Number:integer); - поместить в ячейку X, Y элемент Number
Function Get(X, Y:integer):integer; - получить
Первой процедурой создаем таблицу с размерами X, Y. На самом деле можно создавать только квадратные т.е. 2x2, 5x5, 100x100 ... NxN. Если хотите сделать произвольного размера то надо вводить 2 списка, но мне кажется, что и квадратной формой можно обойтись. А далее, после создания, работаем как с обычным массивом.
 C Table все, хотя я может кого то немножко и озадачил, но использовать массив ( Array ) для задания карт в играх не рекомендую. Причина простая. Допустим, уровни имеют разный размер (в Heroes от 64x63 - 256x256), а с массивом Вы сможете сделать только фиксированный - т.к. размер уровня узнается уже после задания массива. Вторая причина более важна : немножко изменив класс PTable, в нем можно хранить не только индексы спрайтов. Например в каждой ячейке сохраняется информация о :
- ресурсах которые там находятся - количество леса, камня, руды ...
- степень проходимости для юнитов - по болоту медленнее, чем по дороге.
- высоту над уровнем моря :) и многое другое
 В итоге данные карты хранятся в PTable и теперь все это надо сделать это вывести их на экран. Но это задача не так проста т.к. спрайты не прямоугольной формы и следовательно имеется смещение для нечетных столбцов: 
 Я решил эту проблему просто. Сначала проходим цикл для четных, потом для нечетных : 
 Procedure TIFlur.Draw(DestCanvas:TCanvas;SourceRect:TRect);
var
 I, J, dX, dY:integer;
begin
 // рисуем четные столбцы
 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
 if Odd(I)=False then Resource.Draw(DestCanvas, dX, dY, Map.Get(I, J));
 Inc(dY, 30);// !!!!!!!! SpriteHeigth !!!!!!!
 end;
 dy:=0;
 Inc(dX, 30);// !!!!!! SpriteWidth div 2 !!!!!!!
end;
// рисуем нечетные столбцы
 dx:=0;
 dy:=15;// !!!!!! SpriteHeigth div 2 !!!!!!
for I:=SourceRect.Left to SourceRect.Left+SourceRect.Right do
begin
 For J:=SourceRect.Top to SourceRect.Top+SourceRect.Bottom do
 begin
 if Odd(I)=True then Resource.Draw(DestCanvas, dX, dY, Map.Get(I, J));
 Inc(dY, 30);// !!!!!!!! SpriteHeigth !!!!!!!
 end;
 dY:=15;// !!!!!! SpriteHeigth div 2 !!!!!!
 Inc(dX, 30);// !!!!!! SpriteWidth div 2 !!!!!!!
end;
end;
Для наглядности приведу 2 картинки : 
При наложении образуется уже нормальная картинка. Для удобства я написал класс TIFlur смотрите файл IMap.pas. Все практически тоже, что и у TFlur : type TIFlur= class
private
 Resource:TImageList;// Ресурсы графики
public
 Width, Heigth:Integer;// Ширина и высота карты
 Map:PTable; // Массив карты развернутый в список
 Constructor Create(Const X, Y:integer);// ширина, высота карты
 Procedure LoadResource(FileName:String); // путь к BMP ресурсу
 Procedure RandomGenerator; // случайное заполнение Table
 Procedure Draw(DestCanvas:TCanvas;SourceRect:TRect);
 // Canvas для вывода,
 // SourceRect - какой кусок карты Table выводить на экран, В КОЛ_ВАХ СПРАЙТОВ
 Destructor Destroy;override;
end; 
Так, только необходимые процедуры, ничего лишнего.
 Пожалуй, самое сложное это определить координаты клетки где находится мышь. Ведь координаты курсора у нас X и Y, а в карте все ячейки распологаются со сдвигами. Но тут нас выручат маски. Для начала создадим такую маску :
 Получим координаты мыши и целочисленным делением получим координаты клетки где находится курсор ( так мы поступали при простом виде сверху ):
 I:=(X div SpriteWidth)*2; для четных 
 I:=(X div SpriteWidth)*2-1; для нечетных, а Y везде одинаковый :
 J:=Y div SpriteHeigth;
На рисунке эти координаты 2.0
 По такой не слабой формуле мы вычисляем, куда конкретно попал курсор в пределах одной маски : 
 dX:=SpriteWidth*(Frac(X/SpriteWidth));
 dY:=SpriteHeigth*(Frac(Y/SpriteHeigth)); 
Это остато от деления умножается на ширину или высоту маски. Соответственно эти переменные ВСЕГДА лежат в пределах dX (0-60) и dY (0-30). С помощью этих координат мы можем определить цвет куда тыркнулась мышка и по цвету задать смещение. Приведу целиком тело этой процедуры. procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
 dX, dY:Real;
 I, J:integer;
begin
 case Odd(Screen.Left) of
 False: begin
 I:=(X div SpriteWidth)*2;
 end;
 True: begin
 X:=X+30;
 I:=(X div SpriteWidth)*2-1;
 end;
 end;
 J:=Y div SpriteHeigth;
 CursorX:=I;
 CursorY:=J;
 dX:=SpriteWidth*(Frac(X/SpriteWidth));
 dY:=SpriteHeigth*(Frac(Y/SpriteHeigth));
 case Mask.Canvas.Pixels[Trunc(dx), Trunc(dy)] of
 clRed: begin // Красный
 CursorX:=I-1;
 CursorY:=J-1;
 end;
 clBlue: begin // Синий
 CursorX:=I+1;
 CursorY:=J-1;
 end;
 clLime: begin // зеленый
 CursorX:=I-1;
 end;
 clYellow: begin // Желтый
 CursorX:=I+1;
 end;
 end;
Теперь при перемещении мыши по экрану, мы сразу можем определить реальные координаты карты. Они на рисунке обозначены черным цветом 3.0 Для компиляции исходника потребуется компонент THeadedTimer, который находится вместе с исходником. Для чего он нужен/не нежен читайте в Создание карты в игре, методом спрайтов Там же Вы узнаете как избежать "дрожания" курсора. 
 Теперь пару слов о способах задания объектов для карты. Каждый движущийся объект будет иметь по 2 переменные на каждую координату. Допустим первые X и Y координаты в системе карты. А вторые 2 dX и dY пиксельные координаты. Т.е. персонаж ходит по координатам dX и dY, а взаимодействует с картой по координатам X и Y.
 Хотя все это условно и все зависит от Вас.В скором времени я добавлю анимированный спрайт на карту и пару объектов. Все качаем исходник тут.
Источник: http://www.delphigfx.narod.ru

                    
                    
                    
                    
                    
                    


