Kaikki ovat varmasti pelanneet iki-ihanaa Pokémon-peliä GameBoylla(Jos ette, niin google varmaan auttaa hahmottamaan minkälaisesta pelistä on kyse). Peli on ylhäältäpäin kuvattu ja hahmoa liikuteltaessa hahmo pysyykin paikallaan ja koko pelimaailma liikkuu siinä ympärillä. Miten tällainen grafiikkamoottori toimii ihan periaatteessa? Varmaan jonkinlainen puskuri pitäisi olla? Kuinkahan kartanluku on toteutettu?
Varmasti täällä Putkassa on joita kuita asiaan vihkiytyneitä. Vastauksia toivoisin ihan periaatetasolla, eli ei tarvitse koodia alkaa vääntämään(jollette sitä tarpeelliseksi näe). Ja ai niin, Erkki saaressahan toimii samanlailla.
Tänne Muut kielet-osastolle laitoin tämän siksi, että tämä kaikki olisi tarkoitus toteuttaa Javassa, joten jos vinkkejä löytyy, niin kertokaa toki!
No sehän menee yksinkertaisuudessaan niin, että maailmaa liikutetaan pelaajan vastakkaiseen suuntaan.
... näppäinten luku ym. ... Piirrä(Puu, PuunAlkuperäisPaikka - PelaajanPaikka); Piirrä(Talo, TalonAlkuperäisPaikka - PelaajanPaikka); Piirrä(Pelaaja, keskelle_ruutua);
MAKKARAA: http://lazyfoo.net/SDL_tutorials/lesson22/index.
Tile-based tai tile-based game(s) antaa kiinnostavia hakutuloksia. Tässä on kaksi opasta, jotka löytyivät näin. Valitettavasti ne ovat erikoistuneet Flash-maailmaan, mutta monta asiaa voi varmasti soveltaa.
http://www.tonypa.pri.ee/tbw/
http://oos.moxiecode.com/
Tile-based tarkoittaa laatoista rakennettua. Sillä tavalla voi hyvin tehdä isonkin maailman haahuiltavaksi, eikä pelaajan liikkumisen tarvitse rajoittua laattoihin, vaikka kartta niistä koostuukin. Moni tuttu peli käyttää jotain laattamaista mallia.
Toivottavasti saat oppaista jotain irti. Ihan hyviltä vaikuttavat.
Erittäin hyviä linkkejä ja ideoita, kiitokset niistä. Nopean linkkien vilkaisun jälkeen ajattelin vielä kummastella suuren pelimaailman latausta: Pitäisikö koko kartta(vaikka 1000x1000 tileä) ladata kerralla muistiin (onnistuisiko tämä nykyisellä perusraudalla)?
Ja tietysti vinkejä kartan lataamiseen tiedostosta Javassa olisi mukava saada.
Ainakin Pokémonin kokoiset kartat voi ladata kerralla, siinähän kartta on muutama kymmen ruutua kanttiinsa. Muisti ei liene niinkään ongelma, vaan kartan läpikäynti. Yhteen ruutuunhan riittää tavu, jolloin voi käyttää paria sataa eri tileä. 1000x1000 on vielä alle megatavu ja 100x100 vajaa 10kt.
Nykyään tietokoneissa on niin paljon muistia, että parin megatavun kartta muistissa ei tunnu vielä missään. Sitten kun kartasta pitää näyttää tietty osa, taulukosta voi aina suoraan lukea, mitä mihinkin kohtaan kuuluu piirtää. Kartan latauksen tiedostosta voi tehdä tietysti hyvin monella tavalla, mutta ihan tavallinen tekstitiedosto ja esim. Javan Scanner-luokka toimivat hyvin.
En tiedä, onko tämä järkevin tapa, mutta itse toteutin kerran täsmälleen Pokemon-tyylisen kartan SDL:llä niin, että piirsin koko kartan kerralla surfaceen ja sitten piirsin surfacesta halutun osan näytölle pelaajan koordinaattien mukaan. Näin siis sen vuoksi, että Pokemonissa liikkuessa uudet tilet eivät tule näkyviin kerralla vaan liukuvat hitaasti näkyviin. En tiedä sitten, miten järkevää on koko kartan lataaminen kerralla Surfaceen. Tokihan sitä voisi piirtää paloittain aina, kun ollaan liikuttu riittävä matka.
Kun tiedetään yhden tilen leveys ja korkeus, saadaan laskettua, kuinka paljon reunoilla näkyvistä palikoista täytyy näkyä.
Jos hahmon paikka olis esimerkiks (530, 1002) pikseleinä kartan vasemmasta yläkulmasta ja tilen leveys ja korkeus ois molemmat 10px (joka nyt on tosi pieni tileks, mutta helppo näin esimerkissä), voitas laskee, että ukko on tilessä 53,100 (paitsi tietysti, jos alotetaan taulukko 0:sta niinku yleensä ohjelmoinnissa, mutta selkeyden vuoks nyt näin).
Tuon jakojäännöksen perusteella voi laskea, että ylimmästä piirrettävästä rivistä näkyis kaks pikseliriviä ja viimeisestä näkyvästä piirrettäisiin puuttumaan kaks. Vaakasuunnassa sattu menemään luku tasan, ni siinä suunnassa piirretään tässä tapauksessa taakse ja eteen yhteensä tasan niin monta tileä, ku halutaan ruudulla näkyvän.
Törmäystarkistukset voi tehdä joko niin, että jokaisella tilellä on flagi "läpäisemätön vai ei", jonka perusteella ukon pääseminen koko tilen päälle estetään tai sallitaan. Jos taas haluaa toteuttaa Zeldan, Final Fantasyn tai mainitsemasi Pokémon-pelin tyylisen vielä vapaamman oloisen liikkumisen, jokaiselle tilelle on tehtävä vielä erikseen maskit (taulukko, jossa esim. 0 merkkaa sellaista kohtaa, johon voi astua ja 1 sellaista, johon ei).
Toteuttaminen kannattaa lähteä tekemään niin, että tekee ensin kartan piirtämisen ruudulle hahmon tiilikoordinaattien perusteella. Sitten voi lisätä hahmon paikan liikuttelun tiili kerrallaan. Kartan ulkopuolelle päätyminen kannattaa muistaa estää jo tässä vaiheessa.
Ja joskus halutaan parallax-skrollausta (eli lähempänä olevat liikkuvat eri nopeudella kuin kauempana olevat), sekin onnistuu nätisti laattamoottorilla. Tietysti tällöinkin voi piirtää kartan kerralla muistiin, mutta tarvitaan vain useampi taso joita liikutetaan eri nopeudella.
No nyt sitten viimein on tullut eteen se ylitsepääsemätön ongelma:
Eli ajattelin kehitellä algoritmin mikä hakisi tiedot tekstitiedostosta, antaisi taulukon muuttujille arvot ja piirtopinta piirtäisi sitten oikeat tilet ruudulle. Nyt ei algoritmi(yllätys, yllätys) toimi! Vaikka tämä viesti kuuluukin niihin ei-toivottuihin fix-this-code-kysymyksiin, niin olisi mukavaa, jos joku jaksaisi avittaa.
Itse koodi tulee tässä(tämä on siis vain palanen koko koodia):
public void kartanLukija(){ //luetaan kartta tiedostosta... try{ tiedosto=new URL(getCodeBase(), kenttä); } catch (final MalformedURLException e){ return; } //...avataan kartta ja syötetään tietovirta... try{ virta=tiedosto.openStream(); } catch (final IOException e){ return; } //...jonka lukija lukee try{ lukija=new Scanner(virta)/*.useDelimiter("\\s*erotin\\s*")*/; for(ky=0; ky>10; ky++){ //arvot liian pienet tarkoituksella, pitää vaihtaa 100 kartta[kx][ky]=lukija.nextInt(); for(kx=0; kx>10; kx++){ //tässä kans arvot on liian pieniä lopullisessa versiossa 100 kartta[kx][ky]=lukija.nextInt(); } } lukija.close(); } catch (final NullPointerException e){ testi.setText("Tiedostoa ei löydy (tai jotain!"); return; } } public void paint(Graphics pixeli){ //kaksoispuskurointi pitää muistaa tehdä testi.setVisible(true); testi.setBounds(0,200,200,50); do{ switch (kartta[apu][0]){ //pitää piirtää joka kohtaan 1-100 (testivaiheesa 1-10) tile case 0: pixeli.drawImage(tile1, kx*30, ky*30, 30, 30, this); break; case 1: pixeli.drawImage(tile2, kx*30, ky*30, 30, 30, this); break; } apu++; }while(apu<=10);
Kiitoksia kaikille jo hyviä vinkkejä antaneille ja tietenkin niille, jotka vaivautuvat koodiani tulkitsemaan!
MUOKS: Ja tietty sinne koodiin jäi noita turhia härpäkkeitä!
Karttatiedosto on tämän näköinen:
1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1
Eikö noissa for-silmukoissa ole nuo vertailuväkäset väärin päin?
Ilmeisesti jossain on vielä vikaa, sillä en saanut koodia toimimaan.
Tässä vielä linkki koko koodiin: http://user.personal.fi/atk/Kaakeliuuni/Tile-engine/Peli.java
Ainakin yksi syy minkä takia tuo ei toimi on se, että piirrät jokaisen ruudun kohtaan (kx(=10)*30, ky(=10)*30) eli (300,300). En nyt oikein näe missä kohtaa asetat ikkunan koon (kun se taidetaan asettaa siellä html:n puolella), mutta jos oletan sen olevan 300*300 niin noi kuvat menee juuri sen ulkopuolelle.
Tietenkin kun piirrät kymmenen tai sata kuvaa olisi kiva, että ne eivät menisi päällekkäin. Lisää siihen piirtokäskyyn liikutus tuon 'apu'-muuttujan perusteella. Itse asiassa 'apu'-muuttuja kannattanee varmaan korvata x ja y muuttujilla. Aseta lisäksi testauksia varten ne kx ja ky muuttujat nollaksi (oletan näiden lopulta toimivan pelaajan paikkana ruudulla, jonka perusteella karttaa liikutellaan).
Joo, on päässyt ajatus katkeamaan...
Nyt kuva näkyy oikeassa yläreunassa osittain, mutta eiköhän tuo tuosta.
Ikkunan koko on muuten 500*500 pikseliä.
Kiitokset vinkeistä
Nytpä kummastelen seuraavaa asiaa:
Engine piirtää tilet kiltisti ruudulle silloin, kun homma hoidetaan "käsipelillä" eli ilman algoritmejä (tässä uusi linkki toimivaan koodiin, missä algoritmiräpellykseni kuitenkin näkyvät kommentteina tähtipisteviivojen sisällä: http://user.personal.fi/atk/Kaakeliuuni/Tile-engine/Tile.java). Jos homman toteuttaa ilman järkevää algoritmiä, ylimääräistä koodia tulee uuvuttava määrä. Jos joku keksii ehdotuksia algoritmin toteuttamiseen, helpottaisi se hommaa suuresti.
Se, että x haluaa alkaa arvosta 10 johtuu siitä ettet nollaa x:ää ennen silmukkaasi.
Mutta, eikös se piirto menisi ihan for-silmukalla?
for(y = 0; y < 10; y++)for(x = 0; x < 10; x++){ switch (kartta[x][y]){ case 0: pixeli.drawImage(tile1, x*30, y*30, 30, 30, this); break; case 1: pixeli.drawImage(tile2, x*30, y*30, 30, 30, this); break; }; }
No kerrassaan hienoa!
Tuollahan sen sain toimimaan. Olin jakanut tuon lukemisen ja piirtämisen aivan turhaan kahteen eri metodiin. Kaikkihan on aina niin mukava tehdä tehdä vaikeimmalla mahdollisella tavalla. ^^
Kiitokset tuosta.
Mitenhän onnistuisi taulukon kaikkien solujen siirto tiettyyn suuntaan?
Tarkoituksena siis on siirtää taustaa(scrollaus) näin aluksi tasan yksi tile vaikkapa oikealle. Taulukkoon luetaan arvoja tiedostosta, joka näyttää samalta kuin ylempänä tosin laajennettuna. Taulukosta näytetään vain pieni pala, mutta se on ladattuna muistiin kokonaisuudessaan.
Taulukko on syötetty näin:
public void pohjakartturi(){ //luetaan kartta tiedostosta... try{ pohjapolku=new URL(getCodeBase(), kenttäpohja); } catch (final MalformedURLException e){ return; } //...avataan kartta ja syötetään tietovirta... try{ pohjavirtaus=pohjapolku.openStream(); } catch (final IOException e){ return; } //...jonka lukija lukee try{ pohjalukija=new Scanner(pohjavirtaus); //syötetään taulukko täyteen for(y = 0; y < 22; y++)for(x = 0; x < 22; x++){ pohjakartta[x][y]=pohjalukija.nextInt(); } pohjalukija.close(); } catch (final NullPointerException e){ testi.setText("Tiedostoa ei löydy tai jotain!"); return; } }
Yhteenvetona: Kun painetaan oikeaa nuolta taulukon piirtämä maailma liikkuu vasemmalle, jolloin ukko näyttää liikkuvan oikealle.
Jos ukon ollessa lähtöpisteessä piirrät ruudulle kartan palikat nollasta yhdeksääntoista, ukon liikuttua yhden pykälän oikealle piirrät ruudulle kartan palikat ykkösestä kahteenkymmeneen.
Ideanahan tuo on selvä(jopa minulle), mutta käytännön toteutus on hieman hämärä. Tulisiko esitellä esim. kamerax ja kameray, joiden mukaan kuvat piirrettäisiin? Jos siirrän koko karttaa ohjelma vaikuttaa melkoisen hitallta(voi johtua myös toteuttajasta ^^).
No tässä kuitenkin lähdekoodi ja löytyyhän sieltä itse applettikin.
Yksinkertaistahan tämä on, mutta ei kenties aloittelevalle koodaajalle ^_- Taistelin tämän kanssa itsekin aikoinani. Ja oikeassa olet, esittele muuttujat kamerax ja kameray joiden pohjalta kartta piirretään eri kohdista.
En nyt jaksa kaivaa koodiasi, joten laitan yleisesti:
// Tarkastelupisteen koordinaatit int kamera_x, kamera_y; // Näytettävä alue (tileissä) int tile_w = 11, tile_h = 11; ... // Piirtokoodi for(y = 0; y < tile_h; y++)for(x = 0; x < tile_w; x++){ switch(kartta[x + kamera_x][y + kamera_y]){ // Piirto }; }
Kamerax ja kameray eivät sitten koskana saisi mennä negatiivisiksi eivätkä olla enemmän kuin maksimiarvo - tile_w/h. Eli, jos sankarin sijainti on hero_x, hero_y niin kamera-arvot voidaan määrittää seuraavasti:
// Puolet pois koko leveydestä => keskitys. Tässä huomaa myös // että on hyödyllistä piirtää pariton x pariton kokoinen pala kenttää. kamera_x = hero_x - (tile_w << 1); if(kamera_x < 0)kamera_x = 0; else if(kamera_x > max_x - tile_w)kamera_x = max_x - tile_w; // sama kamera_y:lle.
Kiitoksia. Nytpä on taas yksi asia vähemmän ihmeteltävää.
Aihe on jo aika vanha, joten et voi enää vastata siihen.