Изометрия - 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.
Хотя все это условно и все зависит от Вас.В скором времени я добавлю анимированный
спрайт на карту и пару объектов. Все качаем исходник тут.
P.S. Прошу делиться идеями на адрес ниже.
Список ссылок
Адрес автора |
|
Исходный код примера |
|
Taudur
- Demo. Пример реализации изометрического движка. Поддержка спрайтов, столкновений,
выделений объектов. Для компиляции потребуются компоненты DelphiX |
|
DelphiX
Isometric Demo. Еще один пример реализации изометрического движка. Для компиляции
потребуются компоненты DelphiX |
|
|