Kirjoittaja: hunajavohveli (2005).
Kahdessa edellisessä osassa loimme alkeellisen pelimaailman, jossa pelaaja pystyy liikkumaan pääsemättä esteiden läpi. Yksinään kuljeskelu käy kuitenkin helposti tylsäksi, joten on aika luoda pelaajalle seuraa. Siirrymme siis koodamaan toisia itsenäisesti liikkuvia otuksia (hirviöitä). Aloitamme luomalla tietotyypin, jollaiseen hirviöiden tiedot tallennetaan. Jos TYPE
-rakenne ei ole vielä tuttu, se kannattaa ensin opetella. En aio selittää sen käyttöä tässä oppaassa.
TYPE Monster x AS INTEGER y AS INTEGER END TYPE
TYPE
-rakenteet kannattaa kirjoittaa heti DECLARE
-rivien jälkeen ennen DIM
-lauseita. Tällainen alustava tietotyyppi sisältää kaksi elementtiä, x:n ja y:n eli otuksen koordinaatit. Muita ominaisuuksia ei tarvita vielä, ja ne lisätään vasta myöhemmin.
DIM SHARED Monster(1 TO 10) AS Monster, MonsterAmount AS INTEGER
Seuraavaksi luomme taulukon, jonka tyypiksi määritämme äsken luomamme rakenteen (taulukolla ja rakenteella saa olla sama nimi). Luomme myös muuttujan MonsterAmount. Tämä muuttuja sisältää tiedon siitä, kuinka monta hirviötä kartalla on. Muuttuja kasvaa sitä mukaa, kun uusia hirviöitä tulee, ja vähenee, kun niitä kuolee. Minulla on usein tapana lyhentää tämä muuttuja MA:ksi tiiviimmän koodin vuoksi. Tee itse, miten parhaaksi katsot. Oppaassa käytän joka tapauksessa pitempää nimeä. Taulukko ja muuttuja määritellään jaetuiksi kaikkien aliohjelmien kesken. Kokeillaanpa nyt lisätä yksi hirviö:
MonsterAmount = 1 Monster(1).x = 10 Monster(1).y = 5
Voit kirjoittaa koodin taulukon määrittelyn jälkeen. Hirviö on nyt teoriassa luotu. Mitään ei tietenkään vielä näy, joten siirrymme koodamaan piirtorutiinin.
Hirviöiden piirtäminen koodataan heti ukon piirtämisen perään (juuri ennen näppäimen lukemista).
FOR i = 1 TO MonsterAmount LOCATE Monster(i).y, Monster(i).x PRINT "&"; NEXT i
Tarkastellaanpa, mitä koodi tekee. Silmukassa käydään läpi luvut väliltä 1 - MonsterAmount. Sen sisällä on kaksi tutunnäköistä riviä. Noudatamme samaa periaatetta kuin ukonkin piirtämisessä. Nyt vain vaihdamme ukon koordinaatit hirviön koordinaatteihin ja @-merkin sijaan piirrämme &-merkin. Nämä neljä riviä riittävät piirtämään jokaisen hirviön. Kuten huomaat, muuttuja i on Monster-taulukon indeksinä, joten sen arvo määrää, mikä hirviö piirretään. Ja koska i kasvaa yhdellä joka kerta, kun silmukka aloittaa uuden kierroksen, joka kerralla piirretään eri hirviö. Tässä vaiheessa hirviöitä on tosin vasta yksi, joten silmukka loppuu heti, kun se on piirretty.
Indeksimuuttuja i määritellään koodin alussa:
DIM i AS INTEGER
Hirvöiden kumittaminen tehdään samanlaisella silmukalla kuin piirtäminenkin:
FOR i = 1 TO MonsterAmount LOCATE Monster(i).y, Monster(i).x PRINT " "; NEXT i
Eli koodi on muuten sama kuin piirrossa, mutta &-merkin tilalle laitetaan välilyönti. Hirviöiden kumitus kirjoitetaan aivan DO-silmukan loppuun eli heti ukon CanMove-tarkistuksen jälkeen.
Etenemme edelleen samassa järjestyksessä kuin ukon kanssa. Liikuttaminen tehdään siis heti kumittamisen jälkeen. Näppäimen lukeminen jää tietysti väliin, ja se korvataan tekoälyllä, koska tietokone liikuttaa hirviöitä itse. Liikkuminen ja yleensäkin hirviöiden koko toiminta jaetaan kahteen osaan. Ensinnäkin tekoälyyn, joka järkeilee, mitä hirviön kannattaa tehdä, ja toiseksi tarkistuksiin, jotka suorittavat tekoälyn antamat ohjeet, mikäli ne ovat sopivia. Tekoäly siis antaa esimerkiksi käskyn liikkua oikealle. Sitten tarkistetaan, onko siinä suunnassa jokin este, ja jos ei ole, niin liikutaan.
FOR i = 1 TO MonsterAmount LOCATE Monster(i).y, Monster(i).x PRINT " "; ux = Monster(i).x + 1 ' Tekoäly uy = Monster(i).y + 1 IF CanMove(ux, uy) THEN ' Tarkistukset Monster(i).x = ux Monster(i).y = uy END IF NEXT i
Kumittaminen ja liikuttaminen sopivat hyvin samaan silmukkaan, joten lisäämme liikutuskoodin siihen. Ensimmäiset kaksi riviä kumituksen jälkeen ovat alkeellinen tekoäly, joka komentaa hirviötä liikkumaan aina viistosti alas ja oikealle. Voimme käyttää muuttujia ux ja uy myös hirviöiden tapauksessa, sillä ne ovat väliaikaisia muuttujia, joilla ei tarkistuksen jälkeen ole mitään merkitystä. Seuraavat neljä riviä tarkistavat uudet koordinaatit mahdollisen esteen varalta, ja liikuttavat hirviön siihen, jos estettä ei löydy. Nyt voit kokeilla ajaa ohjelman. Hirviö ilmestyy kartalle ja liikkuu, kunnes törmää mahdolliseen esteeseen.
Yritetäänpä seuraavaksi saada hirviö seuraamaan pelaajaa. Tätä varten meidän täytyy määritää, missä suunnassa pelaaja on hirviöön nähden. Oletetaanpa, että pelaaja on koordinaateissa (15, 4) ja hirviö (10, 10). Tällöin voidaan laskea oikea suunta vähentämällä pelaajan koordinaateista hirviön koordinaatit:
X-koordinaatit: 15 - 10 = 5
Y-koordinaatit: 4 - 10 = -6
X-koordinaatti on positiivinen eli hirviötä tulee liikuttaa oikealle. Y taas negatiivinen, joten hirviötä pitää liikuttaa myös ylöspäin. Asiaa havainnollistava kuva:
Päinvastoin taas, jos x-koordinaatti olisi negatiivinen, pitäisi hirviötä liikuttaa vasemmalle. Ja jos y posiitivinen, niin alaspäin. Liikutuksen voisi siis tehdä näin:
IF x - Monster(i).x > 0 THEN ux = Monster(i).x + 1 IF x - Monster(i).x < 0 THEN ux = Monster(i).x - 1 IF y - Monster(i).y > 0 THEN uy = Monster(i).y + 1 IF y - Monster(i).y < 0 THEN uy = Monster(i).y - 1
Mutta on olemassa kätevämpikin keino, jonka käytössä ei tarvita ehtolauseita:
ux = Monster(i).x + SGN(x - Monster(i).x) uy = Monster(i).y + SGN(y - Monster(i).y)
SGN
-funktio palauttaa lukuarvon etumerkin. Jos luku on positiivinen, funktio palauttaa 1, jos negatiivinen, funktio palauttaa -1 ja nolla, jos luku on nolla. Esimerkkilukuarvot 15 - 10 = 5 ja 4 - 10 = -6 palauttavat siis 1 ja -1, jotka lisätään hirviön alkuperäisiin koordinaatteihin. Tällöin hirviö liikkuu viistosti ylös ja oikealle. Oikea suunta määräytyy kaikissa muissakin tilanteissa. Voit nyt kokeilla ajaa ohjelman, jolloin hirviö seuraa pelaajaa. Hirviö pääsee kuitenkin liikkumaan samaan kohtaan pelaajan kanssa. Meidän on siis kehitettävä hirviön törmäystarkistusta.
FUNCTION Reserved (px AS INTEGER, py AS INTEGER) Reserved = 0 ' Funktio palauttaa nollan, jos tarkistukset eivät havaitse estettä IF px = x AND py = y THEN ' Jos pelaaja on ruudussa, Reserved = -1 ' ruutu on varattu EXIT FUNCTION END IF END FUNCTION
Luodaan Reserved-niminen funktio. Funktion sisälle tehdään ehtolause, jossa verrataan parametreiksi annettuja koordinaatteja pelaajan koordinaatteihin. Jos ne täsmäävät, funktio palauttaa -1 (tosi). Jotta pelaajan koordinaatteja voitaisiin verrata funktion sisällä, ne on muutettava jaetuiksi. Lisäämme pelaajan koordinaattien määrittelyyn siis SHARED
in.
DIM SHARED x AS INTEGER, y AS INTEGER
Olemme aikaisemmin käyttäneet muuttujia x ja y myös CanMove-funktiossa. Nyt kun pelaajan koordinaatit muutetaan jaetuiksi, ei kannata enää käyttää CanMovessa saman nimisiä parametrejä. Muutetaan siis kaikki CanMove-funktion muuttujat x ja y muuttujiksi px ja py. Muista vaihtaa muuttujat myös funktion DECLARE
-rivillä.
Nyt kun meillä on Reserved-funktio, lisäämme sen hirviön tarkistuksiin:
IF CanMove(ux, uy) AND NOT Reserved(ux, uy) THEN ' Tarkistukset Monster(i).x = ux Monster(i).y = uy END IF
Jos CanMove palauttaa -1 (eli ruudussa ei ole estettä) ja jos Reserved EI palauta -1 (eli ruutu EI ole varattu), niin silloin ruutuun voidaan liikkua. Nyt hirviö ei siis enää voi liikkua samaan ruutuun pelaajan kanssa. Pelaaja kylläkin voi vielä liikkua hirviön päälle, mikä korjataan hetken kuluttua.
Yksi hirviö toimii jo ihan mukavasti. Nytpä kokeillaan, miten onnistuu useamman hirviön lisääminen yhtä aikaa. Toinen hirviö luodaan, kuten ensimmäinenkin. Samalla kasvatetaan MonsterAmount-muuttujaa.
MonsterAmount = 2 Monster(1).x = 10 Monster(1).y = 5 Monster(2).x = 60 ' Uuden hirviön Monster(2).y = 20 ' tiedot
Kokeilepa nyt ajaa ohjelma. Jos kaikki toimii, kuten pitää, kumpikaan hirviöistä ei pääse pelaajan läpi. Saatat huomata, että hirviöt pääsevät kuitenkin samoihin ruutuihin toistensa kanssa. Muokkaamme siis Reserved-funktiota niin, että se ottaa huomioon myös ruudussa mahdollisesti olevat hirviöt.
FUNCTION Reserved (px AS INTEGER, py AS INTEGER) Reserved = 0 ' Funktio palauttaa nollan, jos tarkistukset eivät havaitse estettä DIM i AS INTEGER ' Indeksimuuttuja IF px = x AND py = y THEN ' Jos pelaaja on ruudussa, Reserved = -1 ' ruutu on varattu EXIT FUNCTION END IF FOR i = 1 TO MonsterAmount ' Verrataan jokaisen hirviön koordinaatteja IF px = Monster(i).x AND py = Monster(i).y THEN ' Jos ruudussa on hirviö Reserved = -1 ' ruutu on varattu EXIT FUNCTION END IF NEXT i END FUNCTION
Nyt Reserved-funktiossa käydään silmukalla läpi jokainen hirviö ja verrataan niiden koordinaatteja parametreiksi annettuihin koordinaatteihin. Jos ne täsmäävät, ruutu on varattu, ja Reserved palauttaa -1. Enää hirviöt eivät pääse samoihin ruutuihin toistensa kanssa, mutta pelaaja voi yhä liikkua niiden päälle. Tästä selvitään yksinkertaisesti tekemällä myös pelaajalle Reserved-tarkistus. Lisäämme sen samalle riville aiemmin luodun CanMove-tarkistuksen kanssa:
IF CanMove(ux, uy) AND NOT Reserved(ux, uy) THEN x = ux y = uy END IF
Nyt törmäystarkistukset ovat täydellisiä, eivätkä pelaaja ja hirviöt pääse enää samoihin ruutuihin.
Nyt kun hirviöiden perusasiat on koodattu, voimme luoda erilaisia hirviöitä. Ensin luomme tietorakenteeseen uuden elementin:
TYPE Monster Race AS INTEGER ' Uusi elementti x AS INTEGER y AS INTEGER END TYPE
Race (eli laji/rotu) määrittelee, minkälaisesta hirviöstä on kyse. Luomme myös toisen tietorakenteen, johon tallennetaan lajien ominaisuudet.
TYPE Race Char AS STRING * 1 ' Merkki Col AS INTEGER ' Väri END TYPE
Tämä tietorakenne pitää sisällään tiedot ominaisuuksia, jotka ovat yhteisiä kaikille samaa lajia edustaville hirviöille (esim. voima, kesto, nopeus). Tässä vaiheessa tarvitsemme vain kahta elementtiä: Char sisältää yhden merkin pituisen merkkijonon, jonka arvo kertoo, minkälaisena merkkinä lajin hirviöt näkyvät kartalla. Col-elementissä taas on muistissa merkin väri. Luodaan myös taulukko, jolle Race-tietotyyppi määritetään:
DIM SHARED Race(1 TO 10) AS Race
Nyt voimme luoda taulukkoon tietoja erilaisille lajeille. Tehdäänpä esim. näin:
' Lajien määrittely Race(1).Char = "b" Race(1).Col = 6 Race(2).Char = "D" Race(2).Col = 2 ' Hirviöiden määrittely MonsterAmount = 2 Monster(1).Race = 1 ' 1. hirviön laji Monster(1).x = 10 Monster(1).y = 5 Monster(2).Race = 2 ' 2. hirviön laji Monster(2).x = 60 Monster(2).y = 20
Lajin numero yksi merkiksi määritetään ruskea b-kirjain ja lajille kaksi vihreä D. Leikitään vaikka, että nämä tarkoittavat lepakkoa ja lohikäärmettä. Nämä rivit tulevat juuri ennen aiemmin koodattua hirviöiden määrittelyä, johon lisäämme nyt Race-ominaisuuden. Ensimmäisen hirviön lajiksi pistetään 1 ja toisen lajiksi 2. Nyt hirviöillä on teoriassa omat lajinsa. Mitään ei tietysti vielä näy, joten muokataanpa piirtokoodia.
FOR i = 1 TO MonsterAmount LOCATE Monster(i).y, Monster(i).x COLOR Race(1).Col PRINT Race(1).Char; NEXT i
Tämä voi olla aluksi vaikea ymmärtää, joten lue seuraava läpi huolella. Aiemmasta piirtokoodista on nyt poistettu rivi, joka piirsi &-merkin. Tilalle on tullut kaksi uutta riviä. Ensimmäinen riveistä määrää tekstin värin ja toinen tulostaa merkin. Arvoina ovat nyt Race(1).Col ja Race(1).Char. Tämä tarkoittaa sitä, että jokaiselle hirviölle tulostetaan ensimmäisen lajin merkki ensimmäisen lajin värillä. Eli jokaisen hirviön tilalle tulostuu ruskea b. Jotta saisimme tulostettua jokaiselle hirviölle sen omaa lajia vastaavan merkin, meidän täytyy antaa Race-taulukolle indeksiksi ykkösen sijaan hirviön lajia vastaava numero. Laji on tallennettuna Monster-taulukon Race-elemettiin, joten ykkösen tilalle laitetaan Monster(i).Race.
FOR i = 1 TO MonsterAmount LOCATE Monster(i).y, Monster(i).x COLOR Race(Monster(i).Race).Col ' Vaihdetaan väri PRINT Race(Monster(i).Race).Char; ' Tulostetaan merkki NEXT i
Nyt koodissa on kaksi taulukkoa sisäkkäin. Ensin annetaan sisimmäiselle (Monster) indeksiksi i, jolloin se palauttaa hirviön lajin. Tämän jälkeen laji annetaan indeksiksi Race-taulukkolle, jolloin se palauttaa kyseisen lajin värin ja merkin. Kokeile ajaa ohjelmaa. Ruudulla pitäisi nyt liikkua kaksi erilaista hirviötä. Jottei pelaajan väri muuttuisi COLOR
-käskyn vuoksi, se täytyy palauttaa takaisin harmaaksi. Tai jos haluat, voit saman tien määrittää pelaajallekin jonkin eri värin.
LOCATE y, x COLOR 7 ' Lisätään värinpalautus PRINT "@";
Tässä olivat tämän oppaan asiat. Seuraavaan vaiheeseen kuuluu niin paljon asioita, että jätän ne suosista seuraavaan oppaaseen. Mutta nytpä on kuitenkin valmiina pohja hirviöiden ohjelmointiin. Tästä eteenpäin jatkamme luomalla pelaajalle ja hirviöille uusia ominaisuuksia ja mahdollistamalla niiden välisen taistelun. Tulemme kehittelemään myös hirviöiden tekoälyä.
Jottei aika tule heti pitkäksi, voisit kokeilla ominpäin ainakin seuraavia asioita:
Jos teet lisäyksiä, muista kuitenkin aina säilyttää alkuperäinen koodi, koska seuraavassa osassa jatketaan siitä, mihin jäätiin ilman näitä lisätehtäviä.
Valmis koodi ja käännetty ohjelma ovat saatavilla tästä.
Ihan kiinnostava tyyli tuo TYPE, täytynee käyttää sitä.
Ja taas! \o/ Ensimmäinen kommentti.
Hyvää työtä :-)
Korjaa Reserved funtion teksti
IF px = x AND py = py THEN
tälläiseksi
IF px = x AND py = y THEN
Koodi esimerkissäsi(se tiedosto) se on oikein, mutta tossa sivulla on virhe
Korjattu on.
Wau! En vielä ehtinyt koodaamaan, mutta oppaan sain luettua ja vaikutti hyvältä!
AARGH!!! Ei vieläkään kerrota sitä kartan latausta!!! :'(
tumpelo... mitä oikein meinaat tolla kartan latauksella?
öö sitä et miten sen kentän saa ladattua oikeaoppisesti eikä niin että map(1,1) = 3 jne... Vähän hiasta tolla tavalla...
Kartan lataaminen onnistuu kyllä erittäin helposti, mutta sen karttatiedoston luominen on paljon mutkikkaampaa ilman sitä varten tehtyä editoria. Joudun luultavasti käsittelemään tätä asiaa kokonaan omassa oppaassaan.
kannattais tehä se opas seuraavaksi, kun se on ainut este jonka takia en pääse koodaamaan erästä peliä :(
Juuh, mahtava opas.
Anteeksi jos joku on jo maininnut, mutta eikös tuonne RandomMap aliohjelmaan kannattaisi laittaa Randomize Timer käsky?
Tuleeko sitten, että kartat on cryptattuja ja ne encryptataan omalla ohjelmalla ja luetaan.
Saatan kirjoittaa jotain kryptauksestakin, mutta senhän voi toki opetella erikseenkin.
Rikesakko: Juu, voihan tuonne laittaa Randomize Timerin, jos haluaa, muttei sillä tämän oppaan kannalta ole mitään merkitystä, koska loppujen lopuksi koko aliohjelmaa ei edes tarvita.
Joo seuraavaks karttatiedoston luomisesta. Tai vois vaikka ees kertoo minkä nimisen editorin tarvii..
Onks sen uuden oppaan kirjottaminen yhtää missää vaiheessa?
Mä mietin ihan samaa.
Editori on oikeastaan hyvä koodata kokonaan itse, joten saatan siitäkin joskus oppaan tehdä. Kirjoittelu on jäänyt tässä nyt hieman vähemmälle, mutta voisin alkaa jossain välissä taas paneutua hommaan.
Hyvä opas vaikken Basiccia osaakaan. Pienellä päättelyllä käänsin tuon c++:lle ja hyvältä näyttää. Uutta osaa odotetaan vähintäänkin ideoiden vuoksi.
Milloinka seuraava osa ilmestyy?
COOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOL!!!!!!!!!!!!!!!!!!!!!
Itse oikeastaan laitan hieman eri järjestykseen nuo rivit ja ne ovat vähän erilaisia. Tuon kun voi toteuttaa usealla eri tavalla.
Minun koneessani muuten luvut 80 ja 25 menevät sekaisin. Näyttö suttaantuu, jos Y menee 25 ja X 80.
Lisäksi laitan SCREEN 13-komennon yleensä alkuun ja vähennän hiukan X:n maksimiarvoa, koska muuten kirjain on leveyssuunnassa pienempi kuin pituussuunnassa.
Tumpelo kirjoitti:
AARGH!!! Ei vieläkään kerrota sitä kartan latausta!!! :'(
Laita alkuun vaikka DIM MapDrawVar(25) AS STRING
Laita sitten RandomMap-juttuun näin:
MapDrawVar(1) = "################################################################################" MapDrawVar(2) = "# # #" MapDrawVar(3) = "# # #" MapDrawVar(4) = "# # #" MapDrawVar(5) = "# ##### #" MapDrawVar(6) = "# #" MapDrawVar(7) = "# # #" MapDrawVar(8) = "# #" MapDrawVar(9) = "# #" MapDrawVar(10) = "# #" MapDrawVar(11) = "# #" MapDrawVar(12) = "# #" MapDrawVar(13) = "# ############ #" MapDrawVar(14) = "# #" MapDrawVar(15) = "# #" MapDrawVar(16) = "# #" MapDrawVar(17) = "# #" MapDrawVar(18) = "# #" MapDrawVar(19) = "# #" MapDrawVar(20) = "# #" MapDrawVar(21) = "# #" MapDrawVar(22) = "# #" MapDrawVar(23) = "# #" MapDrawVar(24) = "# #" MapDrawVar(25) = "################################################################################" FOR GetMapX = 1 TO 80 FOR GetMapY = 1 TO 25 Map(GetMapX, GetMapY) = 0 IF MID$(MapDrawVar(GetMapY), GetMapX) = "#" THEN Map(GetMapX, GetMapY) = 1 NEXT GetMapY NEXT GetMapX
EDIT::::
Dude kirjoitti:
tuon vois teherä FOR jutulla
tarkoitin että tuonne väliin saa sitten #-merkeillä seinät minne haluaa.
Juhko kirjoitti:
Laita alkuun vaikka DIM MapDrawVar(25) AS STRING
Laita sitten RandomMap-juttuun näin:...
tuon vois teherä FOR jutulla
Ois jo korkea aika saada se neljäs osa tänne. Kohta 2 vuotta jo menny.
Niinpä. Ettei hunajavohveli olisi unohtanut?
mulla ei toimi numpadilla mulla toimii vaan numeronäppäimillä!:(
^ Ehkä tyhmä kysymys, mutta oliks sulla Num Locki päällä?
jaa koskakohan tähän tulee uus osa vai onko enää ees tulossa? oli aika hyödyllinen itselleni ainakin, vaikka käytin FreeBasicia.
onko mahdollista tehdä ns nazi-tagia?
hunajavohveli vois kyl kirjottaa sen loppuu ku muuten en ikinä opi tekee merkkigrafiikkapelejä
iha hyvä
Pikkasen kestää seuraava osa... Eihän seuraavan osan lupa amisesta ole kuin vasta 8 vuotta.
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.