En nyt parempaa nimeä keksinyt, mutta liittyyhän tämä homma kuintekin kartan lataamiseen x). Eli olen alkanut vääntää pientä peliä ja heti olen törmännyt itselleni liian korkeaan seinään. Eli koodi on tässä..
#include <SDL/SDL.h> #include <stdlib.h> #include <stdio.h> struct Kartta { /* Kartta tietue sisältää kartan tiedot */ int leveys, korkeus; /* Kartan leveys ja korkeus */ char *data; /* Kartan data */ }; int LataaKartta(char *tnimi, Kartta *kartta){ /* Fuktio kartan lataamiseen tiedostosta */ FILE *fp = NULL; /* Tiedosto osoitin */ int virhe = 0; /* Muuttuja virheiden tarkistamiseen */ if( ( fp = fopen(tnimi, "r") ) == NULL ){ /* Yritettän avata tiedostosto */ fprintf(stderr, "Karttaa %s ei voitu ladata\n", tnimi); return 0; } while( !virhe ){ /* Looppi jossa ladataan kartta ei loopata kuin kerran */ /* Yritetään ladataan kartan leveys ja korkeus */ if( fscanf(fp, "LEVEYS: %i KORKEUS: %i\n", kartta->leveys, kartta->korkeus) != 2 ){ fprintf(stderr, "Leveys ja/tai korkeus ilmoitettu vaarin\n"); virhe = 1; break; } /* Varataan kartan datalle muistia */ kartta->data = (char *) malloc( sizeof(char) * kartta->leveys * kartta->korkeus ); if( kartta->data == NULL ){ fprintf(stderr, "Kartan datalle ei voitu varata muistia\n"); virhe = 1; break; } int i = 0; while( i < kartta->leveys * kartta->korkeus ){ /* Ladataan kartan data merkki kerrallaan */ if( feof(fp) ){ /* Jos ollaan jo tiedoston lopussa */ fprintf(stderr, "Kartan dataa puuttuu\n"); virhe = 1; break; } /* Haetaan merkki, jos se on välilyönti ei huomioida */ if( ( kartta->data[i] = getc(fp) ) != '\n' ) i++; } break; /* Poistutaan loopista */ } fclose(fp); /* Suljetaan tiedosto */ if(virhe){ /* Jos lataamisessa on sattunut virhe */ free(kartta); /* Vapautetaan muisti varmuudeksi ja palautetaan 0 */ return 0; } return 1; /* Palautetaan 1 onnistumisen merkiksi */ } int main(int argc, char **argv) { atexit(SDL_Quit); /* Ajetaan SDL_Quit() kun poistutaan */ if( SDL_Init(SDL_INIT_VIDEO) < 0 ){ /* Alustetaan SDL */ fprintf( stderr, "Ei voitu alustaa SDL:llää: %s\n", SDL_GetError() ); return 1; } /* Asetetaan näyttötila 800x600x16 käyttäen näytönohjainta ja kaksoispuskurointia */ SDL_Surface *screen = SDL_SetVideoMode(800, 600, 16, SDL_HWSURFACE | SDL_DOUBLEBUF); if( screen == NULL ){ fprintf( stderr, "Ei voitu asettaa näyttötilaa 800x600x16: %s\n", SDL_GetError() ); return 1; } Kartta *kartta; if( !LataaKartta("maa.lvl", kartta) ){ fprintf(stderr, "Karttaa ei voitu ladata\n"); return 1; } SDL_Event event; /* Tapahtumat */ Uint8* keys; /* Näppäinten tiedot */ int Loop = 1; /* Loopataanko */ while( Loop ){ while( SDL_PollEvent(&event) ){ /* Käydään läpi kaikki tapahtumat */ if(event.type == SDL_QUIT) Loop = 0; /* Jos painetaan ruksia poistutaan */ } keys = SDL_GetKeyState(NULL); /* Haetaan näppäinten tiedot */ if( keys[SDLK_ESCAPE] ) Loop = 0; /* Jos painetaan esciä poistutaan */ } return 0; }
Ja tuohon stderr tiedostoon tulee virhe ilmoitus:
Fatal signal: Segmentation Fault (SDL Parachute Deployed), joka
on käsittääkseni muistivuoto tai jokin sellainen.
Tuo maa.lvl on seuraavan lainen
LEVEYS: 5 KORKEUS: 5 ##### # # # # # # #####
Eli mikä tässä on vika?
if( fscanf(fp, "LEVEYS: %i KORKEUS: %i\n", kartta->leveys, kartta->korkeus) != 2 ){
Tuossa on ainakin virhe. Pidemmälle en jaksanut katsoa.
Muuta "kartta->leveys" muotoon "&kartta->leveys" (eli annetaan scanf:lle osoite tuohon kartta->leveys-muuttujaan, kuten kuuluukin). Toinen vastaavasti.
No tuo ei ainakaan auttanut x(...
Voit etsiä virhettä poistamalla ohjelmasta osuuksia (kommentoimalla), kunnes muistivirhettä ei tule. Voit myös tutkia muuttujien sisältöä ohjelman eri vaiheissa.
Löysin ongelmaan ratkaisun eli
Kartta *kartta = (Kartta *) malloc( sizeof(Kartta) ); /* Osoitin kartan dataan */
on oikea muoto kartta tietuuen tekemiseen
Muista sitten myös vapauttaa muisti lopuksi. free(kartta); Tai sitten älä käytä osoitinta.
Juu lisäsin tuon freen tuonne loppuun. Onko muuten mitään muuta palautetta koodista.
No sellainen palaute, että varsin hyvältä näyttää :)
Jos meinaat tuosta vähän isomman tehdä, niin voi olla paikallaan siirtää itse peliin liittyvät funktiot ja muuttujat omaan tiedostoonsa. Muutenkaan en pitäisi karttaa main-funktion sisäisenä muuttujana...
Ai niin, muista myös se kartan data vapauttaa erikseen. Nyt kun vilkaisin tuota Kartta-struktuuria, niin kartta voisi hyvin olla globaali muuttuja. Aika turha siinä on osoitinta käyttää, jos on vain yksi kartta kerrallaan.
Juu kiitoksia palautteesta :D
Tuossa on tarkoituksena tulla pieni puzzle peli, jossa on tarkoitus päästä pisteestä toiseen työntelemälle kiviä ja keräämällä timantteja.
No laitan tämän nyt tähän, eli jos jotain kiinnostaa niin käännetyn version ja sourcet saa http://www.freepgs.com/samlui/files/PuzzleGame.
Tässä vaiheessa peli tukee vain yhtä kenttää. Olisi ihan kiva saada palautettaa koodista.
Puzzlepeli yhteen (tai väliviivalla, jolloin pelin nimi on Puzzle ja halutaan korostaa, että se on nimenomaan peli). (Nipo nipo :)
Hyvältähän tuo näyttää, mitä pikaisesti lukaisin läpi. Käytännön virheitä ei tosiaan kummemmin ole. Luulen kuitenkin (rajallisella SDL-tietämykselläni), että ladatut kuvat pitäisi lopuksi tuhota muistista. Siis SDL_FreeSurface jokaiselle ladatulle kuvalle. Jossakin neuvottiin tekemään tuo myös sille pinnalle, joka saadaan SDL_SetVideoMode-funktiolta, siitä en sitten tiedä.
Toinen "virhe" on tuo otsikkojen käyttö. Otsikkotiedostoon ei yleisesti kuulu laittaa itse funktioita, vain niiden esittelyt. Funktiot pitäisi pitää cpp-tiedostoissa tai vastaavissa. Eli tällainen rakenne:
kartta.h:
#ifndef __KARTTA__ #define __KARTTA__ // Vain ne includet, joita tämä otsikkotiedosto tarvitsee #include <SDL/SDL.h> struct Paikka { int x, y; }; //Tietue sisältää paikan koordinaatit struct Kartta { //Kartta tietue sisältää kartan tiedot int leveys, korkeus; //Kartan leveys ja korkeus char *data; //Kartan data Paikka alku; //Kartan alotuspaikan koordinaatit SDL_Surface *kuvat[6]; //Kartan kuvat 0 = tausta, 1 = seina, 2 = kivi, 3 = timantti, 4 = maali }; // extern kertoo, että funktio on jossakin muussa tiedostossa extern int LataaKartta(char *tnimi, Kartta *kartta); extern int PiirraKartta(SDL_Surface *kohde, Kartta *kartta); #endif
kartta.cpp:
// Ei tarvita ifdef-tarkistusta, koska tätä tiedostoa ei includella liitetä // Omasta otsikosta tulevat struct-määrittelyt ja funktioesittelyt #include "kartta.h" #include <SDL/SDL.h> // yms. includet // Sitten ne funktiot aivan normaalisti. int LataaKartta(char *tnimi, Kartta *kartta) { // ... return 0; }
Seuraava etappi voisi olla se, että teet tuosta tuollaisenaan viimeistellyn pelin, eli valikko ja pistelista tarvitaan. Vasta siinä vaiheessa voi antaa lopullisen arvion pelin tasosta. Mutta kyllä tämä jo näinkin yltää korkealle.
Muita pieniä ehdotuksia myöhemmälle ajalle:
- Hahmo voisi liikkua jatkuvasti (sopivalla ajastuksella), kun nappia pidetään pohjassa. Välillä on naputtamista seuraavaan paikkaan. Eli tutustu ihmeessä SDL_GetTicks-funktioon ja pelien reaaliaikaisuuteen.
- Animoitu liikkuminen (hahmon ja kivien) olisi myös hieno, ja samallahan sitä laittaisi timantit kimaltamaan kauniisti :)
- Tasoeditori, luonnollisesti.
Usein vain on paljon hankalampi lisätä ominaisuuksia kuin jos olisi tehnyt alusta asti niitä silmällä pitäen :) Mutta onnea matkaan. Apua saa aina, kun sitä tavitsee.
Käsittääkseni SDL_Quit vapauttaa surfacet automaattisesti, mutta en ole varma siitä. Jos joku tietää tästä enemmän olisi mukavan saada varmistus.(SDL:llän dokumentaatiossa asia on vähän epäselvästi ilmaistu :/).
Kiitos Metabolixille palautteesta. Tuossahan riittää tekemistä ainakin joksikin aikaa.
Kysyisin vielä onko mitäään helppoa tapaa tehdä SDL:llä tekstikenttiä? Tietysti sellaisen voi itse toteuttaa paremman puutteessa.(Hieman vaikeaa tosin)
Tekstilaatikolle ei ole helppoa tapaa suoraan SDL:ssä. Kuitenkin jos ihan vain tuloslistaa ajattelit, niin siihen on melko helppo tehdä vastaava. täytyy vain selvittää, mihin kohti pelaaja pääsee nimensä kirjoittamaan, ja sitten lukea näppäimiä SDL_PollEvent-silmukan avulla. Muistaakseni SDL_KEYDOWN-viesti on jutun juju.
Ja mitä minä nyt Googlen avulla löysin pintojen vapauttamiseen liittyen, niin useimmat vaikuttavat olevan sitä mieltä, että SDL_Quit ei vapauta kuin piirtopinnan. Kaikki loput pitää siis itse vapautella. Lähdekoodistahan tuo selviäisi...
Edit: Niin, lukaisin SDL_Quit-funktion ja sen sukulaisten lähdekoodit. Ensin mainitusta löytyy tällainen kohta:
#ifdef CHECK_LEAKS /* Print the number of surfaces not freed */ if ( surfaces_allocated != 0 ) { fprintf(stderr, "SDL Warning: %d SDL surfaces extant\n", surfaces_allocated); } #endif
Tuosta voi siis päätellä, että niitä ei automaattisesti vapauteta. Projektisi SDL.dll ei nähtävästi ole sellainen käännös, joka ilmoittaisi virheestä. (Huomaa ifdef.)
Juu täytyy ilmeisesti muistaa vapauttaa nuo pinnat erikseen :)
Mietin tässä että kannattaisiko alkaa mahdollisesti alkaa toteuttamaan noita luokilla? Olen vähän tutustunut niihin ja ne vaikuttavat aika kivoilta x)
Kyllä, luokat ovat hyvin kivoja :) Kunhan niitä ei käytä aivan liikaa. Joskus kannattaa käyttää myös nimiavaruuksia, ja usein riittää vanha kunnon struct.
Jatkan nyt tähän samaan, kun en viitsi aloittaa uutta aihetta x) Eli olen nyt alkanut toteuttaa tuota animointi systeemiä. Olen saanut viritettyä jonkinlaisen systeemin kasaan joka kylläkin toimii :D Tällä hetkellä koodi on seuraavanlainen
#ifndef __ANIM__ #define __ANIM__ #include <SDL/SDL.h> struct Frame { //Animaation yhden framen tiedot SDL_Surface *kuva; //Framen kuva int pause; //Kuinka kauan kuvaa näytetään }; class Sprite { //Itse animaatio luokka private: Frame *kuvat; //Animaation kuvat int kuvaMaara; //Animaation kuvien määrä int kuvaNyt; //Mikä kuva on tällä hetkellä vuorossa int paivitys; //Milloin kuva on viimeksi päivitetty public: Sprite(); //Luokan konstruktori int LataaKuvat(char *tnimi); //Lataa animaation kuvat tiedostosat int PaivitaKuva(); //Vaihtaa animaation kuvaa SDL_Surface *HaeKuva(); //Palauttaa vuorossa olevan kuvan }; #endif
Eli tuo luokka ei hoida muuta kuin kuvien säilytyksen, niiden latauksen ja näytettävän kuvan vaihtamisen. Eli sitten muissa paikoissa haen näytettävän kuvan tuolla HaeKuva()-metodilla. Kysyisinkin nyt onko tämä ihan hyvä tapa vai kannattaisiko luokan hoitaa myös piirtäminen ym.?
En nyt laita noiden funktioiden koodeja tänne koska ne tuskin vaikuttavat asiaan.
Juuri tuohon tapaan. Minun näkemykseni olisi, että luokan ei pitäisi edes säilyttää kuvia, vaan pelkästään vaihtaa kuvan numero, ja piirtofunktio sitten osaisi sen piirtää. Eihän sillä sinänsä ole merkitystä, mutta minusta piirtokoodi on mukava pitää samassa paikassa. Toisaalta voisihan kaikkien luokkien piirtofunktiot kerätä samaan tiedostoon...
Tilan ja tiedostojen säästämiseksi, älä laita joka framea omaan tiedostoonsa, vaan tee niistä yksi tiedosto, jossa peräkkäiset kuvat ovat. Kun joka framen koko on vaikkapa 32x32, ja kuvassa on 8x8 framea tai loput tyhjää. (Tottumuksesta heitän aina sivun pituudeksi 2^x, kun 3D-kirjastot vaativat sen.) Luokka palauttaa kuvan numeron 0-63, ja tämän perusteella piirtofunktiossa valitaan oikea kohta piirrettäväksi (jako ja jakojäännös).
unsigned char kohta = Hahmo->Kuvan_Numero(); // Framen leveys ja korkeus on 32 pikseliä. // Optimointina bittioperaatioita // unsigned char x = 32 * (kohta % 8); unsigned char x = (kohta & 7) << 5; // unsigned char y = 32 * (kohta / 8); unsigned char y = (kohta >> 3) << 5; unsigned char x2 = x + 31, y2 = y + 31; // Niistä oikean alakulman sijainti
Päädyin Metabolixin ehdotusten perusteella tälläiseen ratkaisuun...
#ifndef __SPRITE__ #define __SPRITE__ #include <SDL/SDL.h> /* LUOKKA Kuvat: Säilyttää animaation kuvia, hoitaa myös kuvan piirtämisen ja lataamisen */ class Kuvat { friend class Sprite; //Jotta Sprite pääsisi käsittelemään luokan privaatteja muuttujia private: SDL_Surface *kuvat; //Animaation kuvat(yksi kuva) int kuvaMaara; //Animaation kuvien määrä int kuvaLeveys; //Animaation yhden kuvan leveys public: int haeMaara() { return kuvaMaara; } //Palauttaa animaation kuvien määrän int haeLeveys(){ return kuvaLeveys; } //Palauttaa animaation yhden kuvan leveyden int lataaKuvat(char *nimi, int leveys, int colorKey, Uint32 vari); //Lataa kuvat int piirraKuva(SDL_Surface *pinta, int x, int y, int kuva); //Piirtää kuvan ~Kuvat(){ SDL_FreeSurface(kuvat); } //Vapauttaa kuvien varaaman muistin }; /* LUOKKA Sprite: Säilyttää animaation perustiedot: vuorossa olevean kuvan, kuvien välillä olevan tauon jne. Huolehtii animaation päivittämisestä */ class Sprite { private: Kuvat *kuvat; //Osoitin animaation kuvadataan int kuvaNyt; //Mikä kuva on vuorossa int paivitys; //Milloin kuva vaihdettiin viimeksi int tauko; //Kuinka monta millisekunttia kuvaa näytetään kerralla public: Sprite(){ //Luokan konstrukori, nollaa muuttujat kuvaNyt = 0; paivitys = 0; tauko = 0; } void asetaTauko(int tauko) { this->tauko = tauko; } //Asettaa tauon suuruuden void asetaKuvat(Kuvat *kuvat){ this->kuvat = kuvat; } //Asettaa osoittimen kuvadataan void vaihdaKuva(int i) { this->kuvaNyt = (kuvat != NULL && i >= 0 && i < kuvat->kuvaMaara) ? i : 0; } //Asettaa vuorossa olevan kuvan int haeKuva() { return kuvaNyt; } //Palauttaa vuorossa olevan kuvan numeron int paivitaKuva(); //Paivittaa vuorossa olevaa kuvaa }; #endif
Tuntuivat nuo luokat niin kivoilta, että taidan päivittää tuon kartta, ym jutut luokiksi myös olio-pohjaiseksi. Joudun nimittäin uudistamaan tuota karttasysteemiä muutenkin, kun alan pakertamaan tuota kartta-editoria. Data kannattaa varmaan tallentaa binääri-muodossa, ja muitakin muutoksia varmaan tulee..
Ennen kuin alan kartta-editoria vääntämään ajattelin, että kannattaisin varmaan vääntää jonkilainen fontti-luokka jolla voisi kirjoittaa tekstiä ruudulle. Ja jos tämä onnistuu niin voisi kokeilla myös jonkilaisten tekstikenttien tekemistä jotta voisi lukea tekstiä.
Olisi kiva tietää, miten toteuttaa tämä string-pohjaisena:
/* Yritetään ladataan kartan leveys ja korkeus */ if( fscanf(fp, "LEVEYS: %i KORKEUS: %i\n", kartta->leveys, kartta->korkeus) != 2 ){ fprintf(stderr, "Leveys ja/tai korkeus ilmoitettu vaarin\n"); virhe = 1; break; }
Tarkoitan tuota fscanf()-funktion käyttöä. Miten saisin toteutettua vastaavan fstream- ja string-luokan kanssa? Auttaisi roimasti omissa koodeissa, kun vastaavalla menetelmällä saisi skannattua kuvatiedoston ynm. Ajattelin, että lataisin kartan, jonka syntaksi olisi (tiedoston nimi on kartta.dat):
TILET: data/tiletiedosto.dat LEVEYS: 6 KORKEUS: 6 ###### #....# #....# #....# #....# ######
Taaseen tiedosto tiletiedosto.dat käyttäisi seuraavanlaista syntaksia:
#: graf/seina.bmp .: graf/maa.bmp
(Ovat vasta suunnitteluvaiheessa, kuten varmasti huomaatte.)
samlui:Hienolta vaikuttaa projektisi, Go go! :]
TeeVee: Purkkana getline(tiedosto, str) ja sscanf(str.c_str(), ...)
Melkein kannattaisin turhien tekstien jättämistä pois karttaformaatista. Ainahan voi mahdollistaa kommentit sillä, että luetaan ensin riviltä yksi luku ja sitten luetaan rivinvaihtomerkkiin asti. Mutkikkaampi kartta pitää yleensä kuitenkin tunkea enemmän tai vähemmän binäärimuotoon.
Aihe on jo aika vanha, joten et voi enää vastata siihen.