játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5448
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2520

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(C++) Végtelenített háttér SDL rendszerben 2006.08.19 12:00


Végtelenített háttér SDL rendszerben

 Ez a cikk egy lehetséges megoldást mutat be, amivel platform játékokhoz mozgó hátteret lehet rajzolni. A végtelenített háttér lényege, hogy a háttérben folyamatosan mozgó grafika legyen, ami haladáskor lassan elmozdul. Több réteg felhasználásával nagyon egyszerű mélységélményt lehet nyerni: a legtávolabbi réteg (például felhők) lassabban mozdulnak, mint a közeli semleges háttér (fák, házak).

Példa a rétegekre:


égbolt


hegyek


fák

 Ebben a példában csak oldalra mozognak a hátterek, de különösebb nehézség nélkül ez kiegészíthető függőleges irányba is. Hasonló megszorítás, hogy ez a példa 16 bites grafikát használ, ami a vonatkozó konstansok módosításával szintén könnyen átírható 32 bitesre.

 Először is tekintsük át az SDL környezetet. Az SDL igazi előnye az, hogy ügyesen ötvözi a rugalmasságot az alacsony szintű programozással. Ez elsősorban hatékonyságot jelent, másodsorban könnyű megtanulhatóságot, és átlátható, egyszerű kódot.

Az SDL inicializálásához lerójuk a szükséges köröket:
    // initialize SDL video
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
        printf( "Unable to init SDL: %s
", SDL_GetError() );
        return 1;
    }

    // make sure SDL cleans up before exit
    atexit(SDL_Quit);

    // create a new window
    SDL_Surface* screen = SDL_SetVideoMode(640, 480, 16,SDL_HWSURFACE|SDL_DOUBLEBUF);
    if ( !screen ) {
        printf("Unable to set 640x480 video: %s
", SDL_GetError());
        return 1;
    }

Minták (Surface)

 Ezek a kötelező lépések adnak nekünk egy 640x480 méretű ablakot, amire igen könnyen rajzolhatunk. Ebben a példában a rajzolás kizárólag minták másolását fogja jelenteni. Egy mintát az SDL-ben úgy hívnak, hogy SDL_Surface. Amint az a fenti kódrészletben látható, maga a képernyő tartalom is egy minta. Erre a mintára fogjuk felrajzolni a többi mintát.

 Mintára mintát rajzolni a következoképpen kell :

SDL_BlitSurface(mit, honnan-ablak , hová, hová-ablak);

 Ez a függvény a "mit" mintának a "honnan-ablak"-ba eső részét rámásolja a "hova" mintára, már ami a "hová-ablak"-ba belefér. Ezzel elérhető, hogy a kilógó részek ne okozzanak problémát. Ha az ablakok helyett nullmutatót adunk át paraméternek, akkor az alapértelmezett, teljes ablakot érti alatta a BlitSurface, vagyis a teljes "mit" rákerül a teljes "hová"-ra, úgy, hogy a bal felső sarkok egybeessenek.

 A scrollozás effekthez a "honnan-ablak"-kal fogunk játszani. Ennek a fo oka az, hogy így elkerülhető a negatív koordináták használata.

 A következő szerkezetet választottam: A háttér több rétegből áll, ezeket a rétegeket leírom egy osztállyal, és a rétegeket összefogandó, egy háttér osztályt is létrehozok, ami egyben kezeli a rétegeket. Nézzük, mit várunk el egy rétegtol:

- BackgroundLayer(string filename, const SDL_Surface * screen, SDL_Rect dst, double speed, Uint32 amask=0, Uint32 ckey=NoCKey)

 Kell egy konstruktor. Adunk egy fájlnevet (jelen esetben egy .bmp fájl neve, ez az SDL_image csomag használatával könnyen kiterjeszthető), megadjuk a célmintát, ami a méretek miatt fontos, megadjuk a "hová-ablak"-ot, a sebességet, az átlátszóságot (ambiens hátterekhez) és az átlátszónak ítélt színt.

- ~BackgroundLayer()

Kell egy destruktor is, mert illedelmesek vagyunk, és takarítunk magunk után.

- void move(double m)

Egy mozgatás rutin: amikor mozog a főszereplő valamelyik irányba, akkor a háttérnek is mozdulnia kell.

- void draw(SDL_Surface* screen)

végül a kirajzolás. Ez lényegében egy BlitSurface hívás lesz.


Nézzük meg részletesebben a mezőket és a metódusokat:

int _periodsize;: milyen széles a kép, ilyen hosszú egy periódus az ismétlésben
double _pos;: hol vagyunk most éppen. A kényelmes használat érdekében egy nem egész
SDL_Surface *_bgr;: maga a kép, csak néhányszor egymás mellé festve
SDL_Rect *_srcrect;: a "honnan-ablak"
SDL_Rect *_dstrect;: a "hová-ablak"
double _speed;: a sebesség. Az egyes rétegek eltéro sebességgel mozognak


A metódusok:

    BackgroundLayer(string filename, const SDL_Surface * screen, SDL_Rect dst,
                            double speed, Uint32 amask=0, Uint32 ckey=NoCKey) {
// a sebesség feljegyzése:
        _speed=speed;
// A hová-ablak feljegyzése (copy konstruktor hívással)
        _dstrect = new SDL_Rect(dst);
// betöltjük a .bmp fájlt. A fájlnév string
        SDL_Surface* bmp = SDL_LoadBMP(filename.c_str());
        if (!bmp) {
            printf("Unable to load bitmap: %s
", SDL_GetError());
            exit(1);
        }
// A scrollozás legfontosabb konstansa a kép szélessége. Ez a kép szélessége
        _periodsize = bmp->w;
/* Hány periódusnyit kell ebből egymás mellé rajzolni? Minimum két példány kell,
 még akkor is, ha a kép szélesebb, mint a képernyő, az ismétlés miatt. */

        int periodcount = (screen->w / bmp->w) +2;
//  A színkonstansok 16 bites módhoz:
//      1111100000000000, 0000011111100000, 0000000000011111 maszkok

        Uint32 rmask=63488,gmask=2016,bmask=31;

/* létrehozunk egy nagy képet, amin egymás mellett többször szerepel a kép. Itt
 lehetne némi memóriát spórolni, mert elég lenne a rövidebb képet is csinálni,
 de a gyártás egyszerűsége miatt ezt a megoldást választottam: annyiszor egymás
 mellett a teljes kép, ahányszor szükség van rá. Spórolni az utolsó képpel lehet,
 gondold végig majd, hogy miért.*/

        _bgr = SDL_CreateRGBSurface(SDL_SWSURFACE, periodcount*_periodsize,
                                        bmp->h, 16, rmask, gmask, bmask, amask);
        SDL_Rect rect = {0, 0};
        for (int i=0;i<periodcount;i++) {
            rect.x=i*_periodsize;
            SDL_BlitSurface(bmp,0, _bgr, &rect);
        }


// A "honnan-ablak" és a pozíció beállítása kezdeti értékekre:
        _srcrect = new SDL_Rect;
        _srcrect->x=0;
        _pos=0.0;
        _srcrect->y=0;
        _srcrect->w=screen->w;
        _srcrect->h=bmp->h;
// a .bmp fájl memóriafoglalására már nincs szükség:
        SDL_FreeSurface(bmp);

// az átlátszónak ítélt szín megadása:
        if (ckey!=NoCKey)SDL_SetColorKey(_bgr, SDL_SRCCOLORKEY, ckey);
    }


A mozgatás:

    void move(double m) {
// odébbtesszük a pozíciót
        _pos+=m*_speed;
/*
  Ha már lehet ugrani, akkor ugrunk. Ez a végtelenítés trükkje, amikor egy
 periódust megtettünk, akkor a "honnan-ablak" ugrik egy periódusnyit. Az
 eredményen az ugrás nem látszik, mert a háttérképen egymás mellett többször
 is szerepel ugyanaz a kép.

  Ezzel elkerüljük a negatív koordinátákat, mert a "honnan-ablak" mindig
 nemnegatív értéket vesz fel, pontosabban eleme a [0,_periodsize] intervallumnak.
*/

        if (_pos > _periodsize) _pos-=_periodsize;
        if (_pos<0) _pos+=_periodsize;
// egészre konvertálás:
        _srcrect->x=int(_pos);
    }


//A teljes háttér összefogása így elég rövid lehet:


class Background {
public:
    Background() {}
    void addLayer(BackgroundLayer *bg) {
        _bg.push_back(bg);
    }
    void move(double m) {
        for (list<BackgroundLayer*>::iterator it=_bg.begin();it!=_bg.end();it++)
            (*it)->move(m);
    }
    void draw(SDL_Surface* screen) {
        for (list<BackgroundLayer*>::iterator it=_bg.begin();it!=_bg.end();it++)
            (*it)->draw(screen);
    }
protected:
    list<BackgroundLayer *> _bg;
};

/*
  Vagyis az addLayer metódussal új réteget adhatunk a meglevoekhez.
 A hozzáadási sorrend nagyon fontos, ugyanis ilyen sorrendben fogják
 kitakarni mindig az elozoeket. A mozgatás és a kirajzolás pedig sima
 ügy az STL lista konténerrel.
*/


A felhasználás:

    Background bgr;
    SDL_Rect dstrect  = {0,100,screen->w, screen->h};
    SDL_Rect dstrecth = {0,130,screen->w, screen->h};
    SDL_Rect dstrect2 = {0,150,screen->w, screen->h};
    BackgroundLayer *bgr1 = new BackgroundLayer("eg.bmp", screen, dstrect,1.0,0);
    BackgroundLayer *bgrh = new BackgroundLayer("hegyek.bmp", screen, dstrecth,1.5,0,31);
    BackgroundLayer *bgr2 = new BackgroundLayer("fak.bmp", screen, dstrect2,2,0,31);
    bgr.addLayer(bgr1);
    bgr.addLayer(bgrh);
    bgr.addLayer(bgr2);

/* Vagyis a háttérhez legyártjuk a rétegeket, és beillesztjük. Ebben a példában
három réteg van.
*/

    while ('az SDL fő ciklusa, benne minden, ami kell, most nem részletezzük'){

/* Ha nyíl gombot nyomnak, mozgatjuk a hátteret:
*/

        if (SDL_GetKeyState(0)[SDLK_RIGHT]) {
            bgr.move(3);
        }
        if (SDL_GetKeyState(0)[SDLK_LEFT]) {
            bgr.move(-3);
        }
// képernyőtörlés: az átlátszó részek miatt szükséges!
        SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 0));
// háttérrajzolás
        bgr.draw(screen);

// Ide jöhet a játék több eleme, minden egyéb mozgóalkatrész kirajzolása
        SDL_Flip(screen);
        SDL_Delay(15);
    } // end main loop
    return 0;
}


Itt a vége, remélem érthető volt. Ezúton is ajánlom minden érdeklődőnek az SDL környezetet. A példaprogramot Code::Blocks környezettel készítettem.

flugi

Letölthető példaprogram forráskóddal

(gfx by HomeGnome)

Értékelés: 8.19

Új hozzászólás
bloodknife          2006.08.25 09:50
Szép.Jó a leírása.grat
gopher          2006.08.23 07:02
Hopsz, nyolcast akartam... Na mindegy, attól függetlenül nagyon jó cikk
flugi          2006.08.19 18:26
A forráskód véletlenül kimaradt a csomagból, itt letölthető:

Végleges forráskód: scrollozó háttér, és egyszerű animált figura
flugi          2006.08.19 18:22
Mindenekelőtt köszönet illeti HomeGnomet a nagyszerű grafikáért