CPU3D.comКомпьютерная графикаДвухмерная графика → Изометрия - 2,5 мерное пространство

Изометрия - 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