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++) Foxi 2D-s programozás leckéi 3. - Sprite 2007.03.31 12:24


Sprite I.

Elérkeztünk a sprite-osztályhoz. Azonban van még egy adósságom a RAJZ osztályból:
Az egérkurzort eltüntetheted a Main cpp-ben a rajz.init() függvény elé kell beírni:

    ShowWindow(hwnd,nFunsterStil);
    ShowCursor(false);    //egér eltüntetése
    if(!rajz.init())MessageBox(NULL,"directdraw nem oké","",0);

A másik, hogy amikor leteszed az alkalmazást kis méretbe, akkor elveszik a memória felülete, és amikor újból előjön az ablak, akkor nincs kép, csak villódzás..
Orvoslása a flip() függvényben:
if(inf!=DD_OK ){obj->RestoreAllSurfaces();UpdateWindow( hwnd);load_init();};
azaz ha nem sikerült a flip, meghívjuk a restore() függvényt, helyreállítja a felületeket, de a bmp képadatok elvesztek, és ezért betöltjük újból mindet.

Mivel egy sprite- nak nagyon sokféle adata lehet, ezért, hogy ne legyen káosz, ezeket az adatokat összefogjuk egy struktúrába, és ebből a struktúrából létrehozunk egy tömböt, hogy az összes sprite adatát egységesen lehessen kezelni. Mik is ezek az adatok????
Van szélessége, magassága, x helyzete és y helyzete a képernyőn, van sebessége x és y irányban,
van egy kép valahol amit ki kéne rajzolni, ha animált, akkor annak hány fázisa van, és mennyi az összes fázis? (pl egy forgó kerék hány képkockából áll és melyik az aktuális) az animáció sebessége.
Szabadon megválaszthatod, mire van szükséged, de ha mindet feltünteted, akkor sincs baj, legfeljebb nem használod fel. A mai 512, 1024 Mega RAM- os gépeknél ez a párszor 2 byte nem oszt nem szoroz..
Tehát hozz létre a projectben egy új fájl-t Sprite.hpp néven. És hozd létre a SPRITE osztályt:

#ifndef _SPRITE_
#define _SPRITE_

//dekralációk:
class SPRITE
{
    public:
        SPRITE ();
        ~SPRITE();
    protected:
};

//megvalósítások:


#endif

Ha eddig megvagy, akkor készítsd el a struktúrát is: a dekralációk fölé
/**/
typedef struct
{
    int SP_X; //sprite x helyzete a képernyőn
    int SP_Y; // y helyzet
    int SP_SZ; // szélesség pixelekben
    int SP_M; // magasság pixelekben
    char SP_AN; // aktuális képpozíció
    char SP_ANMAX; // maximum ennyi képpozíció van
    char SP_X_IRANY; //
    char SP_Y_IRANY;
    int SP_KEPE; // melyik betöltött képen van a sprite rajza
    int SP_KEP_X; // a képen az x helyzet
    int SP_KEP_Y; // a képen az y helyzet
    int SP_FX; // forrópont x
    int SP_FY; // forrópont y
}SP, *LPSP;
/**/

Tehát létrejött egy SP típus és egy LPSP típusú mutató. Tehát ha valahol azt írod, hogy LPSP mut, akkor a mut egy üres mutató, ami "lefoglal" egy sprite -nyi területet, de mivel nincs még értéke sehova sem mutat.
A protected: //védett tagváltozók alá elhelyezzük a tömbünket:
SP sp[25]; //25 sprite számára hely

Lehet akár 500 db is....
Kellene a sprite-okat inicializálni értékkel feltölteni, ezért dekraláljuk az init() függvényt! Ezt esetleg más osztályokból fogjuk hívni, ezért public- okhoz tesszük:
void sp_init();

A megvalósítása úgy lesz, hogy a pozíciói és az iránya sebessége szinte minden véletlen szerűen lesz inicialzálva, mert ez nem egy játék, csak oktató progi. Csillag lesz, ami animált.
Az egyes dolgokhoz megírjuk külön külön a függvényeket.
Megvalósítás:

void SPRITE::sp_init()
{
    srand(GetTickCount()); //véletlenszám generátor inicializálása a rendszerszámlálóval
    for (int i=1;i<25;i++)
    {
        LPSP mut=&sp[i];//csak azért hoztam létre a mutatót, mert nehezebb állandóan sp[i] -t irni.
        mut->SP_X=rand()%752;
        mut->SP_Y=rand()%552; //x és y helyzet a képernyőn
        mut->SP_SZ=mut->SP_M=48;//szélesség magasság 48 pixel
        mut->SP_AN=rand()%10; //hogy ne egyszerre villogjanak
        mut->SP_ANMAX=10; //ennyi kép van
        mut->SP_X_IRANY=rand()%4-2; //xértéke -2 és 2 között lesz
        if(!mut->SP_X_IRANY)mut->SP_X_IRANY=1;//ha nullaa random,akkorlegyen 1
        mut->SP_Y_IRANY=rand()%4-2;
        if(!mut->SP_Y_IRANY)mut->SP_Y_IRANY=1;
        mut->SP_KEP_X=197; //a bmp képen a sprite x képe
        mut->SP_KEP_Y=mut->SP_AN*49;// megnézheted paint-ban mutatja a koordínátát alul
    }
}

Ha többféle tulajdonsággal kellene az init, akkor a for(   ) ciklust többször és kisebb db számmal kell meghívni. Eldöntheted, rakhatod a konstruktorba is. Vagy másfélét kell írni, lehet több pálya.stb.
Ez most nem feladat.
A sprite-okat ki is kell rajzolni a képernyőre! dekraláció:
void sp_rajz();

Azonban kellene a RAJZ osztály rajzol függvénye.... Nem egy új new operátorral, hanem amit a main cpp-ben jött létre: extern RAJZ rajz;
A rajzolás megvalósítása:
void SPRITE::sp_rajz()//függetlenüla tulajdonságaiktól és méretüktől mindet kirajzoljuk
{
    for(int i=0;i<25;i++)
    {
        LPSP mut=&sp[i];//átvesszük az aktuális sprite mutatóját
        rajz.rajzol(mut->SP_KEPE,mut->SP_KEP_X,mut->SP_KEP_Y,mut->SP_SZ,
        mut->SP_M,-1,mut->SP_X, mut->SP_Y);//a sprite strukrúra minden adatot tartalmaz
    }
}

Most már ki kéne tudni rajzolni a sprite- okat, azonban létre kell hozni még egy függvényt, mert ebben az osztályban több függvényt is meg kell írni, és egyszerűbb egy frissít nevűt meghívni "kívülről" majd a frissít() szépen sorba meghívja az osztályon belül az összes többit. tehát dekr.:
void frissit();

Megvalósítása:

void SPRITE::frissit()
{
    rajz.rajzol(0,0,0,800,600,-1,0,0)//háttér
    sp_rajz();
    rajz.flip();
}

Látható most már innen vezéreljük a kirajzolást,tehát a RAJZ osztály flip() függvényét soványítani kell:
void RAJZ::flip()
{
    BOOL inf=front->Flip(NULL,0);
    if(inf!=DD_OK /*&& aktiv*/){obj->RestoreAllSurfaces();UpdateWindow( hwnd);load_init();};
}


Megyünk a Main Cpp-be!

Itt a lap tetején csatoljuk a Sprite forráskódot:
#include <Sprite.hpp>
A rajz osztály mellé tesszük a sprite osztályt is:
SPRITE sprite;

Az maradt csak hátra, hogy kicsit módosítsuk a végtelen ciklus környékét:

    if(!rajz.init())MessageBox(NULL,"directdraw nem oké","",0);
    sprite.sp_init();// nem lehet a rajzinit() előtt mert a felületeket előbb inicializálni kell!
    while (fut)
    {
        uzenetkezelo();
        sprite.frissit();// a sprite osztály átvette a rajzoló rutinokat
    }
    return 0;

Fordít--- futtat. A sprite-oknak meg kell jelenniük a képernyőn, véletlenszerű pozícióval, és véletlen animációs képpel.

Vissza Sprite hpp-be!
Megcsinálom még az animációját is. dekr.:
void anim();
Mivel ez az osztály belügye tedd a protected közé! Megvalósítás:
void SPRITE::anim()
{
    static char szamlalo=5;//minden 5 képkirajzolásakor léptetünk
    szamlalo--;
    if(szamlalo)return;
    szamlalo=5;
    for(int i=0;i<25;i++)
    {
        LPSP mut=&sp[i];
        mut->SP_AN++; //+1
        if(mut->SP_AN==mut->SP_ANMAX)mut->SP_AN=0;//ha elérte a maxot akkor 0
        mut->SP_KEP_Y=mut->SP_AN*49;//jelen esetben a bmp rajzon egymás alatt vannak
        //tehát y koordináta egy rajzzal lejjebb ugrik
    }
}

Most már csak használni kell, a SPRITE::frissit() függvényébe kell beírni:

void SPRITE::frissit()
{
    rajz.rajzol(0,0,0,800,600,-1,0,0);//háttér
    sp_rajz();
    rajz.flip();
    anim();
}

fordít- futtat. Ha nem gépeltünk el ketten semmit, akkor mennie kell!
Ha nem, itt a forrás:
Következik a mozgatás függvény és az ütközés függvény.


Sprite II.

Most az ütközésekről, és a mozgatásról lesz szó!
Mivel a progi folyamatosan rajzolja ki a sprite- okat, ezért csak annyi a dolgunk, hogy változtatjuk az x és y koordinátáikat. Most, hogy csak egyféle van egy ciklussal megoldjuk, de csak 24 -et rajzolunk.
A 0. lesz a billentyűvel mozgatott bábunk. A csillagok addig mozognak a képernyőn, amíg el nem érik a szélét, és akkor irányt vált. Azaz a SP_X_IRANY előjelet vált ha x irányban elérte a szélt. Az y szintén.
Fontos a jelen progi a sprite-okat a tömbben elfoglalt helyük szerinti sorrendben rajzolja ki, tehát a 0. sprite- ot mindegyik letakarja, és a 24.-et egyik sem. Ennek akkor van jelentősége, ha egymást átfedik, és fontos lenne, hogy melyik legyen felül ....
Tehát dekraláció:
void sp_mozgat();
A megvalósítása:
/**/
void SPRITE::sp_mozgat()
{
    for(int i=1;i<25;i++) //csak 24 db-ot mozgatunk az első a mi bábunk lesz a billentyűzet
    { // irányítja később
        LPSP mut=&sp[i]; // átadjuk az aktuálisat nem kéne,de könnyebb gépelni...
        mut->SP_X+=mut->SP_X_IRANY; //x helyzet + sebesség= uj helyzet
        if((mut->SP_X < 0) |( mut->SP_X > 752))mut->SP_X_IRANY= - mut->SP_X_IRANY;
        // ha elérte a bal oldalt vagy a jobb oldalt, akkor a sebesség
        //átvált ellenkező előjelűvé
        mut->SP_Y+=mut->SP_Y_IRANY; //ugyanaz csak y-ra
        if((mut->SP_Y <= 0) | (mut->SP_Y > 552))mut->SP_Y_IRANY= - mut->SP_Y_IRANY;
    }
}

Használata:
A frissít() függvényben :
sp_mozgat();

Fordít- futtat....
Elég látványos,de nem kellett sokat gépelgetni. Könnyen lehet többféle függvényt írni a többféle sprite mozgatásához, de az legyen a ti gondotok, ez alapján már könnyű!

Ütközések:

Mondhatni hála a struktúrának, csak összekell hasonlítani az x és y koordinátákat, Ha egy intervallumon belül van, akkor ütközött! Azonban mégsem ilyen egyszerű, mert az egyik lehet 18pixel, a másik meg 48 (a mi példánkban) és lehet teljesen más méret is. Azért, hogy a program meg tudja állípítani az ütközést, inicializálni kell a sprite adatai között a forrópontokat. Most a forrópont a golyónál 9 pixel és a csillagnál 24 pixel x és y irányban is. A sprite x és y helyzetéhet képest ennyivel van eltolva a forrópontjuk (közép)
Az sp_init() ben:
    mut->SP_FX=mut->SP_FY=24; //forrópontok a sprite-nak.

Az ütközéshez kell mostmár a golyó, tehát a 0. sprite-ot is inicializáljuk:
    mut->SP_FX=mut->SP_FY=24; //forrópontok a sprite-nak
}
sp[0].SP_X=391; //0.sprite init
sp[0].SP_Y=291;
sp[0].SP_AN=0;
sp[0].SP_ANMAX=1;
sp[0].SP_KEP_X=sp[0].SP_KEP_Y=0;//balfelső sarok piros golyó
sp[0].SP_SZ=sp[0].SP_M=18;
sp[0].SP_KEPE=1; //az atom bmp ről vesszük

Az üközésvizsgálathoz pedig segítségünkre van az abs(...) függvény, mert igy a relatív távolságot kapjuk, és nem az abszolútat. A függvényt úgy írom meg, hogy két akármelyiket összehasonlítsa, vagy mindet! ezt úgy érhető el,hogy a függvényt már dekralációkor feltöltjük értékkel,és ha bemenő adat nélkül "üresen" hívjuk meg, akkor mindet végignézi,amúgy csak 2- őt vizsgál. Ha nem adunk meg szórást,akkor a két forrópontnak 5 pixelen belül kell lennie.
dekraláció:
bool sp_utk( int melyik,int melyikkel=-1,int szoras=5);

Megvalósítás:

bool SPRITE::sp_utk(int melyik,int melyikkel,int szoras)
{
    LPSP M=&sp[melyik];LPSP M1=&sp[melyikkel];//átvesszük a két sprite mutatóját
    if(melyikkel ==-1)
    {
        for(int i=0;i<25;i++)
        {
        M1=&sp[i];
        if(melyik==i)continue;//saját magával nem vizsgáljuk
        if( (abs(M->SP_X+M->SP_FX-(M1->SP_X+M1->SP_FX))< szoras) & //ha az x helyzetek különbsége kisebb mint 5
        (abs(M->SP_Y+M->SP_FY-(M1->SP_Y+M1->SP_FY))< szoras)) // és az y is kisebb,
            return true;
        }
        return false;
    }
    if( (abs(M->SP_X+M->SP_FX-(M1->SP_X+M1->SP_FX))<szoras) & //ha az x helyzetek különbsége kisebb mint szoras
    (abs(M->SP_Y+M->SP_FY-(M1->SP_Y+M1->SP_FY))<szoras)) // és az y is kisebb,
        return true;
    return false;
}

A használata a frissít függvényben:

void SPRITE::frissit()
{
    rajz.rajzol(0,0,0,800,600,-1,0,0);//háttér
    sp_rajz();
    if(sp_utk(0))rajz.kiirbetu("ÜTKÖZÉS TÖRTÉNT",10,10);//golyót vizsgáljuk az összessel
    rajz.flip();
    anim();
    sp_mozgat();
}

Látható nem kell második, és harmadik paraméter, ekkor mindet végignézi, és akkor jelez, ha a két forrópont 5 pixelen belül van. Más értékkel 20 pixelhez így kell meghívni:

sp_utk(0,-1,20);

fordít futtat......Futás közben némi adatot ki tudunk íratni mindig, csak lényeg, hogy a flip() függvény előtt tegyük pl. írassuk ki a 10. sprite x és y adatait:

void SPRITE::frissit()
{
    rajz.rajzol(0,0,0,800,600,-1,0,0);//háttér
    sp_rajz();
    if(sp_utk(0))rajz.kiirbetu("ÜTKÖZÉS TÖRTÉNT",10,10);//golyót vizsgáljuk
    rajz.kiirbetu("10.sprite x",10,30);rajz.kiirszam(sp[9].SP_X,100,30);
    rajz.kiirbetu("10.sprite y",10,50);rajz.kiirszam(sp[9].SP_X,100,50);

    rajz.flip();
    anim();
    sp_mozgat();
}

Az elmaradhatatlan kód:


foxi


Kapcsolódó linkek:

A cikksorozat további részei:

Értékelés: 1.00

Új hozzászólás
Asylum          2007.09.13 02:39
nem kötekedni akarok de hpp kiterjesztést nem templatekhez használunk?
Csaba42          2007.07.12 04:29
KergeDelfin: nem lehet elvárni egy tutortól, hogy minden apróbb részletet lefedjen. Ha megadja az alapokat, akkor már elérte a cikkíró vele a célját.

Nekem amúgy egy bajom van, hogy nincs kép a végeredményről (ha meg szeretném nézni, pontosan mi is fog történni, el kell indítanom a progit, rosszabb esetben újra kell fordítani)!
KergeDelfin          2007.04.05 02:58
Nagyon jó a cikk, két apró észrevételem lenne csupán:
1. Ha C++ -t használsz, szerintem használd ki a képességeit. Pl. egyik fontos OOP alapelv: egységbezárás. Az 'sp'-ből adattagot lehetne csinálni a SPRITE-ban. Vagy még OOPsebb lenne hogyha a SPRITE nem egyfajta sprite managert jelölne hanem a sprite-okat, ezzel a módszerrel megszabadulhatnál a struktúrától és sokkal logikusabb, áttekinthetőbb lenne az egész. Emellett persze lehet sprite-manager is, annál olvashatóbb és letisztultabb lesz a kód. Szerintem.
2. Megint csak C++-al kapcsolatos. SP sp[25] Ez nem túl elegáns megoldás és meg is köti a programozó kezét. Dinamikus adatszerkezettel sokkal célszerűbb lenne szerintem.
Persze, tudom, példaprogram meg minden, be lehet tudni kötözködésnek is, nem annak szántam.
Ja igen, még valami: Ütközésvizsgálat, szerintem még annyival kiegészíthetnéd hogy hogy lehet megvalósítani a pontos ütközésvizsgálatot (tehát ahol a két sprite valójában fedi egymást, nem csak a befoglaló téglalapjuk).
HCodename47          2007.04.02 09:06
jaj de jó, ezt kerestem hetekig
Egyszer megtaláltam googleve, felírtam a címet (honlapod) , de nem jött be többet.
Végreeeeeeee!