Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: Java: Pelin enginen toteutus

Sivun loppuun

Mobel [02.03.2007 23:46:35]

#

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!

Legu [02.03.2007 23:56:39]

#

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);

tgunner [03.03.2007 01:04:06]

#

MAKKARAA: http://lazyfoo.net/SDL_tutorials/lesson22/index.php ja sit kans http://lazyfoo.net/SDL_tutorials/lesson21/index.php

Pekka Karjalainen [03.03.2007 08:00:44]

#

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.

Mobel [03.03.2007 12:52:22]

#

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.

ezuli [03.03.2007 13:25:38]

#

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.

Antti Laaksonen [03.03.2007 14:48:46]

#

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.

hunajavohveli [03.03.2007 23:52:37]

#

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.

Cornix [04.03.2007 03:18:25]

#

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.

feenix [05.03.2007 15:01:57]

#

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.

Mobel [13.03.2007 17:37:36]

#

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

TsaTsaTsaa [13.03.2007 18:44:25]

#

Eikö noissa for-silmukoissa ole nuo vertailuväkäset väärin päin?

Mobel [13.03.2007 20:21:41]

#

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

FooBat [13.03.2007 20:47:43]

#

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).

Mobel [14.03.2007 23:24:28]

#

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ä

Mobel [18.03.2007 17:13:31]

#

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.

sqwiik [18.03.2007 17:30:29]

#

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;
  };
}

Mobel [18.03.2007 22:53:16]

#

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.

Mobel [29.03.2007 17:09:29]

#

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.

Blaze [29.03.2007 18:12:05]

#

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.

Mobel [29.03.2007 19:31:22]

#

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.

sqwiik [30.03.2007 09:29:03]

#

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.

Mobel [30.03.2007 14:25:46]

#

Kiitoksia. Nytpä on taas yksi asia vähemmän ihmeteltävää.


Sivun alkuun

Vastaus

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

Tietoa sivustosta