Osa 2A. Kehittyneempi typejen käsittely: Muut listojen läpikäyntitavat
Oppaan ensimmäinen osa käsitteli typejen käytön perusasioita. Nyt käsittelemme monipuolisempia tapoja hallita typejä ja erilaisia "kikkoja", joilla niistä saadaan kaikki hyöty irti. Olisi suositeltavaa osata tai ainakin jotenkuten ymmärtää seuraavat asiat jo ennestään: If-lauseet, While...Wend -silmukoiden toimintaperiaate, mikä on lista (siis se "sisäinen" linkitetty lista, johon luodut instanssit tallentuvat, ja josta puhuttiin ensimmäisessä osassa) sekä viitemuuttujien käyttö. Myös funktioiden toimintaperiaatteen ymmärtäminen auttaa myöhemmissä vaiheissa, joissa käsitellään typejen käyttöä funktioiden kanssa.
Kaikissa seuraavissa lista- ja Insert-esimerkeissä on käytetty tällaista yksinkertaista esimerkki-typeä:
Code: Select all
Type Esimerkki
Field Obj% //Instanssin objekti
Field HP% //Instanssin kestopisteet (health)
End Type
Heti ensimmäisenä ajattelin käydä läpi neljä uutta typeihin liittyvää komentoa, joita aiemmassa osassa ei vielä käytetty (Paitsi Firstiä graafisessa esimerkissä):
First, Last, After ja
Before. Englantia osaavat luultavasti pystyvät päättelemäänkin jo komentojen tarkoitukset niiden nimistä. Jokainen näistä komennoista palauttaa viitteen, joka voidaan tallentaa viitemuuttujaan (joiden käyttöä siis käytiin läpi ensimmäisessä osassa). Käydäänpä komennot pikaisesti läpi:
First([Type-määrittelyn nimi])
Tämä komento palauttaa viitteen listan ensimmäiseen instanssiin. Tulos on NULL, jos ainuttakaan instanssia ei ole olemassa.
Käyttöesimerkki:
Code: Select all
Esim.Esimerkki = First(Esimerkki) //Palauttaa ensimmäisenä listassa olevan Esimerkki-instanssin
//viitteen ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
Last([Type-määrittelyn nimi])
Tämä komento palauttaa viitteen listan viimeiseen instanssiin. Tulos on NULL, jos ainuttakaan instanssia ei ole olemassa.
Käyttöesimerkki:
Code: Select all
Esim.Esimerkki = Last(Esimerkki) //Palauttaa viimeisenä listassa olevan Esimerkki-instanssin
//viitteen ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
Jos listassa on vain yksi instanssi, First ja Last palauttavat molemmat saman instanssin (koska se on sekä ensimmäinen että viimeinen).
After([Viitemuuttuja])
Tämä komento palauttaa viitteen annettua instanssia (annetaan viitemuuttujan kautta) SEURAAVAAN instanssiin. Jos annettu instanssi oli listan viimeinen (eli seuraavaa instanssia ei ole), on tulos NULL. Huomaa, että annetun viitemuuttujan on osoitettava johonkin instanssiin ennen tämän komennon käyttöä.
Käyttöesimerkki:
Code: Select all
Esim.Esimerkki = After(Esim.Esimerkki) //Palauttaa Esim.Esimerkki -viitemuuttujan viittaamaa instanssia
//seuraavan instanssin, ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
//Eli, jos viitemuuttuja osoitti ENNEN tätä komentoa esimerkiksi listan ensimmäiseen instanssiin, se osoittaa
//komennon suorittamisen JÄLKEEN listan toiseen instanssiin (tai NULL:iin, jos annettu viitemuuttuja osoitti listan ainoaan tai viimeiseen instanssiin)
Käytännössä For...Each-rakenteessa Next [tunnuksen nimi tähän] -rivillä siis suoritetaan tämän kaltainen operaatio, eli For -lauseessa annettu tunnus asetetaan osoittamaan nykyistä instanssia seuraavaan instanssiin. Jos ajatellaan tätä ensimmäisessä osassa olleen linkitetyn listan toimintaa esittävän kuvan kautta, After palauttaa seuraava -nuolen osoittaman instanssin.
Before([Viitemuuttuja])
Tämä komento palauttaa viitteen annettua instanssia (annetaan viitemuuttujan kautta) EDELTÄVÄÄN instanssiin. Jos annettu instanssi oli listan ensimmäinen (eli edeltävää instanssia ei ole), on tulos NULL. Huomaa, että annetun viitemuuttujan on osoitettava johonkin instanssiin ennen tämän komennon käyttöä.
Käyttöesimerkki:
Code: Select all
Esim.Esimerkki = Before(Esim.Esimerkki) //Palauttaa Esim.Esimerkki -viitemuuttujan viittaamaa instanssia
//seuraavan instanssin, ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
//Eli, jos viitemuuttuja osoitti ENNEN tätä komentoa esimerkiksi listan viimeiseen instanssiin, se osoittaa
//komennon suorittamisen JÄLKEEN listan toiseksi viimeiseen instanssiin (tai NULL:iin, jos annettu viitemuuttuja osoitti listan ainoaan tai ensimmäiseen instanssiin)
Jos ajatellaan tätä ensimmäisessä osassa olleen linkitetyn listan toimintaa esittävän kuvan kautta, Before palauttaa edellinen -nuolen osoittaman instanssin. Tätä voidaan siis käyttää hakemaan tiettyä instanssia edeltävä instanssi. Komentoja voidaan myös yhdistää:
Code: Select all
Esim.Esimerkki = Before(Last(Esimerkki)) //Palauttaa viimeistä edeltävän instanssin
Näitä komentoja käytetään myös Insert -komennon kanssa, mutta palaan siihen myöhemmin.
Nyt voidaan jatkaa aiheesta, johon ensimmäinen osa päättyi: listojen läpikäynti. Ensimmäisenä lähdetään liikkeelle mielestäni yksinkertaisimmasta tapauksesta: For...Each:n rakentaminen edellä esitettyjen komentojen avulla. Tässähän ei tietysti siinä mielessä ole mitään järkeä, että CB:ssä tosiaan on jo hyvin toimiva For...Each -rakenne, mutta sen toteutus on niin yksinkertainen, että sillä on helpointa esittää First:n ja After:n toimintaa.
For...Each -rakennetta voitaisiin käyttää Esimerkki-typen kanssa tähän tapaan:
Code: Select all
//Esim.Esimerkki asetetaan osoittamaan listan ensimmäistä instanssia,
//ja jos sellainen on, siirrytään silmukan sisäpuolelle
For Esim.Esimerkki = Each Esimerkki
//Typelle voitaisiin tehdä tässä välissä jotain
//Haetaan seuraava instanssi, ja jos sellaista ei ole, poistutaan silmukasta
Next Esim
Tämähän lienee jo tuttua oppaan ensimmäisestä osasta. Lähdetään siis liikkeelle For...Each:n toteuttamisesta First:llä ja Afterilla:
Code: Select all
// ------------- For Esim.Esimerkki = Each Esimerkki ----------------
//Haetaan viitemuuttujaan listan ensimmäinen instanssi
Esim.Esimerkki = First(Esimerkki)
//Aletaan suorittaa silmukkaa, jos saatiin ensimmäinen instanssi (eli jos While -lauseen sulkujen välissä oleva ehto on tosi)
While(Esim <> NULL)
// ------------- For Esim.Esimerkki = Each Esimerkki ----------------
//Tässä kohtaa voitaisiin tehdä esim. typen kentille jotain,
//samoin kuin yleensä For...Each - Next lauseiden välissä
// ------------- Next Esim ----------------
//Haetaan nykyistä seuraava instanssi
Esim.Esimerkki = After(Esim.Esimerkki)
//Wend -lauseen kohdalla suoritus palaa While -lauseen ehtotarkistukseen, ja
//jos ehto ei pidä enää paikkaansa, siirrytään Wend:iä seuraavalle riville
Wend
// ------------- Next Esim ----------------
Eli, // ---- -kommentein merkittyjen rivien välissä on vastaavan komennon toteutus muilla keinoin. Kun For...Each -silmukka alustetaan, hakee se annettuun viitemuuttujaan ensin listan ensimmäisen instanssin. Tämä on toteutettu esimerkkikoodissa First:n avulla, joka siis hakee Esim.Esimerkki-viitemuuttujaan ensimmäisen Esimerkki-instanssin listan alusta. Suoritus siirtyy While-silmukan sisäpuolelle, jos listassa on yksikin instanssi, eli Esim.Esimerkki sisältää viitteen johonkin muualle kuin NULLiin. Silmukan sisäpuolella voitaisiin instanssille tehdä mitä ikinä tarvitseekaan, samoin kuin For...Each -silmukassa varsinaisen For -rivin ja Next -rivin välillä. Lopussa on Next Esim -lauseen toteutus, eli haetaan nykyistä instanssia seuraava instanssi samaan viitemuuttujaan, ja aloitetaan silmukan suoritus alusta. Kannattaa tosiaan huomata, että tämä lause (Esim.Esimerkki = After(Esim.Esimerkki) ) hakee siis todellakin ITSEÄÄN seuraavan instanssin, ja tallentaa sen viitteen "itsensä päälle". Tämän jälkeen Esim.Esimerkki osoittaa siis siihen instanssiin, jonka edeltävää instanssia se osoitti ennen After-riviä. Tässä kohtaa tapahtuu uudelleen While-lauseen ehdon tarkistus, ja jos ollaan tultu listan loppuun, osoittaa Esim.Esimerkki NULLiin ja silmukan suoritus päättyy.
Tällaista rakennetta ei tietenkään ole tarpeen käyttää, sehän vain toteuttaa saman asian kuin For...Each -silmukkarakennekin. Jos Esim.Esimerkki -viitemuuttujaan kuitenkin sijoitetaan viite johonkin muuhun, kuin listan ensimmäiseen instanssiin, se käykin läpi ainoastaan kaikki ko. instanssia seuraavat instanssit. Tätä hyödynnetään hetken kuluttua, kun katsotaan miten tehdä törmäystarkistukset tehokkaasti saman listan instanssien kesken.
Huomionarvoista on myös, että jos silmukassa poistetaan jokin instanssi viitemuuttujan läpi, on viite seuraavaan otettava jo ennen poistoa talteen johonkin toiseen viitemuuttujaan, koska poiston jälkeen viite on NULL, eikä After(...) -komento pysty palauttamaan seuraavaa instanssia NULL:sta! For...Each:n kanssa vastaavaa ongelmaa ei ilmene, ilmeisesti se pitää viitteen seuraavaan instanssiin tallessa jossain muualla, eli instanssin poisto keskellä silmukkaa ei YLEENSÄ aiheuta ongelmia. Poikkeuksena sääntöön on, jos viitteen läpi yritetään tehdä mitään poiston jälkeen, ennen kuin viite osoittaa jo johonkin toiseen instanssiin, kaatuu ohjelma jälleen MAV:iin (NULL-viitteen läpi ei saa
KOSKAAN yrittää tehdä mitään!).
Nyt kuitenkin otetaan vielä toinen yksinkertainen esimerkki. Tällä kertaa kuljetaankin listaa toiseen suuntaan, eli viimeisestä instanssista ensimmäiseen. Silmukan toimintaperiaate on vastaavanlainen kuin edellä, nyt vain siis kuljetaan toiseen suuntaan:
Code: Select all
//Haetaan listan viimeinen instanssi viitemuuttujaan
Esim.Esimerkki = Last(Esimerkki)
//Suoritetaan silmukkaa niin kauan, kunnes Esim osoittaa NULLia
While(Esim <> NULL)
//Tässä voitaisiin tehdä typelle jotain...
//Haetaan nykyistä edeltävä instanssi
Esim.Esimerkki = Before(Esim.Esimerkki)
//Wend:n kohdalla tehdään taas ehtotarkistus, ja jos se pitää paikkansa, silmukka suoritetaan uudelleen
//Jos ollaan tultu jo listan alkuun, ensimmäistä edeltävä on taas NULL, ja poistutaan silmukasta
Wend
Sisäkkäiset silmukat ovat käteviä vertailu- ja tarkistusrakenteiden tekemisessä. Jos verrataan vaikkapa jokaista ammusta jokaiseen pelihahmoon, käytetään kahta sisäkkäistä For...Each -silmukkaa, joista toinen käy läpi hahmot ja toinen ammukset, ja näiden välistä törmäystä tarkistetaan.
Code: Select all
Type Ammus
Field Obj%
Field Vahinko%
End Type
Type PeliHahmo
Field Obj%
Field HP%
End Type
//Törmäystarkistus
//Käydään läpi pelihahmot
For Hahmo.PeliHahmo = Each PeliHahmo
//Käydään läpi ammukset
For Am.Ammus = Each Ammus
//Tarkistetaan, törmääkö Ammus-instanssin objekti PeliHahmo-instanssin objektiin
If(ObjectsOverlap(Am\Obj%, Hahmo\Obj%)) Then
//Vähennetään pelihahmon hp:stä ammuksen vahinko
Hahmo\HP% = Hahmo\HP% - Am\Vahinko%
//Poistetaan ammuksen objekti ja instanssi
DeleteObject Am\Obj% //Jos tätä ei tehdä, objekti jää muistiin vaikka instanssi poistetaan!!
Delete Am.Ammus //Poistetaan instanssi
EndIf
Next Am
Next Hahmo
Yllä oleva koodi ei yksinään tee mitään, sillä siitä puuttuu ammusten ja pelihahmojen luonti ja päivitys, sekä ruudun piirto. Ideana on vain esittää, miten kahden erityyppisen instanssin objektien välinen törmäys voitaisiin tarkistaa sisäkkäisillä For..Each:eilla.
HUOM! Koska ammus- ja pelihahmo- esimerkissä sisemmässä silmukassa poistetaan Ammus-instanssi, on sillä merkitystä, kumpaa selataan sisä- ja kumpaa ulkosilmukassa! Miksikö? Jos Ammus-instanssit selattaisiin ulommalla silmukalla, voisi syntyä tilanne, jossa sisempi silmukka on menossa PeliHahmo-listan keskellä. Ammus törmää pelihahmoon, ja se poistetaan. Seurauksena, kun sisempi silmukka siirtyy seuraavaan PeliHahmoon, Am-viite onkin NULL, ja ohjelma kaatuu ObjectsOverlap:in kohdalla, koska NULL-viitteen läpi yritetään lukea Am\Obj% -kenttää! Tällaisia ajatusvirheitä sattuu hyvin helposti, joten kannattaa aina käydä mielessään tai pienellä testiohjelmalla läpi, miten silmukoiden toiminta itseasiassa etenee suorituksen aikana. On aina pidettävä huolta, että jos jossain kohtaa poistetaan viitemuuttujan kautta jokin instanssi, viitemuuttujan läpi ei yritetä tehdä mitään, ennen kuin siihen on taas saatu uusi viite (tai vähintäänkin tarkistetaan viitemuuttuja NULL:n varalta ennen sen käyttöä)!
Sisäkkäisiä silmukoita käyttämällä voidaan verrata myös saman listan kahta erillistä instanssia toisiinsa. Näin saatettaisiin tehdä esimerkiksi, jos tarvitsee tarkistaa kahden saman tyyppisen instanssin objektien törmäystä toisiinsa (esim. kaikkien samalla typellä toteutettujen pelihahmojen väliset törmäykset pitäisi tarkistaa). Koska molemmat objektit sijaitsevat samassa listassa, voitaisiin käyttää kahta sisäkkäistä For...Each -silmukkaa. Mutta on olemassa syy, miksi näin ei kannata tehdä tilanteessa, jossa verrataan samassa listassa sijaitsevia objekteja! Katsotaan kuitenkin nyt ensiksi, miten törmäystarkistus tehtäisiin kahdella sisäkkäisellä For...Each -silmukalla samassa listassa sijaitsevien instanssien objekteille:
Code: Select all
//Käydään lista läpi
For Esim.Esimerkki = Each Esimerkki
//Käydään lista läpi uudelleen, tällä kertaa viitteet tallennetaan toiseen viitemuuttujaan
For Esim2.Esimerkki = Each Esimerkki
//Jos kyseessä ei ole sama instanssi...
If Esim2 <> Esim Then
//Tarkistetaan objektien törmäys
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
//Jos Objektit törmäävät, vähennetään hp:tä molemmilta
Esim\HP% = Esim\HP% - 1
Esim2\HP% = Esim2\HP% - 1
EndIf
EndIf
Next Esim2
Next Esim
Yllä olevassa koodissa oletetaan että ko. typestä on luotu jo instansseja, ja näillä instansseilla on jokin objekti, joka on tallennettu Obj -kenttään (itseasiassa ko. objektin "handle" eli "kahva", joka on myöskin eräänlainen viite, on tallennettu kyseiseen kenttään, mutta se on sivuseikka).
Ensimmäinen For...Each -silmukka käy siis läpi kaikki instanssit tallentaen viitteen aina Esim.Esimerkki -viitemuuttujaan, ja toinen tekee samoin, mutta tallentaa viitteen Esim2.Esimerkki -viitemuuttujaan. Sisemmän silmukan sisällä tarkistetaan ensin, etteivät molemmat viitteet osoita samaa instanssia. If Esim2 <> Esim -lohkon sisäinen koodi siis suoritetaan vain, kun Esim2- ja Esim- viitemuuttujien osoittamat instanssit eivät ole yksi ja sama instanssi (mainitsin ensimmäisessä osassa NULL:in käsittelyn yhteydessä, että samaa type-määrittelyä käyttäviä viitemuuttujia voidaan verrata keskenään). Jos tätä tarkistusta ei olisi, tarkistettaisiin jokaisen objektin törmäys myös sen itsensä kanssa, jolloin ne törmäisivät jokaisella törmäystarkistuskerralla itseensä, ja kuolisivat pian pois. Viimeisenä silmukan sisällä tarkistetaan vasta objektien törmäys, tässä tapauksessa ObjectsOverlapilla, toki sen voi tehdä muullakin tavoin. Jos Objektit törmäävät, molemmilta vähennetään HP -kentästä yksi (1).
Jos kyseessä olisivat erityyppiset instanssit (kuten aikaisemmin Hahmo.PeliHahmo ja Am.Ammus), tämä olisi oikea lähestymistapa, ja silloin ei tietenkään tarvittaisi tuota If Esim <> Esim2 -tarkistusta, koska:
CB antaa suorittaa myös kahden eri tyyppi-määrittelyn instanssin välisen vertailun, tästä ei kuitenkaan ole mitään hyötyä koska sanoohan jo järkikin, etteivät ne voi olla yksi ja sama. Pahimmassa tapauksessa kuitenkin If-lause saattaisi tulkita kaksi eri tyyppistä instanssia samaksi instanssiksi, jos niiden viite sattuu olemaan samanlainen, tästä syystä eri tyyppi-määrittelyjen instansseja ei tule koskaan verrata! En ole kokeillut, voiko näin itseasiassa tapahtua, mutta ainakin koodi, jossa verrataan kahta erityyppistä viitemuuttujaa, menee syntaksitarkistuksesta läpi että heilahtaa.
Demonstroin nyt koodin toimintaa selostuksella, jos oletetaan että listassa on kolme instanssia, joilla on omat objektinsa. Jos ymmärsit ylläolevasta selostuksesta, miten sisäiset silmukat toimivat tässä tapauksessa, voit huoletta hypätä tämän yli. Pahoittelen myös mahdollista selostuksen sekavuutta, en ikävä kyllä keksinyt parempaakaan tapaa kuvata koodin suoritusta tavallisen selostuksen lisäksi:
Italiccina (eli näin) kirjoitettu teksti tarkoittaa yleistä kommenttia tai selostusta, mitä missäkin tapahtuu
Boldina (eli näin) kirjoitettu teksti on koodia
Normaalina (eli näin) nuoli-merkinnän ( -> ) jälkeen näkyvä teksti kertoo seurauksia koodin suorittamisesta
Alussa saavutaan riville For Esim.Esimerkki = Each Esimerkki, oletetaan että Esimerkki-instansseja on luotu 3 kpl, jokaisella oma objektinsa jne.
Oletetaan myös, että instanssien 2 ja 3 objektit ovat sijoittuneet siten että ne törmäävät
Merkitsen viitemuuttujien osoitusta aina Esim = [monesko instanssi] ja Esim2 = [monesko instanssi]
For Esim.Esimerkki = Each Esimerkki -> Esim = 1
Saavutaan riville For Esim2.Esimerkki = Each Esimerkki
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1
Verrataan sisältöjä, huomataan että ne osoittavat samaa instanssia, eli ei suoriteta lohkon sisällä sijaitsevaa törmäystarkistusta
If Esim <> Esim2 Then -> 1 <> 1 Ei pidä paikkaansa, 1 = 1, eli if-lohkon sisäistä koodia ei suoriteta
Saavutaan riville Next Esim2, eli Esim2 osoittaa seuraavaan instanssiin, Esim osoittaa edelleen 1:een
Next Esim2 -> Esim2 = 2
Nyt Esim ja Esim2 osoittavat eri instansseihin, joten if-lohkon sisälle päästään ja törmäystarkistus tapahtuu
If Esim <> Esim2 Then -> 1 <> 2 totta, suoritetaan if-lohko
1 ja 2 eivät törmää Kuten aiemmin mainittiin, oletetaan että tilanne on sellainen, että vain instanssien 2 ja 3 objektit törmäävät
Next Esim2 -> Esim2 = 3
If Esim <> Esim2 Then -> 1 <> 3 totta, suoritetaan if-lohko
1 ja 3 eivät törmää
Next Esim2 -> Esim2 = NULL
Tässä kohtaa Esim2 -viitemuuttuja on saapunut listan loppuun, ja siirrytään siis Next Esim2 -riviä seuraavalle riville
Nyt liikutetaan Esim.Esimerkki -viitemuuttujaa (eli ulomman For...Each -silmukan viitemuuttujaa) eteenpäin listalla
Next Esim -> Esim = 2
Palataan silmukan alkuun, sisempi silmukka alustetaan taas uudelleen
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1
If Esim <> Esim2 Then-> 2 <> 1 totta, suoritetaan if-lohko
2 ja 1 eivät törmää
Next Esim2 -> Esim2 = 2
If Esim <> Esim2 Then-> 2 <> 2 Ei pidä paikkaansa, eli if-lohkon sisäistä koodia ei suoriteta
Next Esim2 -> Esim2 = 3
If Esim <> Esim2 Then -> 2 <> 3 totta, suoritetaan if-lohko
2 ja 3 TÖRMÄÄVÄT Kuten alkuoletuksissa sanottiin
Vähennetään HP:tä instanssilta 2 Esim.Esimerkin osoittama instanssi -> Esim\HP
Vähennetään HP:tä instanssilta 3 Esim2.Esimerkin osoittama instanssi -> Esim2\HP
Next Esim2 -> Esim2 = NULL
Taas lähdetään alusta, nyt Esim.Esimerkki siirtyy osoittamaan listan kolmatta, ja samalla viimeistä instanssia
Next Esim -> Esim = 3
Taas alustetaan sisäsilmukka uudelleen
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1
If Esim <> Esim2 Then-> 3 <> 1 totta, suoritetaan if-lohko
3 ja 1 eivät törmää
Next Esim2 -> Esim2 = 2
If Esim <> Esim2 Then-> 3 <> 2 totta, suoritetaan if-lohko
3 ja 2 TÖRMÄÄVÄT Kuten alkuoletuksissa sanottiin
Vähennetään HP:tä instanssilta 3 Esim.Esimerkin osoittama instanssi -> Esim\HP
Vähennetään HP:tä instanssilta 2 Esim2.Esimerkin osoittama instanssi -> Esim2\HP
Next Esim2 -> Esim2 = 3
If Esim <> Esim2 Then-> 3 <> 3 Ei pidä paikkaansa, eli if-lohkon sisäistä koodia ei suoriteta
Next Esim2 -> Esim2 = NULL
Next Esim -> Esim = NULL
Ja näin sisäkkäiset silmukat on suoritettu loppuun
Jos katsotaan vertailujärjestystä, merkiten instanssit järjestyslukuina, vertailut suoritettiin näin (sulkuihin merkityt jäävät tarkistamatta, koska kyseessä on sama instanssi):
Mitä -> mihin
Esim Esim2
(1 -> 1)
1 -> 2
1 -> 3
2 -> 1
(2 -> 2)
2 -> 3 (Törmäys)
3 -> 1
3 -> 2 (Sama törmäys tunnistetaan uudelleen)
(3 -> 3)
Mikä tässä sitten muka on vialla? Kaikki objektit käydään läpi, ja kaikki törmäykset kyllä huomataan, jos niitä tapahtuu. Esimerkin mukaisesti samaa listaa läpikäytäessä kahdella eri viitteellä tässä tulee kuitenkin kaksikin ongelmaa: Jo jälkimmäisestä (sekavasta) askel kerrallaan suorituksen selostavasta osasta huomataan, että instanssien 2 ja 3 objektien välinen törmäys tarkistetaan kahdesti, ja näin ollen ne menettävät kaksi hitpointtia (hp) molemmat! Ensimmäisellä kerralla viite Esim osoittaa instanssia 2 ja Esim2-viite instanssia 3, jälkimmäisellä kerralla toisinpäin. Koska instanssin 2 objekti törmää instanssin 3 objektiin, täytyy saman tapahtua myös toisinpäin. Sama vertailu tapahtuu kaikkien instanssien objektien kohdalla, aina molempiin suuntiin. Teemme siis valtavan määrän turhia tarkistuksia, joka hidastaa ohjelman suoritusta, ja lisäksi se käytännössä tuplaa vahingon, koska sama tarkistus suoritetaan turhaan kahdesti!
Jos mietitään hetki, miten nuo kannattaisi tarkistaa, niin nopeasti tajuamme, että jos instanssin 1 objektia on jo verrattu kaikkiin sen jälkeisten instanssien objekteihin, ei niitä kannata enää verrata myöhemmin instanssin 1 objektiin, koska törmäystarkistus tuottaa aina saman tuloksen, riippumatta kumpaa verrataan kumpaan. Tässä kohtaa meidän tarvitsee siis käydä listaa läpi hieman eri tavalla, jolloin sekä vertailujen määrä pienenee, ja samalla pääsemme eroon tuplavahinko-ongelmasta.
Sisäkkäisiä silmukoita käytetään edelleen, mutta tällä kertaa sisempi silmukka siis käy läpi vain kaikki ne instanssit, jotka tulevat ulomman silmukan viitemuuttujan osoittaman instanssin JÄLKEEN. Jos muistat alussa käydyn For...Each:n toteutuksen After:n ja While-silmukan avulla, arvannet miten tämä toteutetaan:
Code: Select all
//Käydään lista läpi
For Esim.Esimerkki = Each Esimerkki
//Haetaan Esim.Esimerkki-viiteosoittimen osoittaman instanssin jälkeen tuleva instanssi
Esim2.Esimerkki = After(Esim.Esimerkki)
//Suoritetaan sisäsilmukkaa (While...Wend) niin kauan, kuin Esim2.Esimerkki osoittaa johonkin instanssiin
While(Esim2 <> NULL)
//Tarkistetaan törmäys
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
//Jos Objektit törmäävät, vähennetään hp:tä molemmilta
Esim\HP% = Esim\HP% - 1
Esim2\HP% = Esim2\HP% - 1
EndIf
//Haetaan Esim2 -viitemuuttujaan sen nykyistä instanssia seuraava instanssi
//Jos seuraava instanssi = NULL, While...Wend -silmukka päättyy
Esim2.Esimerkki = After(Esim2.Esimerkki)
Wend
//Haetaan Esim.Esimerkki-viitemuuttujaan listan seuraava instanssi
Next Esim
Tarkastellaanpa jälleen koodin toimintaa: Totuttuun tapaan, alussa alustetaan For...Each -rakenne, jolla käydään kaikki listan instanssit läpi. Tämän jälkeen alustetaan Esim2.Esimerkki -viitemuuttuja, joka osoittaa For...Each -silmukan viitemuuttujan Esim.Esimerkki jälkeiseen (After-komento) instanssiin. Jos siis Esim.Esimerkki osoittaa listan ensimmäiseen instanssiin (kuten se tekee For...Each -silmukan alustuksen jälkeen), Esim2.Esimerkki osoittaa alustuksensa jälkeen listan toiseen instanssiin (jos sellainen on). Tämän jälkeen tullaan While...Wend -silmukkaan. While -komennon yhteydessä oleva ehtotarkistus suoritetaan heti, ja jos Esim2 osoittaa jo NULL:iin (Mahdollista vain, jos listassa oli vain yksi instanssi), ei silmukan sisältöä suoriteta ollenkaan. Mitäpä järkeä siinä olisikaan, jos listalla on vain yksi instanssi, koska ei ole mitään, minkä kanssa törmäystarkistus suoritettaisiin.
Oletetaan kuitenkin esimerkin vuoksi, että listassa on useampia instansseja, jolloin ehtotarkistus pitää paikkansa ja siirrytään silmukan sisälle. Tällä kertaa ei tarvitse tarkistaa, osoittavatko Esim ja Esim2 samaan instanssiin, koska tällainen tilanne ei ole mahdollinen esitetyssä koodissa. Viitemuuttujien instanssien objektien törmäystä verrataan taas ObjectsOverlapilla, ja jälleen vähennetään HP:tä, jos objektit törmäävät. Koska nyt ei ole käytössä For...Each-rakennetta Esim2 -viitteelle, ei seuraavaa instanssia saadakkaan Next Esim2:lla, vaan asetetaan Esim2 -viite osoittamaan itseään seuraavaan instanssiin, kuten näytettiin aikaisemmassa For...Each:n toimintaa simuloivassa silmukassa. Wend-komennon kohdalla suoritus palaa taas While -lauseen ehtotarkistukseen, ja jos tämä ei ole totta, poistutaan silmukasta ja jatketaan koodin suoritusta Wend:iä seuraavalta riviltä. Näin siis käydään aina kaikki Esim -viitettä seuraavat instanssit läpi Esim2-viitteen avulla, ja jokaisen instanssin objektia verrataan aina kaikkien seuraavien instanssien objekteihin, kunnes koko lista on käyty läpi.
Tarkastellaan toimintaa taas samanlaisella rivi-riviltä -selostuksella kuin aikaisemmin:
Italiccina (eli näin) kirjoitettu teksti tarkoittaa yleistä kommenttia tai selostusta, mitä missäkin tapahtuu
Boldina (eli näin) kirjoitettu teksti on koodia
Normaalina (eli näin) nuoli-merkinnän ( -> ) jälkeen näkyvä teksti kertoo seurauksia koodin suorittamisesta
Alussa saavutaan riville For Esim.Esimerkki = Each Esimerkki, oletetaan että Esimerkki-instansseja on luotu 3 kpl, jokaisella oma objektinsa jne.
Oletetaan myös, että instanssien 2 ja 3 objektit ovat sijoittuneet siten että ne törmäävät
Merkitsen viitemuuttujien osoitusta aina Esim = [monesko instanssi] ja Esim2 = [monesko instanssi]
For Esim.Esimerkki = Each Esimerkki -> Esim = 1
Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = 2
While(Esim2.Esimerkki <> NULL) -> 2 <> NULL Totta, siirrytään silmukan sisälle
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 1 ja Esim2 = 2, vain instanssien 2 ja 3 objektit törmäävät, törmäystä ei siis tapahdu
Esim2.Esimerkki = After(Esim2.Esimerkki) -> Esim2 = 3
Tullaan Wend-riville, palataan ehtotarkistukseen
While(Esim2.Esimerkki <> NULL) -> 3 <> NULL Totta, siirrytään silmukan sisälle
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 1 ja Esim2 = 3, vain instanssien 2 ja 3 objektit törmäävät, törmäystä ei siis tapahdu
Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = NULL 3. instanssi oli listan viimeinen
Tullaan Next Esim -riville
Next Esim -> Esim = 2
Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = 3
While(Esim2.Esimerkki <> NULL) -> 3 <> NULL Totta, siirrytään silmukan sisälle
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 2 ja Esim2 = 3, törmäys siis tapahtuu
Vähennetään instanssien hp:tä kuten kahta sisäkkäistä For...Each -silmukkaakin käyttävässä esimerkissä
Esim2.Esimerkki = After(Esim2.Esimerkki) -> Esim2 = NULL
Tullaan taas Wend-riville, While -lauseen ehto ei toteudu, eli poistutaan silmukasta
Taas haetaan Esim.Esimerkki-viitteeseen seuraava instanssi, joka on 3. ja listan viimeinen
Next Esim -> Esim = 3
Tällä kertaa Esim.Esimerkillä ei ole seuraavaa instanssia
Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = NULL
Nyt While-ehto ei toteudu, While...Wend -silmukkaan ei siis mennä ollenkaan
While(Esim2.Esimerkki <> NULL) -> NULL <> NULL Epätosi, silmukan sisälle ei siirrytä
Siirretään Esim osoittamaan seuraavaa instanssia
Next Esim -> Esim = NULL
Koska Esim on nyt NULL, silmukka on suoritettu loppuun
Nyt verrattiin siis instanssien objekteja ObjectsOverlapilla näin:
Mitä -> mihin
Esim Esim2
1 -> 2
1 -> 3
2 -> 3 (Törmäys)
Kaikkia verrattiin siis toisiinsa, koska on aivan sama verrataanko 1:stä 2:een, vai 2:sta 1:een. Tässä tapauksessa varsinaisia ObjectsOverlap-tarkistuksia tehtiin siis vain kolme, verrattuna aiemman esimerkin kuuteen tarkistukseen. Suuremmilla instanssimäärillä ero alkaa kasvaa huomattavasti (ensimmäinen vaihtoehto suorittaa
n*(n-1) törmäystarkistusta, jossa n = instanssien lukumäärä), ja jos kyseessä on tarkistus, jossa ei ole merkitystä "kummin päin" tarkistus tehdään, on jälkimmäinen tarkistussilmukka huomattavasti tehokkaampi, sen tarkistusmäärän ollessa
(n*(n-1)) / 2. Eli sadan (100) samalla type-määritelmällä toteutetun instanssin objektien törmäystarkistus keskenään ensimmäisellä metodilla tarvitsee 9900 tarkistusta, kun jälkimmäisellä selvitetään törmäykset "vaivaisella" 4950:llä tarkistuksella. Suhde pysyy tuossa 2:1 instanssien määrästä riippumatta, eli ensimmäisellä tavalla tarvitaan aina kaksi kertaa enemmän tarkistuksia, kuin jälkimmäisellä.
Vaikka sitä tässä ohimennen läpi käytiinkin, optimointi ei kuitenkaan kuulu tämän oppaan aihepiiriin, ja törmäystarkistuksissa voidaan tehdä myös monia muunlaisiakin optimointeja, samoin silmukoita voidaan toteuttaa myös muilla tavoilla. Tärkeämpää tässä tapauksessa on kuitenkin ymmärtää miten esitetyt silmukat toimivat. Ensimmäinen kahta For...Each:ia ja Esimerkki-typeä käyttävä oli aika selkeä, sama lista vain käydään läpi kahdesti kahden erillisen viitteen avulla. Viimeisessä listaa käydään läpi myös kahden viitemuuttujan avulla, mutta sisempi silmukka käy lävitse vain Esim2.Esimerkki = After(Esim.Esimerkki) -rivillä annetun Esim.Esimerkki-viitemuuttujan antaman instanssin JÄLKEISET instanssit listan loppuun saakka. Osannet tässä vaiheessa jo itse päätellä, miten toteuttaisit saman niin, että silmukka käy läpi kaikki instanssit ENNEN Esim.Esimerkki -instanssia listan alkuun saakka, jos moista sattuisit syystä tai toisesta tarvitsemaan.
Nyt on siis käyty läpi komentojen First, Last, After ja Before käyttöä listan läpikäynnissä. Kun ymmärrät miten nämä komennot toimivat, pystyt rakentamaan listojen läpikäyntiin juuri sellaisia silmukoita, joita kulloinkin tarvitset.
Tässä välissä esittelen pienen esimerkin, jossa näytetään ylläolevat törmäystarkistus-menetelmät käytännössä (käyttää jälleen CB:n omaa mediaa):
Code: Select all
Type Esimerkki
Field Obj% //Instanssin objekti
Field HP% //Instanssin kestopisteet (health)
End Type
//"Normi"alustukset
SCREEN 640, 480, 0, 1
Randomize Timer()
//Ladataan prototyyppi (master) -objekti, ja piilotetaan se
//Kaikki näkyvät objektit kloonataan tästä
Proto_Objekti = LoadObject("Media\guy.bmp", 36)
ShowObject Proto_Objekti, OFF
//Lasketaan ruudun keskipiste
RuutuKeskiX = ScreenWidth() / 2
RuutuKeskiY = ScreenHeight() / 2
//Luodaan 10 Esimerkki-typen instanssia
For i = 1 To 10
//Luodaan instanssi, ja otetaan sen viite Esim.Esimerkki-viitemuuttujaan
Esim.Esimerkki = New(Esimerkki)
//Asetetaan koordinaatit ympyrään ruudun keskipisteen ympärille
X# = RuutuKeskiX - Cos(36 * i) * 100
Y# = RuutuKeskiY - Sin(36 * i) * 100
//Kopioidaan prototyyppi-objekti
Esim\Obj% = CloneObject(Proto_Objekti)
//Sijoitetaan instanssin objekti, tässä tapauksessa näyttökoordinaattien mukaan
ScreenPositionObject Esim\Obj%, X#, Y#
//Käännetään objekti osoittamaan näytön keskipisteeseen
RotateObject Esim\Obj%, -36 * i
//Asetetaan instanssin hp:t
Esim\HP% = 10
Next i
//Kutsutaan pääsilmukkaa
PaaSilmukka()
//Tarkistetaan objektien väliset törmäykset
Function TarkistaTormaykset()
//Käydään lista läpi
For Esim.Esimerkki = Each Esimerkki
//Käydään lista läpi uudelleen, tällä kertaa viitteet tallennetaan toiseen viitemuuttujaan
For Esim2.Esimerkki = Each Esimerkki
//Jos kyseessä ei ole sama instanssi...
If Esim2 <> Esim Then
//Tarkistetaan objektien törmäys
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
//Jos Objektit törmäävät, vähennetään hp:tä molemmilta
Esim\HP% = Esim\HP% - 1
Esim2\HP% = Esim2\HP% - 1
EndIf
EndIf
Next Esim2
Next Esim
End Function
//Nopeampi ja parempi törmäystarkistus SAMANTYYPPISTEN instanssien välillä
Function TarkistaTormayksetOpt()
//Käydään lista läpi
For Esim.Esimerkki = Each Esimerkki
//Haetaan Esim.Esimerkki-viiteosoittimen osoittaman instanssin jälkeen tuleva instanssi
Esim2.Esimerkki = After(Esim.Esimerkki)
//Suoritetaan sisäsilmukkaa (While...Wend) niin kauan, kuin Esim2.Esimerkki osoittaa johonkin instanssiin
While(Esim2 <> NULL)
//Tarkistetaan törmäys
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
//Jos Objektit törmäävät, vähennetään hp:tä molemmilta
Esim\HP% = Esim\HP% - 1
Esim2\HP% = Esim2\HP% - 1
EndIf
//Haetaan Esim2 -viitemuuttujaan sen nykyistä instanssia seuraava instanssi
//Jos seuraava instanssi = NULL, While...Wend -silmukka päättyy
Esim2.Esimerkki = After(Esim2.Esimerkki)
Wend
//Haetaan Esim.Esimerkki-viitemuuttujaan listan seuraava instanssi
Next Esim
End Function
//Päivitetään objektien tilat
Function PaivitaObjektit()
//Käydään läpi instanssi-lista
For Esim.Esimerkki = Each Esimerkki
//Tarkistetaan onko instanssin HP nollassa tai alle?
If Esim\HP% <= 0
//Jos hp on nolla tai alle, tuhotaan objekti ja instanssi
DeleteObject Esim\Obj%
Delete Esim
Else
//Muutoin instanssilla on hp:itä jäljellä, liikutetaan sen objektia
//Käännetään objektia sattumanvaraisesti, maksimissaan 5 astetta jompaan kumpaan suuntaan
TurnObject Esim\Obj%, Rnd(-5, 5)
//Liikutetaan objektia eteenpäin
MoveObject Esim\Obj%, 1
EndIf
Next Esim
//Kutsutaan törmäystarkistusta
//TarkistaTormaykset()
//Kutsutaan parempaa törmäystarkistusta
TarkistaTormayksetOpt()
End Function
Function PaaSilmukka()
Repeat
PaivitaObjektit()
DrawScreen
Until KeyDown(1)
End Function
PaivitaObjektit -funktiossa ovat kutsut törmäystarkistuksiin. TormaysTarkistus() -funktio käyttää kahta sisäkkäistä For...Each:ia, ja TormaysTarkistusOpt() -funktio esiteltyä tehokkaampaa metodia. Esimerkkikoodin funktiot eivät vastaanota mitään parametreja, joten tuon pystyy helposti muokkaamaan myös aliohjelmia käyttäväksi.
Osa 2B. Kehittyneempi typejen käsittely: ...listojen läpikäynti (jatkuu), Insert:n käyttö, typet funktioissa ja typet typeissä
Seuraavana vuorossa: Insert. Insert (verbi; työntää/pistää/panna/lisätä väliin/jonnekin) nimensä mukaisesti mahdollistaa instanssin paikan asettamisen listassa. Insert:iä yleensä After/Before/Last/First -komentojen tai näiden yhdistelmän kanssa.
Code: Select all
//Insert Instanssi-viite, Paikka
Insert Esim.Esimerkki, First(Esimerkki) //Asettaa Esim.Esimerkin osoittaman instanssin listan ensimmäiseksi
Insert Esim.Esimerkki, After(First(Esimerkki)) //Asettaa Esim.Esimerkin osoittaman instanssin listan toiseksi ensimmäiseksi
Insertiä tarvitsee harvemmin, esimerkkitapauksista tulisi mieleen lähinnä listan järjestäminen jonkin kentän arvon mukaisesti, esim. piirtojärjestys "syvyyssijainnin" mukaan, jos tehtäisiin jonkinlaista pseudo-3d:tä.
Insertin käyttöön liittyy joitakin vaaroja, esimerkiksi jos yritetään asettaa instanssi viimeisen jälkeiseksi [ After(Last(Esimerkki)) ], tai ensimmäistä edeltäväksi [ Before(First(Esimerkki)) ] päästään tutustumaan ihan uudenlaiseen virheilmoitukseen:
ERROR: Memory leak due to invalid type access attempt
Jos jokin halutaan asettaa listan ensimmäiseksi tai viimeiseksi, käytetään paikkana vain First(Esimerkki) tai Last(Esimerkki):iä, jossa Esimerkki -tyypin tilalle tulee tietysti halutun tyyppi-määritelmän nimi.
Jos insertillä jostain syystä (tosin en tiedä miksi joku näin tekisi) yritetään asettaa instanssi sen omalle paikalle:
Code: Select all
Insert Esim.Esimerkki, Esim.Esimerkki
saattaa koko instanssi kadota listasta, tai tulla MAV. Myös yritys asettaa listan viimeinen instanssi itsensä edelle näyttäisi jättävän tekemättä mitään (instanssi säilyy viimeisenä). Jos todella tarvitset Insert:iä johonkin, suosittelen ensin tekemään mahdollisimman yksinkertaisen testiohjelman, jolla kokeilla miten Insert käyttäytyy halutunkaltaisessa tilanteessa. Itselle ei nyt tule mieleen ainuttakaan tapausta, jossa Insert:iä olisin tarvinnut.
Ikävä kyllä, Insert:iä koskeva osuus oppaasta jää varsin suppeaksi. Toivottavasti joku, joka todella tätä tarvitsee, ja jaksaa kokeilla kaikki ns. "sudenkuopat" sen käytöstä läpi, vaivautuu myös kirjoittamaan jonkinlaisen oppaan aiheesta.
Jäljellä ovatkin enää kaksi viimeistä type-komentoa:
ConvertToInteger ja
ConvertToType. Nämä ovat todellista herkkua funktioita käyttäville, ja miksei muillekin.
Esitelläänpä komennot taas lyhyesti:
ConvertToInteger([Viitemuuttuja])
Tämä komento palauttaa viitteen osoittaman instanssin eräänlaisen "handlen" ("kahvan"), ts. kokonaisluvun, joka voidaan myöhemmin muuttaa takaisin viitemuuttujaksi ConvertToTypellä. (Myös CB:n kuvat ja objektit ovat siis tällaisia kokonaislukumuuttujiin tallentuvia "handleja", eli aiemmassa esimerkissä kun luotiin objekteja, niiden handlet tallentuivat typen Obj%-kenttään.)
Käyttöesimerkki:
Code: Select all
Handle% = ConvertToInteger(Esim.Esimerkki) //Palauttaa kokonaisluvun, joka voidaan muuttaa takaisin
//Esim.Esimerkin osoittaman instanssin viitteeksi ConvertToType:llä
Mikäs tässä nyt on niin mullistavaa? No tietysti se, että kokonaislukuja voidaan tallentaa mihin tahansa kokonaislukumuuttujaan: tavallisiin kokonaisluku-muuttujiin, jotka voivat tietysti olla myös globaaleja, jolloin handle on saatavilla kaikkialla, typejen kenttiin, taulukoihin, MemBlockeihin (vink vink)... Niitä voidaan myös syöttää normaaleina parametreina funktioihin, ja palauttaa funktioista.
ConvertToType([Kokonaisluku, joka sisältää ConvertToIntegeriltä saadun handlen])
Tämä komento muuttaa annetun handlen takaisin viitemuuttujaksi. On kuitenkin suositeltavaa tarkistaa, että saatu viite todellakin on kelvollinen (eli ei-NULL)!
Käyttöesimerkki:
Code: Select all
Esim.Esimerkki = ConvertToType(Handle) //Palauttaa viitteen siihen instanssiin, josta Handle%
//saatiin ConvertToIntegerin avulla
//Tarkistetaan että viite on kelvollinen
//Tässä tapauksessa ohjelma heittää MakeError:lla virheen, mikäli näin ei ole
If(Esim.Esimerkki = NULL) Then MakeError("Epäkelpo handle!")
ConvertToType on siis ConvertToInteger:in "vastafunktio", eli sillä voidaan muuttaa saatu handle takaisin type-instanssiviitteeksi.
Oma tapani on ollut laittaa typeihin ID% -kenttä, johon tallennetaan instanssin oma handle sen luonnin yhteydessä:
Code: Select all
Type JokinType
Field ID%
End Type
//Luodaan uusi instanssi ja tallennetaan viite
Joku.JokinType = New(JokinType)
//Otetaan viitteestä handle typen omaan ID% -kenttään
Joku\ID% = ConvertToInteger(Joku.JokinType)
Ja mitäs hyötyä tästä taas olikaan? Kun viite instanssiin on jo olemassa, ei tarvitse joka kerta hakea ConvertToIntegerillä handlea uudestaan, vaan esimerkiksi funktiokutsujen yhteydessä voidaan käyttää instanssin oman kentän sisältöä suoraan:
Code: Select all
//Oletetaan että Joku.JokinType osoittaa tässä jotain instanssia, joka halutaan välittää funktiolle
JokinFunktio(Joku\ID%)
Funktion päässä taas handle:sta kaivetaan viite uudelleen esille:
Code: Select all
//Funktio joka ei oikeastaan tee mitään
Function JokinFunktio(Handle%)
//Haetaan viite handlella
Joku.JokinType = ConvertToType(Handle%)
//Tarkistetaan viitteen kelpoisuus
If(Joku.JokinType = NULL) Then
//Lisätään vaikkapa näytölle ilmoitus virheestä:
AddText("Funktiossa JokinFunktio havaittiin virheellinen handle!")
//Palautetaan virhekoodi
Return -1 //Tämä funktio palauttaa vain kokonaislukuja, on ohjelmoijasta kiinni mitä, jos yleensä mitään
//palautetulla arvolla tehdään
EndIf
//Kaikki ok, palautetaan 1
Return 1
//Funktion ei ole pakko palauttaa mitään, se palauttaa aina 0, jos se pääsee End Function -riville saakka
//Funktiosta voidaan palata myös ennenaikaisesti pelkällä Return [arvo] -komennolla mistä tahansa kohtaa funktiota
End Function
Funktio siis ottaa kokonaisluku-parametrin, joka muutetaan funktion sisällä viitteeksi, ja viitteen oikeellisuus tarkistetaan. Syy miksi näin täytyy tehdä on se, ettei viitteitä voida välittää funktioille sellaisinaan, eikä viitemuuttujista voida tehdä globaaleita, niin että ne näkyisivät funktioiden "sisälle". Taulukot ja globaalit muuttujat "näkyvät" (ts. voidaan käyttää normaalisti) funktioiden sisällä.
Samaan tapaan funktion sisällä voidaan muuttaa viite handleksi ConvertToInteger:illä, ja palauttaa saatu handle, joka taas toisessa päässä muutetaan takaisin viitteeksi:
Code: Select all
//Kutsutaan funktiota, joka luo uuden instanssin ja palauttaa sen handlen
Handle% = JokuMuuFunktio()
Viite.JokinType = ConvertToType(Handle%)
//Tarkistetaan viite
If(Viite = NULL) Then MakeError("Epäkelpo handle")
//Funktio luo uuden instanssin ja palauttaa sen handlen
Function JokuMuuFunktio()
//Luodaan uusi instanssi
Viite.JokinType = New(JokinType)
//Instanssiin voitaisiin asettaa jotain arvoja tässä
//....
//Haetaan handle
Handle% = ConvertToInteger(Viite.JokinType)
//Tallennetaan typen oma handle siihen itseensä (ei pakollista mutta kätevää)
Viite\ID% = Handle%
//Palautetaan handle
Return Handle%
//Sama voitaisiin kirjoittaa lyhyemmin:
//Viite\ID% = ConvertToInteger(Viite.JokinType)
//Return Viite\ID% //Kentän sisältö voidaan palauttaa funktiosta, sehän on tässä tapauksessa vain kokonaisluku
End Function
Koska instanssit tallentuvat aina sisäiseen linkitettyyn listaan, voidaan niitä luoda myös funktioiden sisällä, eikä handlen palauttaminen ole välttämätöntä. Jos viitteitä kuitenkin halutaan välittää funktioille, on ainoa tapa (ainakin toistaiseksi) handlejen käyttö, joko parametrien kautta, taulukoilla tai globaaleilla muuttujilla. Henkilökohtaisesti suosin nimenomaan parametreja, mutta ei muissakaan keinoissa mitään vikaa ole. Viite kannattaa aina tarkistaa muutoksen jälkeen, jos vaikka oletkin välittänyt väärän arvon funktiolle. Myös Insert SAATTAA vaikuttaa handleihin, varma en kuitenkaan ole.
Jos instanssiin tarvitsee tallentaa useampia handleja (vaikkapa kaikki esineet toteutettaisiin typeinä, ja hahmoille tallennettaisiin handlet niiden poimimiin esineisiin), kannattaa tutustua MemBlockien käyttöön. Näillä voidaan myös toteuttaa muunkinlaisia muuttuvankokoisia taulukoita instanssien sisään, mutta käyttö vaatii jonkin verran harjoittelua. MemBlockit ovat nimensä mukaisesti "muistinpalasia", joihin voidaan kirjoittaa arvoja, mutta näiden kanssa tulee helposti virheitä. Tekemällä oppii, ja CB:n manuaalikin on harvinaisen kattava. Suosittelen lämpimästi lukemaan myös sieltä tässä oppaassa esitetyistä asioista.
CB:n oman manuaalin termistö eroaa tässä käyttämästäni, tässä vastaavuudet pääpiirteittäin:
- Tämä opas <=> CB:n manuaali
- Lista, Linkitetty lista <=> Kokoelma, joskus myös "taulukko", joskus "lista"
- Instanssi,Esiintymä <=> Jäsen
- Viite, -muuttuja, -tunnus <=> Osoitin, Tyyppimuuttuja, joskus myös pelkkä "muuttuja"
- Kenttä <=> Muuttuja
- Type-määritelmä <=> Kokoelma, Tyyppi
- Handle <=> Osoitin (Toisessa paikkaa viitettä sanotaan osoittimeksi, mene ja tiedä... )
Tässä tämä nyt taas toistaiseksi, seuraavassa ja samalla viimeisessä osassa keskitytäänkin jo sitten varsinaiseen peliesimerkkiin, joka käyttää funktioita, handleja, törmäystarkistuksia sisäkkäisillä silmukoilla ja luultavasti jonkinlaista MemBlock-systeemiä joidenkin handlejen talletukseen (käytin vastaavaa jo SuttuPrötössä, joten pääsen kierrättämään koodia vaivojeni säästelemiseksi :rolleyes: ). Idea on vasta hiljalleen muotoutumassa pääkopassani, mutta koitan saada vähän kaikkea mahdollista mukaan (kuitenkin niin, ettei joka funktiossa ole valtavasti kikkailua, vaan pyrin tekemään erillisiä funktioita, jotka esittelevät yhden tai kahden "tempun" hyödyntämistä). Aikataulusta en uskalla mitään sanoa, jos hyvin käy, ennen ensi viikkoa, jos ei, niin voi vierähtää pidempäänkin (koulu alkaa taas 8. päivä).
Näiden kahden osan perusteella luulen jo valtaosan oppaat kunnolla opiskelleiden pystyvän toteuttamaan omia projektejaan typejen kanssa. Pelkillä New-, Delete- ja For...Each -komennoilla pääsee hyvin pitkälle, mutta tässä osassa esitellyt keinot laajentavat typejen käyttötarkoituksia huomattavasti, ja suosittelenkin niiden kokeilua. Tietyissä asioissa taulukot ovat edelleen parempia (Esim. kartat ja vastaavat jo valmiiksi "taulukkomaiset" asiat), mutta typejen etuja tietyissä asioissa (vihollis- ja ammus-listat yms) ei voi kieltää.