Kirjoittaja: Metabolix
Kirjoitettu: 14.01.2008 – 14.01.2008
Tagit: koodi näytille, vinkki
Moni tuntuu ajattelevan, että peli on aivan helppo juttu. Moni myös alkaa tehdä peliä mutta törmää jo kohta vaikeuksiin: Miten tämän saa toimimaan samalla nopeudella kaikilla? Miten saa ammuksen lentämään ja ukon liikkumaan samaan aikaan? Miten tarkistetaan, törmäävätkö asiat? Osaavammat vain kysyvät takaisin: Miten oikein kuvittelet tuon toimivan, kun tuolla on tuollainen silmukka? Miksi teit noin, jos et halua, että se toimii noin? Ja ennen kaikkea: Miten tuosta saa mitään selvää, kun kaikki koodi on samassa funktiossa? Mikset koodaa paremmin? Tämän vinkin tarkoitus on tarjota aloittelevalle pelintekijälle hieman ohjeita siitä, miten peliä voi lähteä rakentamaan laajennettavaksi ja toimivaksi kokonaisuudeksi.
Yksi yleisimpiä virheitä on heikko suunnittelu ja siitä johtuva umpikuja. Jos asioita ei mieti tarpeeksi pitkälle, voi myöhemmin huomata, ettei aiempi koodi taivukaan aivan siihen, mihin pitäisi. Ei kuitenkaan kannata ruveta hienostelemaan liikaa, nimittäin liian venyväksi suunniteltu projekti hajoaa helposti käsiin.
Peliä tehdessä on myös erittäin hyödyllistä panostaa koodin selkeyteen. Tämän kannalta oleellista on jakaa ohjelma selviin toiminnallisiin osiin: alustukset omaan funktioonsa, sulkemistoiminnot toiseen, logiikka kolmanteen ja grafiikka neljänteen. Kun jokin funktio venyy yli viidenkymmenen rivin mittaiseksi, on hyvä ruveta miettimään, josko sen voisi edelleen jakaa pienempiin osasiin. Liiallisuuksiin ei silti tässäkään pidä mennä.
Ohjelman osittelun hyötynä on myös se, että järkevää funktiota voi käyttää monessa paikassa, jolloin samaa koodia ei tarvitse kirjoittaa aina uudelleen. Hyvä esimerkki on törmäystarkistus: Yleensä kaikkia pelin hahmoja koskevat samat rajoitteet, samat seinät ja samat muut esteet. Törmäystarkistukseen kelpaa siis sama koodi — kunhan jollain tavalla huolehditaan siitä, ettei otus vahingossa törmää itseensä. Tarvittaessa tästä funktiosta pitää tietysti vielä välittää tieto siitä, mitkä asiat törmäsivät.
Reaaliaikaisessa pelissä on tärkeää, että asiat tapahtuvat samalla nopeudella tietokoneen tehosta riippumatta. Tähän on olemassa helppo ja vaikea tapa. Vaikea tapa edellyttää, että hallitsee hieman fysiikan lakeja ja matematiikkaa, jotta voi suhteuttaa kaiken kuluneeseen aikaan. Esimerkiksi uutta sijaintia laskettaessa nopeus pitää kertoa kuluneella ajalla, ja vastaavasti uutta nopeutta laskettaessa kerrotaan kiihtyvyys. Lisäksi pitää vielä huomioida se, että keskinopeus framen aikana on alku- ja loppunopeuksien keskiarvo, ei kumpikaan arvo suoraan. Tämä taas mutkistaa sitä, kuinka hahmon käytös törmäyksen yhteydessä pitäisi käsitellä, ja...
Jos tämä kaikki tuntuu hajottavan pään, voi tehdä asiat yksinkertaisemmalla tavalla ja säätää yhden fysikaalisen framen vakiomittaiseksi, jolloin samat virheet tapahtuvat kaikilla ja yksinkertaisetkin laskut riittävät. Koodi sisältää esimerkit molemmista tavoista, mutta laskut on tietenkin toteutettu mutkikkaamman mukaan. Käytännössä on usein parempi käyttää vakiomittaista aika-askelta, koska valinnan vaikutus pelin tehokkuuteen on pienehkö mutta ohjelmoinnin tehokkuuteen hyvin suuri.
Seuraava koodi on pelin runko, jossa on nuolilla liikuteltava hahmo, muutama tekoälyn ohjaama vastustaja ja tason ympärillä seinä. Huomionarvoisia kohtia ovat erityisesti ne funktiot, joissa kutsutaan monia muita funktioita. Esimerkiksi main-funktiosta on tehty mahdollisimman yksinkertainen, jotta ohjelman normaali kulku käy selväksi. Samoin alustusfunktio kutsuu edelleen eri osien alustusfunktioita, ja toimintafunktiosta käsin kutsutaan tekoälyfunktiota ja varsinaista olion toimintafunktiota. Piirto ja logiikka on erotettu toisistaan, kuten hyviin tapoihin kuuluu.
Toimiakseen ohjelma tarvitsee kuvat tausta.bmp (640x480), ukko.bmp (32x32) ja vihollinen.bmp (64x64). Ohjelma käyttää grafiikan piirtoon SDL:ää.
main.c
#include "peli.h" #include <stdio.h> #include <stdlib.h> #include <time.h> void alusta(void); /** * funktio main - ohjelman pääfunktio **/ int main(int argc, char **argv) { float dt; alusta(); while (viestit() == 0) { dt = kulunut_aika(); /* fps_framen_vaihto(dt); */ toiminta(dt); piirto(); } return 0; } /** * funktio alusta - kaikki kuntoon aluksi **/ void alusta(void) { srand(time(NULL)); if (alusta_systeemi() != 0) { printf("Virhe grafiikan alustuksessa!\n"); exit(1); } if (alusta_peli() != 0) { printf("Virhe pelin alustuksessa!\n"); exit(1); } /* Nollataan aikalaskuri */ kulunut_aika(); }
peli.h
#ifndef _PELI_H #define _PELI_H 1 /* Helppo vai vaikea tapa fysiikan mallintamiseen? */ #define VAKIOFRAMEMENETELMA 1 #if VAKIOFRAMEMENETELMA #define FRAMEN_PITUUS (0.01) /* 10 ms frameja */ #else #define FRAMEN_PITUUS (0.1) /* Ei yli 100 ms frameja */ #endif /* Näyttötilaa varten tiedot */ #define RUUTU_W (640) #define RUUTU_H (480) #define RUUTU_BPP (8) /* Paljonko yhden yksikön muutos sijainnissa tekee pikseleinä? */ #define TILE_W (32) #define TILE_H (32) /* Otusten määrä */ #define VIHOLLISIA (4) #define OTUKSIA (VIHOLLISIA + 1) /* Ukkokin on otus */ /* Lista kuvista */ enum KUVA_ENUM { KUVA_TAUSTA, KUVA_UKKO, KUVA_VIHOLLINEN, KUVIEN_LKM /* Määrä saadaan kätevästi näin */ }; #define KUVATIEDOSTO_TAUSTA ("tausta.bmp") #define KUVATIEDOSTO_UKKO ("ukko.bmp") #define KUVATIEDOSTO_VIHOLLINEN ("vihollinen.bmp") /** * Rakenne otusten tietoja varten **/ struct otus { float sx, sy; /* Sijainti */ float vx, vy; /* Nopeus */ short ax_ohjaus, ay_ohjaus; /* Kiihdyttääkö (painetaanko nappeja)? */ float vmax, amax; /* Maksiminopeus ja -kiihtyvyys */ float r; int kuva; }; /** Systeemin funktiot mainia varten **/ extern int alusta_systeemi(void); extern int viestit(void); extern void piirto(void); extern float kulunut_aika(void); /** Pelin toiminnalliset funktiot mainia varten **/ extern void toiminta(float dt); extern int alusta_peli(void); /* Apufunktio desimaaliluvun arvontaan */ extern float randf(float min, float max); /** Otukset piirtoa varten **/ extern struct otus otukset[OTUKSIA]; #endif
systeemi.c
#include "peli.h" #include <SDL.h> /* Tai SDL/SDL.h, jos kääntäjän asetukset ovat pielessä */ #include <stdlib.h> /** * Tärkeät kuvapinnat **/ SDL_Surface *ruutu, *kuvat[KUVIEN_LKM] = {0}; /** * Tieto ladatuista asioista, jotta ne osataan vapauttaa lopuksi; * katso 'alusta' ja 'lopeta' **/ int alustuksen_tila = 0; /** * Esittelyjä **/ int lataa_kuva(int i, const char *nimi); void lopeta_systeemi(void); /** * funktio viestit - hakee viestit SDL:ltä * 0 == kaikki kunnossa, -1 == saatiin SDL_QUIT (eli rastia painettiin) tai painettiin esc. **/ int viestit(void) { SDL_Event e; int q = 0; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) { q = -1; } if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { q = -1; } } return q; } /** * funktio piirto - piirtää ruudulle, mitä pitääkin **/ void piirto(void) { int i; SDL_Rect paikka; /* Taustakuva */ SDL_BlitSurface(kuvat[KUVA_TAUSTA], 0, ruutu, 0); /* Otukset */ for (i = 0; i < OTUKSIA; ++i) { paikka.x = otukset[i].sx * TILE_W - (kuvat[otukset[i].kuva]->w / 2); paikka.y = otukset[i].sy * TILE_H - (kuvat[otukset[i].kuva]->h / 2); SDL_BlitSurface(kuvat[otukset[i].kuva], 0, ruutu, &paikka); } SDL_Flip(ruutu); } /** * funktio lataa_kuva - lataa kuvan kuvat-taulukkoon **/ int lataa_kuva(int i, const char *nimi) { kuvat[i] = SDL_LoadBMP(nimi); if (!kuvat[i]) { printf("SDL_LoadBMP(%s): %s\n", nimi, SDL_GetError()); return -1; } SDL_SetColorKey(kuvat[i], SDL_SRCCOLORKEY, SDL_MapRGB(kuvat[i]->format, 0xff, 0, 0xff)); return 0; } /** * funktio alusta_systeemi - käynnistetään SDL ja ladataan kuvat **/ int alusta_systeemi(void) { if (alustuksen_tila) { printf("Alusta vain kerran!\n"); return -1; } atexit(lopeta_systeemi); if (SDL_Init(SDL_INIT_VIDEO) != 0) { printf("SDL_Init: %s\n", SDL_GetError()); return -2; } alustuksen_tila = 1; /* Merkitään, että on alustettu */ if (!(ruutu = SDL_SetVideoMode(RUUTU_W, RUUTU_H, RUUTU_BPP, SDL_DOUBLEBUF))) { printf("SDL_SetVideoMode: %s\n", SDL_GetError()); return -3; } if (lataa_kuva(KUVA_UKKO, KUVATIEDOSTO_UKKO)) return -4; if (lataa_kuva(KUVA_TAUSTA, KUVATIEDOSTO_TAUSTA)) return -5; if (lataa_kuva(KUVA_VIHOLLINEN, KUVATIEDOSTO_VIHOLLINEN)) return -6; return 0; } /** * funktio lopeta_systeemi - suljetaan SDL ja vapautetaan kuvat **/ void lopeta_systeemi(void) { int i; if (!alustuksen_tila) { /* Ei suljettavaa */ return; } for (i = 0; i < KUVIEN_LKM; ++i) { if (kuvat[i]) { SDL_FreeSurface(kuvat[i]); } } SDL_Quit(); alustuksen_tila = 0; } /** * funktio kulunut_aika - palauttaa aina edellisestä kutsusta kuluneen ajan **/ float kulunut_aika(void) { /* Pysyviä muuttujia */ static Uint32 t1, t2; const Uint32 vakioframen_pituus = (Uint32)(FRAMEN_PITUUS * 1000); /* Aika viimeksi = viime kerran uusi aika */ t1 = t2; #if VAKIOFRAMEMENETELMA t2 = SDL_GetTicks(); /* Jos frame ei ole vielä täynnä, nukutaan */ if ((t2 - t1) < vakioframen_pituus) { SDL_Delay(vakioframen_pituus - (t2 - t1)); } t2 = SDL_GetTicks(); #else /* Haetaan uusi aika */ t2 = SDL_GetTicks(); if (t1 == t2) { /* Ei kelpuuteta 0 ms framea */ SDL_Delay(1); t2 = SDL_GetTicks(); } #endif /* Erotus ja muunto sekunteihin */ return (t2 - t1) / 1000.0; }
peli.c
#include "peli.h" #include <SDL.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> /* Vakio, joka säätää tekoälyn suunnanmuutostodennäköisyyttä */ #define ALYVAKIO (0.1) /** * Rakenne maailman reunoja varten **/ struct rajat_t { float x0, y0, x1, y1; }; const struct rajat_t maailma = { 1.5, 1.5, RUUTU_W / TILE_W - 1.5, RUUTU_H / TILE_H - 1.5 }; /** * Otukset **/ struct otus otukset[OTUKSIA]; struct otus * const ukko = &otukset[0]; /* Ukko on ensimmäinen otus */ struct otus * const viholliset = &otukset[1]; /* Viholliset ovat otustaulussa 1-n */ /** * Funktioiden esittelyt **/ void oikea_toiminta(const float dt); void otus_toimii(const float dt, struct otus * const otus); int tormays(struct otus * const otus); void tekoaly(const float dt, struct otus * const otus); void laske_otuksen_kiihtyvyys(float ax_ohj, float ay_ohj, float vx, float vy, float amax, float vmax, float dt, float *ax_ptr, float *ay_ptr); /** * funktio alusta_peli - asetetaan ukkojen paikat jne. **/ int alusta_peli(void) { int i; /* Nollaus on hyvä tapa */ memset(otukset, 0, sizeof(otukset)); /* Ukon asetukset */ ukko->kuva = KUVA_UKKO; ukko->sx = 5; ukko->sy = 5; ukko->amax = 8.1; ukko->vmax = 8.1; ukko->r = 0.5; for (i = 0; i < VIHOLLISIA; ++i) { viholliset[i].amax = 5.2; viholliset[i].vmax = 5.2; viholliset[i].r = 1; viholliset[i].kuva = KUVA_VIHOLLINEN; while (tormays(&viholliset[i])) { viholliset[i].sx = randf(maailma.x0, maailma.x1); viholliset[i].sy = randf(maailma.y0, maailma.y1); } } return 0; } /** * funktio toiminta - tämä kutsuu alempaa toimintafunktiota * @dt : kulunut aika sekunneissa **/ void toiminta(float dt) { #if VAKIOFRAMEMENETELMA /* Kaikilla samaan tulokseen pääsee, kun tekee framesta vakiomittaisen. Tällöin ei tarvitse vaivata päätään mutkikkailla kaavoilla siitä, miten framen pituuden vaihtelun vaikutus kumottaisiin. */ static float t; t += dt; /* Aikaa varastoon */ while (t > FRAMEN_PITUUS) { /* Pyöritetään vain kokonaisia frameja */ oikea_toiminta(FRAMEN_PITUUS); t -= FRAMEN_PITUUS; } #else /* Jos laskuja on oikein paljon (esimerkiksi raskasta 3D-geometriaa), niin hitaille koneille on kevyempää tehdä laskut vain kerran jokaista näkyvää framea kohden. Jos kone on erittäin hidas, kerralla liikuttavat matkat kasvavat valtaviksi ja sopivanlainen törmäystarkistus saattaa päästää hahmon kerralla esteen toiselle puolelle asti. Siksi framen pituus on usein hyvä rajoittaa johonkin mielekkääseen maksimiin. Tämä menetelmä ei kuitenkaan ole oleellisesti tehokkaampi normaaleissa tapauksissa, ja ainakaan ohjelmoijan kannalta se ei missään nimessä ole ollenkaan tehokas. Lisäksi tämä tapa aiheuttaa hyvin luultavasti ylimääräistä epätarkkuutta laskuihin, vaikka toteutus olisikin hyvä. Kaikkea ei voi ratkaista paperilla tarkasti. */ while (dt > FRAMEN_PITUUS) { oikea_toiminta(FRAMEN_PITUUS); dt -= FRAMEN_PITUUS; } /* Ajetaan vielä osittainenkin frame */ oikea_toiminta(dt); #endif } /** * funktio oikea_toiminta - pelin varsinaiset tapahtumat * @dt : edettävä aika sekunneissa **/ void oikea_toiminta(const float dt) { const Uint8 *napit; int i; /* Reagoidaan käyttäjän tekosiin: haetaan liikesuunnat */ napit = SDL_GetKeyState(0); ukko->ax_ohjaus = (napit[SDLK_RIGHT] ? 1 : 0) - (napit[SDLK_LEFT] ? 1 : 0); ukko->ay_ohjaus = (napit[SDLK_DOWN] ? 1 : 0) - (napit[SDLK_UP] ? 1 : 0); /* Tekoäly vihollisille */ for (i = 0; i < VIHOLLISIA; ++i) { tekoaly(dt, &viholliset[i]); } for (i = 0; i < OTUKSIA; ++i) { otus_toimii(dt, &otukset[i]); } } /** * funktio otus_toimii - otuksen toiminta ohjauksen mukaan * @dt : kulunut aika sekunneissa **/ void otus_toimii(const float dt, struct otus * const otus) { float s1x, s1y, s2x, s2y; float ax, ay; float p, v; laske_otuksen_kiihtyvyys( otus->ax_ohjaus, otus->ay_ohjaus, otus->vx, otus->vy, otus->amax, otus->vmax, dt, &ax, &ay /* Tallennetaan näihin */ ); /* Tässä vaiheessa pitää laskea uusi sijainti ja ottaa vanha talteen */ s1x = otus->sx; s1y = otus->sy; /* s = s0 + v0 * t + 0.5 * a * t^2 */ s2x = s1x + dt * (otus->vx + ax * dt / 2); s2y = s1y + dt * (otus->vy + ay * dt / 2); /* Päivitetään nopeuskin */ otus->vx += ax * dt; otus->vy += ay * dt; /* Ei liian kovaa vauhtia */ v = hypot(otus->vx, otus->vy); if (v > otus->vmax) { abort(); otus->vx *= otus->vmax / v; otus->vy *= otus->vmax / v; } /* Liikutaan osuus p matkasta; p pienenee, kunnes liikkuminen onnistuu */ for (p = 1; p > 0.05; p /= 2) { otus->sx = s1x + p * (s2x - s1x); otus->sy = s1y + p * (s2y - s1y); if (!tormays(otus)) { /* Liikkuminen onnistui! Silmukka loppuu. */ break; } /* Törmättiin, palataan takaisin ja yritetään lyhyemmällä matkalla */ otus->sx = s1x; otus->vx /= 2; otus->sy = s1y; otus->vy /= 2; } } /** * funktio tekoaly - satunnaista toimintaa muille otuksille * @dt : edettävä aika sekunneissa **/ void tekoaly(const float dt, struct otus * const otus) { /* Arvotaan jotain toimintaa */ if (randf(0, ALYVAKIO) < dt) { otus->ax_ohjaus = rand() % 3 - 1; } if (randf(0, ALYVAKIO) < dt) { otus->ay_ohjaus = rand() % 3 - 1; } } /** * funktio tormays - tarkistetaan otuksen törmäys **/ int tormays(struct otus * const otus) { int i; float dx, dy; /* Ei saa mennä reunan yli */ if (otus->sx <= maailma.x0) return 1; if (otus->sx >= maailma.x1) return 1; if (otus->sy <= maailma.y0) return 1; if (otus->sy >= maailma.y1) return 1; /* Ei saa törmätä muihin */ for (i = 0; i < OTUKSIA; ++i) { if (otus == &otukset[i]) { /* Ei törmää itseensä */ continue; } /* Oletetaan pyöreät otukset, tarkistetaan etäisyys */ dx = otus->sx - otukset[i].sx; dy = otus->sy - otukset[i].sy; if (hypot(dx, dy) <= otus->r + otukset[i].r) { return 1; } } /* Ei törmätty */ return 0; } /** * funktio laske_otuksen_kiihtyvyys - toimii nimensä mukaan, paluuarvot osoitinten avulla. **/ void laske_otuksen_kiihtyvyys(float ax_ohj, float ay_ohj, float vx, float vy, float amax, float vmax, float dt, float *ax_ptr, float *ay_ptr) { float ax, ay, p; float a, v; if (ax_ohj) { /* Kiihdytään ohjauksen mukaan */ ax = ax_ohj; } else { /* Ei ohjata, joten hidastutaan nopeutta vastaan */ ax = -vx / dt; if (abs(ax) > 1) { ax = (ax < 0) ? -1 : 1; } } /* Jos olisi painovoima, asetettaisiin ay:hyn vain sen mukaan arvo. */ /* Tämä peli kuvataan ylhäältä, joten y-suunnassakin voi liikkua itse. */ if (ay_ohj) { ay = ay_ohj; } else { ay = -vy / dt; if (abs(ay) > 1) { ay = (ay < 0) ? -1 : 1; } } ax *= amax; ay *= amax; /* Ei liian kiihtyvästi */ a = hypot(ax, ay); if (a > amax) { ax *= amax / a; ay *= amax / a; } /* Ei kiihdytetä maksiminopeuden yli */ v = hypot(vx + ax * dt, vy + ay * dt); if (v > vmax) { p = (sqrt(ay * ay * (vmax * vmax - vx * vx) + 2 * ax * ay * vx * vy + ax * ax * (vmax * vmax - vy * vy)) - (ax * vx + ay * vy)) / ((ax * ax + ay * ay) * dt); ax *= p; ay *= p; } *ax_ptr = ax; *ay_ptr = ay; } /** * funktio randf - satunnainen desimaaliluku tietyltä väliltä **/ float randf(float min, float max) { return min + (rand() / (float)RAND_MAX) * (max - min); }
Sultahan pitkiä koodeja tulee..:P
toimiiks ton perus periaate jos tossa käyttäis c++:aa ja luokkia?
aika hyvältä vaikuttaa.
Hyvä ja selkeä koodiesimerkki. Ei mitään erityisen vaikeaa, mutta silti opettavaa. Aloittelijoille loistopätkä!
Sama esimerkki vielä oliopohjalla niin olisi täydellinen. :P
ByteMan kirjoitti:
toimiiks ton perus periaate jos tossa käyttäis c++:aa ja luokkia?
Toimii nyt kai kun muutat kaiken muunkin käyttämään luokkia.... tai jotain...
En jaksanut lukea (myönnän, olen laiska), mutta hyvältä kyllä näyttää :)
Mitä veikkaatte, kuinka moni aloittelija lähtee heti yrittämään 3D-peliä nähtyään tämän?
Metabolix: Jälleen kerran erinomainen vinkki.
Joo, vinkki hieman venähti, kun piti jotain toimintaakin saada mukaan. 450 rivin kohdalla luovuin ajatuksesta, että lisäisin esimerkkiin vielä esteitä ja ammuksia.
ByteMan kirjoitti:
toimiiks ton perus periaate jos tossa käyttäis c++:aa ja luokkia?
Toki. Tällöin voisi main-funktiossa kutsua ohjelmaolion pääsilmukkafunktiota, joka taas voisi näyttää vaikka jokseenkin tältä (vielä ylimääräisellä äänisysteemillä):
int ohjelma::silmukka() { float dt; while (jarjestelma->hoida_viestit()) { dt = jarjestelma->kulunut_aika(); if (tila == PELI) { peli->toiminta(dt); } else if (tila == VALIKKO) { valikko->toiminta(dt); } else { // Virheellinen tila, palataan valikkoon tila = VALIKKO; valikko->alusta(); } grafiikka->piirra(tila); aanet->puskuroi(dt); } return 0; }
Valitsin kieleksi C:n, jotta vinkkiä olisi helpompi lähestyä, vaikkeivat oliot olisikaan tuttuja. Oleellinen muutos C:stä C++:aan on oikeastaan se, että koodi ja data tulee kapseloitua vielä kätevämmin ja selkeämmin erillisiin toiminnallisiin yksiköihin, jolloin kokonaisuuksia on usein helpompi hahmottaa. Sinänsä olio-ohjelmointikin onnistuu C:llä aivan samoin kuin C++:lla, kun vain jäsenfunktiot a->f(x) muutetaan C:lle kelpaavaan muotoon f(a, x).
Hieno vinkki todellakin.
Mikäs on vikana. kun dev-cpp herjaa tälläistä, kun yrittää käynnistää:
[Linker error] undefined reference to `SDL_PollEvent' [Linker error] undefined reference to `SDL_UpperBlit' ...
SDL on kuitenkin asennettu.
Asennettu ehkä muttei linkitetty mukaan ohjelmaan. Asiaa on puitu ainakin täällä:
https://www.ohjelmointiputka.net/keskustelu/
Hakusanalla "-lSDL" löytyy lisää keskusteluja.
Sain toimimaan. Tää tosin sopisi enemmän ralli- kuin roolipelin rungoksi, kun tässä on nää kiihtyvyys- ja kitkasysteemit.
Kiihtyvyys ja kitka eivät varsinaisesti kuulu itse runkoon vaan ovat tässä vain demonstroimassa perusajatusta siitä, miten pelin toiminta tapahtuu joka framella suhteutettuna aikaan.
55 [Warning] converting to `Sint16' from `float'
56 [Warning] converting to `Sint16' from `float'
In function `int alusta_systeemi()':
86 `atexit' undeclared (first use this function)
Tämmöiset antaa kääntäjä kun koitan kääntää esimerkkikoodia. Mistähän on kyse? Koitin itse selvitellä mutta enpäs juuri päässyt puusta pitkään.
Virhe johtuu ilmeisesti stdlib.h-otsikon puuttumisesta systeemi.c-tiedostossa (lisäsin). SDL-versiosi ei siis kaiketi sisällytä sitä. Varoitukset taas johtuvat siitä, ettei kyseisillä riveillä (piirto-funktiossa) ole erikseen mainittu, että tulos halutaan muuttaa kokonaisluvuksi. Kääntäjä varoittaa asiasta, koska kyseessä saattaisi olla ohjelmoijan virhe. Tässä muunnos on kuitenkin täysin tarkoituksellinen. Varoitukset saa pois muuttamalla kääntäjän asetuksia tai lisäämällä koodiin erillisen muunnoksen.
mainittakoot nyt vielä, että ainakin itsellä piti lisätä myös vielä memory.h, että memset tuolla jossainkohtaa toimi -.-
tuo nyt tosin ei kai ollut mitenkään järin oleellinen tieto..
Hieno koodivinkki ja käytinkin tuota vakioframen menetelmää omassa pelissäni. Huomasin kuitenkin samalla, että tällä menetelmällä pelin FPS on aina korkeintaan hieman alle tavoiteltu FPS.
kulunut_aika funktiota muuttamalla tähän malliin sain FPS pysymään tuossa tavoitemäärässä:
float kulunut_aika(void) { /* Pysyviä muuttujia */ static Uint32 t1, t2; const Uint32 vakioframen_pituus = (Uint32)(FRAMEN_PITUUS * 1000); /* Aika viimeksi = viime kerran uusi aika */ Uint32 aika_viimeksi = t2; t2 = SDL_GetTicks(); /* Jos frame ei ole vielä täynnä, nukutaan */ if ((t2 - t1) < vakioframen_pituus) { SDL_Delay(vakioframen_pituus - (t2 - t1)); t1 += vakioframen_pituus; } else { t1 = t2; } t2 = SDL_GetTicks(); /* Erotus ja muunto sekunteihin */ return (t2 - aika_viimeksi) / 1000.0; }
Jouduin nyt lisäämään uuden muuttujan kun en keksinyt muuta keinoa tehdä tuota ja tässä ei t2 tarvitsisi sinänsä olla enää pysyvä myöskään. Onko tässä jotain haittapuolia verrattuna tuohon vinkissä olevaan? Rasitustesteissä tuntui pelin toiminta toimivan oikein ja normaalitilanteessa fps pysyi tasaisesti 50:ssä (0.02 framen pituudella).
Edit: Huomasin tuossa sen, että rasituksen jälkeen peli alkoi käyttää täydet prosessoritehot ruudun piirtämiseen niin kauan kunnes saavuttanut keskimäärin tuon tavoite-fps:n. Nyt else tehty sitä varten ja
t1 += vakioframen_pituus;
rivi siirretty.