Mivel az előző számban, a Java rovatban kicsit látványos dologgal foglalkoztunk, úgy gondoltam, hogy a mai Delphi rovatban is valami csini, jópofa dolgot kellene csinálnunk. Hát legyen a már közismert X-EYES, vagy szabad fordításban a "Gülü szem". Ennek megvalósítása nem valami bonyolult, de szükséges valamennyi matematikai ismeret hozzá.

Lássuk először a programozási nyelv részéről követelt újabb ismereteket:

  1. Először is kellene készítenünk egy kör objektumot. Azért kell ilyet készíteni, mert a TShape objektum-nak kör esetén se tudjuk úgy megadni a méreteit, hogy a kör középpontja, és sugara.

Lássuk az új kör objektum definícióját:

type   TCircle = class(TShape)  private      Radius: Integer;      Center: TPoint;      procedure RefreshCircle;  public     constructor Create(AOwner: TComponent); override;     procedure SetRadius(r: Integer);     function GetRadius: Integer;     procedure SetCenter(ACenter: TPoint);     function GetCenter: TPoint;     procedure SetColor(AColor: TColor);     function GetColor: TColor;   end;

Látható módon (a régi OOP rovatban elmondottak szerint), a TShape objektumból származtatjuk a TCircle kör objektumunkat. Új jellemzői lesznek a kör sugara a Radius ill. a kör középpontja a Center mezők. Továbbá van egy RefrreshCircle; nevű metódus is mely az objektum belső működésében használt. A két mező ill. ezen utolsó metódus szerepükből adódóan privátra lettek definiálva, ugyanis nem kívánatos a külső elérésük.

A további metódusok pedig új, munkánkat megkönnyítő rutinok.

  • A Create() metódust - az objektumunkat tulajdonképpen létrehozó, inicializáló metódust - azért írjuk felűl, mert az alapértékeket be kell állítanunk. Lássuk:
    constructor TCircle.Create(AOwner: TComponent);begin   Inherited Create(AOwner);   Shape:=stCircle;   Brush.Color:=clBlack;   Width:=20; Height:=20;   Center.X:=10; Center.Y:=10;   RefreshCircle; end;
  • Az Inherited() kulcsszó meghívja az előd azonos metódusát.
  • A TShape-nek beállítjuk formának a kört
  • A kör színe alapértelmezés szerint fekete
  • majd a szélességet és magasságot
  • a kör középpontját
  • s meghívjuk a frissítést
  • A SetRadius() / GetRadius() metódusokkal beállíthatjuk ill. lekérdezhetjük a kör sugarát.
    procedure TCircle.SetRadius(r: Integer);begin   Radius:=r;   RefreshCircle; end;
    function TCircle.GetRadius: Integer;begin   GetRadius:=Radius; end;
  • A SetCenter() / GetCenter() metódusokkal beállíthatjuk ill. lekérdezhetjük a kör középpontját.
    procedure TCircle.SetCenter(ACenter: TPoint);begin   Center:=ACenter;   RefreshCircle; end;
    function TCircle.GetCenter: TPoint;begin   GetCenter:=Center; end;
  • A SetColor() / GetColor() metódusok a kör színének lekérdezését teszik lehetővé:
    procedure TCircle.SetColor(AColor: TColor);begin   Brush.Color:=AColor; end;
    function TCircle.GetColor: TColor;begin   GetColor:=Brush.Color; end;
  • Az utolsó szükséges lépés a kör képét frissítő metódus elkészítése:
    procedure TCircle.RefreshCircle; var true_left, true_top: Integer;begin   Width:=Radius div 2;   Height:=Radius div 2;   true_left:=Center.X-Radius; if true_left < 0 then true_left:=0;   true_top:=Center.Y-Radius; if true_top < 0 then true_top:=0;   Left:=true_left; Top:=true_top;   Refresh; end;
  • Először a sugárból visszaszámoljuk a szélességet (Width) és magasságot (Height).
  • Majd kiszámítjuk a kör bal szélének koordinátáját, a középpont mínusz a sugár. Ha ez az érték kisebb lenne nullánál, akkor 0-t adunk neki, hogy ne lógjon ki az ablakról.
  • Hasonlóan számítjuk ki a kör tetejének koordinátáját.
  • Majd meghívjuk a Refresh; metódust mely újrarajzolja a megadott értékekkel a kört.
Nos, hogy idáig eljutottunk, a kedélyek borzolása végett közlöm, hogy a programban mégsem ezt a TCircle objektumot fogjuk használni, hanem maradunk a sima TShape-nél.

2. Második lépés a form elkészítése:

     

    2.1 Hozzuk létre a bal oldali szem részt, a TShape vizuális komponenssel:

      • A Shape property-t állítsuk stCircle - az-az körre,
      • A Brush tulajdonság alatt a Color tulajdonságnál állítsuk fehérre (clWhite)
      • A shape Left koordinátája legyen 5, a Top koordinátája úgyszintén legyen 5.
      • A shape mérete: Width és Height egyaránt (hisz kör) 100 egység.
      • Ennyi egy rész beállítása
    2.2 Hozzuk létre a jobb oldali szemet hasonló méretekkel, de a:
      • Left érték = 110 és a
    2.3 Hozzuk létre a szem testet, azt a fekete kis gumót.
      • A lényeg csak a szín és a méret, hisz az egér mozgására majd úgy is fel kell vennie a megfelelő helyet:
      • Szín a Brush.Color = clBlack
      • és a méret: Height és Width egyaránt = 20
3. Lássuk a matematikai alapokat:

A két belső körön kell mozognia a szemgolyóknak. A lényeg, hogy mindig kövesse a két szempár mozgása az egér mozgását. Két megoldás kínálkozik:

  • Vagy használjuk a kör egyenletét:

  •  
  • Vagy egy köztes megoldás, külön számoljuk a belső kör középpontjától a szemgolyó x távolságát ez elég egyszerű, s ekkor csak a középponttól való függőleges eltérést kell kicsit tovább számolni.
Inkább az utóbbit fogjuk használni, mert ez tűnik az egyszerűbbnek.
  • Első lépés a szemgolyó X (vízszintes) irány menti mozgatása:
  • Mivel az egér kurzor bárhol elhelyezkedhet, s nem csak a belső körön belül (lásd ábra), amin a szemgolyó mozog ahhoz, hogy a szemgolyó ne mozduljon ki a szemből meg kell vizsgálni, hogy az egér a belső körön belül vagy kívül van -e. Ezt megtehetjük a:

    egér_X_koordinátája - belsőkör_középpontja_X_koordináta

    Ha ez az érték kisebb, mint a belső kör sugara, akkor az egér a belső körön belül van így az egér X koordinátája ténylegesen megadja, hol kell elhelyezkedni a szemnek. Ha a fenti ábrán a téglalapokban helyezkedik el az egér kurzor akkor kell csak korrekció. (Mert e nélkül a szemgolyó a körön kívül esne.)

    Ezt programban így oldottuk meg:

      r_bal:=X-bal_szem_kozep.X;   if r_bal > szem_sugar   then r_bal:=szem_sugar   else if r_bal < -szem_sugar        then r_bal:=-szem_sugar;

    Az r_bal a bal oldali szemgolyó X tengely szerinti középponttól való eltérését adja meg. Látható, ha a belső kör középpontjától balra helyezkedik el az egérkurzor akkor negatív értéket kapunk. Ezt direkt készítjük el, hogy majd újra a középponthoz adva fix értéket kapjunk. Tehát eddigi ténykedésünk lényege, hogy az X koordinátát jól beállítsuk, s ha a fenti ábrán a téglalapokban helyezkedik el az egér kurzor akkor a szemgolyó csak legkívülre a körben, ne a körön kívül helyezkedjen el. Az alábbi ábra mutatja, hogy mikor lehet negatív az r_bal változó:

  • Ezt az algoritmust alkalmazhatjuk mind a két szemre, csak a jobb szemnél más koordinátákkal, de hasonló módon kell számolni. A jobb szemgolyó számolása:
  •   r_jobb:=X-jobb_szem_kozep.X;
      if r_jobb > szem_sugar
      then r_jobb:=szem_sugar
      else if r_jobb < -szem_sugar
           then r_jobb:=-szem_sugar;
     
     

  • Amit még nem mondtam el ezzel kapcsolatban:
  • A bal_szem_kozep ill. jobb_szem_kozep adja a bal ill. jobb oldali szem közepének koordinátáit. Ez egy TPoint típusú rekord, így az X érték a vízszinteset az Y meg a függőlegeset adja.

    A szem_sugar pedig annak a körnek (az ábrán a belső körnek) a sugarát adja meg melyen a szemgolyó mozog.

  • Nos ezek után megadhatjuk az abszolút vízszintes koordinátáit a szemgolyóknak:
  • GuluBal.Left := bal_szem_kozep.X+r_bal-10;
    GuluJobb.Left := jobb_szem_kozep.X+r_jobb-10;

    A belső kör középpontjához adjuk az előbb kiszámított eltolási értéket, és levonunk 10-et, mert ennyi a szemgolyó sugara.

    Hurrá már működik is X irányban a szemgolyó mozgása !!!


     
  • További elmés feladatunk a szemgolyó Y irány menti mozgatása. Ez már kicsit finomabb:
  • Lássunk először is egy ábrát:

    Ismert (kiszámítható), hogy az egér kurzor milyen messze van Y irányban a szimmetriatengelytől, ill. kiszámítható még a kör középpontjától való X irányú távolság. Itt teljes, nem a belső körön belüli távolságokkal számolunk, hogy a szemgolyó az egérre nézzen. Két hasonló háromszög van a nagyobb h és c oldalakkal határolt és a kisebb melynek az a oldalát keressük a belső kör sugarának és a most kiszámítandó a-nak az ismeretében. Számolhatnánk aránypárral is de így jobb. Később kiderül miért. Tehát számoljuk ki az a-t:

    ArcTan( (Y-bal_szem_kozep.Y) / Abs(X-bal_szem_kozep.X) ) )

    A fenti ábrán látható módon számítjuk ki az a szöggel szemközti ill. a melletti befogók arctg-e adja az a-t. (Középiskola 2. osztály.) Hogy az Abs (abszolút érték) miért van ott ?; hogy a kör teljes területén helyesen számoljunk, hisz ennek a kivonásnak az eredménye lehet negatív is (s e nélkül az ellenkező síknegyedbe kerülne a szemgolyó). Mivel meg van már az a, így számolhatjuk egy sima sin-szal az a befogót:

    a = sin( a ) * belsokor_sugara


     
  • Fontos, hogy meggyőződjünk arról, hogy az X nem egyenlő bal_szem_kozep.X-mel hisz ekkor 0-val való osztási hiba lépne fel.
  • Lássuk az Y koordináta kiszámítását a programban:
  • A bal szemgolyóra:

    if X-bal_szem_kozep.X <> 0 then   GuluBal.Top:=Integer(Round(     Sin( ArcTan( (Y-bal_szem_kozep.Y) / Abs(X-bal_szem_kozep.X) ) )* szem_sugar   ))+bal_szem_kozep.Y-10;

    A jobb szemgolyóra:

    if X-bal_szem_kozep.X <> 0 then   GuluBal.Top:=Integer(Round(     Sin( ArcTan( (Y-bal_szem_kozep.Y) / Abs(X-bal_szem_kozep.X) ) )* szem_sugar   ))+ jobb_szem_kozep.Y-10;

    A Round() parancs kerekíti az értéket, az Integer() parancs pedig a kerekített Longint szám típust alakítja integer-ré. Azért adunk hozzá a bal_szem_kozep (ill. a jobb_szem_kozép) változót, mert egész végig a belső kör középpontjához viszonyítva számoltunk. A 10-et megint a szemgolyó sugaraként vontuk le.

  • Ezek után már szinte kész is a programunk. A eddig leírt forráskódokat rakjuk egy MakeMouse(X, Y: Integer); rutinba melynek X és Y paramétere az ablakon (a from-on) mozgó egér abszolút koordinátái. A Form objektum OnMouseMove() eseményét állítsuk be az objectinspector Events fülére egyszer, s az OnMouseMove() feliratra duplán klikkantva. Ide írjuk be a MakeMouse(X, Y) sort. Itt az X és Y tényleg abszolút koordinátákat reprezentálnak. Így ez lett:
  • procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);begin   MakeMouse(X, Y);end;
     
  • Ekkor már nagyjából fut is a program. De ha a szemre (a fehér háttérre) megyünk akkor nyílván nem kap a Form1 eseménykezelése eseményt hisz a szem kezeli le. Így meg kell annak is hívni az OnMouseMove() metódusát, hogy az tényleg lekezelje a nála keletkezett eseményt. Itt viszont ha X-et és Y-t adunk meg akkor az a saját X és Y-ját adja vissza (a bal felső csücsökhöz viszonyítva) így hozzá kell adni az X-hez az objektum Left mezőjét (a bal oldali kezdő koordináta) és az Y-hoz a Top mezőt. Így megkapjuk a kívánt abszolút koordinátát, s az egérkurzort a szem felett mozgatva is működik a dolog. Így:
  • procedure TForm1.SzemBalMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);begin   MakeMouse(X+SzemBal.Left, Y+SzemBal.Top);end;procedure TForm1.SzemJobbMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);begin   MakeMouse(X+SzemJobb.Left, Y+SzemJobb.Top);end;
    A megfelelő szemeknek a megfelelő koordinátákat adjuk az elmondottak szerint hozzá.
  • Csinosíthatjuk egy orral és szájjal is a programunkat:
  •  

     
  • Egy apróságot még leírok melyet érdemes itt a Delphi-s cikkek elején még elmondani. AboutBox. Használjunk pár AboutBox-ot, hogy ne macerálják a figuránk orrát és száját. Paraméterezés:
    MessageBox(Text, Title: PChar, Field: Integer): Integer;

    A text az a szöveg amit kiírjon, a Title az AboutBox ablakának címe, s a harmadik paraméter a megjelenített gombok típusa. Pl:

    Application.MessageBox('Don''t touch my nose !', 'Warning !', MB_ABORTRETRYIGNORE)

    Az Application előtagot oda kell tenni, különben másik aboutbox kerül meghívásra melyet másképp kell paraméterezni.