Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Изометрия

Автор: Павел

Сейчас, с появлением полностью 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 TForm1.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.

Хотя все это условно и все зависит от Вас.В скором времени я добавлю анимированный спрайт на карту и пару объектов. Все качаем исходник тут.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay