[Grafika] [WebTip] [Fotografování] [Galerie] [MujMac] [Printing]
  Redakce: info (at) builder.cz   Inzerce: reklama (at) grafika.cz
Diskuzní fóra
.Net (68471)
ASP (1591)
ActiveX (168)
Allegro (136)
Assembler (3926)
C++ Builder (23160)
C/C++ (44499)
Databáze (30680)
Delphi (78807)
DelphiX (1655)
DirectX (1464)
Java (39508)
JavaScript (12598)
Matematické programy (2178)
OOP a UML (732)
OpenGL (6920)
Php (65224)
PowerBuilder (464)
Problémy a algoritmy (10473)
Programování v Linuxu (2000)
Právo a programování (3384)
Python (1353)
Ruby (136)
Visual Basic (12078)
Visual C++ (12956)
Wap (56)
Web (10895)
Web servery (5549)
Win32 (13553)
Windows CE (865)
XML/XSL (1860)
Textová inzerce
Služby Builder.cz
  • Bazar - koupím(0)
  • Bazar - prodám(0)
  • Hledám práci(0)
  • Nabízíme práci(0)
  • Projekty(0)
  • Kolize ve hrách v DelphiX
    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ů.
    Tvorba her v DelphiX
    Předchozí díl: Bludiště v DelphiX

    Následující díl: Posuvný text v DelphiX bez komponenty
    Autor: Václav Krejčí
    Rubrika: Delphi
    Publikováno: 31.08. 2001
     Tisk článku
    Poslat odkaz emailem
     

    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)


    Hodnocení článku
    1 | 2 | 3 | 4 | 5
    Aktuální známka: 2.65
    (Počet známek: 4242)

    Komentáře k článku
    Napoleon04.02.19:21A čo SpriteEngine?
    Napoleon04.02.19:19A čo SpriteEngine?
    Rak23.04.13:22co takhle pres bity?
    BuFran17.09.12:40RE: co takhle pres bity?
    Azazel28.02.22:17Reakce na reakci strejdy Billa
    Bill Gates15.09.17:24To by me "opravdu" nenapdalo...
    Vaclav Krejci15.09.18:29RE: To by me
    digri31.08.22:19PixelCheck
    MegaBoss01.09.19:43RE: PixelCheck
    Harik31.08.21:22Najde se někdo...
    Vaclav Krejci01.09.10:44RE: Najde se někdo...
         





    info@builder.cz
    Vydává Grafika Publishing, s.r.o.
    Copyright (c) 1997-2002 Všechna práva vyhrazena