Kirjoittaja: hunajavohveli (2005).
Tämä opas(sarja) käsittelee tilepohjaisen pelin toteuttamista merkkigrafiikalla. Tilepohjainen peli tarkoitaa peliä, jossa pelimaailma koostuu ruuduista ja hahmot liikkuvat pykälittäin ruudusta toiseen. Merkkigrafiikalla toteutetut pelit eroavat muista tilepohjaisista peleistä vain siten, että ruudut piirretään tavallisen grafiikan sijaan merkeillä. Tutuimpia tällaisista peleistä ovat mm. Nethack, Rogue ja ADOM. Joten jos olet pelannut yhtäkin näistä, tiedät mistä on kyse (Ja jos et ole, niin suosittelen että kokeilet, jos haluat opetella tekemään vastaavia tämän oppaan avulla). Pelimoottorin yksityiskohtaisen kehityksen yhteydessä käsittelemme myös asioita, joita tarvitaan useissa muissakin peleissä, kuten esimerkiksi ukon liikuttamista näytöllä, törmäystarkistuksia, pelin tallentamista ja lataamista yms.
Esimerkki pelistä Ancient Domains of Mystery:
Ennen tällaisen pelin aloittamista sinulla pitäisi olla jonkinlainen pohja valmiina ohjelmoinnista yleensä. Lähdemme tässä liikkeelle aika lailla alkeista, joten valtavaa määrää kokemusta ei vaadita. Mikäli oppaassa käsiteltävät asiat kuitenkin tuntuvat sinusta vierailta, suosittelen lukemaan ensin Ohjelmointiputkan Aloittelijan QBasic-oppaan. Tuossa oppaassa esiteltävät asiat, muuttujat, ehtolausekkeet, silmukat ym. tärkeät rakenneasiat sinun tulisi hallita. Jos ne ovat hallussasi, voit siirtyä eteenpäin:
Yritämme aluksi luoda yksinkertaisen esimerkin, jossa pelaaja pystyy liikuttamaan @-merkkiä näytöllä, kuten useimmissa tämänlaisissa peleissä. Aluksi on hyvä tehdä suunnitelma siitä, mitä pelissä tapahtuu ja missä järjestyksessä. Peruslista suoritettavista asioista on seuraavanlainen:
Neljännen kohdan jälkeen siirryymme jälleen kohtaan yksi, jolloin ukko piirretään uuteen paikkaan, siihen, johon pelaaja sen halusi siirtyvän.
Nyt käymme läpi, miten edellä mainitut neljä kohtaa toteutetaan koodina. Ennen näitä kohtia luomme kuitenkin kaksi muuttujaa, x ja y, joihin tallennamme ukon koordinaatit näytöllä. Asetamme näiden muuttujien arvoiksi aluksi 40 ja 12, jolloin ukko on suurin piirtein keskellä näyttöä. WIDTH
-komento varmistaa, että kyseessä on 80x25-suuruinen tekstitila.
DIM x AS INTEGER, y AS INTEGER ' Esittelemme muuttujat ja määritämme niiden tyypiksi kokonaisluvun (INTEGER) x = 40 ' Asetamme x:n arvoksi 40 y = 12 ' Ja y:n arvoksi 12 WIDTH 80, 25 ' 80x25 tila CLS ' Näyttö on myös hyvä tyhjätä kaiken varalta
Siirrymme listan ensimmäiseen kohtaan eli ukon piirtämiseen. Tunnet oletettavasti PRINT
-käskyn, jolla tulostetaan tekstiä näytölle. Käytämme tätä tuiki tavallista käskyä myös tässä, mutta meidän täytyy myös kertoa käskylle, että haluamme tulostaa siihen kohtaan, missä ukko on. Tähän käytämme LOCATE
-käskyä. LOCATE
-käskylle annetaan (tässä tapauksessa) kaksi parametriä, rivi ja sarake. Meidän tapauksessamme rivi on sama kuin ukon y-koordinaatti, ja sarake sama kuin x-koordinaatti. Huomaa, että rivi annetaan ennen saraketta, joten ensin annamme y:n ja sitten vasta x:n.
LOCATE y, x ' Asetamme PRINT-käskyn tulostamaan oikeaan kohtaan PRINT "@"; ' Tulostamme @-merkin tähän kohtaan
Tässä vaiheessa voit yhdistää nämä läpikäydyt kaksi koodinpätkää ja todeta, että merkki todellakin ilmestyy suunnilleen keskelle näyttöä. Jos näin ei tapahdu, käy nämä ohjeet läpi uudelleen ja katso, mikä meni vikaan. Puolipiste PRINT
:n perässä estää rivinvaihdoin. Tämä siksi, että muuten aiheutuisi ongelmia liikuttaessa ruudun alalaidassa (minkä tosin estämme mahdollisesti tulevissa osissa).
Tämän jälkeen siirrymme kohtaan kaksi eli annamme pelaajan painaa näppäintä, jonka on tarkoitus ohjata ukkoa. Tunnet arvatenkin INPUT
-käskyn, joka antaa pelaajan kirjoittaa luvun tai tekstiä, joka tallentuu muuttujaan. Tässä tilanteessa emme kuitenkaan voi käyttää INPUT
:ia, jos haluamme, että pelkkä näppäimen painallus riittää jatkamaan suoritusta. INPUT
:han vaatii aina Enterin painalluksen kuittaukseksi. Siksi luemme näppäimen INKEY$
-funktiolla. Tämä funktio ei kuitenkaan keskeytä ohjelman suoritusta siksi aikaa, kunnes näppäintä on painettu, joten tämä keskeytys meidän on tehtävä itse. Keskeytyksen teemme silmukalla, josta poistutaan vasta, kun näppäintä on painettu.
DO ' Aloitetaan silmukka a$ = INKEY$ ' Luetaan painettu näppäin muuttujaan a$ LOOP UNTIL a$ <> "" ' Poistutaan, kun jotain näppäintä on painettu
Luomme siis DO
-silmukan, jota toistetaan niin kauan, kunnes a$ on erisuuri kuin ei-mitään eli toisin sanoen niin kauan, kunnes a$:ssa on jokin arvo. Ja koska a$:n arvo otetaan INKEY$
:sta, silmukka loppuu vasta, kun INKEY$
saa jonkin arvon, mikä taas tapahtuu vain, kun jotain näppäintä painetaan. Niin kauan, kuin mitään näppäintä ei paineta, INKEY$
:ssä ei ole mitään arvoa eikä silmukasta päästä pois (Pääsilmukan sisään tulee siis toinen DO
-silmukka. Älä sekoita niitä keskenään). Itse olen tottunut käyttämään a$-nimistä muuttujaa kysyttäessä näppäintä, mutta sillehän voit tietysti pistää ihan sen nimen, minkä itse haluat. Käyttäjän painaman näppäimen näppäinkoodi on nyt siis tallessa muuttujassa, jossa se saa odottaa, kunnes sitä tarvitaan.
Tässä välissä siirrymme kohtaan kolme eli ukon kumittamiseen. Muutenhan näytölle tulisi aina vain lisää ukkoja, kun ukko jäisi näkyviin jokaiseen kohtaan, missä se on ollut. Ukon voimme kumittaa aivan samalla tavalla, kuin piirsimmekin sen. Ukon tilalle vain tulostetaan tällä kertaa välilyönti, joka on siis tässä esimerkissä sama merkki kuin taustassa.
LOCATE y, x ' Asetetaan tekstintulostus jälleen oikeaan kohtaan PRINT " "; ' Tulostetaan välilyönti ukon paikalle
Kaikki tapahtuu kuitenkin niin nopeasti, ettei pelaaja ehdi huomata, että ukko välissä katoaa, ennen kuin se piirretään uudelleen (Tässä vaiheessa voit jälleen yhdistää koodirivit peräkkäin ja ajaa ohjelman. Tässä vaiheessa se siis piirtää ukon näytölle, antaa painaa jotain näppäintä ja kumittaa sitten ukon näytöltä).
Nyt kun ukko on siis kumitettu nykyisestä paikastaan, on aika siirtää se uuteen paikkaan, sen mukaan, mitä näppäintä pelaaja painoi. Miten ukkoa sitten liikutetaan? Se tehdään muuttamalla sen koordinaatteja. Koordinaatit ovat siis merkkigrafiikan tapauksessa rivejä ja sarakkeita.
Tekstitila on kutakuinkin tällainen:
Kuten kuvasta näkyy, x-koordinaatti kasvaa oikealle päin mentäessä ja y-koordinaatti alaspäin mentäessä, joten jos haluamme liikuttaa ukkoa oikealle, kasvatamme x-koordinaattia. Ja jos taas haluamme liikuttaa vasemmalle, niin päinvastoin vähennämme x-koordinaattia. Huomaa, että normaalissa matemaattisessa koordinaatistossa arvot kasvavat ylöspäin mentäessä ja vähenevät alaspäin mentäessä, mutta näytöllä tämä tapahtuu toisinpäin eli alaspäin mentäessä y-koordinaatti kasvaa.
x = x + 1
Tällaisella koodilla voimme kasvattaa muuttujan arvoa yhdellä. Jos x on tässä tilanteessa vaikkapa 40, tämä luku sijoitetaan tuohon koodiin, jolloin se olisi sama kuin:
x = 40 + 1
Ja 40 + 1 on tietysti 41, ja tämä luku sijoitetaan nyt muuttujaan sen edellisen arvon tilalle. X-koordinaatti kasvaa, ja kun ukko seuraavan kerran piirretään, se näyttää liikkuneen yhden pykälän oikealle. Sama temppu tehdään myös y-koordinaatille. Nyt meidän on vain ehtolausekkeella tarkistettava, mitä näppäintä pelaaja painoi, ja sen mukaan kasvatettava tai vähennettävä oikeaa koordinaattia. Tarkastellessa yhden muuttujan arvoja on yleensä hyvä idea käyttää CASE
-rakennetta.
SELECT CASE a$ CASE "4" x = x - 1 ' Painettiin 4, liikutaan vasemmalle CASE "6" x = x + 1 ' Painettiin 6, liikutaan oikealle CASE "8" y = y - 1 ' Painettiin 8, liikutaan ylöspäin CASE "5" y = y + 1 ' Painettiin 5, liikutaan alaspäin CASE CHR$(27) END ' Lopetetaan ohjelman suoritus END SELECT
Viimeinen vaihtoehto CHR$(27)
tarkoitaa Esc-näppäintä. CHR$
-funktio palauttaa parametriksi annettua numeroa vastaavan merkin. Muiden hyödyllisten näppäimien numerokoodeja löytyy tästä taulukosta. Jos haluat koodista tiiviimpää, voit halutessasi kirjoittaa sen vaikkapa näin:
SELECT CASE a$ CASE "4": x = x - 1 ' Painettiin 4, liikutaan vasemmalle CASE "6": x = x + 1 ' Painettiin 6, liikutaan oikealle CASE "8": y = y - 1 ' Painettiin 8, liikutaan ylöspäin CASE "5": y = y + 1 ' Painettiin 5, liikutaan alaspäin CASE CHR$(27): END ' Lopetetaan ohjelman suoritus END SELECT
Nyt koodi on valmis, joten voimme yhdistää sen. Koko koodissa olemme siis piirtäneet ukon, kysyneet näppäintä, kumittaneet ukon ja siirtäneet sen uuteen paikkaan, joten voimme jälleen aloittaa koko koodin alusta ja piirtää ukon uuteen paikkaan. Tämän teemme DO
-silmukalla. Eli lisäämme toistettavan koodin alkuun DO
ja loppuun LOOP
. On myös hyvä osata sisentää koodi, niin siitä tulee helpommin luettavaa. Perussääntöihin kuuluu mm. silmukan sisäisen koodin sisentäminen, mutta niistä voi toki halutessaan poiketa. Kokonaisuutena koodi näyttäisi siis tältä:
DIM x AS INTEGER, y AS INTEGER ' Esittelemme muuttujat ja määritämme niiden tyypiksi kokonaisluvun (INTEGER) x = 40 ' Asetamme x:n arvoksi 40 y = 12 ' Ja y:n arvoksi 12 WIDTH 80, 25 ' 80x25 tila CLS ' Tyhjäämme näytön DO ' Aloitamme silmukan LOCATE y, x ' Asetamme PRINT-käskyn tulostamaan oikeaan kohtaan PRINT "@"; ' Tulostamme @-merkin tähän kohtaan DO ' Aloitetaan silmukka a$ = INKEY$ ' Luetaan painettu näppäin muuttujaan a$ LOOP UNTIL a$ <> "" ' Poistutaan, kun jotain näppäintä on painettu LOCATE y, x ' Asetetaan tekstintulostus jälleen oikeaan kohtaan PRINT " "; ' Tulostetaan välilyönti ukon paikalle SELECT CASE a$ CASE "4": x = x - 1 ' Painettiin 4, liikutaan vasemmalle CASE "6": x = x + 1 ' Painettiin 6, liikutaan oikealle CASE "8": y = y - 1 ' Painettiin 8, liikutaan ylöspäin CASE "5": y = y + 1 ' Painettiin 5, liikutaan alaspäin CASE CHR$(27): END ' Lopetetaan ohjelman suoritus END SELECT LOOP ' Silmukka päättyy ja hyppäämme jälleen DO-riville, josta suoritus jatkuu
Kun ajat ohjelman, ruudulle ilmestyy @-merkki, jota voi nyt siis liikuttaa näppäimillä 4, 6, 8 ja 5. On tärkeää, että ymmärrät tähän asti käsitellyt asiat etkä vain kopioi suoraan tätä valmista koodia. Sillä tämä on pohja kaikille tulevaisuuden lisäyksille ja on siksi syytä osata hyvin.
Haluaisit kenties, että ukkoa voisi ohjata myös nuolinäppäimillä. Näillekin näppäimille on olemassa omat numerokoodinsa, jotka löytyvät em. taulukosta. Nuolinäppäinten koodit koostuvat kahdesta merkistä. Merkkiyhdistelmät ovat seuraavanlaiset:
(taulukossa myös kirjaimet on esitetty niiden ASCII-koodeina)
Vasen: CHR$(0)
+ "K"
Oikea: CHR$(0)
+ "M"
Ylös: CHR$(0)
+ "H"
Alas: CHR$(0)
+ "P"
Sinun tarvitsee vain vaihtaa tai lisätä nämä näppäimet CASE-rakenteeseen tähän tapaan:
SELECT CASE a$ CASE "4", CHR$(0) + "K": x = x - 1 ' Painettiin 4, liikutaan vasemmalle CASE "6", CHR$(0) + "M": x = x + 1 ' Painettiin 6, liikutaan oikealle CASE "8", CHR$(0) + "H": y = y - 1 ' Painettiin 8, liikutaan ylöspäin CASE "5", CHR$(0) + "P": y = y + 1 ' Painettiin 5, liikutaan alaspäin CASE CHR$(27): END ' Lopetetaan ohjelman suoritus END SELECT
Nyt olemme kasanneet peruspohjan pelimoottorille, jota kehitämme oppaan seuraavassa osassa. Jos aliohjelmat, funktiot ja taulukot tai niiden käyttäminen tuntuvat vierailta, sinun kannattaa lukea em. alkeisopasta ja harjoitella näiden asioiden käyttämistä. Tulevaisuudessa koodi nimittäin kasvaa, ja tulemme siksi pilkkomaan sen aliohjelmiksi selkeyden vuoksi.
Lopuksi sanon vielä, että monet merkkigrafiikkapelien harrastajat eivät juuri käytä nuolinäppäimiä, koska lähes koko ajan on tarve liikkua myös vinottain eli näppäimillä 1, 3, 7 ja 9. Nekin on siis hyvä lisätä CASE-rakenteeseen.
CASE "9" ' Vaikkapa näin x = x + 1 y = y - 1 CASE "9": x = x + 1: y = y + 1 ' Tai näin
Kaksoispiste erottelee lausekkeet toisistaan, ja molemmat suoritetaan vain, jos painettiin 9. Tuosta ymmärrät luultavasti idean, joten saat yrittää omin päin koodata kolme muuta näppäintä. Mietit vain, pitääkö koordinaatteja vähentää vai kasvattaa. Mikäli hallitset satunnaisluvut, voit ennen opassarjan seuraavaa osaa myös yrittää tehdä koodin, joka saa ukon aloittamaan aina satunnaisesti eri paikasta näytöllä.
Valmiin koodin ja käännetyn ohjelman ilman em. lisäyksiä voit ladata tästä.
No niin juuri tälläistä odotettiinkin, mäkin oon tekemässä merkkigrafiikka peliä mutta teen tuon liikkumisen aivan eri tavalla.
Minkäslaista tapaa käytät?
No se on kyllä huonompi menetelmä, mutta kun kerran aloitin sillä en tohtisi kirjoittaa uutta. Siinä ei käytetä CASE komentoa vaan IF :D, ja se looppaa kokoajan, joten jos pelaaja ei paina mitään se siltikin looppaa.
Eli peliruutu "vilkkuu" kokoajan? Huh.
Jos liikkuu nopeesti niin vois näyttää paremmalta jos eka piirretään uus ukko ja vasta sitten pyyhitään edellinen...
näin
herkko kirjoitti:
Eli peliruutu "vilkkuu" kokoajan? Huh.
Voihan sen estää käyttämällä eri muistisivuja ja PCOPYä
Mutta jatkuva luuppaaminen ei siltikään kannata, ellei peli ole reaaliaikainen, ts. elleivät muut pelihahmot voi liikkua silloinkin, kun pelaaja ei paina mitään.
Tuossahan on ideaa :)
mites tuolle ukolle tehää seinä niinku ettei pääsis joka paikasta läpi.. vai lukiks jottai semmosta tuolla
Katsopa opassarjan seuraava osa.
Mulla on 1.1 versio ja se ei haluu tehä noita $ ja @ ja muita vastaavia merkkejä miksi`?
Hyvä opas!
Nitro´s:
Hanki jostain uudempi versio.
Kiitti! Täst oppaast oli hyötyy! Mä osaan QB:tä jo kyllä muutenki aika hyvin mut mä opin täst viel uutta!
Nonniin! Nyt tuli jälleen yksi omista projekteistani pelastettua!
Ja totta tosiaan: tästähän oli mulle muutenkin hyötyä!
EDIT: Ja nyt tuli yksi mshta-sovellus omalle koneelle lisää. Keräilen oppaita omalle koneelle, jotta voisin niitä myöhemmin lukea.
Hunajavohveli kirjoitti:
Lopuksi sanon vielä, että monet merkkigrafiikkapelien harrastajat eivät juuri käytä nuolinäppäimiä, koska lähes koko ajan on tarve liikkua myös vinottain eli näppäimillä 1, 3, 7 ja 9. Nekin on siis hyvä lisätä CASE-rakenteeseen.
Ajattelin vain, että näinkin voi tehdä:
SELECT CASE A$ CASE "4", CHR$(0) + "K": ux = x - 1: uy = y 'vasemmalle CASE "6", CHR$(0) + "M": ux = x + 1: uy = y 'oikealle CASE "8", CHR$(0) + "H": uy = y - 1: ux = x 'ylös CASE "5", CHR$(0) + "P": uy = y + 1: ux = x 'alas CASE "5+6", CHR$(0) + "M+P": ux = x + 1: uy = y + 1 'oikealle alas" CASE "4+8", CHR$(0) + "K+H": ux = x - 1: uy = y - 1 'vasemmalle ylös CASE "6+8", CHR$(0) + "M+H": ux = x + 1: uy = y - 1 'oikealle ylös CASE "5+4", CHR$(0) + "K+P": uy = y + 1: ux = x - 1 'vasemmalle alas END SELECT
Eli liikkuminen nuolinäppäimillä ylä- ja alaviistoon.
EDIT: Ei näköjään toimi kaikilla koneilla toivotulla tavalla.
Joo ei kuule toimi!
mie oon tuhma ja muutan @ merkin ¥ merkiksi.
Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.