Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Kartan Lataaminen + SDL

Sivun loppuun

samlui [01.01.2006 14:16:18]

#

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?

thefox [01.01.2006 15:51:51]

#

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.

samlui [01.01.2006 16:07:20]

#

No tuo ei ainakaan auttanut x(...

Antti Laaksonen [01.01.2006 16:08:37]

#

Voit etsiä virhettä poistamalla ohjelmasta osuuksia (kommentoimalla), kunnes muistivirhettä ei tule. Voit myös tutkia muuttujien sisältöä ohjelman eri vaiheissa.

samlui [01.01.2006 17:00:58]

#

Löysin ongelmaan ratkaisun eli

Kartta *kartta = (Kartta *) malloc( sizeof(Kartta) );   /* Osoitin kartan dataan */

on oikea muoto kartta tietuuen tekemiseen

Metabolix [01.01.2006 17:12:29]

#

Muista sitten myös vapauttaa muisti lopuksi. free(kartta); Tai sitten älä käytä osoitinta.

samlui [01.01.2006 17:55:03]

#

Juu lisäsin tuon freen tuonne loppuun. Onko muuten mitään muuta palautetta koodista.

Metabolix [02.01.2006 12:29:50]

#

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.

samlui [02.01.2006 13:06:14]

#

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.

samlui [04.01.2006 12:36:55]

#

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.zip
Tässä vaiheessa peli tukee vain yhtä kenttää. Olisi ihan kiva saada palautettaa koodista.

Metabolix [04.01.2006 17:38:57]

#

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.

samlui [04.01.2006 18:15:39]

#

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)

Metabolix [04.01.2006 19:31:07]

#

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.)

samlui [05.01.2006 11:28:09]

#

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)

Metabolix [05.01.2006 12:43:05]

#

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.

samlui [05.01.2006 22:24:22]

#

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.

Metabolix [05.01.2006 23:02:43]

#

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

samlui [06.01.2006 14:45:49]

#

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ä.

TeeVee [06.01.2006 22:19:11]

#

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! :]

Metabolix [06.01.2006 23:00:30]

#

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.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta