Olen tässä viimepäivinä alkanut koodaamaan "2.5"-ulotteista peliä (eli 3-ulotteista ilman perspektiiviä, esim. SimCity- ja Sims- sarja sekä monia muita), ja nyt on tullut vakava ongelma - peli lagaa heti kun vain lisää muutamankin objektin. Koodi:
#include "sdl/sdl.h" #include <string> #include <math.h> int getR(int col); int getG(int col); int getB(int col); const int SCREEN_WIDTH = 800; const int SCREEN_HEIGHT = 600; const int SCREEN_BPP = 32; SDL_Surface *screen = NULL; SDL_Surface *gui1=NULL; SDL_Surface *gui2=NULL; SDL_Surface *ground1 = NULL; SDL_Surface *wall1a = NULL; SDL_Surface *wall1b = NULL; SDL_Surface *ground2 = NULL; SDL_Surface *ground3 = NULL; SDL_Surface *ground4 = NULL; SDL_Surface *ground5 = NULL; SDL_Surface *ground6 = NULL; SDL_Surface *ground7 = NULL; SDL_Surface *ground8 = NULL; int jutttu; void CLS(){ int ul; for(ul=0;ul<800*600;ul++) { ((unsigned int*)screen->pixels)[ul]=0x000000; } } void apply_surface(int x,int y,SDL_Surface* source,SDL_Surface* destination) { SDL_Rect offset; offset.x=x; offset.y=y; SDL_BlitSurface (source,NULL,destination,&offset); } void apply_clip(int x,int y,SDL_Surface* source,SDL_Surface* destination,SDL_Rect* clip=NULL) { SDL_Rect offset; offset.x=x; offset.y=y; SDL_BlitSurface (source,clip,destination,&offset); } SDL_Surface *load_image( std::string filename ) { SDL_Surface* loadedImage = NULL; SDL_Surface* optimizedImage = NULL; loadedImage = SDL_LoadBMP( filename.c_str() ); if( loadedImage != NULL ) { optimizedImage = SDL_DisplayFormat( loadedImage ); SDL_FreeSurface( loadedImage ); } return optimizedImage; }
Sitten pätkä, jossa epäilen ongelman olevan:
void apply_withAlpha(int x,int y,SDL_Surface* source,SDL_Surface* destination) { SDL_Surface *temp = NULL; temp=load_image("reuna.bmp"); SDL_Rect offset; offset.x=x; offset.y=y; int a; for (a=0;a<800*600;a++) { ((unsigned int*)temp->pixels)[a]=0xff00ff; } SDL_BlitSurface (source,NULL,temp,&offset); a=0; for (a=0;a<800*600;a++) { if (((unsigned int*)temp->pixels)[a]!=0xff00ff){ ((unsigned int*)destination->pixels)[a]=((unsigned int*)temp->pixels)[a]; } } ((unsigned int*)destination->pixels)[a]=((unsigned int*)destination->pixels)[a]; SDL_FreeSurface(temp); }
ja loput:
int main(int argc, char* args[]) { if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) { return 1; } screen = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE ); if( screen == NULL ) { return 1; } SDL_WM_SetCaption( "Hello World", NULL ); gui1=load_image("reuna.bmp"); gui2=load_image("reuna2.bmp"); ground1=load_image("emptyslot.bmp"); wall1a=load_image("woodenwallne.bmp"); wall1b=load_image("woodenwallnw.bmp"); if (SDL_Flip(screen)==-1){return 1;} int mouse,mx,my,mr,ml,ms; while(1) { CLS(); SDL_Event event; while (SDL_PollEvent(&event)) { mouse=SDL_GetMouseState(&mx, &my); switch (event.type) { case SDL_KEYDOWN: break; case SDL_KEYUP: // If escape is pressed, return (and thus, quit) if (event.key.keysym.sym == SDLK_ESCAPE) return 0; if (event.key.keysym.sym == SDLK_DOWN) if (event.key.keysym.sym == SDLK_UP) if (event.key.keysym.sym == SDLK_LEFT) jutttu--; if (event.key.keysym.sym == SDLK_RIGHT) jutttu++; break; case SDL_MOUSEBUTTONDOWN: if (event.button.button==SDL_BUTTON_LEFT) if (event.button.button==SDL_BUTTON_RIGHT) if (event.button.button==SDL_BUTTON_WHEELUP) if (event.button.button==SDL_BUTTON_WHEELDOWN) break; case SDL_MOUSEBUTTONUP: if (event.button.button==SDL_BUTTON_LEFT) if (event.button.button==SDL_BUTTON_RIGHT) break; case SDL_QUIT: return(0); } } apply_withAlpha(200+jutttu,200,ground1,screen); apply_withAlpha(240+jutttu,224,ground1,screen); apply_withAlpha(280+jutttu,200,ground1,screen); apply_withAlpha(320+jutttu,224,ground1,screen); apply_withAlpha(200+jutttu,248,ground1,screen); apply_withAlpha(240+jutttu,272,ground1,screen); apply_withAlpha(280+jutttu,248,ground1,screen); apply_withAlpha(320+jutttu,272,ground1,screen); apply_withAlpha(200+jutttu,126,wall1a,screen); apply_withAlpha(240+jutttu,126,wall1b,screen); apply_withAlpha(280+jutttu,126,wall1a,screen); apply_withAlpha(320+jutttu,126,wall1b,screen); apply_withAlpha(360+jutttu,150,wall1b,screen); apply_withAlpha(360+jutttu,174,wall1a,screen); apply_withAlpha(0,0,gui1,screen); apply_withAlpha(0,0,gui2,screen); if (SDL_Flip(screen)==-1){return 1;} } SDL_Quit(); return 0; } int getR(int col) { int val; val=col/(256*256); return val; } int getG(int col) { int val; val=col/256; val=val%256; return val; } int getB(int col) { int val; val=col%256; return val; }
Kuten tyyliini kuuluu, ohjelmaan on jälleen unohtunut kaikkea turhaa eikä ole kommenteilla pilattu :'(
Epäilemäni pätkä siis piirtää koko kuvan paitsi alueet, joissa on väriä 0xff00ff (magenta), jotta turhat kohdat eivät piirtyisi. Se ensin siirtää kuvan toiselle pinnalle poistaen 0xff00ff:n ja siirtää takaisin. Näin monimutkaisesti siksi, koska muuten tarkistin voisi mennä alueen ulkopuolelle ja kaataa koko ohjelman.
Testaamiseen tarvitaan reuna.bmp,reuna2.bmp,emptyslot.bmp,woodenwallne.bmp ja woodenwallnw.bmp. Kannattaa varautua isoon lagiin.
Syy jumalattomaan lagaukseen on todellakin mainitsemassasi funktiossa. Se on raskas (ladataan bittikartta & vapautetaan se, pitkä silmukka laskutoimituksineen yms.)
Kannustaisin sinua miettimään & kehittelemään hieman vähhemän konetehoa syövän ratkaisun -_-
Eikös tuon koko läpinäkyvyyskäsittelyn voi korvata ihan vaan SDL:n omilla ominaisuuksilla, jotka ovat luultavasti huomattavasti nopeampia kuin tuo.
SDL_SetColorKey näyttää lupaavalta, vaikka en olekaan SDL:ää oikein käyttänyt (mitä nyt olen muutaman koodivin lukenut ja vähän manuaaleja selaillut).
Miksi muuten tuossa funktiossa ladattiin joku taustakuva, jonka päälle kuitenkin kirjoitettiin pelkkää magentaa?
Ja eikös näissä SDL:n pikselin manipulaatiossa pitänyt käyttää rivin leveytenä screen->pitch arvoa eikä vain olettaa, että pikseleitä on leveys*korkeus.
Tosiaan tuo SDL_SetColorKey on ratkaisu. Kokeilepa lisätä kuvan lataukseen ennen SDL_DisplayFormat-kutsua laittaa läpinäkyvä väri ja mahdollisesti vähän kiihdykettä. Jos pinnat eivät ole 32-bittisiä, pitää oikea väri valita SDL_MapRGB-funktiolla.
SDL_SetColorKey(loadedImage, SDL_RLEACCEL | SDL_SRCCOLORKEY, 0xff00ff);
Piirto taas tehdään suoraan näin:
SDL_BlitSurface(source, NULL, destination, &offset);
Tuolla nykyisellä koodillasi säädät ihan turhaan temp-pinnan kanssa, voisit tehdä siirron samoilla tarkistuksilla suoraan source-destination-parilla (kun käyttäisit sisäkkäisiä silmukoita etkä vain yhtä isoa).
Et muuten vapauta main-funktiossa lataamiasi kuvia.
ahh,kiitos!
p.s. se koko ruudun täyttäminen magentalla johtu siitä että se karsii sen värin pois piirrettäessä screenille, mutta mustaa ei. Ja muutenkin se lataa 800*600-kokoisen bmp:n jotta sitä saa ruudun kokoisen (joo, on sitä parempiakin ratkaisuja nähty...), joten koko ruutu pitää täyttää jollain värillä ettei ruutu.bmp tule näytölle. screen->pitch-arvosta en paljoa tiedä, koska yritän aina tehdä ohjelmaa vain sillä tiedolla mitä olen sieltä täältä onkinut...ja ellen tiedä jotain tärkeää, säädän tarpeeksi kauan jotta saan sen tehtyä jollain muulla tavalla. Ja tuloksena on aina jotain sivuvaikutuksia :s
Ja kiitos kun kerroit vapauttamattomista kuvista, Metabolix! : )
Hmm, jälkikirjoitus on muuta tekstiä pidempi :D
KoodiNoppa kirjoitti:
se koko ruudun täyttäminen magentalla johtu siitä että se karsii sen värin pois piirrettäessä screenille, mutta mustaa ei.
Olisit voinut karsia sen suoraankin. Suunnilleen näin siis:
Uint32 *src = (Uint32*) kuva->pixels; Uint32 *dest = (Uint32*)((char*)ruutu->pixels + (y * ruutu->pitch)) + x; for (j = 0; j < h; ++j) { for (i = w; i--;) { if (src[i] != VARI) { dest[i] = src[i]; } } // Seuraava rivi src = (Uint32*)((char*)src + kuva->pitch); dest = (Uint32*)((char*)dest + ruutu->pitch); }
KoodiNoppa kirjoitti:
Ja muutenkin se lataa 800*600-kokoisen bmp:n jotta sitä saa ruudun kokoisen (joo, on sitä parempiakin ratkaisuja nähty...), joten koko ruutu pitää täyttää jollain värillä ettei ruutu.bmp tule näytölle.
SDL_CreateRGBSurface auttaa.
KoodiNoppa kirjoitti:
Ja kiitos kun kerroit vapauttamattomista kuvista, Metabolix! : )
Olepa hyvä. Muistivuodoista on aina ilo nipottaa. ;)
Avaanpa topicin uudestaan, vaikka sain k.o. koodin toimimaan. Jatkoin pelin tekoa ja lag palasi jälleen uudessa muodossa.
Tuolla on koodi, alue jossa vika selvästi on on korostettu. Se laskee ruudun alueen ja piirtää ruudun siihen jonka jälkeen se tarkistaa onko hiiri alueella ja tekee sen jokaiselle ruudulle. Olen jo yrittänyt nopeuttaa laskemista niin paljon kun osaan (laittanut ruutujen x- ja y- arvot taulukkoon sekä ne 40- ja 24-kertaisina) mutta lag jatkuu edelleen. Nyt vauhti vaihtelee ja hitauden näkee paljaalla silmällä. Se poistuu vasta kun SDL_Delay on ~100 eli liikaa.
Ehdotuksia laskujen yksinkertaistamiseksi / turhan tavaran karsimiseksi jos on?
Käytät tuolla renderstoren sisällä edelleen sitä omaa hidasta funktiota apply_withAlpha. Tosin teet sen kai vain yhdelle ruudulle, mutta silti. Siellä oleva kuvan lataus on _erittäin_ hidas operaatio, jota ei tosiaan kannata tehdä realiaakaisessa grafiikassa joka päivityksen yhteydessä. Kannattaa muistaa, että kaikki kovalevyn käsittely on tuhansia kertoja hitaanpaa kuin muistin käsittely.
Muita yleisiä parannusajatuksia:
Pitääkös sinun nyt välttämättä piirtää kaikki 2500 tileä joka kerta ruudulle? Jos osa noista on ruudun ulkopuolella ei niitä kannata edes yrittää piirtää.
Jos liikutat vain kursoria, riittää, että piirrät uudestaan vain ruudut, joissa kursori oli edellisellä kerralla ja ruudun johon se siirtyi. Tosin jos sinulla on isometrinen viritys niin saattaa joutua piirtämään myös joitain ruutuja ympäriltä. Pystyt varmasti helposti laskemaan minkä ruudun kohdalla kursori on ilman, että joudut käymään läpi kaikki ruutuja.
Yksi vanha kikka kursorin piirtämiseen on ottaa ennen kursorin piirtämistä muistiin kuva kursorin alla olevasta kohdasta ennen kursorin piirtämistä ja vasta sitten piirtää kursori siihen. Sitten kun kursoria liikutetaan, voidaan edellisen kohdan päälle suoraan piirtää se muistissa oleva kuva ja tehdä sama temppu uudelle kursorin kohdalle. Isometrille kursoreille tuota voi ehkä hiukan joutua soveltamaan.
Jos pelialue on isompi, kuin näkyvä alue ja ruutua halutaan skrollata, voidaan näkyvää aluetta isompi osa piirtää kerralla muistiin ja sitten ruutua vieritettäessä piirtää tästä muistissa olevasta kuvasta osia näytölle. Tässä pitää tietenkin harkita hyödyn suhdetta muistinkulutukseen.
Jos pelialueen pohja pysyy jatkuvasti samana, muttä sen päällä olevat oliot vaihtavat paikkaa, voidaan piirtoa mahdollisesti nopeuttaa, jos pohja piirretään vain kerran muistiin ja sitten jokaisella piirtokerralla pohja kopioidaan ruudulle yhtenä kuvana sen sijaan, että piirrettäisiin 2500 kuvaa. Pohjan päälle voi sitten piirtää eri objektit.
Kannattaa nimetä noi käyttämäsi vakioarvot jotenkin ja sitten kun sulla on ne nimetyt vakiot, kannattaa myös käyttää niitä (wink, wink, SCREEN_WIDTH*SCREEN_HEIGHT <-> 800*600).
Aihe on jo aika vanha, joten et voi enää vastata siihen.