Знакомимся с изометрией

Графика (2D, 3D)   7 Декабрь 2010  Автор статьи: admin 

В этом уроке я покажу как создать свой простой изометрический движок на Delphi.

Для начала, разберемся: Что же такое изометрия? Прежде всего стоит отметить, что изометрическим называется графический движок, который, не используя современных 3d технологий, позволяет прорисовывать в компьютерных играх например игровой мир. Раньше, когда 3d технологии были слабо развиты, изометрический движок называли 2,5 мерным. В качестве примера игры с таким графическим движком можно привести Diablo или Age of Empires II:

Пример изометрической графики

Конечно, изометрический движок имеет ряд преимуществ перед 3d движком: гораздо более низкие требования к железу, удобный вид, не надо управлять (поворачивать, приближать) камеру. Несмотря на это есть и ряд недостатков. Например, если в игре есть персоонажи, то прийдется прорисовывать их под разными углами с разным оружием (например) отдельно.

Теперь узнаем как устроен изометрический движок. Думаю, что вы заметили, что вся плоскость игрового мира состоит из изображений-ромбов. Например таких:Чаще всего эти изображения имеют такие размеры, что их длина в 2 раза больше ширины. При состыковке нескольких таких изображений мы получим плоскость:

Для удобства красными линиями я выделил границы изображений-ромбов, обозначил оси координат x,y и их начало, цифрами 1, 2 и 3 обозначил типы текстур. Теперь представим эту карту в виде матрицы:

Именно в таком виде мы и будем хранить матрицу в игре. Теперь поставим перед собой задачу:

Предположим у нас есть файл map.ini со следующей структурой:

[main]
name=тестовая карта
sizex=5
sizey=5
[map]
0=grass,grass,grass,grass,grass
1=grass,grass,ice,grass,grass
2=grass,grass,grass,ice,grass
3=grass,grass,grass,grass,grass
4=grass,grass,grass,grass,grass

Про то, как работать в delphi с ini файлами подробно описано в статье «Ini файлы в Delphi».

В папке tiles будут находиться наши изображения плоскостей с различными текстурами. Эти изображения будут названы своим идентификатором в матрице, а их расширение будет *.bmp. Приступим к написанию кода. Для начала откроем delphi и сохраним проект. В папке с проектом поместим наш файл map.ini и папку tiles, в которой мы будем хранить наши изображения.

Для считывания названий изображений мы будем использовать следующие функции:

(подробнее о них вы можете узнать из статьи «Хранение параметров в строке«)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function TForm1.paramscol(s: string; ch: char=','): integer;
label 1;
var n,i,nn:integer;
begin
nn:=1;
s:=s+' ';
1:
n:=posex(ch,s,n+1);
inc(nn);
if n=length(s) then result:= nn-1 else goto 1;
end;

function TForm1.gname(s: string; ch: char='='): string;
begin
Result := copy(s, 1, pos(ch, s) - 1);
end;

function TForm1.gval(s: string; ch: char='='): string;
begin
Result := copy(s, pos(ch, s) + 1, length(s));
end;

function TForm1.getvalue(name:string;rt:string): string;
var i,n:integer;
begin
result:='';
n:=paramscol(rt);
for i := 1 to n do
if (gname(getparam(rt,i))=name) then result:=gval(getparam(rt,i));
end;

function Tform1.getparam(s: string; n: integer; ch: char=','): string;
var
i, a1, m, nn: integer;
A: string;
begin
if pos(ch, s) <> 0 then
begin
m := 1;
A := '';
for i := 1 to length(s) do
begin
if s[i] = ch then
begin
inc(m);
continue;
end
else if m = n then
A := A + s[i]
else if (m = n) and (s[i] = ch) then
break;
end;
Result := A;
end
else
begin
if n = 1 then
Result := s
else
Result := '';
end;
end;

 

В связи с тем, что в функциях мы используем функцию PosEx, то надо добавить модуль StrUtils в раздел uses. Также мы в дальнейшем будем работать с ini файлом, поэтому добавим модуль inifiles в раздел uses. Теперь добавим следующие глобальные переменные:

1
2
3
4
5
6
7
8
mapdata: tinifile;
mapsize: tpoint;
map: array [0..5000, 0..5000] of string;
abstoreal: array [0..5000, 0..5000] of tpoint;
realtoabs: array [0..5000, 0..5000] of tpoint;
Cur: tpoint;
mask: tbitmap;
cursorx, cursory: integer;

Все эти переменные понадобятся нам для прорисовки самой карты и определения, на каком элементе карты находится курсор. Определить положение курсора — будет труднее всего. Начнем с того, как это можно реализовать в теории:

В первую очередь нам понадобится некоторый bmp файл-маска. Можете взять этот:

А сам принцип получения координат будем реализовывать следующим образом:

