Титульная страница DelphiGFX Сделать закладку Написать письмо автору сервера 

  Главная - Документация - 2D Графика

Мастера Delphi | Corba  

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

Copyright © 2000 Мироводин Дмитрий  

Сейчас, с появлением полностью 3х мерных технологий, этот способ представления игрового поля называется изометрический, а раньше он назывался 2,5 мерный. Но смысл остался один и тот же. Существует плоскость расположенная под неким углом к неподвижной камере. Эффект перемещения достигается скроллингом карты.

На рисунке показан вид сбоку, где
N - нормаль к поверхности пространства
a - угол обзора камеры.
Для начала стоит упомянуть где использовался этот способ представления : Diablo, Fallout, Gorky 17 ( хотя несколько видоизменено ). С примерами игр, я конечно могу и ошибаться.

Начнем с разработки спрайтов. В отличии от простого 2D представления, они имеют более сложную форму :

2Dsprite

Размер выбирайте : ... 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
Титульная страница DelphiGFX Сделать закладку Написать письмо автору сервера
Hosted by uCoz