Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: SDL: Kirjain kerrallaan ilmestyvä teksti

Sivun loppuun

erakko- [26.03.2009 11:10:46]

#

Käytän nyt SDL:n TTF-kirjastoa tehdäkseni "kirjain kerrallaan ilmestyvää" tekstiä, joka omasta mielestäni on erittäin järkevä vaihtoehto jos ei ääninäyttelyitä pysty toteuttamaan. Kuka on pelannut esim. Phoenix Wright pelejä niin tietää mitä nyt ajan takaa...

Jostain kuulin että mieluummin string, kuin char, mutta päätin kuitenkin käyttää charia sillä hetken etsinnän jälkeen en löytänyt funktiota jolla saa string muuttujan merkkijonoa selattua. Muuten tämä toimii, mutta ainoa ongelma on kirjaimien näyttäminen yksi kerrallaan tietyllä aikavälillä.

int counter = 1000;
while(ind <= strlen(text))
{
	if(counter < 0)
	{
		counter = 1000;
		n_text[ind] = text[ind];
		Text(font, &image, cl1, cl2, n_text);
		ind++;
		break;
	}
	else
	{
		counter -= 1;
	}
	Draw(image, screen, 20, 20);
}

Ainakin oman järjenkäyttöni mukaan tämän pitäisi näyttää kirjain kerrallaan, mutta se ei jostain syystä muuta counter-muuttujaa takaisin 1000:een, joten se näyttää koko tekstin kerralla kun counter on alle nollan.

Kiitos jo etukäteen mahdolliselle avulle.

kayttaja-3842 [26.03.2009 14:56:50]

#

erakko- kirjoitti:

Jostain kuulin että mieluummin string, kuin char, mutta päätin kuitenkin käyttää charia sillä hetken etsinnän jälkeen en löytänyt funktiota jolla saa string muuttujan merkkijonoa selattua.

Miten olisi jos esim. käyttäisit stringin "at" jäsenfunktiota?

string str = "Terve";

 //Tämä tulostaa "T"
 cout << str.at(0) << endl;

...muutenkin kannattaa tutustua hieman stringin muihinkin jäsenfunktioihin esim. täällä: http://www.cplusplus.com/reference/string/string/

ville-v [26.03.2009 15:16:59]

#

erakko- kirjoitti:

Kuka on pelannut esim. Phoenix Wright pelejä niin tietää mitä nyt ajan takaa...

Muuuuiiissstttaaa sssiiittteeeeeennn eeettttttäää jjjoootttkkkuuuttt llluuukkkeeevvvaaattt nnnooopppeeeaaammmiiinnn kkkuuuiiinnn sssiiinnnäää jjjooottteeennn ooollliiisssiii kkkiiivvvaaa eeetttttäää kkkeeelllaaauuutttuuummmiiissseeennn vvvoooiiisssiii lllaaaiiittttttaaaaaa pppoooiiisss pppääällltttäää eeetttttteeeiii tttaaarrrvvviiitttssseee tttuuurrrhhhaaauuutttuuuaaa hhhiiitttaaassstttiii iiilllmmmaaaaaannntttuuuvvvaaaaaannn ttteeekkkssstttiiiiiinnn jjjaaa lllääähhhttteeeäää kkkaaahhhvvviiilllllleee...

Gaxx [26.03.2009 16:43:20]

#

Ja itse ongelmaan: char-merkkijonojen kanssa täytyy muistaa käyttää sitä lopettavaa nollatavua. Se on yksi hyvä syy vaihtaa niihin stringeihin.

Ymmärrät myös varmaan sen, että koodinpätkässäsi pyörii niin monta muuttujaa, joiden nimestä ei voi päätellä alkuunkaan niiden tarkoitusta.

Edit: Vähän myös epäilen, että se kyllä muuttaa counter arvon 1000:een, mutta koska nykyajan prossut ovat sen verran nopeita, tuo tuhat on aivan liian pieni tuohon hommaan.

Muutenkin tuollainen prossun tehokkuudesta riippuva nopeus on huono toteutustapa. Muunmuassa SDL_GetTicks() palauttaa ohjelman alusta kuluneiden millisekuntien lukumäärän, jota voisi helposti hyödyntää tässäkin tapauksessa.

TsaTsaTsaa [26.03.2009 16:53:47]

#

erakko- kirjoitti:

Ainakin oman järjenkäyttöni mukaan tämän pitäisi näyttää kirjain kerrallaan, mutta se ei jostain syystä muuta counter-muuttujaa takaisin 1000:een, joten se näyttää koko tekstin kerralla kun counter on alle nollan.

Kyllä se varmasti muuttaa counterin 1000:een, jonka jälkeen se piirtää ilmeisesti koko tekstin tuolla Text-funktiolla image-pintaan ja lopettaa silmukan (break, varmaan continueta olet hakenut). Tuollainen silmukassa pyöriminen ei ole lainkaan hyvä tapa viivytellä, nykykoneilla tuhat kierrosta pyörähtää aika nopeasti. Parempi varmaan olisi käyttää jotain nukkumisfunktiota, vaikkapa SDL_Delay.

Ja tosiaan stringistä saa yksittäisiä kirjaimia indeksoitua ihan samalla tavalla kuin char-taulukostakin.

Tässä mahdollisesti avulias korjausehdotus:

string text = "Teksti stringinä";
for(unsigned int ind = 0 ; ind < text.length() ; ++ind)
{
	n_text[ind] = text[ind];
	// En tiedä miten Text toimii, mutta olettaisin parametrien
	// olevan fontti, piirtopinta, x-koordinaatti, y-koordinaatti ja
	// teksti
	Text(font, &image, cl1, cl2, n_text[ind]);
	// Tässä välissä koordenaatteja pitäisi muuttaa, että seuraava
	// merkki tulee eri kohtaan
	Draw(image, screen, 20, 20);
	SDL_Delay(1000); // Odotellaan sekunti
}

EDIT: Ja jos tuo Text toimii kuten epäilen, tuo n_text-muuttujakin on täysin turha...?

erakko- [27.03.2009 13:31:54]

#

Juu, kiitos avusta. Sovelsin tuota TsaTsaTsaan koodia, ja kääntäjä ainakin tykkäsi. Mutta nyt se antaa Segmentation Faulttia, todennäköisesti juuri tuota Text-funktiota kutsuessa.

Text-funktio siis menee näin:

void Text(TTF_Font *font, SDL_Surface **dest, SDL_Color cl1, SDL_Color cl2, char text[])
{
	*dest = TTF_RenderText_Solid(font, text, cl1);
}

Muutin sen tälläiseksi että se ymmärtäisi string muuttujia:

void Text(TTF_Font *font, SDL_Surface **dest, SDL_Color cl1, SDL_Color cl2, string text)
{
	*dest = TTF_RenderText_Solid(font, text.c_str(), cl1);
}

Tämän jälkeen ongelmaksi muodostui se, että Text-funktiota kutsuessa "text.at(ind)" antaa ainoastaan yhden merkin (ei käynyt tämän vaaditun string parametrin kanssa, ja valitti "invalid convert from char to const char*"), joten muunsin sen "(const char*)text.at(ind)", ja tämän jälkeen kääntäjä ei herjannut mitään. Eli näin käytin Text-funktiota:

Text(font, &image, cl1, cl2, (const char*)text.at(ind));

Mutta, Segmentation Faulttia lykkää, ja missäs muualla kuin näiden joukossa se ongelma piilee. Eilen en vielä tätä asiaa ehtinyt tutkia mutta tänään paneudun tähän asiaan enemmän.

Legu [27.03.2009 14:52:26]

#

Tuollainen suora castaaminen siihen tyyppiin, mihin se vaaditaan, toimii hyvin harvoin. Tarkoitan siis tilannetta "invalid convert from char to const char*". Ei tuossa sovi mennä suin päin castailemaan char -> const char*.

Ongelma on siinä, että string::at palauttaa merkin, siis char:n, vaikka ilmeisesti haluaisit sen palauttavan jotain muuta (osoittimen, siis C-tyylisen merkkijonon char*). En nyt syvällisemmin lähde selittämään, saat ottaa itse selvää osoittimien ja merkkijonojen toiminnasta.

Tässä nyt yksi tapa, jolla tuosta ongelmasta pääsee helposti eroon (selviää muuten ihan kayttaja-3842n linkistä):

// Ei näin ...
Text(font, joku_jännä_muuttuja, cl1, cl2, (const char*)text.at(ind));
// ... vaan näin
Text(font, joku_jännä_muuttuja, cl1, cl2, text.substr(ind, 1));

Ja siis Text-funktio ottaa viimeiseksi parametrikseen string-olion.

erakko- [29.03.2009 20:53:05]

#

Juu, kiitos. Mutta... periaatteessa nyt ainoa mihin tuli muutos on rakenne. Lopputulos ei muuttunut miksikään. Odottaa hetken aikaa, sitten räväyttää koko tekstin esiin.

Tällä hetkellä piirtäminen siis onnistuu tällä tavalla:

int place = 20;

TTF_Font *font = TTF_OpenFont("Tahoma.ttf", 14);
for(unsigned int ind = 0; ind < text.length(); ++ind)
{
	Text(font, &image, cl1, text.substr(ind, 1));
	Draw(image, screen, place, 20);
	place += 9;
	SDL_Delay(10);
}

Muutin SDL_Delayn 10 sillä 1000 oli liian pitkä aika, jos kirjainten ilmestyminen olisi siis se väli mitä sain odotella koko tekstiä ennen.

Metabolix [29.03.2009 20:58:28]

#

Onkohan nyt niin, että sinulla on vasta joskus tuon silmukan jälkeen SDL_Flip?

Saanen huomauttaa myös muistivuodosta: Joka kerta, kun luot kirjaimen, luot sille uuden pinnan image-muuttujaan. Missään vaiheessa et vapauta näitä pintoja. Järkevin ratkaisu olisi luoda kaikki kirjaimet valmiiksi jo ohjelman alussa ja vapauttaa ne ohjelman lopussa. Silmukassa voisit sitten vain piirtää oikean kirjaimen näistä valmiista.

erakko- [01.04.2009 10:03:14]

#

Noniin, kiitos Metabolix. Aluksi luulin ettei tuon SDL_Flipin laittaminen sinne autta yhtään mitään mutta olinkin väärässä. Mutta nyt toimii juuri niin kuin pitääkin.

Muutin myös rakenteen juuri sellaiseksi kun kerroit. Luodaan kokoelma tietueita, jossa jokainen kirjain on erikseen (id:n kanssa, joka on ehkä aika turhaa), ja ne käydään läpi yksitellen. Kuitenkin yksi muistivuoto ongelma tuli esiin, mutta vaikuttaa pahasti toimivuuteen (olion constructorissa):

    //käydään läpi niin pitkään kunnes viimeinen merkki tulee vastaan
	for(int i = 0; i < text.length(); i++)
	{
            //luodaan pinta
		SDL_Surface *image = NULL;
            //lisätään edelliseen pintaan kohdalla oleva kirjain
		Text(font, &image, cl1, text.substr(i, 1));
            //luodaan tietue let johon lisätään uusi pinta ja id
		Letter let = {image, i};
            //lisätään tietua kokoelmaan letters
		letters.push_back(let);
	}

Itselläni ainakin pistää silmään se että koko ohjelma ei toimi jos image vapautetaan silmukan lopussa.

tneva82 [01.04.2009 12:22:14]

#

Hmm. Luot uuden pointterin jokaisessa silmukassa muttet vapauta missään. Muisti vuotaa siis.

Jos *imagen on tarkoitus muutta jokaisella loopilla niin tee taulukko SDL_surfaceja ja käytä loopin indexiä sen käyttöön. Esmes:

SDL_Surface *image=new SDL_Surface[text.length];

Ja sitten vain image[i] missä tarvitset. Lopuksi sitten vapautat kaikki taulukon jäsenet.

Vaihtoehtoisesti pitää vapauttaa image ennen for loopin loppumista mutta tietty jos tuota tarvitaan for loopin jälkeen niin sitten se ei toimi. Missävaiheessa joko käytät yhtä imagea(alustat sen ennen for looppia) tai käytät ennen for looppia luotua taulukkoa image olentoja.

erakko- [01.04.2009 12:38:41]

#

Tein niin että loin pinnan ennen for-silmukkaa, ja vapautan sen silmukan jälkeen. Silmukan lopussa sitten "tyhjennän" tämän pinnan asettamalla sen arvoksi NULL. Ohjelma toimii, eikä ainakaan omissa silmissäni muistivuotoa näy.

eq [01.04.2009 21:16:12]

#

erakko- kirjoitti:

Tein niin että loin pinnan ennen for-silmukkaa, ja vapautan sen silmukan jälkeen. Silmukan lopussa sitten "tyhjennän" tämän pinnan asettamalla sen arvoksi NULL. Ohjelma toimii, eikä ainakaan omissa silmissäni muistivuotoa näy.

Osoittimen nollaaminen (asettaminen NULL:ksi) ei tyhjennä mitään pintaa. Myöskään null-osoittimen vapauttamisesta (mitä siis käsittääkseni silmukan jälkeen nyt teet) SDL_FreeSurfacella ei seuraa mitään. Voit hyvin luoda osoittimen for-silmukan sisällä, sillä C(++) vapauttaa kyllä osoittimen viemän muistin sen esiintymisalueen lopussa (ei toki sen osoittamaa muistialuetta, mitä et haluaisikaan, sillä:)

Et voisikaan vapauttaa pintoja, sillä käytät niitä ilmeisesti myöhemmin (kas kun, miksi muuten tallentaisit ne letters-muuttujaan).

Jos haluat välttää muistivuodon, täytyy letters-taulukon tai -listan (mikä ikinä onkaan) alkioiden sisältämien osoittimien osoittamat SDL_Surface:t vapauttaa ennen muuttujan poistamista.

tneva82 kirjoitti:

SDL_Surface *image=new SDL_Surface[text.length];

En ole toviin (vuosiin) käyttänyt SDL:n piirto-ominaisuuksia, mutta muistaakseni SDL_Surface-funktioiden kanssa hypistellään pelkästään osoittimia. Yllä oleva dynaaminen muistinvaraus SDL_Surfaceille on siinä mielessä turha, että kaikki SDL-funktiot tekevät muistivarauksensa itse, eikä tällaisen taulukon käyttämisestä esimerkiksi TTF_*-funktioiden kanssa ole kätevää tai triviaalia (ts. ensin TTF-funktio luo oman pintansa, joka sitten kopioidaan tähän taulukkoon (memcpy), jonka jälkeen TTF-funktion luoma pinta vapautetaan - ja tämä taulukko myös aikanaan).

Metabolix [02.04.2009 12:03:32]

#

Tässä on vielä mainittuja ja uusiakin korjauksia.

Ei sählätä osoittimilla vaan palautetaan arvo:

SDL_Surface *Text(TTF_Font *font, SDL_Color cl1, string text)
{
    return TTF_RenderText_Solid(font, text.c_str(), cl1);
}

Piirto ja vapautus, jos käytät yo. funktiota piirtovaiheessa:

SDL_Surface *image = Text(font, c1, txt.substr(i, 1));
Draw(image, screen, 20, 20);
SDL_FreeSurface(image);
image = NULL;

Seuraava koodi vastaa esittämääni tapaa, jossa kukin merkki luodaan vain kerran. Toteutus on kuitenkin hieman edistyneempi, nimittäin merkit luodaan vasta, kun niitä tarvitaan, ja säilytykseen käytettävä olio tuhoutuu automaattisesti ohjelman lopussa ja vapauttaa samalla merkit, joten tästäkään ei tarvitse erikseen huolehtia.

#include <stdexcept>
#include <map>
#include <SDL_ttf.h>

class SDL_TTF_char {
	// Kaikki kuvan luontiin vaadittava (fontti, merkki, väri)
	struct SDL_TTF_char_info {
		char c;
		TTF_Font *font;
		SDL_Color color;
		SDL_TTF_char_info(char _c, TTF_Font *_font, SDL_Color _color):
			c(_c), font(_font), color(_color) {}

		// Tehokkaaseen säilytykseen tarvitaan vertailuoperaattori.
		// Sisältö on sinänsä yhdentekevä, kunhan vertailu toimii
		bool operator < (const SDL_TTF_char_info& i) const {
			if (i.c != c) return i.c < c;
			if (i.font != font) return i.font < font;
			if (i.color.r != color.r) return i.color.r < color.r;
			if (i.color.g != color.g) return i.color.g < color.g;
			if (i.color.b != color.b) return i.color.b < color.b;
			return false;
		}
	};
	// Itse merkki sisältää vain pinnan.
	SDL_Surface *surface;

	// Luomiseen käytetään äskeistä infotyyppiä
	SDL_TTF_char(SDL_TTF_char_info info) {
		char str[2] = {info.c, 0}; // 'c' => "c"
		surface = TTF_RenderText_Solid(info.font, str, info.color);
		if (!surface) {
			throw std::runtime_error("Virhe tekstin luonnissa!");
		}
	}
	// Kun merkki tuhotaan, pinta pitää vapauttaa
	~SDL_TTF_char() {
		SDL_FreeSurface(surface);
	}
public:
	// Tämä operaattori mahdollistaa luokan käytön SDL_Surfacen paikalla.
	// Käytetään automaattisesti merkin kuvaa.
	operator SDL_Surface* () {
		return surface;
	}
	// Seuraavalla funktiolla haetaan merkkejä.
	static SDL_TTF_char& get(TTF_Font *font, char c, SDL_Color color) {
		// Säilytykseen oma olio...
		struct SDL_TTF_char_map {
			typedef std::map<SDL_TTF_char_info, SDL_TTF_char*> map_type;
			map_type map;
			// ... joka tuhoaa säilötyt merkit lopussa.
			~SDL_TTF_char_map() {
				for (map_type::iterator i = map.begin(); i != map.end(); ++i) {
					delete i->second;
				}
			}
		};
		// Luodaan kys. olio staattisesti, jolloin se luodaan ohjelman
		// alussa ja tuhotaan ohjelman lopussa.
		static SDL_TTF_char_map chars;

		// Laitetaan merkin tiedot inforakenteeseen
		SDL_TTF_char_info info(c, font, color);
		// Haetaan ja tarvittaessa luodaan merkki.
		if (!chars.map[info]) {
			chars.map[info] = new SDL_TTF_char(info);
		}
		// Palautetaan viittaus merkkiin; säilössähän oli osoittimia.
		return *chars.map[info];
	}
};

Tässä on vielä esittämääni luokkaa ym. rakenteita käyttävä testiohjelma. Muuta ei tarvita kuin SDL_TTF_char::get, pintojen luominen ja vapauttaminen tapahtuvat siististi piilossa.

#include <SDL.h>

int main() {
	SDL_Init(SDL_INIT_VIDEO);
	SDL_Surface *s = SDL_SetVideoMode(640, 480, 32, SDL_DOUBLEBUF);

	// Ladataan fontti
	TTF_Init();
	TTF_Font *f = TTF_OpenFont("xyz.ttf", 16);
	SDL_Color c = {0x33, 0x99, 0x66};
	std::string txt = "Tulostetaan nyt oikein kunnon koetekstit.";

	SDL_Rect r = {0};
	for (std::string::size_type i = 0; i < txt.length(); ++i) {
		SDL_PumpEvents();

		// Haetaan merkki, piirretään se ja siirretään piirtokohtaa.
		SDL_Surface *tmp = SDL_TTF_char::get(f, txt.at(i), c);
		SDL_BlitSurface(tmp, 0, s, &r);
		r.x += tmp->w;

		SDL_Flip(s);
		SDL_Delay(100);
	}
	SDL_Delay(1000);

	// Lopussa vapautetaan fontti
	TTF_CloseFont(f);
	TTF_Quit();
	SDL_Quit();
	return 0;
}

Sivun alkuun

Vastaus

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

Tietoa sivustosta