Во время прорисовки карты мы получим абсолютные координаты прямоугольника с элементом плоскости и привяжем его посредством двумерного массива к его реальным координатам. Когда курсор будет наведен на какой-либо элемент, мы будем узнавать его абсолютную позицию и по ней с помощью того самого двумерного массива получим его реальные координаты. При этом мы будем использовать bmp маску, благодаря которой, будем модифицировать полученное абсолютное положение курсора, если курсор зашел на раскрашенные части маски. В данном случае маска будет модифицировать координаты следующим образом:

Если курсор будет находится на черной части маски, то модифицирование координат происходить не будет.

Продолжим написание кода. Рисовать саму карту мы будем в plane: TImage. Добавим процедуру инициализации карты:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
procedure TForm1.initmap;
var i,j:integer;
absx,absy:integer;
tempbmp:tbitmap;
s:string;
begin
mapdata:=TIniFile.Create(extractfilepath(application.ExeName)+'map.ini');
//извлекаем размеры карты
mapsize.X:=mapdata.ReadInteger('main','sizex',1);
mapsize.Y:=mapdata.ReadInteger('main','sizey',1);
//изменяем размер plane в соответствии с размерами карты
plane.Width:=mapsize.X*100;
plane.Height:=mapsize.Y*50;
//следующий цикл прорисовывает саму карту
for j := 0 to mapsize.Y-1 do begin
s:=mapdata.ReadString('map',inttostr(j),'error');
for i := mapsize.X-1 downto 0 do begin
tempbmp:=TBitmap.Create;
tempbmp.LoadFromFile('tiles\'+getparam(s,i+1)+'.bmp');
tempbmp.TransparentColor:=clwhite;
tempbmp.Transparent:=true;
absx:=trunc((i*50+j*50)/50);
if j*25-i*25+mapsize.Y*25-25 mod 10 = 5 then
absy:=trunc((j*25-i*25+mapsize.Y*25-25)/50) else
absy:=trunc((j*25-i*25+mapsize.Y*25+25)/50)-1;
abstoreal[absx,absy].X:=i;
abstoreal[absx,absy].Y:=j;
realtoabs[i,j].x:=absx;
realtoabs[i,j].y:=absy;
// выведем реальные координаты, чтобы нам было их видно
tempbmp.Canvas.TextOut(40,25,inttostr(i)+','+inttostr(j));
plane.Canvas.Draw(i*50+j*50,j*25-i*25+mapsize.Y*25-25, tempbmp);
tempbmp.Free;
end;
end;
end;

Далее создадим обработчик PlaneMouseMove:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
procedure TForm1.planeMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
I, J: integer;
dx, dy: real;
begin
case Odd(plane.Width) of
False:
begin
I := (X div 100) * 2;
end;
True:
begin
X := X + 30;
I := (X div 100) * 2 - 1;
end;
end;J := Y div 50;J := Y div 50;Cur.X := I;
Cur.Y := J;dX := 100 * (Frac(X / 100));
dY := 50 * (Frac(Y / 50));
// модифицируем cur по маске
case Mask.Canvas.Pixels[Trunc(dx), Trunc(dy)] of
clRed:
begin // Красный
Cur.X := I - 1;
Cur.Y := J - 1;
end;
clBlue:
begin // Синий
Cur.X := I + 1;
Cur.Y := J - 1;
end;
clLime:
begin // зеленый
Cur.X := I - 1;
end;
clYellow:
begin // Желтый
Cur.X := I + 1;
end;
end;
cursorX:=abstoreal[cur.X,cur.Y].X;
cursorY:=abstoreal[cur.X,cur.Y].Y;
label1.Caption:=inttostr(CursorX)+' '+inttostr(CursorY);
end;

 

В результате в cursorX и cursorY будут содержаться реальные координаты элемента, на который будет наведен курсор. Остается лишь прописать обработчик OnCreate у формы:

1
2
3
4
5
6
procedure TForm1.FormCreate(Sender: TObject);
begin
initmap; // запускаем инициализацию карты
mask:=TBitmap.Create; // создаем маску для определения координат
mask.LoadFromFile('mask.bmp');
end;

[note]Скачать исходник полученного простого изометрического движка[/note]

 

  • DarkBlade

    СПАСИБО ОГРОМНОЕ!!!!!

  • DarkBlade

    СПАСИБО ОГРОМНОЕ!!!!!

  • авва

    плиз помоги, автор, или кто нить ещё, в написании этого движка, просто понять не могу(

    • http://cybern.ru/ Cyberexpert

      Мне движок за вас написать? Я вроде бы исходник к статье приложил :)

      • авва

        может за денюшку поможешь?

      • авва

        может за денюшку поможешь?

  • Fallout

    все понятно!спасибо!

Научиться программировать

  • на Delphi

  • на Java

  • на C++