Kolize ve hrách 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:

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

Kolize ve hrách v DelphiX

delphi_kolize

31. srpna 2001, 00.00 | Chcete ve vaší hře zjistit, zda raketa narazila do meteoritu, do zdi, nebo střela do nepřítele ? Přečtěte si dnešní článek, ve kterém je popsáno, jak zjistit nejpoužívanější kolize - bodu v obdélníku, dvou obdélníků a dvou kruhů.

Kolize jsou jedna z vůbec nejdůležitějších věcí ve všech hrách (nepočítáme-li šachy a podobné věci :) Vždy je důležité vědět, zda do sebe dvě a více věcí narazili, abychom podle toho uspůsobili hru a provedli další věc (výbuch, odhození do strany nebo jen zastavení pohybu tělesa). Málokdo si asi dovede představit, že by ve hře prošel zdí (teď myslím bez cheatů), propadl by se pohlahou nebo že by vystřelená střela prošla jen tak tělem nepřítele. Existuje samozřejmě jako vždy hned několik možností pro několik případů. Každá možnost má své výhody a nevýhody, a proto si dnes ukážeme několik řešení. Tedy, dáme se do toho.

Kolize bod v obdélníku

I když jsem to již několikrát psal, opakování je matka moudrosti, a proto začneme touto jednoduchou věcí. Pokud chceme zjistit, nachází-li se daný bod v určitém obdélníku, použijeme k tomu funkci PtInRect. První parametr je obdélník typu TRect a druhý je bod typu TPoint. Funkce vrací hodnotu True, pokud došlo ke kolizi a false pokud ne.


Kolize dvou obdélníků

A na řadu přichází již trochu složitější věc. Naštěstí i na případ kolize dvou obdélníků windows pamatují, a tak tu máme funkci IntersectRect. Její druhý parametr je první zkoumaný obdélník, třetí parametr je druhý zkoumaný obdélník. Pokud došlo ke kolizi, vrací funkce hodnotu true, a "kolizní" obdélník určuje první parametr. Pro lepší pochopení je zde obrázek:


Věc je již složitější, proč si tedy neudělat nějaký ten vzorový příklad. Definujeme si tedy tři obdélníky typu TRect, a to MoveRect,StayRect a CollisionRect. Dále si definujeme proměnnou Collision typu boolean, podle které poznáme, došlo-li ke kolizi. Na formulář dáme komponenty DXDraw, DXTimer, DXImageList a DXInput, DXInput proto, že s jedním obdélníkem budeme pro názornost hýbat. Do DXImageList si přidáme obrázek, který pojmenujeme grid a který budeme kreslit na pozadí. Stáhnout si jej můžete zde:


Dále si vytvoříme proceduru Form.OnCreate, ve které nastavíme náhodné pozice obou obdélníků. Procedura bude vypadat nějak takto:


procedure TMainForm.FormCreate(Sender: TObject);
begin
 Randomize;
 // nastavení náhodných čísel...
 MoveRect.Left:=Random(DXDraw1.SurfaceWidth-60);
 MoveRect.Top:=Random(DXDraw1.SurfaceHeight-40);
 MoveRect.Right:=MoveRect.Left+60;
 MoveRect.Bottom:=MoveRect.Top+40;

 StayRect.Left:=Random(DXDraw1.SurfaceWidth-120);
 StayRect.Top:=Random(DXDraw1.SurfaceHeight-80);
 StayRect.Right:=StayRect.Left+120;
 StayRect.Bottom:=StayRect.Top+80;

 Collision:=false;
end;


Dále si vytvoříme proceduru DXTimer.OnTimer ve které všechno nakreslíme a hlavně otestuejeme kolizi. Celý výpis procedury je zde:


procedure TMainForm.DXTimer1Timer
(Sender: TObject; LagCount: Integer);
var gx,gy:integer;
begin
 // když nemůžeme kreslit tak pryč
 if not DXDraw1.CanDraw then exit;

 // draw grid
 for gx:= 0 to DXDraw1.SurfaceWidth div 50 do
  begin
   for gy:= 0 to DXDraw1.SurfaceHeight div 50 do
    begin
     DXImageList1.Items.Find('grid').Draw
     (DXDraw1.surface,gx*50,gy*50,0);
    end;
  end;

 // update klávewsnice
 DXInput1.Keyboard.Update;

 // má se hýbat s MoveRect ???
 if isLeft in DXInput1.Keyboard.States then // doleva
  begin
   Dec(MoveRect.Left);
   Dec(MoveRect.Right);
  end;
 if isRight in DXInput1.Keyboard.States then // doprava
  begin
   Inc(MoveRect.Left);
   Inc(MoveRect.Right);
  end;
 if isUp in DXInput1.Keyboard.States then // nahoru
  begin
   Dec(MoveRect.Top);
   Dec(MoveRect.Bottom);
  end;
 if isDown in DXInput1.Keyboard.States then // dolu
  begin
   Inc(MoveRect.Top);
   Inc(MoveRect.Bottom);
  end;

 // není náhodou kolize ???
 if InterSectRect(CollisionRect,MoveRect,StayRect)
 then
  Collision:=true
 else collision:=false;

 // kreslit RECt
 with DXDraw1.Surface.Canvas do
  begin
   Pen.Width:=2;
   Brush.Style:=bsClear;
   Pen.Color:=clYellow;
   Rectangle(MoveRect);
   Rectangle(StayRect);

   // kreslit collisionRect když je kolize
   if Collision then
    begin
     Pen.Color:=clLime;
     Font.Color:=clBlack;
     Font.Name:='Tahoma';
     Font.Style:=[fsBold];
     Font.Size:=15;
     TextOut(10,10,'!! KOLIZE !!');

     Brush.Style:=bsSolid;
     Brush.Color:=clRed;
     Rectangle(CollisionRect);
    end;

   // na to nezapomanout
   Release;
  end;

 // a flip ...
 DXDraw1.Flip;
end;


Nyní můžete projekt uložit, přeložit a spustit. Zkuste hýbat šipkami s jedním obdélníkem, a všimněte si, že program doopravdy funguje. Zde jsou ukázány dvě možnosti programu:




Tentokrát je k downloadu pouze zdrojový kód programu, který hledejte dole v sekci Download.

Kolize dvou kruhů

Bohužel, windows ani Delphi nepamatují na všechno, a tak nám v určitých případech nezbývá nic jiného, než abychom si pomohli sami. Tak je to i v případě kolize dvou kruhů. Naštěstí však máme ještě matematiku, a taky pythagorovu větu, takže to nebude žádný problém. Nejprve si spočítáme vzdálenost středů kruhů a poté ji porovnáme se součtem poloměrů. Pokud bude vzdálenost menší, došlo ke kolizi. Více snad pochopíte z následujícího obrázku:


Opět si tedy vytvoříme ukázkový projekt (mnoho věcí bude stejných nebo částečně podobných, proto nebudu vše znovu opakovat). Na začátek si opět definujeme pár proměnných. Zaprvé MoveCircle a StayCircle jako record složený ze středu (CenterX a CenterY) a poloměru (Radius). Vše typu integer. Dále Collision typu boolean, podle které poznáme, došlo-li ke kolizi a pomocnou proměnnou r typu real. Všechny proměnné hezky pohromadě máte zde:


   MoveCircle, StayCircle:record
    CenterX,CenterY,Radius:integer;
    end;

   Collision:boolean;   // došlo ke kolizi ???
   R:real;              // pomocná proměnná


Dále si opět vytvoříme proceduru Form.OnCreate, ve které si nastavíme náhodné polohy obou kruhů:


procedure TMainForm.FormCreate(Sender: TObject);
begin
 Randomize;
 // nastavení pozic kol a poloměrů..
 MoveCircle.Radius:=(Random(30)*2)+5;
 MoveCircle.CenterX:=Random
 (DXDraw1.SurfaceWidth-MoveCircle.Radius);
 MoveCircle.CenterY:=Random
 (DXDraw1.SurfaceHeight-MoveCircle.Radius);

 StayCircle.Radius:=(Random(40)*2)+5;
 StayCircle.CenterX:=Random
 (DXDraw1.SurfaceWidth-StayCircle.Radius);
 StayCircle.CenterY:=Random
 (DXDraw1.SurfaceHeight-StayCircle.Radius);

 Collision:=false;
end;


A nakonec konečně přistoupíme k proceduře DXTimer.OnTimer. K čemu slouží již doufám všuchni víte, a proto jen zopakuji, že v ní nejprve zkontrolujeme, zde-li nedošlo ke kolizi a poté oba kruhy nakreslíme. Výpis celé procedury je zde:


procedure TMainForm.DXTimer1Timer
(Sender: TObject; LagCount: Integer);
var gx,gy:integer;
begin
 // pokud nemůžeme kreslit, tak hned pryč
 if not DXDraw1.CanDraw then exit;

 // vymazat předchozí kresbu
 DXDraw1.Surface.Fill(0);

 // draw grid
 for gx:= 0 to DXDraw1.SurfaceWidth div 50 do
  begin
   for gy:= 0 to DXDraw1.SurfaceHeight div 50 do
    begin
     DXImageList1.Items.Find('grid').Draw
(DXDraw1.surface,gx*50,gy*50,0);
    end;
  end;

 // update klávesnice
 DXInput1.Keyboard.Update;

 // pohnout s MoveCircle ???
 if isLeft in DXInput1.Keyboard.States  // doleva
  then Dec(MoveCircle.CenterX);
 if isRight in DXInput1.Keyboard.States // doprava
  then Inc(MoveCircle.CenterX);
 if isUp in DXInput1.Keyboard.States    // nahoru
  then Dec(MoveCircle.CenterY);
 if isDown in DXInput1.Keyboard.States  // dolu
  then Inc(MoveCircle.CenterY);

 // nedošlo mezi kruhy ke kolizi ???
 R:=Sqr(MoveCircle.CenterX-StayCircle.CenterX)+
    Sqr(MoveCircle.CenterY-StayCircle.CenterY);
 R:=Sqrt(R);
 if R <= (MoveCircle.Radius+StayCircle.Radius)
  then Collision:=true
 else Collision:=false;

 // kreslit kruhy
 with  DXDraw1.Surface.Canvas do
  begin
   Brush.Style:=bsClear;
   Pen.Width:=2;
   if Collision then
    begin
     Pen.Color:=clLime;
     Font.Color:=clBlack;
     Font.Name:='Tahoma';
     Font.Style:=[fsBold];
     Font.Size:=15;
     TextOut(10,10,'!! KOLIZE !!');
    end
   else
    begin
     Pen.Color:=clYellow;
    end;

   Ellipse(MoveCircle.CenterX-MoveCircle.Radius,
   MoveCircle.CenterY-MoveCircle.Radius,
   MoveCircle.CenterX+MoveCircle.Radius,
   MoveCircle.CenterY+MoveCircle.Radius);

   Ellipse(StayCircle.CenterX-StayCircle.Radius,
   StayCircle.CenterY-StayCircle.Radius,
   StayCircle.CenterX+StayCircle.Radius,
   StayCircle.CenterY+StayCircle.Radius);

   // na tohle nezapomenout !!!!
   Release;
  end;

 // a flip...
 DXDraw1.Flip;
end;


Zde jsou ještě dva screenshoty aplikace:




Jednoduchou úpravou naší procedury můžeme samozřejmě vytvořit funkci PtInCircle, která bude zkoumat, zda-li se daný bod nachází v kruhu. Bude na stejném principu, pouze nemusíme sčítat oba poloměry, jelikož bod žádný poloměr nemá. To je ale již na vás...

Závěr

Samozřejmě jsem nenapsal všechny možnosti, které můžete použít, jelikož by jich bylo nespočet. Každá se hodí pro torchu něco jiného. Například nejpřesnější je zjišťovat, zde-li některý pixel jednoho obrázku překrývá některý pixel obrázku druhého. Tato metoda je všek velice pomalá. Výhoda metod popsaných v tomto článku spočívá hlavně v rychlosti, bohužel u kolize dvou kruhů je často velká nepřesnost. Více pochopíte z tohoto obrázku:


Podle našeho postupu ke kolizi došlo, ve skutečnosti však nikoliv. Takovéto situace se řeší tím, že se jeden objekt rozdělí na více malých kruhů, čím více kruhů, tím větší přesnost:


A vše je již vyřešeno. Tímto zárověň ukončuji dnešní článek.

Download

Zde si můžete stáhnout dnes vytvořený příklad ( pouze zdrojáky ) - (14 kB).

Relevantní články

Seriál - Tvorba her v DelphiX
Seriál - Tvorba hry Had v DelphiX
Zobrazení části obrázku v DelphiX
Komponenta DXImageList
Simulace sněžení v DelphiX
Zobrazení kurzoru v DelphiX
Posuvný text v DelphiX
Isometrický engine v DelphiX
Sprity v DelphiX (C++ Builder)

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: