Grafiikkakurssi 2
Tämä grafiikkakurssi 2 on jatkoa edelliselle grafiikkakurssilleni, 2D-grafiikan alkeet Allegroa käyttäen. Tässä kakkososassa menemme hieman pidemmälle: opettelemme käyttämään Allegron BITMAP:pejä sekä pcx- ja bmp-kuvia. Lopuksi luomme pienen katsauksen peleissä käytettyihin kikkoihin. Tämäkin on jo vanha tutoriaali, mutta edelleen ajankohtainen.
3.12.2001 julkaistun artikkelin on kirjoittanut jalaine.
Tämä grafiikkakurssi 2 on jatkoa edelliselle grafiikkakurssilleni, 2D-grafiikan alkeet Allegroa käyttäen. Ykkösosassa opetttelimme yksinkertaisia piirtofunktioita ja piirsimme kuvia. Tässä kakkososassa menemme hieman pidemmälle. Opettelemme käyttämään Allegron BITMAP:pejä ja piirustusohjelmilla piirrettyjä pcx- ja bmp-kuvia. Lopuksi luomme pienen katsauksen peleissä käytettyihin kikkoihin.
Virkistetään muistia #
Oletan, että olet lukenut ykköskurssin grafiikasta ja käyn siksi nämä perusasiat Allegron käyttämisestä vain nopeasti läpi. Jos kaipaat tarkempaa selvitystä, löydät sen artikkelista, 2D-grafiikan alkeet Allegroa käyttäen.
[DEFTABLE]
[D]allegro_init();[/D]
[E]Tämä funktio alustaa Allegron niin, että se on valmis käytettäväksesi. Muista kutsua tätä ennen kuin käytät muita Allegron funktioita[/E]
[D]set_gfx_mode();[/D]
[E]Tällä funktiolla asetat grafiikkatilan haluamaksesi.[/E]
[D]allegro_exit();[/D]
[E]Muista laittaa tämä funktiokutsu ohjelmasi loppuun. Se lopettaa Allegron käytön.[/E]
[D]install_keyboard();[/D]
[E]Jos haluat käyttää Allegron näppäimistöfunktioita, kutsu tätä funktiota ohjelmasi alussa.[/E]
[/DEFTABLE]
Nuo ovat ne asiat, jotka sinun on ykkösosasta muistettava, muita pohjatietoja et tarvitse. Ja sitten asiaan!
Mitä ovat bitmapit? #
Kurssin edellisessä osassa en vielä viitsinyt sekoittaa sinua bitmapeillä, mutta silti tietämättäsi (tai tietoisesti) niitä käytit. Nimittäin aina kun kirjoitit johonkin kohtaan screen käytit yhtä bitmappien erikoistyyppiä, joka esittää näyttöä. Mutta eiköhän selvitetä tämä bitmap-asia nyt alusta alkaen...
Allegrossa oikeastaan kaikkien grafiikkafunktioiden pohjana on BITMAP, joka tarkoittaa bittikarttaa. Oikeastaan sitä voisi ajatella jonkinlaisena piirtopohjana. DirectX:ssä niitä nimitetäänkin pinnoiksi. En halua sinua sekoittaa, mutta toivoin tuon selkeyttävän asiaa. Siis, kun piirrät, piirrät aina jollekin bitmapille, piirtopinnalle.
Voit luoda itse bitmappejä esittelemällä ne ikäänkuin muuttujina seuraavaan tapaan:
BITMAP *kuva;
Siis tuossa esiteltiin kuva-niminen bitmappi. Tai itse asiassa siinä esiteltiin osoitin bitmappiin, jota ei vielä ole olemassa. Tätä osoitinajattelua sinun ei kuitenkaan vielä ole pakko ymmärtää, joten jatka vain eteenpäin. Sen koon ja muut sellaiset asiat voit määritellä kahdella tavalla. Ensimmäinen tapa on luoda kuva create_bitmap-funktiolla, esimerkiksi:
kuva=create_bitmap(320,200);
Siinä siis luotiin kuvapinta, joka on 320 pikseliä leveä ja 200 korkea. Ja jos puhutaan osoittimista, niin nyt kuva-osoitin osoittaa uuteen bitmappiin, joka on olemassa. Mutta kuva esittää nyt siis joka tapauksessa tuota uutta piirtoalustaa.
Toinen tapa on helpompi, koska se tapahtuu kuvan lataamisen yhteydessä itsestään. Puhutaan siitä kuitenkin lisää vähän myöhemmin.
Ja sitten kun et enää tarvitse kuvaa, kutsu funktiota destroy_bitmap, joka poistaa kuvan muistista. Esimerkiksi näin poistaisimme muistista tuon vähän aikaa sitten luodun kuva-bitmapin:
destroy_bitmap(kuva);
Näitä asioita harjoittelemme pian käytännössä. Kyllä ne sitten tulevat paremmin tutuiksi ja toivottavasti myös jokseenkin rutiiniksi.
Syvemmälle paletteihin #
Myös paletteja katsoimme pikkuisen ja niiden perusperiaatteen taisinkin jo selittää. Puhuimme niistä RGB-structeista ja laittelimme palettiin värejä. Se oli kuitenkin vielä suhteellisen hankalaa.. Nyt katsomme paletteja hieman tarkemmin ja otamme käyttöön helpompia tapoja käyttää paletteja.
Siis muistathan, että jokainen paletin 256 väristä esitetään RGB-structeissa ilmoittamalla niiden punaisen, vihreän ja sinisen värin komponentit.
Paletti on Allegrossa 256 RGB-structin taulukko, joka on nimeltään PALETTE. Jos siis haluat tehdä uuden paletin, esittelet sen muuttujana seuraavasti:
PALETTE oma_paletti;
Siis tuossa loimme paletin nimeltä oma_paletti.
Silloin, kun viimeksi paleteista puhuimme, täyttelimme itse muutamia värejä palettiin. Paletin voit kuitenkin täyttää helpomminkin, niin, että sinun ei melkein tarvitse edes huomata käyttäväsi paletteja. Nimittäin kuvanlatausfunktio, johon paneudumme seuraavassa kappaleessa ottaa yhtenä syötteenä osoittimen palettiisi ja täyttää sen kuvan väreillä. Mutta siitä lisää seuraavassa kappaleessa. Tässä kakkoskurssissa ei tämän enempää paleteista puhuta, paitsi että seuraavassa kappaleessa tulee aika paljon palettiasiaa, kolmoskurssissa olen ajatellut kertoa sitten vielä muutamia hauskoja palettikikkailuja.
Ladataan kuva #
Jees. Alku tähän asti on kaikki ollut oikeastaan johdantoa kuvien lataamiselle. No, onhan noista asioista muutenkin hyötyä, mutta ainakin nyt pääsemme tekemään hauskempia juttuja..
Kuvan, jonka nyt lataamme on oltava 256-värinen BMP- tai PCX-kuva. Lataamme sen omaan bitmappiinsä ja otamme myös kuvan paletin talteen. Tehdään tästä kokonainen ohjelma vaikkakin paloittain.. Nyt se alkaa:
#include <allegro.h>
#include <stdio.h>
int main(void)
{
BITMAP *kuva;
No niin.. Siinä tulivat alkujutut, jotka ovat jo varmasti tulleet sinulle tutuiksi ja niiden jälkeen kuva-bitmappimme esittely. Jatketaan..
PALETTE paletti;
Ja sitten luotiin paletti nimeltään paletti. Huomaa, että tässä vaiheessa kuvaa ei oikeastaan ole edes olemassa, sille ei ole varattu muistia, eikä paletissa ole mitään tietoja. Niitä ei siis vielä saa käyttää muuten kuin luomalla uusi kuva, minkä teemme tuossa kohtapuolin.
// Allegron alustukset
allegro_init();
install_keyboard();
set_gfx_mode(GFX_AUTODETECT,640,480,0,0);
// Ladataan kuva
kuva=load_bitmap("kuva.pcx",&paletti);
Siinä tulikin tärkeä rivi. Niin tärkeä, että pysähdytäänpä tähän hetkeksi.
Nimittäin tuo on se funktio, joka lataa kuvan bitmappiin (tässä tapauksessa tuohon minkä nimi on kuva) ja paletin. Funktion palautusarvo on siis BITMAP *, mikä tarkoittaa että se palauttaa kuvan, eikä mitä tahansa kuvaa vaan sen kuvan, jonka nimi on mainittu ensimmäisenä funktion parametrinä lainausmerkeissä (kuva.pcx, voisi olla myös kuva.bmp tai vaikka ihan mikä vaan kunhan se on jotain seuraavista tyypeistä: bmp, pcx, lbm ja tga). Paletin se lataa siihen palettiin, jonka nimi on toisena parametrina. Palettejahan voi esitellä niin monta kuin vain haluaa, mutta vain yhtä kerrallaan käytetään ruudulle piirrettäessä, nimittäin sitä, joka on valittu käytettäväksi paletiksi funktiolla set_palette. Mutta se tulee ohjelmassa seuraavaksi, joten eiköhän jatketa eteenpäin.
// valitaan käytettävä paletti set_palette(paletti); // piirretään kuva näytölle blit(kuva,screen,0,0,0,0,kuva->w,kuva->h);
Tuosta blit-rivistä sinun ei tarvitse vielä välittää, laitoin sen tuohon, jotta näkisit että kuva on latautunut oikein. Se siis piirtää kuvan ruudulle, mutta puhutaan siitä seuraavassa kappaleessa, joka on kokonaan omistettu juuri tälle funktiolle. Ohjelman lopun pitäisi olla sinulle aika tuttua:
// odotellaan että joku painaa spacea
while(!key[KEY_SPACE]){}
// lopetetaan Allegro
allegro_exit();
return(0);
}
Noin, nyt on kuva ladattu ja toivottavasti myös lataamisen toimet ymmärretty. Voinemme siis siirtyä tutkimaan erilaisia tapoja piirtää näitä ladattuja kuvia näytölle.
Länttäillään kuvat ruudulle #
Kun olemme ladanneet kuvan tai kuvia, on ne tietenkin piirrettävä ruudulle. Tähän tarkoitukseen Allegro tarjoaa kaksi funktiota, itse voit tietenkin kehitellä niitä lisää.. Mutta pitäydytään nyt vielä Allegron funktioissa.
Yksinkertaisin kuvanruudullepiirtofunktio on blit, joka vain länttää kuvan haluamaasi paikkaan toisessa bitmapissä. Eiköhän siis jätetä jaarittelut väliin ja mennä käytäntöön.
Siis... Blit-funktiolla voit kopioida yhdestä bitmapistä haluamasi kokoisen palan toiseen funktioon. Otetaan esimerkki samantien.
blit(kuva,screen,0,0,100,100,30,30);
Tuossa siis kopioidaan kuva-bitmapista kohdasta 0,0 alkaen 30 pikseliä leveä ja 30 pikseliä korkea palanen screen-bitmappiin eli näytölle kohtaan 100,100. Tulikohan tuo tuosta yhtään selvemmäksi? Toivottavasti, sillä nyt mennään eteenpäin.
Blit-funktio on erityisen käyttökelpoinen, jos halutaan piirtää esimerkiksi isoja nelikulmaisia kuvia, esimerkiksi pelien taustoja. Mutta jos haluamme piirtää pallon tai jonkin muun epämääräisen muotoisen kuvan, jättää blit kuvion ympärille ärsyttävät nelikulmaiset reunat. Mutta onneksi on myös toinen funktio, siitä seuraavassa kappaleessa.
Liikkuvia kuvia, eli spritejä #
No niin. Nyt siis olisi tarkoitus tutustua spriteihin. Käytännössä sprite on bitmappi. Ennenmuinoin, silloin kuusnelosten aikaan, spritet olivat jotain aivan erilaista, mutta ei siitä sen enempää. Jos se aika kiinnostaa, varmaan netistä löytyy paljonkin kuusnelosasiaa, ja täälläkin ehkä joskus saatan niistä jutuista kirjoitella.
Siis. Sprite on Allegrossa bitmappi, ei sen kummempaa. Usein spriteihin liitetään myös ajatus siitä, että ne ovat ikäänkuin pelin liikkuvia objekteja. Näin saattaa usein olla, mutta ei suinkaan aina.
Okei, enköhän ole sekoittanut sinua jo aivan tarpeeksi. Unohda kaikki edelläoleva spritehöpinä. Ei vaineskaan, kyllä ne voit muistaa, mutta niillä ei ole käytännön kanssa paljonkaan tekemistä.
Se mitä tässä kappaleessa varsinaisesti haluan selittää, on Allegron toinen bitmapinpiirtofunktio, draw_sprite. Se on muuten aika samanlainen kuin blit, mutta se sisältää läpinäkyvän värin.. Kaikki mikä bitmapissä on piirretty paletin värillä 0 näkyy läpinäkyvänä eli ei näy. Tämä on tosi hyödyllistä esimerkiksi, jos haluamme piirtää toisen kuvan päälle pelaajan ukon tai auton tai raketin tai vaikka mitä.
Ai että mitenkö se tehdään? Helppoa:
draw_sprite(screen,kuva,20,20);
Kas noin. Siinä siis piirrämme bitmapin kuva kokonaan bitmappiin screen, joka sattuu olemaan näyttö, kohtaan 20,20. Tämä on siis yksinkertaisempi funktio ohjelmoijan kannalta kuin blit. Sinun ei tarvitse huolehtia siitä minkä kokoinen bitmappisi on tai minkä kohdan siitä haluat piirtää. Funktio piirtää sen aina kokonaan. Sinun tarvitsee vain kertoa mihin sen haluat piirtää.
Näillä kahdella funktiolla pystyt tekemään vaikka mitä. Lisäksi allegrosta löytyy pari erikoisempaa piirtofunktiota, mutta niistä saat ottaa itse selvää, jos ne kiinnostavat. Lisäksi voit tehdä omia blittisysteemejä, esimerkiksi osittain läpinäkyvä (läpikuultava) blitti on tosi hieno efekti. Kirjoitan varmaan siitä tutoriaalin lähiaikoina.
Nyrkkisääntönä voinemme pitää blitin ja draw_spriten käytössä sitä, että blitillä blittaillaan taustoja ja muita isompia juttuja ja draw_spritellä piirretään peliobjektit kuten pelaaja, vastustaja, pallo... Joo, eiköhän tuo tullut selväksi. Otetaan tehtävä.
Tehtävä: Tee ohjelma, joka lataa kaksi kuvaa ja sitten niistä toisen piirtää taustaksi blit-funktiolla ja toisen sen päälle draw_spritellä. Muistathan käyttää paletin 0-väriä draw_spritellä piirrettävän kuvan taustassa.
Tehtävä #
Äskeisessä tehtävässä siis latasit kaksi kuvaa ja piirsit toisen taustaksi ja toisen siihen päälle draw_spritellä. Kuvia et kuitenkaan pystynyt liikuttelemaan. Nyt korjataan se puute ja lisätään tehtävän ohjelmaan liikettä.
Tarkoituksena on siis, että tausta pysyy edelleen paikallaan, mutta pienempää kuvaa, spriteä pystyy liikuttelemaan taustan päällä nuolinäppäimillä.
Mitä tarvitsemme tämän tehtävän suorittamiseen? Ensinnäkin kaksi muuttujaa säilyttämään spriten paikkaa ruudulla. Olkoot ne vaikka x ja y, inttejä tietysti. Toiseksi tarvitsemme tietysti kuvat, ne voi piirtää vaikka Paint Shop Prolla. Ja kolmanneksi tarvitsemme while-silmukan, jonka aikana luetaan näppäimistösyötettä (nuolet) ja liikutellaan kuviota sen mukaan.
Luulisin, että osaat tehdä sen itsekin, joten olkoon se tehtävä. PS. Jos kaikki ei näytä aivan oikealta, älä huolestu sillä seuraavassa kappaleessa tulee ratkaisu taustaongelmaan :).
Korjataan taustaongelma #
Kuten ennen tehtävää lupasin, tulee nyt ratkaisu sinua varmaankin pitkään askarruttaneeseen ongelmaan. Eikös vain käynytkin niin, että kun liikutit spriteä, jäi vanha kuva taustakuvaan "kiinni" ja seurauksena oli vain sotkua? Jos näin ei käynyt, olet luultavasti huijannut ja piirtänyt taustankin joka kerta uudestaan. Sehän ei toki ole kiellettyä, mutta paljon hitaampaa. Siksi tässä tulee nyt nopeampi ratkaisu.
Idea on yksinkertainen: Ennen kuin piirrät spriten uuteen paikkaan, piirrä vanhan paikan tausta takaisin ja laita talteen uuden paikan tausta. Okei, ei kuulostanut kauhean yksinkertaista, mutta sitä se on. Katsotaan asiaa kaikessa rauhassa esimerkkiohjelman avulla:
int main(void)
{
BITMAP *tausta; // semmoinen iso kuva jonka päälle piirretään
BITMAP *sprite;
BITMAP *spriten_taus; // spriten tausta
PALETTE pal;
int x=10;
int y=50;
allegro_init();
install_keyboard();
set_gfx_mode(GFX_AUTODETECT,320,200,0,0);
// kuvien lataaminen
tausta=load_pcx("taus.pcx",&pal);
set_palette(pal);
sprite=load_pcx("sprite.pcx",&pal);
// Ja tässä on nyt sitten sitä uutta asiaa
spriten_taus=create_bitmap(sprite->w,sprite->h);
Siis. Nyt meillä on bitmappi nimeltään spriten_taus, joka on täsmälleen saman kokoinen kuin sprite. Siihen tallennamme siis spriten taustat. Aluksi otamme taustan talteen kohdasta x,y, johon sprite kohta piirretään.
blit(tausta,spriten_taus,x,y,0,0,spriten_taus->w,spriten_taus->h); draw_sprite(tausta,sprite,x,y);
Tuossa siis kopioimme taustasta talteen sen kohdan, joka kohta jää spriten sotkemaksi ja piirsimme spriten taustabitmappiin. Nyt sitten se piirtosilmukka:
while(!key[KEY_ESC]){
blit(spriten_taus,0,0,x,y,spriten_taus->w,spriten_taus->h);
Ensin piirretään tausta takaisin spriten päälle ja sitten luetaan näppäimistöltä uudet x- ja y-koordinaatit.
if(key[KEY_LEFT]) x--;
if(key[KEY_RIGHT]) x++;
if(key[KEY_UP]) y--;
if(key[KEY_DOWN]) y++;
Siispä nyt meillä on uudet koordinaatit ja otamme taas taustan talteen ja sitten piirrämme spriten..
blit(tausta,spriten_taus,x,y,0,0,spriten_taus->w,spriten_taus->h);
draw_sprite(tausta,sprite,x,y);
// Ja koko komeus näytölle
blit(tausta,screen,0,0,0,0,tausta->w, tausta->h);
}
allegro_exit(0);
return(0);
}
Huhhuh... Tuo oli raskasta, mutta toivottavasti ei liian vaikeaa. Vielä yksi juttu. Noista kuvista nimittäin. Kun itse tein tämän esimerkin, käytin taustakuvana sellaista kuvaa joka on 320x200 pikselin kokoinen ja spritenä paljon pienempää. Suosittelen samaa myös sinulle. Ja sitten eteenpäin, enää ei ole paljoa jäljellä. Vilkaistaan vielä kaksoispuskurointia ja sitten osaatkin jo vaikka mitä!
Ja sulavuutta kaksoispuskurilla #
Jaahas! Nyt olemme päässeet siihen vaiheeseen, että on sopivaa puhua kaksoispuskuroinnista. Kokeile allegron esimerkkiohjelmaa ex8.exe. Siitä saat hyvän kuvan kaksoispuskuroinnin hyödyllisyydestä. Ohjelmassa pallo kulkee ruudun poikki ensin ilman kaksoispuskurointia, niin, että se piirretään suoraan näytölle (screen-bitmappiin). Toisella kerralla se piirretään kaksoispuskurointia käyttäen. Huomaat eron, ilman kaksoispuskurointia kuvan katsominen oli suorastaan sietämätöntä, kaksoispuskuroinnin kanssa jo ihan siedettävää.
Mutta arvaas mitä! Tuo hienous on niin helppoa, että ihan naurattaa.. Sinäkin olet jo käyttänyt kaksoispuskurointia, tietämättäsi. Yllätyitkö? Se oli tuossa edellisessä kappaleessa. Siinä teimme kaikki piirtämiset tausta-bitmapillä ja lopussa vain blittasimme sen näytölle. Kuulostaako tutulta? Eikä todellakaan ole vaikeaa. Kaksoispuskurointi tarkoittaa siis sitä, että mitään ei piirretä suoraan näytölle vaan aina ensin kaksoispuskuriin, joka sitten lopuksi yhdellä nopealla blitillä heitetään näytölle.
Nyt varmaan mietit, että miten tuollaisella pikkujutulla voi olla näin suuri vaikutus. Se johtuu vain siitä, että näyttömuistiin piirtäminen on paljon hitaampaa kuin tavalliseen perusmuistiin. Jos käytämme draw_spriteä screen-bitmapin kanssa on se siis paljon hitaampaa kuin vaikkapa tausta-bitmapillä. Siinä on kaksoispuskuroinnin salaisuus.
Olisikohan nyt pelien aika? #
Oho, tämäkin kurssi meni nopeasti. Kirjoitinkin sen oikeastaan yhdessä päivässä - niin, ja korjailin hieman nyt pari vuotta kirjoittamisen jälkeen. Korjauksia joudun varmaan vieläkin tekemään, mutta kaipa tämä on valmis. Pääasia on, että nyt hallitset blittailut ja paljon muuta. Mikään ei enää estä sinua tekemästä ensimmäistä peliäsi. Voit tehdä sen vaikka nyt, mutta muista, älä aloita liian vaikeasta!
Tietenkään sinun ei ole pakko jatkaa peliohjelmoinnin suuntaan vaikka sinne sinua jatkuvasti tyrkinkin (anteeksi siitä..), voit ihan yhtä hyvin tehdä vaikka kuvannäyttelyohjelman tai jotain muuta hyödyllistä.
Joka tapauksessa, valitsit sitten minkä tahansa suunnan tästä eteenpäin, toivotan onnea ja toivon voivani auttaa sinua edelleen!
