Tvorba her v DelphiX - 3. díl - 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:

Soutěž

Sponzorem soutěže je:

IDIF

 

Kde se koná výstava fotografií Luďka Vojtěchovského?

V dnešní soutěži hrajeme o:



Delphi

Tvorba her v DelphiX - 3. díl

delphi_vrh

6. dubna 2001, 00.00 | Dnes si popíšeme jak udělat reálný let střely v DelphiX, efekt používaný ve většině her již od dob Atari. Střelu budeme umět nejen vykreslovat, ale i zaměřovat..

Před mnoha lety, když jsem ještě vlastnil počítač (pokud se tomu dalo říkat počítač) Atari jsem míval v oblibě jednu hru (jinak to vpravo, to je moje atari). Na název si dnes již nevzpomenu, ovšem jsem si jist, že ji znáte všichni. Jedná se o 2d hory, na kterých jsou náhodně rozmístěny tanky hráčů, a ti se postupně snaží zničit jeden druhého. Konají tak postupně, zadáním úhlu, rychlosti a následným vytřelením. V původní hře z Atari bychom se mohli dohadovat o tom, zda se jednalo o tanky, či něco úplně jiného, jelikož se objekty podobaly obrácenému písmenu T a další dvě barvy na obrazovce byli zmíněné hory a pozadí. Jedno však mají s dnešními hrami stejného typu společného, a to budou mít společné navždy - fyzikální zákony.
Bez těch se neobejdeme v žádné složitější hře, a proto si v dnešním článku vysvětlíme a názorně ukážeme jeden z pohybů, který využívá již právě zmíněná hra - vrh šikmý vzhůru.

Pokud opravdu netušíte o co se jedná a ani jste nikdy nehráli podobnou hru, zkuste jít nachvíli od počítače ven a hodit si kamenem :) Po chvíli letu kámen dopadne do určité vzdálenosti, která záleží hned na několika faktorech. Jedná se o sílu, kterou jste kámen hodili, dále pod jakým úhlem jste kámen hodili a nakonec ještě jaká gravitace na kámen působila. Dráha potom bude část paraboly, v normálním světě navíc trochu zdeformovaná, jelikož na ní působí i další věci, a proto bude mít podobu balistické křivky. My se však spokojíme s parabolou.
Následným doplněním do vzorce získáme data, která potřebujeme. Zde jsou tedy ty vzorce:


Nyní by bylo asi na místě si vysvětlit, jaký že to je rozdíl mezi počítačem a realitou. Pokud si prohlédnete vzorce výše, zjistíte, že budeme počítat x-ovou a y-ovou polohu bodu, který letí. Rychlost, úhel a gravitační sílu budeme mít již definovanou a jelikož budeme počítat neustále, měnit budeme pouze čas. Ten při každém volání procedury DXTimer.OnTimer zvýšíme o určitou hodnotu. Jelikož budeme počítat s desetinnou čárkou, použijeme pro většinu proměnných typ real (čímž si také program zpomalíme). Nejprve tedy definujeme počáteční rychlost Vo jako Rychlost:real, dále jednotlivé složky rychlosti Vox (vx) a Voy (vy) a čas (time), vše typu real. Nakonec ještě uhel (uhel), vzdálenost povrchu od vrchu (YNullPos) a pozici bodu x a y, typu integer.
Začneme tedy tím, že si na formulář dáme komponenty DXDraw a DXTimer z palety DelphiX a jedno tlačítko. Proměnné máme již nadefinované, tudíž přistoupíme rovnou k výpočtu. Vytvoříme si proceduru Button1.OnClick (nebo jak jste tlačítko pojmenovali), ve které nejprve vynulujeme data, uhel nastavíme například na 45° rychlost např. na 7:


 uhel:=45;
 time:=0;
 rychlost:=7;
 x:=0;
 y:=0;


Dále bychom si mohli vypočítat Vox (vx) a Voy (vy), jelikož uhel a Vo, tedy rychlost známe. Pozor jen na to, že funkce sin, con, tg, a cotg vrací hodnotu v radiánech, což zřejmě nechceme. Pro převod mezi stupni a radiány použijeme funkce RadToDeg s DegToRad (pozor - tyto funkce jsou v unitě Math, musíme tedy to uses přidat Math). To bude k tlačítku vše, nakonec ještě nastavíme YNullPos na 200 a DXTimer.Enabled na true. Veškeré další výpočty se budou provádět právě tam. Zde je ještě výpis:


 //  VoX = cos alpha * Vo 
 //  VoY = sin alpha * Vo 

 vx:=cos(DegToRad(uhel));
 vx:=vx * rychlost;
 
 vy:=sin(DegToRad(uhel));
 vy:=vy * rychlost;

 YNullPos:=200;

 DXTimer1.Enabled:=true;


