Jälleen kerran ohjelmani syö liikaa tehoja, enkä tiedä mitä tehdä...
Ohjelma piirtää Map[][]-taulukon avulla isometrisellä projektiolla maiseman. Maisema koostuu tiilistä, jotka voivat liukua eri tekstuurien välillä niin kuin liukuvärit liukuvat kahden värin välillä.
Seuraavalla funktiolla ohjelma piirtää ruudulle tiilen sen neljällä kulmalla olevien tekstuurien mukaan. LEVEL_TILE_W on tiilen leveys, -H on sen korkeus. t1,t2,t3 ja t4 ovat tiilen kulmilla olevien tekstuurien numerot, esim. 0 on vettä, 1 hiekkaa ja 2 ruohoa.
SDL_Surface* terrain(SDL_Surface* surf,int t1,int t2,int t3,int t4,int x,int y) { SDL_Surface* temp= SDL_CreateRGBSurface( SDL_SWSURFACE, LEVEL_TILE_W, LEVEL_TILE_H, SCREEN_BPP, 0xff0000, 0xff00, 0xff, 0xff000000); int for1,for2; for (for1=0; for1<LEVEL_TILE_W; for1++){ //Mennään jokainen pikseli läpi... for (for2=0; for2<LEVEL_TILE_H; for2++){ double tempX= (for2*8+(for1-40)*5) /400.0; //Pikselin paikka isometrisellä neliöllä double tempY= (for2*8-(for1-40)*5) /400.0; // if (tempX>=-0.01 && tempX<=1 && tempY>=0 && tempY<=1) //piirretään vain tiilen alueelle { Uint8 r1,g1,b1,a1, r2,g2,b2,a2, r3,g3,b3,a3, r4,g4,b4,a4; //värit SDL_GetRGBA( ((unsigned int*)Terrain //mikä väri a-kulmassa? [t1]->pixels) [for1+for2*LEVEL_TILE_W], PureTerrain[0]->format, &r1,&g1,&b1,&a1); SDL_GetRGBA( ((unsigned int*)Terrain //... [t2]->pixels) [for1+for2*LEVEL_TILE_W], PureTerrain[0]->format, &r2,&g2,&b2,&a2); SDL_GetRGBA( ((unsigned int*)Terrain [t3]->pixels) [for1+for2*LEVEL_TILE_W], PureTerrain[0]->format, &r3,&g3,&b3,&a3); SDL_GetRGBA( ((unsigned int*)Terrain [t4]->pixels) [for1+for2*LEVEL_TILE_W], PureTerrain[0]->format, &r4,&g4,&b4,&a4); double Aam,Bam,Cam,Dam; //k.o. värien määrä k.o. pikselissä Aam=(1.0-tempX) * (1.0-tempOrY); Bam=(1.0-tempX) * (tempOrY); Cam=(tempX) * (tempOrY); Dam=(tempX) * (1.0-tempOrY); r1=int(r1 *Aam+r2 *Bam+r3 *Cam+r4 *Dam); //pikselin lopullinen väri g1=int(g1 *Aam+g2 *Bam+g3 *Cam+g4 *Dam); b1=int(b1 *Aam+b2 *Bam+b3 *Cam+b4 *Dam); a1=int(a1 *Aam+a2 *Bam+a3 *Cam+a4 *Dam); ((unsigned int*)temp->pixels) [for1+for2*LEVEL_TILE_W] = SDL_MapRGBA(temp->format, r1,g1,b1,a1); } } } apply_surface(x, y, temp, surf); // piirretään tiili ruudulle
Ohjelma piirtää puskuriin, jonka koko on 3 ruudunleveyttä * 3 ruudunkorkeutta ennalta osan maisemaa. Kameran siirtyessä tarpeeksi puskuri päivittyy. Puskurista piirretään osa ruudulle.
camx+camy on nykyinen kuvattava kohta.
if (abs(camX-camXb) > SCREEN_WIDTH || abs(camY-camYb) > SCREEN_HEIGHT) {mapChange=1; camXb=camX; camYb=camY;} //jos mennään rajan yli, puskuri pitää päivittää tempRect.w = SCREEN_WIDTH*3; tempRect.h = SCREEN_HEIGHT*3; if(mapChange==1) {SDL_FillRect(mapBuffer, &tempRect, 0xff000000);} for (x=0; x<99*99; x++){ //kartan jokainen tiili käydään läpi if (0<int(x/99)*40 - (x%99)*40 + camX + LEVEL_TILE_W + SCREEN_WIDTH && int(x/99)*40 - (x%99)*40 + camX < SCREEN_WIDTH*2 && 0<int(x/99)*25 + (x%99)*25 + camY + LEVEL_TILE_H + SCREEN_HEIGHT && int(x/99)*25 + (x%99)*25 + camY < SCREEN_HEIGHT*2 && mapChange==1) //jos tiili on puskurin alueella ja puskuri pitää päivittää { terrain(mapBuffer, Map[x%99][x/99], Map[(x+1)%99][x/99], Map[(x+1)%99][x/99+1], Map[x%99][x/99+1], int(x/99)*40 - (x%99)*40 + camX + SCREEN_WIDTH, int(x/99)*25 + (x%99)*25 + camY + SCREEN_HEIGHT); } } mapChange=0; //puskuri päivitetty, ei tarvitse enää päivittää apply_surface(-800 + camX - camXb, -600 + camY - camYb, mapBuffer, screen); //puskuri piirretään ruudulle
Muuten moottori on tarpeeksi nopea, mutta kun puskuri pitää päivittää, tulee noin kahden sekunnin viive. Koko maisemaa en viitsi ladata muistiin ettei muisti lopu. Myös erilaisten tiilivaihtoehtojen muistiin laittaminen kuluttaisi liikaa muistia, koska niitä pitäisi tehdä (tiilienmäärä^4) kpl.
Myös muita vaihtoehtoja "liukuvien" tiilien toteuttamiseen otetaan vastaan.
Hieman sekavasti selitetty...kysykää jos on kysyttävää.
(Mod. lisäsi hieman välejä koodiin, ettei leiska hajoa ylipitkiin riveihin. Samalla koodista tuli myös hieman luettavampaa. Teethän tuon toimituksen ensi kerralla itse?)
Yksi juttu ainakin olisi muuttaa tuo läpikäyntisilmukkasi kahdeksi erilliseksi silmukaksi, jotta pääset tuosta mielettömästä if-lauseesta eroon.
Toinen juttu olisi, että muutat terrain-funktion palauttamaan osoittimen luotuun pintaan ja piirrät puskuriin vasta tuolla silmukassa. Tällöin on helpompi toteuttaa toisenlainen puskurointi: säilytetään luotuja tilejä jossain, jottei yleisimpiä tarvitse aina luoda uudestaan.
Päivityssilmukka voisi näyttää vaikkapa tältä:
if (mapChange) { for (ry = 0, y = camY - RUUDUN_KORKEUS / 2; ry < RUUDUN_KORKEUS; ++y, ++ry) { for (rx = 0, x = camX - RUUDUN_LEVEYS / 2; rx < RUUDUN_LEVEYS; ++x, ++rx) { piirra( terrain(map[x][y], map[x][y+1], map[x+1][y], map[x+1][y+1]), ruutu, rx * TILE_W, ry * TILE_H ); } } mapChange = 0; }
Alkeellisen säilytysjärjestelmän kanssa terrain-funktio voisi olla vaikkapa tällainen:
#include <map> // Käytä sitä muuttujakokoa, jota tarvitset. Pienemmillä toimii joskus nopeammin. typedef unsigned short KULMA; // Tyyppi tilen tunnistamiseen struct tiletyyppi { KULMA t00, t01, t10, t11; // Kulmat // Jonkinlainen järkevä vertailufunktio, koska käytettävä map-tietorakenne vaatii sellaisen inline bool operator < (tiletyyppi const& t) const { if (t00 < t.t00) return true; if (t00 > t.t00) return false; if (t01 < t.t01) return true; if (t01 > t.t01) return false; if (t10 < t.t10) return true; if (t10 > t.t10) return false; if (t11 < t.t11) return true; return false; } }; // Tilestä tarvittavat tiedot struct tiletieto { SDL_Surface *pinta; int kayttomaara; }; // Map-tyyppi tilejä varten; avaimena tiletyyppi ja arvona tiletieto typedef std::map <tiletyyppi, tiletieto> tilemap; // Tiletaulu funktiota varten tilemap tilet; SDL_Surface *terrain(KULMA t00, KULMA t01, KULMA t10, KULMA t11) { tilemap::iterator i; tiletyyppi t = {t00, t01, t10, t11}; // Etsitään valmiista tileistä tätä lajia i = tilet.find(t); if (i != tilet.end()) { // Löytyi; lisätään käyttömäärää ja palautetaan pinta i->second.kayttomaara ++; return i->second.pinta; } // Ei löytynyt; luodaan SDL_Surface *temp; // ... tähän se entinen luontikoodi, mutta EI piirtoa! // Tarkistetaan vielä muistissa olevien tilejen määrä: if (tilet.size() >= MAX_TILEJA) { // Tässä pitäisi poistaa muutama vähiten käytetty tile muistista, mutta kehitä tämä vasta, kun saat muuten systeemin toimimaan } // Lisätään uusi tilemalli muistiin tiletieto tieto; tieto.pinta = temp; tieto.kayttomaara = 1; tilet[t] = tieto; return temp; } // Tietenkin muisti pitää lopuksi tyhjentää void tyhjenna_muisti(void) { // Vapautetaan silmukassa pinnat tilemap::iterator i; for (i = tilet.begin(); i != tilet.end(); ++i) { SDL_FreeSurface(i->second.pinta); } // Tyhjennetään koko map-rakenne tilet.clear(); }
Muokkasin koodia ehdotustesi mukaisesti. Latausajat vähenivät selvästi, mutta ne ovat edelleen liian pitkiä.
Lataamistahan ei huomaisi, jos se tapahtuisi liikkumisen aikana. Miten sen saa aikaan?
SDL:n piirtofunktiot ovat hitaita, voi tulla ihan jo sekin vastaan. OpenGL on hyvä jatkovaihtoehto. Mutta aivan ensiksi varmista, että näyttöpinta, puskuri ja tilet ovat samassa formaatissa.
Vielä yksi merkittävä parannus olisi se, että piirtäisit puskurista vain tarpeellisen osan uudestaan. Seuraavassa esimerkissä numeroilla merkitään tietyllä liikkumiskerralla piirrettyjä osia.
Aluksi puskuri kattaa ruudun, dx = dy = 0.
* ******* +------ *|000000* *|000000* *|000000* *|000000* *|000000* * *******
Pelaaja liikkuu vasemmalle, jolloin hän näkee yhden uuden rivin.
Rivi olisi oikeasti vasemmassa reunassa, mutta se piirretäänkin puskurin siihen osaan, jota ei enää näy. Edellisen kohdan piirustukset säilyvät samassa kohti! dx--;
****** ** -----+- *00000|1* *00000|1* *00000|1* *00000|1* *00000|1* ****** **
Pelaaja liikkuu kaksi askelta alas, kummallakin puskurin yläreunasta vapautuu rivi. dy++; dy++;
****** ** *33333|3* *22222|2* -----+- *00000|1* *00000|1* *00000|1* ****** **
Pelaaja liikkuu askeleen oikealle, jolloin ruudun vasemman reunan rivi korvautuu uudella. Tämä rivi on puskurissa oikealla. dx++;
* ******* *|333334* *|222224* +------ *|000004* *|000004* *|000004* * *******
Muuttujat dx ja dy kannattaa säilyttää niin, että (0, 0) <= (dx, dy) < (ruudun mitat). Toki laskun voi helposti tehdä vaikkapa kameran sijainnin perusteella.
dx %= w; if (dx < 0) {dx += w;} dy %= h; if (dy < 0) {dy += h;}
Vielä piirtoidea:
void piirra(kohde, x, y, lahde, x, y, w, h); // vasempaan yläkulmaan puskurin alue kohdasta (dx, dy) loppuun piirra(ruutu, 0, 0, puskuri, dx, dy, w - dx, h - dy); // Oikeaan alakulmaan puskurin vasemmasta yläkulmasta kohtaan (dx, dy) asti piirra(ruutu, w - dx, h - dy, puskuri, 0, 0, dx, dy); // Muut kulmat vastaavasti piirra(ruutu, w - dx, 0, puskuri, 0, dy, dx, h - dy); piirra(ruutu, 0, h - dy, puskuri, dx, 0, w - dx, dy);
Toivottavasti tästä sai selvää. Vielä kuva piirrosta:
Puskuri, dx = dy = 2, jätä viivat huomiotta ;)
*** **** *11|222* *11|222* --+--- *33|444* *** ****
Ruutu:
******* *44433* *22211* *22211* *******
Toivottavasti ei tullut väsyneenä tyhmyyksiä kirjoitettua. :)
Edit. Ja koordinaatiston oletin nyt kiltisti väärin päin, kuten pikseligrafiikassa on tapana. Meinasin itsekin mennä jo sekaisin tuossa, kun olen tottunut pitämään y-akselia ylöspäin.
Aihe on jo aika vanha, joten et voi enää vastata siihen.