Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: SDL_mixerillä musiikkisoittimen teko

Sivun loppuun

Macro [26.09.2009 12:59:17]

#

Putkassa on mahtava SDL-opas, ja sen perusteella sain päähäni tehdä ohjelman jolla voi valita mitkä kappaleet soitetaan ja se soittaisi niitä siinä sitten.

Asensin SDL_mixerin, eli laitoin SDL_mixer.h tiedoston include/SDL kansioon ja lib kansion tiedostot projektikansioon.

#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>

int main() {
    // alustetaan SDL, käytettävä SDL_INIT_AUDIO -flagia
    if( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ) {
        fprintf(stderr, "SDL:n alustus ei onnistunut: %s", SDL_GetError());
        return 1;
    }

    // 44,1 khz, perusmuoto, 2 kaiutinta
    if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)==-1) {
        fprintf(stderr, "SDL_Mixerin alustus ei onnistunut.");
    }

    Mix_Chunk *aani;
    aani = Mix_LoadWAV_RW(SDL_RWFromFile("miley.wav", "rb"), 0);

    // olemme jo ladanneet äänitiedoston
    if(Mix_PlayChannel(0, aani, 1) == -1)
        fprintf(stderr, "Virhe : %s ", Mix_GetError());   // virhe

    Mix_FreeChunk(aani);
    aani = NULL; //varmuuden vuoksi
}

Tältä näyttää suoraan SDL oppaan perusteella tehty "ohjelma". Tämä kuitenkin herjaa jokaisesta SDL_mixerin funktiosta, joten se asennus ei tainnutkaan onnistua. Ohjelman herjaukset näyttävät tältä:

\MediaPlayer\main.cpp||In function `int SDL_main()':|
\MediaPlayer\main.cpp|25|warning: control reaches end of non-void function|
obj\Debug\main.o||In function `_Z8SDL_mainv':|
\MediaPlayer\main.cpp|12|undefined reference to `_Mix_OpenAudio'|
\MediaPlayer\main.cpp|17|undefined reference to `_Mix_LoadWAV_RW'|
\MediaPlayer\main.cpp|20|undefined reference to `_Mix_PlayChannelTimed'|
\MediaPlayer\main.cpp|23|undefined reference to `_Mix_FreeChunk'|
C:\Program Files\CodeBlocks\MinGW\lib\libSDLmain.a(SDL_win32_main.o)||In function `console_main':|
\tmp\sdl-tmp\SDL-1.2.13\.\src\main\win32\SDL_win32_main.c|199|undefined reference to `_SDL_main'|
||=== Build finished: 5 errors, 1 warnings ===|

Kun tuon saan korjattua, niin seuraava kysymys olisi tälläinen:
En tietenkään voi määrittää kappaleita mitä soitetaan koodiin suoraan, niin käyttäjän (siis minun) pitäisi saada sellainen ikkuna mistä voin valita soitettavat kappaleet.

Äänenvoimakkuutta voitaisiin säätää nuolilla ylös ja alas. Oppaan mukaan äänenvoimakkuuden tulee olla koodissa määritelty välille 0-128.

int voimakkuus = 50;
while(1) {
    SDL_PumpEvents();

    const Uint8 * const napit = SDL_GetKeyState(0);

    if (napit[SDLK_ESCAPE]) break;
    else if(napit[SDLK_UP]) {
        voimakkuus += 1;

        int Mix_VolumeChunk(Mix_Chunk *aani, voimakkuus);
    } else if(napit[SDLK_DOWN]) {
        voimakkuus -= 1;

        int Mix_VolumeChunk(Mix_Chunk *aani, voimakkuus);
    }

    SDL_Delay(10);
}

SDL_Quit();

Tällä siis saisi säädettyä äänenvoimakkuutta, vai saako? Samalla tyylillä voisi vaihtaa kappaletta, että vasenta nuolta painettaessa mentäisiin yksi kappale taaksepäin ja oikeata painiketta painettaessa eteenpäin.

Metabolix [26.09.2009 13:16:04]

#

Kaikki .a-päätteiset tiedostot kuuluvat aina kääntäjän lib-hakemistoon. Projektihakemistoon ei tule kuin dll-tiedostoja, jos niitä on. Virhe taas tulee siitä, ettet ole linkittänyt kirjastoa (.a-tiedostoa) ohjelmaan. Etsi projektin asetuksista linkkerin asetukset ja lisää sille komento -lSDL_mixer. Viimeinen virhe korjaantuu, kun vaihdat main-funktion tällaiseksi:
int main(int argc, char **argv)

Macro [26.09.2009 13:26:41]

#

Latasin tuon paketin SDL:n sivuilta, eikä siellä ollut mitään *.a tiedostoja, vain pari dll tiedostoa ja lib-tiedosto.

Laitoin sen sinne linkkeriin ja tuli: ld.exe||cannot find -l-lSDL_mixer|
En ole varma, laitoinko oikeaan paikkaan, mutta: Media Player -> Build Options -> Media Player -> Linker Settings -> Add: -lSDL_mixer

Metabolix [26.09.2009 13:42:55]

#

Vaihda lib-tiedoston nimeksi libSDL_mixer.a ja laita se lib-hakemistoon. (GCC:n eli Windowsissa MinGW:n kirjastotiedostot ovat tavallisesti nimeltään libX.a, missä X on kirjaston nimi.)

Oikea asetus on sittenkin ilman -l:ää; kuten näet virheestä, Code::Blocks on lisännyt siihen automaattisesti ylimääräisen -l:n eteen. Linkkerille (ld) annettavassa komennossa -l tarkoittaa "linkitä ohjelmaan tämä kirjasto", eli -lSDL_mixer tarkoittaa "linkitä ohjelmaan SDL_mixer".

Onneksi olkoon, onnistuit ilmeisesti löytämään asetuksista oikean kohdan hyvin epätarkoilla neuvoilla, kun monelle aloittelijalle se on ylivoimaista, vaikka annettaisiin tuo tarkka paikka, jonka omaan viestiisi kirjoitit. :) Tämä ennustaa erittäin hyvää jatkoa.

Macro [26.09.2009 13:52:28]

#

Metabolix kirjoitti:

Vaihda lib-tiedoston nimeksi libSDL_mixer.a ja laita se lib-hakemistoon. (GCC:n eli Windowsissa MinGW:n kirjastotiedostot ovat tavallisesti nimeltään libX.a, missä X on kirjaston nimi.)

Oikea asetus on sittenkin ilman -l:ää; kuten näet virheestä, Code::Blocks on lisännyt siihen automaattisesti ylimääräisen -l:n eteen. Linkkerille (ld) annettavassa komennossa -l tarkoittaa "linkitä ohjelmaan tämä kirjasto", eli -lSDL_mixer tarkoittaa "linkitä ohjelmaan SDL_mixer".

Kiitos, taas oppii jotain uutta :) Sain ohjelman käynnistymään, mutta eipä kuulu mitään. Onkohan ääni oletuksena 0, vai onko jokin muu väärin mitä ylempänä lähetin? Tuo miley.wav löytyy projektikansiosta.

Metabolix kirjoitti:

Onneksi olkoon, onnistuit ilmeisesti löytämään asetuksista oikean kohdan hyvin epätarkoilla neuvoilla, kun monelle aloittelijalle se on ylivoimaista, vaikka annettaisiin tuo tarkka paikka, jonka omaan viestiisi kirjoitit. :) Tämä ennustaa erittäin hyvää jatkoa.

Toivotaan parasta, ja kiitos =)

Metabolix [26.09.2009 14:03:01]

#

Esimerkissä on muistivuoto, nimittäin avattua tiedostoa ei suljeta lainkaan. Tässä on kaksi korjaustapaa:

// aani = Mix_LoadWAV_RW(SDL_RWFromFile("miley.wav", "rb"), 0);
// Vaihdetaan loppuun ykkönen, jolloin latausfunktio sulkee tiedoston:
aani = Mix_LoadWAV_RW(SDL_RWFromFile("miley.wav", "rb"), 1);
// Tai ladataan yksinkertaisemmin (parempi vaihtoehto):
aani = Mix_LoadWAV("miley.wav");

Lisäksi kannattaa varmuuden vuoksi tarkistaa, että lataus onnistuu:

if (!aani) {
  fprintf(stderr, "Virhe latauksessa.\n");
}

Varsinainen ongelma on, että ohjelma sulkeutuu heti, jolloin ääntä ei ehditä soittaa. Lisää soittamisen perään vaikka SDL_Delay(1000), joka odottaa sekunnin. Tänä aikana ääni ehtii soida.

Toinen virhe on soittamisessa: viimeinen parametri kertoo toistojen määrän, jolloin nolla tarkoittaa "ei toisteta" eli "soitetaan kerran" ja ykkönen tarkoittaa "toistetaan kerran" eli "soitetaan kahdesti".

Macro [26.09.2009 14:26:18]

#

Koitin tuota parempaa vaihtoehtoa, mutta ei auttanut. Ääntä ei kuulu, ja tein tuon SDL_Delay korjauksen.

Metabolix [26.09.2009 14:30:17]

#

Minulla ainakin ääni kuuluu, kunhan lisään delayn juuri ennen Mix_FreeChunk-riviä. Oletko varma, että tiedoston avaus onnistuu? SDL ohjaa fiksusti tulosteen tiedostoon (stdout.txt ja stderr.txt), katsopa, onko siellä jotain virheilmoitusta. (Uudelleenohjauksen saa pois korvaamalla libSDLmain.a:n tällä versiolla.)

Macro [26.09.2009 14:41:46]

#

En ymmärrä mikä nyt menee väärin. Tein juuri niin kuin sanoin, mutta ei tule mitään erroreita - eikä ääntä. Tiedostoihin ei tulostunut mitään, ja kopioin tuon libSDLmain.a:n uudelleen tuonne ja käänsin ohjelman uudelleen. Kummallista, kun minulla ei toimi, mutta sinulla kyllä.

Metabolix [26.09.2009 14:55:49]

#

Onko delay riittävän pitkä? Jos ääni on yli sekunnin, pitäisi laittaa siihenkin suurempi luku, ja muutenkin se on nyt ehkä järkevin säädettävä asetus. Ovatko koneen äänet yleensäkään tarpeeksi kovalla? Toimiiko äänitiedosto jollain oikealla soittimella soitettuna?

Tässä on vielä varmuuden vuoksi koko koodi.

#include <SDL/SDL.h>
#include <SDL/SDL_mixer.h>

int main(int argc, char **argv) {
	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) != 0) {
		fprintf(stderr, "SDL:n alustus ei onnistunut.\n");
		return 1;
	}

	if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) != 0) {
		fprintf(stderr, "SDL_Mixerin alustus ei onnistunut.\n");
		return 1;
	}

	Mix_Chunk *aani;
	aani = Mix_LoadWAV("miley.wav");
	if (!aani) {
		fprintf(stderr, "Tiedoston lataus ei onnistunut.\n");
		return 1;
	}

	if (Mix_PlayChannel(0, aani, 0) == -1) {
		fprintf(stderr, "Soittaminen ei onnistunut.\n");
	}

	SDL_Delay(1000);
	Mix_FreeChunk(aani);
	SDL_Quit();
	return 0;
}

Macro [26.09.2009 15:24:29]

#

Kaikki on kunnossa, mutta mitään ei kuulu. Jos laitan kappaleen normaalisti soimaan, niin se toimii. Kappale on noin 3,5 minuuttia pitkä, joka pitäisi testinä soittaa.


Ohjelmaan pitäisi tulla nämä ominaisuudet:

Lisäksi äänen pitäisi soida taustalla, ettei tuo while(1) silmukka haittaa sen toistoa. Eli, näppäimiä luettaisiin 10 millisekunnin välein (esimerkiksi) mutta se ei vahingoittaisi äänentoistoa.

Metabolix [26.09.2009 15:48:39]

#

No entä jos laitat delayksi minuutin eli 1000 * 60? Äänethän nimenomaan soivat taustalla, ja siksi tuohon silmukattomaan ohjelmaan tarvitaan delay, kun muuten ohjelma loppuisi heti.

Macro [26.09.2009 15:49:50]

#

Koitin sitäkin, ja vieläkin suurempia aikoja, mutta ei auta yhtään mitään. Lisäsin muuten viestiini lisää asioita.

Macro [27.09.2009 17:45:08]

#

Löysin oppaan netistä, ja sain toimimaan. =)

Ohjelmassa pitäisi tietenkin lukea kaikenlaista, niin miten tulostaisin sen helpoiten? Ajattelin, että teen kuvat a-ö.bmp ja sitten teen funktion missä tulostan riviin jokaista kirjainta vastaavan kuvan.

Kun lueskelin noita edellisiä keskusteluita, niin mieleen jäi että fonttikirjastojen käyttö on hankalaa.


Muokkaus:
Koitin tehdä oman ratkaisun ongelmaan. Tämä kuitenkin antaa erroria, tiedän mistä ne johtuu mutten osaa korjata niitä.

#include <SDL/SDL.h>
#include <string>

using namespace std;

const string kirjaimet[] = {"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x",
"y", "z", "å", "ä", "ö", ",", ".", "!", "?"};

void alustus() {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Surface *ruutu = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF);
}


void kirjoita(string teksti, int vasemmalta, int ylhaalta) {

    for(int i = 0; i < teksti.lenght; i++) {
        int x = vasemmalta;
        int y = ylhaalta;

        if(i != 0)
            x = vasemmalta + (15 * i);

        SDL_Surface *kuva = SDL_LoadBMP("./merkit/" + teksti[i] + ".bmp");
        SDL_Rect kohta = {x, y};
        SDL_BlitSurface(kuva, 0, ruutu, &kohta);
        SDL_FreeSurface(kuva);
        SDL_Flip(ruutu);
    }
}

int main(int argc, char **argv) {
    alustus();
    kirjoita("hellou!", 10, 10);
}

Tältä siis näyttää tuo koodini, ja errorit ovat tämän näköisiä:

\kirjoita\main.cpp||In function `void alustus()':|
\kirjoita\main.cpp|10|warning: unused variable 'ruutu'|
\kirjoita\main.cpp||In function `void kirjoita(std::string, int, int)':|
\kirjoita\main.cpp|16|error: 'struct std::string' has no member named 'lenght'|
\kirjoita\main.cpp|23|error: invalid operands of types `const char*' and `const char[5]' to binary `operator+'|
\kirjoita\main.cpp|25|error: `ruutu' was not declared in this scope|
||=== Build finished: 3 errors, 1 warnings ===|

1. Eli, 'ruutu' ikkunaa ei käytetä.
2. Eikö? Milläköhän saadaan sen pituus sitten tietoon, jollei tuolla?
3. Tätä en osaakkaan.

Katkoin vähän tuota merkistötaulua, että ei veny sivu niin paljon.

Metabolix [27.09.2009 18:16:38]

#

Määrittelet muuttujan ruutu funktion sisällä, joten se on paikallinen muuttuja. Se luodaan funktion alussa ja tuhotaan funktion lopussa. Samasta syystä se ei myöskään näy kirjoita-funktiossa, ja siksi täällä taas ilmoitetaan, että nimeä ruutu ei tunneta ("ruutu was not declared in this scope"). Jos siirrät muuttujan määrittelyn funktion ulkopuolelle, koodi toimii sen osalta. (SDL_SetVideoMode kuuluu silti yhä alustusfunktioon.) Muuttujien näkyvyysalueista kerrotaan C++-oppaan 6. osassa.

Rivin 16 virhe taas johtuu kirjoitusvirheestä (oikein: length). Lisäksi length on funktio eikä muuttuja, joten sitä pitää kutsua, eli lisää sen perään sulut kuten funktiokutsuissa yleensäkin. (Valitettavasti olio-ohjelmointia käsittelevät oppaan osat eivät ole vielä valmiita, joten et tällä kertaa saa linkkiä. :))

Rivillä 23 on ongelmana, että teksti[i] on tyypiltään char, joten yhteenlasku ei toimi. Ratkaisuja on kaksi:

// Tehdään alkuosasta string, niin siihen voi lisätä char-arvon:
std::string("alku") + teksti[i];
// Sama toimii toisinkin päin:
"alku" + std::string(teksti[i]);
// Toinen vaihtoehto on substr-funktio: teksti.substr(alku, pituus);
"alku" + teksti.substr(i, 1);

Mistäpä sitä kaikkea voisi aluksi tietää. :) Varsinaiseen ongelmaan eli tekstin tulostamiseen olet kyllä löytänyt aivan oikean ratkaisun. Hyvä puoli tässä tavassa on, että omia fontteja on helppo tehdä piirustusohjelmilla. Huono puoli fonttikirjastoihin verrattuna on, että nyt fontin koko on valmiiksi määrätty.

Edit. Selvisikö muuten, mikä tuossa SDL_mixer-hommassa oikeastaan oli pielessä?

Macro [27.09.2009 18:33:10]

#

Kiitos avusta, Metabolix! =)

Sain vielä kaksi virhettä ja yhden varoitukset:

\kirjoita\main.cpp|8|error: expected constructor, destructor, or type conversion before '(' token|
\kirjoita\main.cpp||In function `void kirjoita(std::string, int, int)':|
\kirjoita\main.cpp|13|warning: comparison between signed and unsigned integer expressions|
\kirjoita\main.cpp|20|error: cannot convert `std::basic_string<char, std::char_traits<char>, std::allocator<char> >' to `const char*' for argument `1' to `SDL_RWops* SDL_RWFromFile(const char*, const char*)'|
#include <SDL/SDL.h>
#include <string>

using namespace std;

//Merkit tässä, otin pois kun levittävät sivua hirveästi

SDL_Init(SDL_INIT_VIDEO);
SDL_Surface *ruutu = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF);

void kirjoita(string teksti, int vasemmalta, int ylhaalta) {

    for(int i = 0; i < teksti.length(); i++) {
        int x = vasemmalta;
        int y = ylhaalta;

        if(i != 0)
            x = vasemmalta + (15 * i);

        SDL_Surface *kuva = SDL_LoadBMP("./merkit/" + teksti.substr(i, 1) + ".bmp");
        SDL_Rect kohta = {x, y};
        SDL_BlitSurface(kuva, 0, ruutu, &kohta);
        SDL_FreeSurface(kuva);
        SDL_Flip(ruutu);
    }
}

int main(int argc, char **argv) {
    kirjoita("hellou!", 10, 10);
}

Metabolix kirjoitti:

Selvisikö muuten, mikä tuossa SDL_mixer-hommassa oikeastaan oli pielessä?

Ei, mutta luulen nyt osaavani tämän. =) Ihmettelen, että miksei siitä kuulunut mitään, mutta ei antanut virhettäkään.

Metabolix [27.09.2009 18:44:29]

#

Et voi siirtää funktiokutsuja (SDL_Init ja SDL_SetVideoMode) funktion ulkopuolelle.

SDL_Surface *ruutu;

void alustus() {
    SDL_Init(...);
    ruutu = SDL_SetVideoMode(...);
}

Toinen virhe tulee siitä, että SDL_LoadBMP ottaa parametrinaan C:n merkkijonon eli taulukollisen char-arvoja. C++:n string osaa palauttaa tällaisen taulukon (tai osoittimen taulukkoon; näistä selitetään C++-oppaan 7. osassa): teksti.c_str(). Kokonaisuudessaan rivi menisi siis näin:

SDL_Surface *kuva = SDL_LoadBMP(("./merkit/" + teksti.substr(i, 1) + ".bmp").c_str());
// tai apumuuttujalla:
std::string apu = "./merkit/" + teksti.substr(i, 1) + ".bmp";
SDL_Surface *kuva = SDL_LoadBMP(apu.c_str());

Olisi muuten järkevämpää ladata tuo kuva vain kerran ohjelman aluksi (funktiossa alustus) ja vapauttaa main-funktion lopuksi. Kuvien lataaminen on nimittäin aika hidasta ja tässä tapauksessa turhaa työtä.

Lisäksi main-funktion loppuun pitäisi laittaa vielä SDL_Quit-kutsu, joka sulkee SDL:n siististi.

Macro [27.09.2009 18:53:47]

#

Kiitos paljon Metabolix! =) Nyt se vielä vilkahtaa.

#include <SDL/SDL.h>
#include <string>

using namespace std;

//Array

SDL_Surface *ruutu;

void alustus() {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Surface *ruutu = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF);
}

void kirjoita(string teksti, int vasemmalta, int ylhaalta) {

    for(int i = 0; i < teksti.length(); i++) {
        int x = vasemmalta;
        int y = ylhaalta;

        if(i != 0)
            x = vasemmalta + (15 * i);

        SDL_Surface *kuva = SDL_LoadBMP(("./merkit/" + teksti.substr(i, 1) + ".bmp").c_str());
        SDL_Rect kohta = {x, y};
        SDL_BlitSurface(kuva, 0, ruutu, &kohta);
        SDL_FreeSurface(kuva);
        SDL_Flip(ruutu);
    }
}

int main(int argc, char **argv) {
    alustus();
    kirjoita("Kiitos avusta Metabolixille!", 10, 10);

    while(true) {
        SDL_PumpEvents();
        const Uint8 * const nappulat = SDL_GetKeyState(0);

        if(nappulat[SDLK_ESCAPE]) break;
        SDL_Delay(10);
    }

    SDL_Quit();
}

Eli, tuon pitäisi tarkistaa nappuloita, että vasta sitten suljetaan kun painetaan Esciä. Tämä SDL_app ikkuna vain vilahtaa.

Sisuaski [27.09.2009 19:04:22]

#

Main-funktio näyttää oikealta, mutta voi olla että ohjelma kaatuu funktiossa kirjoita.

Ainakin yksi mahdollisuus on, että kuvien lataus ei onnistu. Varmista että nuo kuvatiedostot ovat oikeassa paikassa ja kokeile testiksi lisätä tarkistus onko SDL_LoadBMP:n palauttama osoitin NULL, ja jos on niin jätä kyseinen kirjain piirtämättä.

Macro [27.09.2009 19:08:59]

#

Nähtävästi siinä se kaatuu. Konsoliin tulee joka kuvan kohdalla "virhe latauksessa", kun laitoin näin:

if(kuva != NULL) {
    SDL_Rect kohta = {x, y};
    SDL_BlitSurface(kuva, 0, ruutu, &kohta);
    SDL_FreeSurface(kuva);
    SDL_Flip(ruutu);
} else {
    fprintf(stderr, "Virhe latauksessa.\n");
}

Eli, se SDL_LoadBMP ei toimi oikein, palauttaa tyhjän.

Muokkaus:
Keksin miksi se ei ladannut kuvia: merkit kansion piti olla nähtävästi Debug kansiossa.

Ongelmana on vielä se, että ohjelma sammuu ~0,5 sekunnissa.

Metabolix [27.09.2009 19:33:15]

#

Muuten, SDL_Flip kuuluu kutsua vain aina silloin, kun kaikki piirrettävä on piirretty. Esimerkiksi pelissä piirretään aina tausta ja hahmot ja kutsutaan sitten SDL_Flip. Tässä tapauksessa siis kirjoita ensin koko teksti ja kutsu vasta lopuksi SDL_Flip, mieluiten main-funktiossa. Kirjoitin toiseen keskusteluun hienon paperinkääntövertauksen.

Macro [27.09.2009 19:35:44]

#

Kiitos neuvosta, Metabolix. Tuo ei kyllä auttanut silti siihen vilahdukseen. :/

Metabolix [27.09.2009 19:47:36]

#

Löysinpä.

Määrittelet edelleen ruutu-muuttujan alustus-funktiossa. SDL_SetVideoMode-funktion palauttama osoitin tallennetaan paikalliseen muuttujaan, joka häviää funktion lopussa. Globaaliin ruutu-muuttujaan ei siis tallennu mitään, joten ohjelma kaatuu, kun annat piirtofunktiolle tyhjän osoittimen. Ota siis alusta-funktiosta SDL_Surface* pois, niin ruutu tarkoittaa globaalia ruutu-muuttujaa ja ohjelma toimii oikein.

Macro [27.09.2009 19:50:17]

#

#include <SDL/SDL.h>
#include <iostream>
#include <string>

using namespace std;

const string kirjaimet[] = {...}

SDL_Surface *ruutu;

void alusta() {
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Surface *ruutu = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF);
}

void kirjoita(string teksti, int vasemmalta, int ylhaalta) {
    for(int i = 0; i < teksti.length(); i++) {
        int x = vasemmalta;
        int y = ylhaalta;

        if(i != 0)
            x = vasemmalta + (15 * i);

        SDL_Surface *kuva = SDL_LoadBMP(("./merkit/" + teksti.substr(i, 1) + ".bmp").c_str());

        if(kuva != NULL) {
            SDL_Rect kohta = {x, y};
            SDL_BlitSurface(kuva, 0, ruutu, &kohta);
            SDL_FreeSurface(kuva);
        } else {
            cout << "Virhe kuvan ./merkit/" << teksti.substr(i, 1) << ".bmp!\n";
        }
    }
}

int main(int argc, char **argv) {
    alusta();
    kirjoita("abc", 10, 10);

    SDL_Flip(ruutu);
    while(1) {
        SDL_PumpEvents();
        const Uint8 * const napit = SDL_GetKeyState(0);

        if(napit[SDLK_ESCAPE]) break;
        SDL_Delay(10);
    }
    SDL_Quit();
}

Näin se on ollut aika kauan.

Metabolix [27.09.2009 20:30:51]

#

Kuten sanoin, ota SDL_Surface* pois alustus-funktiosta. Näin näytin jo aiemmin.

Macro [27.09.2009 20:32:31]

#

Aa, nyt ymmärsin. Olisi pitänyt lukea uudelleen tuo viestisi. Anteeksi tästä. :D No, nyt se funktio toimii hienosti.

Metabolix [27.09.2009 23:11:32]

#

Esittelyn ja määrittelyn ero ja tarkoitus sekä muuttujien elinaikaan ja näkyvyyteen liittyvät asiat kannattaa opetella alusta asti hyvin. Niitä käsitellään tässä oppaassa. Jos jokin tästä jää epäselväksi, kannattaa ehdottomasti kysyä, niin selvitetään asia ja parannellaan opasta. Asiallinen palaute on aina tervetullutta. :)

Macro [28.09.2009 10:04:55]

#

Yhden ongelman sain vielä aikaan. Kun pitäisi tulostaa muuttuja tekstin sisään, niin en saa sitä tulostettua.

kirjoita("tekstiä" + muuttuja + "tekstiä", vasemmalta, ylhäältä);

Noin tuo antaa virheen.

peli\main.cpp||In function `void kirjoita(std::string, int, int)':|
peli\main.cpp|36|warning: comparison between signed and unsigned integer expressions|
peli\main.cpp||In function `int SDL_main(int, char**)':|
peli\main.cpp|137|error: invalid operands of types `const char*' and `const char[45]' to binary `operator+'|
||=== Build finished: 1 errors, 1 warnings ===|

Metabolix [28.09.2009 10:23:57]

#

Tälläkin kertaa virheenä on, että "teksti" on tyypiltään const char* eli taulukollinen char-muuttujia, ja tällaiselle ei ole määritelty +-operaatiota. Voit käyttää string-muunnosta, jonka neuvoin toisena tapana kuvan polun muodostuksessa. Siinä siis luodaan tekstistä väliaikaisesti string-olio, ja string-olioille on määritelty +-operaatio. Voisit toisaalta myös käyttää alusta asti string-tyyppistä muuttujaa.

Macro [28.09.2009 16:20:06]

#

No, se tässä onkin kummallista. Se antaa tuon virheen, vaikka olen tehnyt neuvojesi mukaan. Eli:

string muuttujani = "tekstiä" + muuttujani2 + "tekstiä";
kirjoita(muuttujani.c_str(), 20, 20);


Sitten vielä toinen kysymys. Kun siihen ohjelmaan ei tule komentoriviä näkymään, niin myös näppäimistöllä kirjoitetut syötteet pitäisi tallentaa. Eli, jos painaa vaikka r (rename), niin pitäisi ilmestyä teksti: Uusi nimi: ja tähän se läpinäkyvä kirjoituskenttä. Kun painaisi vaikka enter, niin se uusi nimi menisi muuttujaan nimi.

Kolmas kysymys. Miten saisin kuvan leveyden tietoon, kun kaikki kuvat eivät ole saman levyisiä.

Metabolix [28.09.2009 16:56:23]

#

Tuossa taas joko "tekstiä" tai muuttujani2 pitäisi olla string, koska plussa on niiden välissä.

Toiseen kysymykseesi saat vastauksia SDL-oppaasta, etsi funktiota SDL_WaitEvent tai SDL_PollEvent. Näppäinten käsittely on tavallaan paljon hankalampaa graafisissa ohjelmissa, koska painallukset eivät itsestään jää talteen vaan niistä vain ilmoitetaan ohjelmalle.

Kuvan leveys selviää SDL_Surface-rakenteen jäsenestä w ja korkeus jäsenestä h, siis kuva->w ja kuva->h.

Macro [28.09.2009 17:09:54]

#

muuttujani2 on int, joten se ei voi olla string. Miten "tekstiä" voi muuttaa stringiksi, kun sehän on stringissä?

Metabolix kirjoitti:

Toiseen kysymykseesi saat vastauksia SDL-oppaasta, etsi funktiota SDL_WaitEvent tai SDL_PollEvent. Näppäinten käsittely on tavallaan paljon hankalampaa graafisissa ohjelmissa, koska painallukset eivät itsestään jää talteen vaan niistä vain ilmoitetaan ohjelmalle.

Nähtävästi ei tullut ajateltua tarpeeksi. Voihan sitä hakea jokaisen erikseen, ja sitten lisätä ne yksi kerrallaan muuttujaan, ja kun painetaan entteriä niin suoritetaan uudelleen nimeäminen.

Metabolix kirjoitti:

Kuvan leveys selviää SDL_Surface-rakenteen jäsenestä w ja korkeus jäsenestä h, siis kuva->w ja kuva->h.

Tämä olisi pitänyt jo arvatakkin. Koska kuvat eivät ole samankokoisia, vaan ne ovat 8-15 pikseliä leveitä, niin ne pitää tietenkin laittaa johonkin tiettyyn kohtaan. En haluaisi tehdä mitään hirveää if-ratkaisua tähän tyyliin:

if(kuva->w == 12) x = vasemmalta + 12;

Ennen se oli helpompi tehdä, kun oli kaikki saman levyisiä, kun ei tarvinnut kuin x = vasemmalta (15 * i), niin saatiin seuraavan kuvan vasemman laidan sijainti.

(Ps. Arvostan suuresti apuanne, Metabolix ja muut!)

Macro [28.09.2009 18:56:37]

#

Haa! Korjasimpas sen nyt. Tässä on, jos joku kaipaa tälläistä. Itse kuvat jäävät kotiläksyksi tehtäväksi. =)

void kirjoita(string teksti, int vasemmalta, int ylhaalta) {
    //Funktion paramentit ovat kirjoitettava teksti, marginaali vasemmalta ja ylhäältä

    //Alustetaan muuttujat x ja y
    int x = vasemmalta;
    int y = ylhaalta;


    //Käydään kaikki kirjaimet yksitellen läpi
    for(int i = 0; i < teksti.length(); i++) {
        //Tämä merkki, mitä pyöritetään silmukassa
        string merkki = teksti.substr(i, 1);

        //Erikoismerkit korvataan toisilla sanoilla, koska kuvan nimi ei voi olla  tyhjä.bmp, ..bmp tai ?.bmp
        if(merkki == " ")
            merkki = "vali";
        else if(merkki == ".")
            merkki = "piste";
        else if(merkki == "?")
            merkki = "kysymys";



        //Ladataan kuva ./merkit/teksti[i].bmp
        SDL_Surface *kuva = SDL_LoadBMP(("./merkit/" + merkki + ".bmp").c_str());

        //Jos kuvan lataus onnistui, piirretään se
        if(kuva != NULL) {
            SDL_Rect kohta = {x, y};
            SDL_BlitSurface(kuva, 0, ruutu, &kohta);
            SDL_FreeSurface(kuva);
        } else {
            //Muutoin tulee virhe
            cout << "Virhe kuvan ./merkit/" << merkki << ".bmp!\n";
        }

        //Laitetaan kuvan leveys seuraavaa kuvaa varten x:ään lisäksi
        x += kuva->w;
    }
}

Macro [29.09.2009 18:54:53]

#

Macro kirjoitti:

muuttujani2 on int, joten se ei voi olla string. Miten "tekstiä" voi muuttaa stringiksi, kun sehän on stringissä?

Esitän tämän vielä kerran, ja sitten seuraavan kysymyksen.

Ohjelmalle pitäisi saada asennusohjelma, jossa voisi valita, minne tiedot "asennetaan", toisin sanoen kopioidaan rename() -funktiolla. Sitten pitäisi vielä pystyä valitsemaan, että laitetaanko pikakuvake työpöydälle ja käynnistysvalikkoon.

Kun tiedän miten saan graafiseen ikkunaan lisättyä toiminnon, että painaa Browse, ja voi valita minne kopioidaan tiedot. Sitten pitäisi vain painaa ok.

Windows pohjaisissa käyttöjärjestelmissä on tämä *.msi asennustiedostot. Olisiko tämä helpoin tapa toteuttaa asennusohjelma, vai tekisikö sen sillä SDL:llä?

Legu [29.09.2009 19:46:10]

#

Macro kirjoitti:

Macro kirjoitti:

muuttujani2 on int, joten se ei voi olla string. Miten "tekstiä" voi muuttaa stringiksi, kun sehän on stringissä?

String streamilla onnistuu, vaikka kysymys olikin epäselvä. Jos siis tarkoituksena on saada vaikkapa luku 123 muotoon "123" (siis stringinä) onnistuu se stringstream-luokalla.

Toi asennusohjelma varmaan kannattaa ottaa valmiina eikä ruveta sitä ite väsäämään. Niitähän löytyy, jos sellasta oikeesti tarvitset. Ihan zippikin käy paremmin kuin hyvin.

vehkis91 [29.09.2009 19:49:17]

#

std::ostringstream os;
std::string tulos;

int muuttujani2 = 2;


os<<muuttujani2;

//nyt muuttujani2 arvo on stringissä tulos.
tulos=os.str();

Macro [29.09.2009 20:08:47]

#

Legu kirjoitti:

Macro kirjoitti:

Macro kirjoitti:

muuttujani2 on int, joten se ei voi olla string. Miten "tekstiä" voi muuttaa stringiksi, kun sehän on stringissä?

String streamilla onnistuu, vaikka kysymys olikin epäselvä. Jos siis tarkoituksena on saada vaikkapa luku 123 muotoon "123" (siis stringinä) onnistuu se stringstream-luokalla.

Ei näin. Siis, katsos Metabolixin viesti ylempäätä, jossa hän sanoo, että miten saadaan toimimaan tuo kirjoita("tekstiä" + muuttuja + "tekstiä");. En vain ymmärtänyt tätä, niin kysyin.

Legu kirjoitti:

Toi asennusohjelma varmaan kannattaa ottaa valmiina eikä ruveta sitä ite väsäämään. Niitähän löytyy, jos sellasta oikeesti tarvitset. Ihan zippikin käy paremmin kuin hyvin.

Millä sanoilla kannattaisi etsiä? En ole tiennytkään, että näitä voi latailla.

Metabolix [29.09.2009 20:11:17]

#

Macro kirjoitti:

Miten "tekstiä" voi muuttaa stringiksi, kun sehän on stringissä?

"tekstiä" on const char*, siis osoitin taulukolliseen char-vakioita, joista viimeisenä on ASCII-koodi 0, jolla merkitään tekstin päättymistä. (Taulukoista, osoittimista ja taulukko-osoittimista kerrotaan C++-oppaassa, kuten varmaankin muistat.) Taulukoilla (tai osoittimilla) ei voi laskea, siis "taulukko + osoitin" ei tee mitään järkevää.

Sen sijaan string on standardikirjaston luokka. Luokassa on määritelty mm. kahden string-olion välinen +-operaatio, jonka tuloksena on kolmas string-olio. Lisäksi on määritelty vastaavat operaatiot string-olion ja char-taulukon välillä sekä se, miten char-taulukko (tai osoitin) kuten "teksti" voidaan muuttaa string-olioksi.

Mikään C++:n teksti tai luku ei ole valmiiksi olio, vaan ne ovat kaikki perustietotyyppejä (int, float, char, char-osoitin, ...). Jos haluaa olion, se täytyy erikseen tehdä, ja tässä tapauksessa siis +-merkin jommallakummalla puolella pitää olla string-olio, jotta tulos olisi oikea.

Macro kirjoitti:

muuttujani2 on int, joten se ei voi olla string.

Voit muuttaa int-muuttujan tekstiksi tai päinvastoin tämän vinkin mukaisesti tekstivirroilla, jotka toimivat kuten cin ja cout mutta joissa syötteenä tai tulosteena on string-olio. Toisaalta kokonaisluvun muuttaminen tekstiksi ja päinvastoin on hyvää harjoitusta, joten voisit tehdä itse funktiot.

int str_to_int(std::string teksti) {
  int luku = 0;
  // Mitä tänne?
  return luku;
}
std::string int_to_str(int luku) {
  std::string teksti;
  // Mitä tänne?
  return teksti;
}

Funktioista ei tule kovin pitkiä: molempiin riittää pari ehtoa ja yksi silmukka. Yhden numeromerkin saa luvuksi ja päinvastoin vähentämällä tai lisäämällä siihen merkin '0' lukuarvon.

// Merkin '0' ASCII-lukuarvo on 48, ja muut numerot seuraavat tätä.
// Siispä '7':n ASCII-lukuarvo on 55.
char c = '7';
int i = c - '0'; // '7' - '0' = 55 - 48 = 7.
char d = i + '0'; // 7 + '0' = 7 + 48 = 55 = '7'

Macro kirjoitti:

Ohjelmalle pitäisi saada asennusohjelma

Sellaisen tekemiseen ei minusta kannata hukata voimiaan. Windowsissa (etenkin uusimmissa versioissa) on sitä paitsi määrätyt paikat, joihin ohjelmat kuuluu asentaa, ja tavat, joilla asennustiedot pitää tallentaa rekisteriin. Asennuspaketit kannattaa siis tehdä siihen suunnitelluilla ohjelmilla. Valitettavasti monet ohjelmista ovat maksullisia tai lisäävät pakettiin omia mainoksiaan, ja ilmaiset ja mainoksettomat vaihtoehdot taas ovat hankalampia käyttää. Vapaan lähdekoodin ohjelma xml2msi vaikutti lupaavalta, tosin käytin asian tutkimiseen huimat kaksi minuuttia. :)

Alkuun on yksinkertaisempaa jakaa omaa tuotosta yhtenä zip-pakettina.

vehkis91 [29.09.2009 20:11:48]

#

EDITTIÄ...

std::ostringstream os;
std::string tulos;

//Muuttujas
int muuttujani2 = 2;


os<<muuttujani2;

//nyt muuttujani2 arvo on stringissä tulos.
tulos=os.str();


std::string tekstia = "kuva";


//Kirjoitetaan teksti
// tässä tapauksessa kuva2 ("kuva" + "2" = "kuva2")
kirjoita(tekstia + tulos);

Eikö tuo muka toimi????


EDIT: Muistaakseni ite tein installerin nsis ohjelmalla.

Macro [30.09.2009 12:38:23]

#

Jes, kiitos näistä! =)

Muokkaus: Eipäs nuo toimineet, edelleen antaa virheen että + operaattori ei kuulu const charille. Miksi muuttujan tyyppi edes on tälläinen, kun aika selvästi ne ovat määritelty stringiksi ja intiksi?

En oikein ymmärrä, että mitä tuolle pitäisi tehdä. Koitin Metabolixin koodivinkkiä ja vehkiksen koodia, mutta eivät toimi - valittavat tuosta samasta + operaattorista.

Metabolix [30.09.2009 15:13:11]

#

Millaiselta koodiriviltä virheilmoitus tulee, ja mitä rivin muuttujat ovat tyypeiltään?

Esimerkiksi "a" + "b" sisältää kaksi const char* -arvoa; kumpikaan näistä ei ole string, kuten äsken selitin. Samanarvoiset laskut lasketaan vasemmalta oikealle, joten riittää, että vasemmassa reunassa on string. Välivaiheet menevät näin:

// string + const char[2], koodi toimii
std::string("a") + "b" + "c" + "d"
    std::string("ab")  + "c" + "d"
        std::string("abc")   + "d"
            std::string("abcd")
// const char* + string, koodi toimii
"a" + std::string("b") + "c" + "d"
    std::string("ab")  + "c" + "d"
        //... sama jatko
// const char[2] + const char[2], koodi ei toimi!
"a" + "b" + "c" + std::string("d")
// Suluilla toimisi:
"a" + ("b" + ("c" + std::string("d")))

Sen sijaan int-muuttujaa ei voi yhdistää tekstiin plussalla, vaan tarvitset funktion, joka muuttaa sen stringiksi, kuten aiemmin mainittiin.


Sivun alkuun

Vastaus

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

Tietoa sivustosta