Jak jsem již napsal, přesuneme se nyní k proceduře DXTimer.OnTimer. Ještě před tím si ale nastavte DXTimer.Enabled an False. A můžeme pokračovat. Nejprve jako vždy otestujeme, zda je možné na surface kreslit, pokud ano, pokračujeme dále. Následuje zvětšení času o 0.3, pokud chcete, aby bod letěl rychleji, můžete zvolit číslo větší, např. 0.4 nabo 0.5. Dále vypočítáme novou pozici x a y. Ty následně upravíme, jelikož v počítači odpovídají souřadnice 0,0 levému hornímu rohu, kdežto v normálním světě levému dolnímu rohu. Z toho plyne, že změníme hodnotu y tak, že ji odečteme od proměnné YNullPos a souřadnici x-ovou pouze posuneme o 20 pixelů doprava. Dále vyplníme bod x,y např. žlutou barvou a následuje již flip. Zde je výpis celé procedury.


procedure TMainForm.DXTimer1Timer(Sender: TObject; LagCount: Integer);
 if not DXDraw1.Candraw then exit;

 //DXDRaw1.Surface.fill(0);

 //  x = VoX * t                                                 
 //  y = voY * t - 1/2 gt*t       

 time:=time+1;

 // vypocitat novou pozici
 x:=Trunc(vx*time);
 y:=Trunc(vy*time-(1/2*0.3*(time*time)));

 x:=x+20;
 y:=YNullPos-y;

 with DXDraw1.Surface.canvas do
  begin
   Pixels[x,y]:=clYellow;

   release;
  end;

 DXDraw1.Flip;
end;


DXDraw1.surface.Fill(0) jsem schválně vypustil, aby se povrch nečistil, a my viděli celou dráhu letu. Jak jste si jiště všimli, gravitaci jsem nastavil na 0.3, což je zcela můj výmysl, který jsem postupným zkoušením zvolil tak, aby při cca 30 snímcích za vteřinu nebyl vrh příliš krátký, ani dlouhý. Ostatně, co způsobí změna této hodnoty si můžete vyzkoušet sami. Program tedy uložtě a spusťte, klikněte na tlačítko, a pokud jste udělali vše dobře, měl by Vám vyletět z místa x:20 a y:200 bod, a pokračovat po parabole. Zde je obrázek, jak program vypadá:


Trochu si program ještě vylepšíme. Přidáním pár řádků do procedury DXTimer.OnTimer zajistíme, aby se naskreslily osy x a y, dále počet snímků za sekundu, hodnotu x a y a úhel. Aby jsme však vše viděli, musíme již přidat před vše DXDraw1.surface.Fill(0) (jinak by se jednotlivé hodnoty překrývali přes sebe, a byly by nečitelné). Zde je výpis, který přidáme do procedury DXTimer.OnTimer za "with DXDraw1.Surface.canvas do begin":


   Pen.Color:=clRed;
   MoveTo(20,0);
   LineTo(20,DXDraw1.surfaceHeight);
   MoveTo(0,YNullPos);
   LineTo(DXDraw1.surfaceWidth,YNullPos);

   Font.Name:='Tahoma';
   Font.Color:=clLime;
   Brush.Style:=bsClear;
   Font.Size:=8;
   TextOut(0,0,'FPS '+IntToStr(DXTimer1.framerate));
   TextOut(0,15,'x: '+IntToStr(x));
   TextOut(0,30,'y: '+IntToStr(y));
   TextOut(0,45,'uhel: '+IntToStr(uhel)+'°');


Program můžete znovu spustit. Jak jistě sami vidíte, vypadá již lépe. Ovšem na vyšších rozlišeních je jeden letící pixel opravdu malý, skoro neviditelný. Jako první způsob můžeme vyplnit i pixely kolem bodu hlavního, kód potom bude vypadat nějak takto:


   Pixels[x+1,y]:=clYellow;
   Pixels[x-1,y]:=clYellow;
   Pixels[x,y+1]:=clYellow;
   Pixels[x,y-1]:=clYellow;


Ovšem, jak sami uznáte, ještě pořád to není ono. Dobře, přidáme si tedy k letícímu pixelu ocas. Ten bude uložen v poli OldPosition s prvky typu TPoint, které si hned dopíšeme do proměnných - oldPosition:array [0..100] of TPoint; Dále musíme vědět, jak dlouhý ocas bude, proto si přidáme další proměnnou - DelkaOcasu, tentokrát typu integer. První bod, tedy ten, který počítáme bude mít index 0. Nejprve si tedy body v proceduře Button.OnClick vynulujeme a nastavíme délku ocasu, například na 30:


 for i:=0 to DelkaOcasu do
  begin
   OldPosition[i].x:=0;
   OldPosition[i].y:=0;
  end;

 DelkaOcasu:=30;


Samozřejmě nesmíme zapomenout na proměnnou i, kterou definujeme rovnou v proceduře a jedná se o integer. Dále do procedury DXTimer1.OnTimer přidáme kód, aby se body posunuly, a počítat budeme pouze OldPosition[0].x a OldPosition[0].y. Zobrazovat také nebudeme pouze jeden bod, ale všechny. Nezapomeňte tedy opět definovat proměnnou i. Zde je kód, který přidáme za time:=time+0.3 (staré řádky pro výpočet nové pozice smažte):


 // nastaveni nove pozice ocasu
 for i:=DelkaOcasu downto 1 do
  begin
   OldPosition[i]:=OldPosition[i-1];
  end;

 // jestli uz nespadnul na zem,
 // tak vypocitat novou pozici
 if OldPosition[0].y < YNullPos then
  begin
   x:=Trunc(vx*time);
   y:=Trunc(vy*time-(1/2*0.3*(time*time)));

   x:=x+20;
   y:=YNullPos-y;

   OldPosition[0].x:=x;
   OldPosition[0].y:=y;
  end;


Dále následuje výpis pro vlastní kreslení, kterým nahradíme stávající:


   for i:=DelkaOcasu-1 downto 0 do
    begin
     Pixels[OldPosition[i].x+1,OldPosition[i].y]:=clYellow;
     Pixels[OldPosition[i].x-1,OldPosition[i].y]:=clYellow;
     Pixels[OldPosition[i].x,OldPosition[i].y+1]:=clYellow;
     Pixels[OldPosition[i].x,OldPosition[i].y-1]:=clYellow;
    end;


Program můžete spustit, a je-li vše v pořádku, uvidíte letící bod, a za ním hezký "ocas". My bychom zřejmě ale chtěli, aby měl také postupnou barvu, aby co nevíc vypadal jako letící střela. Toho dosáhneme jednoduchým způsobem, a to že si nejprve definujeme pole PColors typu TColor - pColors:array [0..100] of TCOlor; dále v proceduře Form.OnCreate nadefinujeme jednotlivé barvy a také proměnnou DelkaOcasu (samozřejmě vymažeme z procedury Button.OnClick řádek, který znovu proměnnou nastavuje). Zde je kompletní výpis procedury Form1.OnCreate:


procedure TMainForm.FormCreate(Sender: TObject);
begin

 DelkaOcasu:=23;

 // nastanevi barev particles
 pColors[0]:=RGB(255,8,0);
 pColors[1]:=RGB(255,44,0);
 pColors[2]:=RGB(255,92,16);
 pColors[3]:=RGB(255,124,16);
 pColors[4]:=RGB(255,160,24);
 pColors[5]:=RGB(255,176,32);
 pColors[6]:=RGB(255,182,40);
 pColors[7]:=RGB(255,200,40);
 pColors[8]:=RGB(255,220,48);
 pColors[9]:=RGB(255,232,48);
 pColors[10]:=RGB(255,252,56);
 pColors[11]:=RGB(255,252,56);
 pColors[12]:=RGB(255,252,48);
 pColors[13]:=RGB(216,216,48);
 pColors[14]:=RGB(200,200,48);
 pColors[15]:=RGB(184,180,40);
 pColors[16]:=RGB(160,156,32);
 pColors[17]:=RGB(144,140,40);
 pColors[18]:=RGB(120,120,42);
 pColors[19]:=RGB(104,100,24);
 pColors[20]:=RGB(80,80,24);
 pColors[21]:=RGB(64,64,16);
 pColors[22]:=RGB(48,48,16);
 pColors[23]:=RGB(24,24,0);

end;


A ještě trochu upravíme kreslení pixelů na:


   for i:=DelkaOcasu-1 downto 0 do
    begin
     Pixels[OldPosition[i].x+1,OldPosition[i].y]:=pColors[i];
     Pixels[OldPosition[i].x-1,OldPosition[i].y]:=pColors[i];
     Pixels[OldPosition[i].x,OldPosition[i].y+1]:=pColors[i];
     Pixels[OldPosition[i].x,OldPosition[i].y-1]:=pColors[i];
    end;


Projekt můžete znovu přeložit a spustit. klikněte na tlačítko a - střela vyletí a navíc má za sebou hezkou stopu, která přes červenou a žlutou barvu mizí do černé. A jelikož máme i pozadí černé, vytváří tak dojem, že se ocas postupně ztrácí. Projekt bude není vypadat nějak takto:



Nastavení úhlu a rychlosti

Nakonec si ještě ukážeme, jak nastavit úhel a rychlost, jelikož pořád pracujeme s jednou a tou samou (uhel = 45°, rychlost = 7). Samozřejmě, mohli bychom jednodušše dát na formulář dvě komponenty Edit, a poté při kliknutí na tlačítko nastavit hodnotu proměnné Uhel na hodnotu jednoho editu, a hodnotu proměnné Rychlost na hodnotu editu druhého.
Ovšem, proč to dělat jednodušše, když to jde i složitě ? A navíc to vypadá lépe. Pokud znáte program Adobe photoshop, jistě víte, jak se v něm nastavují úhly - kliknutím na tlačítko se zobrazí malý kruh, ve kterém prostě kliknete tam, jak velký úhel chcete. A my se pokusíme o něco podobného. Navíc budeme nastavovat i rychlost, a to vše v jednom.
Vše budeme kreslit na komponentu Image, proto si hned na začátek dejte jednu na formulář a nastavte jí velikost 100x100 pixelů. Dále si vytvořte proceduru Image1.OnMOuseMove, ve které se bude vše odehrávat. Nejprve otestujeme, zda je stisknuté levé tlačítko myši, a pokud ano, nakreslíme čáru od levého spodního rohu (ve skutečnosti souřadnice 0,0) ke kurzoru myši, vypočítáme úhel, a podle délky linky také rychlost. Výpočet úhlu je jasný, z rychlostí musíme trochu experimentovat.
V proceduře si definujeme následující proměnné: ax a ay typu integer, ve kterých bude uložena pozice kurozur myši (podle reálu), dále aUhel typu real, ve kterém bude velikost úhlu, a jeho hodnota se na konci přiřadí proměnné uhel a nakonec pomocná proměnné stringa typu string. Abychom věděli všechny hodnoty, a mohli tak jednodušeji odhalit chyby, dáme si na formulář ještě komponentu Label, ve které si jednotlivé hodnoty hezky zobrazíme. Zde je již kompletní výpis procedury Image1.OnMouseMove, doplněný komentářemi, tak doufám, že je vše jasné:


procedure TForm1.Image1MouseMove(Sender: TObject; Shift:
 TShiftState; X, Y: Integer);
var ax,ay:integer;
    auhel:real;
    stringa:string;
begin
 // pokud je stisknuté levé tlačítko tak...
 if ssLeft in (shift) then
  begin
   with Image1.Canvas do
    begin
     // kreslení čtvrt kruhu na pozadí
     Brush.Style:=bsSolid;
     Brush.Color:=clBtnShadow;
     Rectangle(0,0,100,100);
     Pen.Color:=clBlack;
     Brush.Color:=clBtnHighLight;
     Ellipse(-100,0,100,200);
     Brush.Style:=bsClear;
     Rectangle(0,0,100,100);

     // kresli krizek okolo kurzoru
     Pixels[x,y]:=clBlack;
     MoveTo(x-5,y);
     LineTo(x+5,y);
     MoveTo(x,y-5);
     LineTo(x,y+5);
     Ellipse(x-5,y-5,x+5,y+5);

     // čára od 0,0 ke kurzoru
     MoveTo(0,100);
     LineTo(x,y);

     // výpočet úhlu
     ax:=x;
     ay:=100-y;
     if ax = 0 then
      begin
       aUhel:=ay;
      end
     else
      begin
       aUhel:=ay/ax;
      end;
     aUhel:=ArcTan(aUhel);
     aUhel:=RadToDeg(aUhel);

     // výpočet rychlosti
     // pro jinou velikost změňte 
     // 6 na cokoliv jiného
     rychlost:=sqrt((ax*ax)+(ay*ay))/6;

     Str(rychlost:2:4,stringa);

     // vše si zobrazíme v labelu
     Label1.caption:=
     'uhel: '+IntTOStr(Trunc(aUhel))+'°'+#10#13+
     'x: '+IntToStr(ax)+#10#13+
     'y: '+IntToStr(ay)+#10#13+
     'speed: '+ stringa;

     // nakonec ještě nastavíem uhel na Auhel
     uhel:=Trunc(aUhel);
    end;
  end;

 Image1.Refresh;

end;


Program můžete přeložit a spustit. Ovšem nelekejte se, pokud nevidíte nikde nastavování úhlu a rychlosti - zkuste si kliknout na místo, kde přibližně komponenta leží, a hned se vše nakreslí, tak jak má být.
To je ale vcelku nepohodlné, a vy byste asi chtěli, aby bylo nastavování vidět již od začátku. Není problém. Přidejte do procedury Form1.OnCreate následující řádky a vše je vyřešeno (v následujícím řádku simulujeme kliknutí na komponentu):


Form1.Image1MouseMove(nil,[ssLeft],30,30);


Program znovu spusťte a co vidíte ? Ano, vše je již v pořádku a vypadá to nějak takto:


A tímto také dnes skončíme. Doufám, že toho nebylo moc najednou a že jsem vám znovu alespoň trochu přiblížil tvorbu her.

ZDE si můžete stáhnout konečnou verzi projektu (200 kB)

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » 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: