Isometrický engine v DelphiX - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



Komponenty

Isometrický engine v DelphiX

26. února 2001, 00.00 | Popis velmi důležité zobrazovací techniky v tvorbě her. Chcete vytvářet prostředí podobná Simcity či civilizaci? Jak na to pomocí DelphiX vám poradí tento článek, včetně ukázkové aplikace!

Trocha teorie

Všichni asi víte, co to isometrie je. Vzpomeňme na Transport Tyconna, Simcity 2000,Civilizaci II, Age of Empires nebo ještě docela nedávno Stracraft či Rollercoaster Tycoon. Každý isometrický engine je založen na tom, že se několik (spíše více) malých obrázků krajiny (tile) vykresluje vedle sebe. Obrázky mohou mít různý tvar, nejčastěji kosočtverec, čtverec, nebo šestiůhelník (známý z DrD jako hexový papír). Já jsem zvolil kosočtverec, protože si myslim, že vypadá nejlépe a dá se sním taky docela dobře pracovat.

Může vypadat třeba takhle :

Důležitý je poměr mezi výškou a šířkou obrázku (X,Y). Je v podstatě možno zvolit cokoliv, ale podle toho se musí engine taky upravit. Já v příkladu použil poměr 2:1, tj. šířka 64, výška 32.
 
A taková isometrická krajina pak vypadá takto:

Tak to by pro začátek stačilo a jdeme na to!

Než začneme

Co budeme potřebovat:
  • komponenty DelphiX + DirectX
  • pár bmp obrázků o velikosti 64x32 (v příkladu jsou obrázky vykuchané z Transport Tycoona)
  • předpokládám, že máte aspoň základní znalosti s programováním v DelphiX
  • silnou vůli, pokud se to budete snažit pochopit do detailu; já nad tím přemýšlel několik měsíců a nevím, jestli bude v mých silách to dobře vysvětlit
  • zdroják+exe ke stažení tady(201 kB)

A jdeme na to!

Začneme Enginem. Já ho vytvořil jako komponentu, takže v menu Delphi klikněte na Component-New Component. Rodičovskou třídu dejte TComponent, název třídy TDXIsometricEngine, na paletě nezáleží, ale doporučuji si udělat novou, aby nevznikl chaos, jméno jednotky DXIsometricEngine a klikněte na OK. Delphi vám vygenerují prázdný soubor, v jehož části interface doplňte definice:
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DXDraws, DXClass;
type
TMatrix = array[0..1023,0..1023] of byte; // proměnná na uložení povrch
TDXIsometricEngine = class(TComponent)
private
{ Private declarations }
prevA,prevB,x,y:integer;
protected
{ Protected declarations }
public
{ Public declarations }
Matrix : TMatrix;
drawn : integer;
fDXDraw : TDXDraw;
fDXImageList : TDXImageList;
procedure Draw(A,B : Integer); virtual;
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
property DXDraw : TDXDraw read fDXDraw write fDXDraw;
property DXImageList : TDXImageList read fDXImageList write fDXImageList;
end;
procedure Register;

A teď co to všechno znamená.
  • Matrix : TMatrix; - v této proměnné jsou uloženy informace, jak bude povrch vypadat. Jak je vidno z definice, jedná se o dvourozměrné pole (na velikosti celkem nezáleží) čísel (byte). Každé toto číslo určuje, jaká z bitmap DXImageListu bude vykreslena.
  • fDXDraw : TDXDraw; - tato proměnná ukazuje na DXDraw, do kterého se bude kreslit
  • fDXImageList : TDXImageList; - onen zmiňovaný DXImageList s bitmapami povrchu
  • x,y : integer; - tato dvě čísla nám říkají, od jaké pozice v Matrixu se bude kreslit. Pokud bychom měli vykreslovat Matrix celý od pozice [0,0] do [1023,1023], bylo by to časově náročné a většina bitmap by se na obrazovce stejně nezobrazila.
  • prevA,prevB : integer; -popíši později
  • drawn : integer; - nemá vliv na vlastní engine, pouze počítá, kolik bitmap bylo vykresleno
  • procedure Draw(A,B : Integer); virtual; - vlastní procedura vykreslování. Parametry A,B určují x-ovou a y-ovou souřadnici začátku vykreslování (POZOR-neplést s proměnnými x,y)- detaily později
  • constructor Create(AOwner: TComponent); override; - konstruktor pro inicializaci dat.

Teď stiskněte Ctrl+Shift+C, čímž spustíte implementaci (hlavičky všech metod uvedených v části interface se opíší do části implementation s beginem a endem-ušetří to spoustu psaní).
Nyní začneme psát metody, a to od té jednoduší – konstruktor.
constructor TDXIsometricEngine.Create(AOwner: TComponent);
begin
inherited Create(AOwner); //volání rodičovského konstruktoru
prevA:=0; // vynulování
prevB:=0;
x:=0;
y:=0;
end;

 
A teď ta horší část - procedura Draw. Začal bych odsud:
for i:=0 to 32 do //radek-31
for q:=0 to 11 do //sloupcu-11
begin ....

tyto cykly zajišťují vlastní přečtení hodnot z Matrixu a vykreslení na obrazovku. Ono by se to mohlo zdát jednoduché, prostě to přečíst a zobrazit. Ta políčka se ale musí zobrazovat po řádcích, aby nedošlo k překreslení některého políčka jiným. Asi takto:

Záměrně jsem ty čáry natáhl i mimo vlastní povrch enginu. Ty cykly totiž „přejíždějí“ i oblasti, kde žádná informace o terénu není (např Matrix[-2,-1] - to je mimo rozsah - ignoruje se). To by se pořád mohlo zdát jednoduché, ale představte si, jak se ty informace musí číst z onoho pole (červeně jsou pořadová čísla vykreslení bitmapy, černě souřadnice v Matrixu).
 

 
Zeleně jsou políčka, která by vyšla mimo obraz, a proto se nezobrazují. Rám představující obrazovku musí být natočen (jinak by to nebyl požadovaný isometrický engine). Jak bylo již výše zmíněno, proměnné X a Y určují, odkud se má začít vykreslovat, v tomto případě X=0, Y=1. Vzorec vypadá takto
  [-x+q-(i div 2), -y+q+(i div 2)]
pro sudé řádky (1,2,6,7,8,13,14,…) a
   [-x+q-(i div 2), -y+q+i-(i div 2)] pro liché řádky (políčka 3,4,5,9,10,11,…),
kde i a q jsou řídicí proměnné cyklu. Někdo by mohl namítnout, že +(i div 2) je stejné jako +i-(i div 2). To ovšem není tak docela pravda. Například pro i=7 vyjde první výraz 3 a druhý 4.
 
Tak už víme, jak to přečíst, ale jak to vykreslit? Rozebereme si to po částech. Je jasné, že proměnné i,q určují kde se má vykreslovat. Proměnnou q musíme násobit šířkou bitmapy-64 a i výškou-32.
fDXImageList.Items[Matrix[-x+q-(i div 2),-y+q+(i div 2)]].Draw (fDXDraw.Surface, q*64,i*32,0);

Takovéto volání by mělo následující efekt:
 

 
Je to sice pěkné, ale něco tomu chybí. Je třeba každý lichý řádek posunout o 32 pixelů vpravo (o 32 proto, že jsme zvolili šířku bitmapy 64 – tj. polovina):
if (i mod 2=0)then //sudý
begin
fDXImageList.Items[Matrix[-x+q-(i div 2),-y+q+(i div 2)]].Draw
fDXDraw.Surface, q*64 ,i*32,0);
end else
if (i mod 2=1) then //lichý
begin
fDXImageList.Items[Matrix[-x+q-(i div 2),-y+q+i-(i div 2)]].Draw
(fDXDraw.Surface,q*64+32{32 pixelů vlevo} ,i*32,0);
end;

A efekt:

Nezapomeňte, že u sudého a lichého řádku je výraz při čtení z Matrixu jiný. Už je to lepší, ale pořád tomu něco chybí. Zbývá „sesunout“ řádky k sobě. To zajistíme odečtením výrazu (i*16) od y-ové souřadnice sudého i lichého řádku. Celkově to tedy vypadá:
 
for i:=0 to 32 do //radek-31
for q:=0 to 11 do //sloupcu-11
begin
if (i mod 2=0) AND
(-x+q-(i div 2)>=0) AND (-x+q-(i div 2)<=48) AND
(-y+q+(i div 2)>=0) AND (-y+q+(i div 2)<=48) //otestuji, zda jsou požadované souřadnice platné
then
begin
fDXImageList.Items[Matrix[-x+q-(i div 2),-y+q+(i div 2)]].Draw
(fDXDraw.Surface, q*64+c -98,i*32-(i*16)+d -46,0);
inc(drawn); //+1 vykreslených bitmap
 
end else
if (i mod 2=1) AND
(-x+q-(i div 2)>=0) AND (-x+q-(i div 2)<=48) AND
(-y+q+i-(i div 2)>=0) AND (-y+q+i-(i div 2)<=48) //otestuji, zda jsou požadované souřadnice platné
then
begin
fDXImageList.Items[Matrix[-x+q-(i div 2),-y+q+i-(i div 2)]].Draw
(fDXDraw.Surface,q*64+32+c -98,i*32-(i*16)+d -46,0);
inc(drawn); //+1 vykreslených bitmap
end;
end;

Výsledek je už isometrický pohled:

Jistě jste si všimli, že jsou tam ještě přičteny hodnoty +c-98 k x-ové a +d-46 k y-ové souřadnici. Ta písmena c a d jsou pro jemnou korekci zobrazení (vysvětlím později) a to –98 a –46 je proto, že bitmapy jsou vykreslování od souřadnic 0,0 na obrazovce a tak se objevují černé mezery; no prostě zkuste to smazat a uvidíte sami.
 
 
A ještě se nabízí otázka, jak vypočítat hodnoty x,y. Já dostanu jako parametr procedury Draw jakési souřadnice (a,b), které musíme převést na hodnoty pro začátek čtení z Matrixu (x,y).

Červeně je zobrazeno políčko, odkud se začíná vykreslovat. Je vidět, že pokud se bude hodnota A zvyšovat, neroste úměrně podle toho X, ale taky Y se musí snižovat. Roste-li B, zvyšuje se X i Y. Následující kód je založen ta tom, že pokud se proměnná A zvýší/sníží o 64 (nebo proměnná B o 32), upraví se podle toho proměnné X a Y a ještě se do proměnných prevA a prevB uloží hodnota od poslední změny.
if (A>prevA) AND (prevA+64<=A) then //zvýšilo se A ?
begin inc(X); inc(Y); //uprav X,Y
prevA:=prevA+64; end else //změna - musíme vyrovnat prevA
if (A<prevA) then //snížilo se A ?
begin dec(X); dec(Y); //uprav X,Y
prevA:=prevA-64; end; //srovnat prevA
 
if (B>prevB) AND (prevB+32<=B) then //analog.
begin dec(X); inc(Y);
prevB:=prevB+32; end else
if (B<prevB) then
begin inc(X); dec(Y);
prevB:=prevB-32; end;
end;

 
Ono se ale klidně může stát, že se proměnná A nebo B změní od posledního volání procedury Draw a více něž o 64 (32), třeba o 150. V takovém případě by se hodnoty X a Y dostaly na správnou hodnotu až při druhém průchodu. Proto tento algoritmus umístíme do while-cyklu, který se opakuje dokud nejsou všechny hodnoty vyrovnány.
while
((A>prevA) AND (prevA+64<=A)) OR
((A<prevA)) OR
((B>prevB) AND (prevB+32<=B)) OR
((B<prevB)) do
begin
....
....
end;

 
Tohle by už pracovalo správně, ale posouvání by bylo trhané a proto je ještě třeba vypočítat hodnoty pro jemnou korekci posunu – C a D. To zajistí výpočet, který zjistí rozdíl mezi poslední změnou a aktuálním pozicí.
C:=(A-prevA);
D:=(B-prevB);

Pak ještě nějaká ta statistika a je to! Engine je hotov; už zbývá jen vytvořit obslužný program. Nejprve klikněte v menu Component na Install Component. Zadejte cestu k souboru a klikněte na OK.
Potvrďte případné hlášky o tom, jestli se má balíček znovu přeložit. Teď vytvořte novou aplikaci a na Form dejte jeden DXDraw s vlastnostmi Align-alClient, Options-[doFullScreen, doAllowReboot, doCenter, doHardware, doSelectDriver], nedoporučuji pro začátek doFullScreen, protože kdyby to spadlo tak budete muset restartovat počítač. Potom DXTimer; vlastnosti Enabled-True, Interval-0. A ještě DXImageList, do kterého naládujeme bitmapy a nastavíme vlastnost DXDraw na DXDraw1. Nakonec u hlavního formuláře- BorderStyle-bsNone.
V nově vytvořené paletě by se měla objevit komponenta DXIsometricEngine. Jeden exemplář dejte na Form a nastavte vlastnosti DXDraw a DXImageList. Nebudu tady opisovat celý kód řídícího programu, ten si stáhněte. Nedělá kromě nastavení Matrixu v podstatě nic než že po kliknutí myší a posunu mění hodnoty se kterými je volána metoda DXIsometricEngine1.Draw. Tak to spusťte a doufám, že vám to bude fungovat !!!

Tématické zařazení:

 » Rubriky  » Komponenty  

 » Rubriky  » Delphi  

 » Rubriky  » Windows  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: