Svgalib

Üdvözlök mindenkit a linuxos programozás világában. Ez a cikk(sorozat) nem elsősorban a profikhoz szól, hanem azokhoz, akik most kapizsgálják a programozás örömeit(és szenvedéseit). A kezdőket, amilyen és is vagyok még, sem az általános dolgokkal akarom traktálni, hanem inkább érdekességképpen minden hónapban egy-egy C könyvtárral ismerkednénk meg, egy-egy játék programon belül.

Ebben a hónapban a fogás az svgalib lenne. Ez egy alacsony szintű grafikus könyvtár, amely az X-től teljesen függetlenül fut és sokkal gyorsabb nála. Elsősorban a parancsait szeretném bemutatni, valamint azt, hogy hogyan lehet egy szép grafikus programot csinálni egy mintaprogramon keresztül.

Az svgalib beállítását most helyhiány miatt nem taglalom, de a linkek között találhatnak egy nagyon jó oldalt, ami pontosan ezzel foglalkozik.

Az svgalib nagyon sok felbontást támogat, számszerint 49-cet (a teljes lista az svgalib man oldalán megtalálható). Mi a 10-zes felbontást használjuk, ez 640*480 képpontot jelent, melyek mindegyike 256 színben pompázik (amiből mondjuk én csak 16-t használtam). A parancsoknak általában mindig van manual oldala, csak be kell írni egy konzolba például a man vga_init parancsot és rögtön megjelenik a hozzá tartozó szépen tördelt súgó. Amit leírok valahol, az rendszerint minden helyen érvényes lesz, pl. az egér esemény kezelését ugyanúgy oldottam meg mindenhol.

Nos akkor ennyi kertelés után bele is fogok a program leírásába. Először elindítom a grafikus módot, aminek megértéséhez javaslom, hogy nézzék meg a forráskódot.

vga_init()

Ez detektálja a grafikus kártyát, valamint átkapcsol super user módból felhasználó módba, vagyis innentől sima felhasználói jogokkal fut a program.

vga_setmode()

Itt lehet beállítani, a felbontást és a színmélységet. Simán egy számot kell megadni paraméterként, lásd fent.

gl_setvontextvga()

Ekkor teszi ki a képernyőre a vga_setmode-ban meghatározott módot. Leegyszerűsítve a lényeg az, hogy a setmode után legyen és ugyanazt a paramétert kapja.

Az egér beállítása:

vga_setmousesupport(1) (No Comment, ezt így kell:).

gl_setfont(8, 8, gl_font8x8); - az általános betűtípust és méretet ezzel állítjuk be, az első kettő paraméter a méret, a harmadik egy mutató egy betűkészletre (akit érdekelnek a betűtípusok és tud angolul, menjen el erre az oldalra: http://www.svgalib.org/jay/font_tutorial/fonts.html

gl_setwritemode(FONT_COMPRESSED + WRITEMODE_MASKED); - itt állítjuk be a betű kinézetét, a FONT_COMPRESSED azt jelenti, hogy egy pixel egy bájtnyi információ lesz. Helyette írhattunk volna FONT_EXPANDED -t is, de az lassabb.

A WRITEMODE_MASKED azt jelenti, hogy írásnál a szöveg háttérszíne semmi sem lesz, vagyis megmarad az eredti (így pl. írhatunk egy kép elé stb.).Az ellentétje a WRITEMOD_OWERWRITE.

gl_setfontcolors(0, vga_white()); - itt lehet beállítani a szöveg színét, az első a háttérszín, a második a betűk színe.

A gl_write(0, 0, "hello world!") függvénnyel írhatunk a képernyőre, az első kettő paraméter a szöveg elejének az X, Y a koordinátái.

Ennyi volt az elmélet egy rövid ideig, most jöjjön a gyakorlat. Először kiírtam a menüt és kirajzoltam az egeret. Aztán egy ciklusban történik a tulajdonképpeni futás. Itt két dolgot vizsgálok: azt, hogy az egér vagy a billentyűzet  küldött-e eseményt. Billentyű lenyomáskor a program bezárja az egeret (mouse_close()), a grafikus módot (vga_setmode(TEXT)), és kilép a programból.

Ha az egeret megmozdítottuk (vagy kattintottunk) és ha valamilyen szöveg fölött van az egérkurzor, akkor az egeret törlöm, a szöveget kiírom pirossal, és az egeret újra kirajzolom. Ezt mutatja be ez a kódrészlet:

gl_circle(X, Y, 3, 0); gl_setpixel(X, Y, 0); - még a régi helyen törlöm az egeret. Ez azért kell, hogyne maradjon ott a helye.                      

X = mouse_getx(); Y = mouse_gety(); - itt adom meg az új helyet

if ( (257 < mouse_getx()) && (343 > mouse_getx()) && (160 > mouse_gety()) && (147 < mouse_gety())) - megnézem, hogy a menüpont felett van-e az egér

gl_setfontcolors(0, 4); - ha igen, akkor a színt pirosra változtatom

gl_write(260, 150, "New Object"); - itt írom ki a szöveget

gl_setfontcolors(0, vga_white());  - és visszaállítom a színt fehérre.

Ha kattintottunk, akkor ugyanez megtörténik, de utána megnézem, melyik menü felett volt az egér és az annak megfelelő függvényt hívom meg.

A Making függvényben alkalmazott technika kicsit eltér az előbbiektől. Az egér és a billentyűzet kezelése ugyanúgy történik, de itt már nem szövegek, hanem gombok (Button) vannak. Kezdjük az elején. Először a gombokat állítom be (virtuális függvényekkel szórakoztam, mert az elején mást is akartam, de aztán az kimaradt, tudom, hogy sokkal egyszerűben is meg lehetne ezt csinálni, de már nincs kedvem újraírni a forráskódot) és a SetValues függvényel adom meg az elhelyezkedését, a színét és a szöveget, ami rajta van. A ciklus előtt még 2 dolgot csinálok. Először a gombokat rajzolom ki és aztán egy nagyon érdekes dolgot művelek. Erről még nem beszéltem, de úgy adom meg a "rajzot" amit csinálok, hogy van egy 192 négyzetből álló négyzetrács. Mindegyik négyzet(ecske) egy-egy objektum, amik egyenlőre csak a szint tárolják. Ilyen objektumokból áll a Parts[192] tömb. Egy ilyen tömb tartalmazza a rajzot.

Az Objects fájl 3*192 számot tartalmaz. Nem azért írtam így mert nem tudok számolni, hanem, hogy jelezzem: 3 rajzot tartalmaz (a számok a rajz színei). A Making paramétere azt adja meg, hogy hányadik rajzot töltse be az Objects fájlból. Ha 0 a paraméter (vagyis az alapértelmezett lesz), akkor értelemszerűen új rajzot szeretnénk, így feketével töltöm fel a négyzeteket.

Ezek után akkor jöjjön megint egy kis gyakorlat. Ha az egér egy Button felett van, akkor az alatta/felette lévőt és a mellette lévőket is kirajzolom, hogy ne maradjon nyom azokon a gombokon (a kódban bejelöltem a nyomtisztítást, ha meg szeretnék nézni, hogy milyen nélküle, egyszerűen töröljék ki).

/*nyomtisztítás*/

for (i=0; i < 16; i++)

if ((objects[i]->getX() < X)

      && (objects[i]->getX2() > X)

      && (objects[i]->getY() < Y)

      && (objects[i]->getY2() > Y))

{

      DrawButton(

            objects[i]->getX(), objects[i]->getY(),

            objects[i]->getX2(), objects[i]->getY2(),

            objects[i]->getW(), objects[i]->getH(),

            objects[i]->getTextX(), objects[i]->getTextY(),

            objects[i]->getColor(), objects[i]->getText()

      );

 

      if (i!=0)

            DrawButton(

                  objects[i-1]->getX(), objects[i-1]->getY(),

                  objects[i-1]->getX2(), objects[i-1]->getY2(),

                  objects[i-1]->getW(), objects[i-1]->getH(),

                  objects[i-1]->getTextX(),objects[i-1]->getTextY(),

                  objects[i-1]->getColor(), objects[i-1]->getText()

            );

 

      if (i!=15)

            DrawButton(objects[i+1]->getX(), objects[i+1]->getY(),

                  objects[i+1]->getX2(), objects[i+1]->getY2(),

                  objects[i+1]->getW(), objects[i+1]->getH(),

                  objects[i+1]->getTextX(),

                  objects[i+1]->getTextY(),

                  objects[i+1]->getColor(), objects[i+1]->getText()

            );

 

      if (i<8)

      DrawButton(

            objects[i+8]->getX(), objects[i+8]->getY(),

            objects[i+8]->getX2(), objects[i+8]->getY2(),

            objects[i+8]->getW(), objects[i+8]->getH(),

            objects[i+8]->getTextX(), objects[i+8]->getTextY(),

            objects[i+8]->getColor(), objects[i+8]->getText()

      );

 

      if (i>=8)

            DrawButton(objects[i-8]->getX(), objects[i-8]->getY(),

                  objects[i-8]->getX2(), objects[i-8]->getY2(),

                  objects[i-8]->getW(), objects[i-8]->getH(),

                  objects[i-8]->getTextX(), objects[i-8]->getTextY(),

                  objects[i-8]->getColor(), objects[i-8]->getText()

            );

      break;

}

A ciklus azért kell, mert mindegyik gombot megnézem, hogy melyik fölött van. A gomb bal felső sarkának a koordinátái: X,Y. Ezeket az object getX() és a getY() függvénnyel kapom meg. Ezeknél az egér X és Y koordinátáinak nagyobbnak kell lenni. (Az svgalibben a (0,0) koordináta a képernyő bal felső sarka.) Értelemszerűen a Button jobb alsó koordinátáinál (X2 és Y2 ezeket a getX2()-vel és a getY2()-vel kapom meg) kisebbnek kell lennie az egér koordinátáinak, ha az felette van.

Ha i!=0 igaz akkor rajzoljuk ki az előző Buttont (ügye az elsőnél nincsen "előző"), ha pedig az utolsó gomb felett van az egér, nincsen "következő". Az utolsó kettő feltétel dönti el, hogy az adott gomb a felső vagy az alsó sorban van, és ennek megfelelően a fölötte vagy az alatta lévőt frissíti.

A gombokat a DrawButton függvény segítségével rajzolom ki. Amúgy ez is egy külön történet. Először nem vezettem be a DrawButton és ButtonPress függvényeket és mindig kiírtam az egész kódot. Így az egész forrás teljesen átláthatatlanná vált, legfőképpen az utasítás blokkok miatt, mivel nem tudtam melyik hova tartozik és mit hova kell írni (szerk megj: modularizálunk, modularizálnunk? J Nagyon helyes...). Kb 5-ször kezdtem újra íni a forrást egy régebbi verziótól és utána vezettem be ezeket, mert már annyira ideges voltam.

A ButtonPress arra való, ha kattintottunk az egyik gombon, akkor tényleg azt a hatást keltse, mintha benyomódna. Ezt a hatást egyszerűen úgy érem el, hogy a gombon nem a felső és a baloldali vonal lesz fehér hanem az alsó és a jobboldali. Szinte mindenhol így van, a KDE-ben is (azért ott szebben), nézzék meg. Akkor most nézzük a kódot:

if (mouse_getbutton())

      for (i=0; i < 16; i++)

            if ((objects[i]->getX() < X)

                  && (objects[i]->getX2() > X)

                  && (objects[i]->getY() < Y)

                  && (objects[i]->getY2() > Y))

            {

                  ButtonPress(

                        objects[i]->getX(), objects[i]->getY(),                                 objects[i]->getX2(), objects[i]->getY2(),

                        objects[i]->getTextX(), objects[i]->getTextY(),

                        objects[i]->getColor(), objects[i]->getText()

                  );

                  Color = i;

                  break;

            }

Ez szerintem az előzőekhez képest nagyon egyszerű, viszont egy dolog még hátravan. A Color = i; sor. Pont úgy van a sorrend, hogy a gombok indexe egyenlő az adott színnel. Az első gomb fekete és a sorszáma 0 stb. Ezzel állítom be a rajzoláshoz a színt. Ezzel el is érkeztünk a tényleges munkához. Ha az egér a négyzet fölé megy, a négyzetet kirajzolom az eredeti színekkel. Ha a négyzeten belül kattintok, akkor az adott négyzetecske színe a Color változó értéke lesz, amit előzőleg beállítottunk. Ezután, ha az egeret megmozgatjuk, már láthatjuk is az új szint, mivel, csak a mouse_update()-nél frissíti a négyzetet.

if (mouse_getbutton())

{

      for (i=0; i < 16; i++)

            if ...;

            for (i=0; i<16; i++)

            for (i2=0; i2<12; i2++)

            if (

             (280+i*5 < X) &&

             (288+i2*5 < Y) &&

             (280+(i+1)*5 > X) &&

             (288+(i2+1)*5 > Y)

            )

                  Parts[i*12+i2].Color = Color;

}

Billentyűnyomásra megjelenik egy új ablak, egy elmés felirattal. Itt lehet elmenteni művészi rajzunkat, 3 helyre. A wall[], floor[] és a player[] ugyanolyanok, mint a Parts[]. Amikor az egyik gombra rákattintottunk, a fájlból kiveszem a régi rajzokat. Ezekből 2 ugyanaz marad, de az egyiket ki fogjuk cserélni.

file.open("objects", ios::in | ios::out ); //megnyitom a fájlt

POO wall[192], floor[192], player[192];

int i3 = i;

//kiveszem a fajból a rajzokat:

for (i=0; i<16; i++)

      for (i2=0; i2<12; i2++)

            file >> wall[i*12+i2].Color;

for (i=0; i<16; i++)

      for (i2=0; i2<12; i2++)

            file >> floor[i*12+i2].Color;

for (i=0; i<16; i++)

      for (i2=0; i2<12; i2++)

            file >> player[i*12+i2].Color;

file.close();

file.open("objects", ios::out | ios::trunc);

Itt a végén bezárom, és újra megnyitom a fájlt. Kérdezhetik az Olvasók, hogy miért? Figyelmesek észrevehették, hogy most másodszorra a paraméter nem ios::in | ios::out volt hanem ios::in | ios::trunc. Ez azt jelenti, hogy az egész fájlt töröljük és egy újat hozunk létre. Mivel az előbb már kiolvastuk a fájl tartalmát, így ez már nem gond. Csak ugyanabban a sorrendben kell visszarakni, és még azt is amit mi rajzoltunk. Nézzük a kódot.

switch (i3) {

      case 0:{

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << Parts[i*12+i2].Color << '\n';

//  ha az első Buttonra kattintunk, akkor a rajzot menti el előszőr

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << floor[i*12+i2].Color << '\n';

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << player[i*12+i2].Color << '\n';

            break;}

 

      case 1:{

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << wall[i*12+i2].Color << '\n';

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << Parts[i*12+i2].Color << '\n';

//a másodiknál a másodikként menti el

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << player[i*12+i2].Color << '\n';

            break;}

 

      case 2:{

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << wall[i*12+i2].Color << '\n';

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << floor[i*12+i2].Color << '\n';

 

            for (i=0; i<16; i++)

                  for (i2=0; i2<12; i2++)

                        file << Parts[i*12+i2].Color << '\n';

//itt meg a player lesz a mű:)

            break;}

            }

      return ;

Ha jól sejtem itt a vége a Making függvénynek. A rajzunk kész és el is mentettük, úgyhogy vége a munkának. Az Open() ezek után már gyerekjáték, hiszen ugyanezeket a módszereket használom. Megjelenítek 3 rajzot, és fölöttük 3 Buttont, aztán az egérrel választok. A számot, hogy melyik rajzot akarjuk betölteni, pedig visszaadom a függvény visszatérési értékeként.

A kódot GPL alatt adtam ki. A GPL szerződést, ha minden jól megy, a programmal együtt megkapják az olvasók.

A hozzá tartozó játék már készül(get). A honlapomon megnézhetik a legfrissebb információt.

Varga Viktor