Komponenttilistalla on ollut yllättävän hiljaista. Melkein puolet porukasta tekee samanlaista ohjelmaa, mutta mitään keskustelua peruskomponenttien rakenteesta ei ole näkynyt. Siksipä pitää vähän herättää keskustelua! Esimerkki "Räiskintä"pelien komponenteista ========================================== Olkoon vaikkapa vanhan Commadore 64:n kunniaksi pelien peruskomponentin nimi TSprite. TSprite: - paikka - kuva - liikkuminen (ei välttämttä toteutettu kantaluokassa, tällä tavalla saadaan myös kiinteitä "esteitä") Miten liikkuminen hoidetaan? Miten törmäykset ja niistä tiedottaminen hoidetaan? Jos "kentällä" on kaksi spriteä, miten ne voivat tietää toisistaan? Minusta ei oikein mitenkään ilman jonkin ulkopuolisen komponentin apua. Niinhän asia on oikeastikin. Aistien avulla tehdään havaintoja muusta maailmasta. Mutta aistien havainnot välittää jokin ylimääräinen "olio". Äänet ilma, kuvan sähkömagneettinen säteily jne... Pelissä voimme valita täksi välittäjäksi vaikkapa TSpriteAvaruuden. Eli se on avaruus, jossa TSprite-luokan jälkeläiset "asuvat". Nyt pelien luonteen mukaan TSpriteAvaruuden tehtävät voi minusta jakaa kolmene eri katerogiaan (josta samalla seuraa kolme vähän erilaista kategoriaa myös itse TSpriteille): 1) Itsenäisesti liikuvat TSpritet ---------------------------------- - jokaisessa spritessä on oma kello ja se tiedottaa liikahtamisestaan avaruudelle. - kun avaruus saa tiedon jonkin spriten liikahtamisesta, se käy läpi kaikki avaruudessa olevat muut spritet seuraavantapaisella koodilla (pseudokoodia): sprite := liikahtanut_sprite; for i:=0 to SpriteTaulu.Count-1 do begin tutkittava := SpriteTaulu.Items[i]; if ( tutkittava = sprite ) then continue; if ( sprite.Osuiko(tutkittava) ) then sprite.Reagoi(tutkittava) end; - eli TSprite luokkaan lisätään metodit TSpite.Osuiko(tutkittava:TSprite); TSprite.Reagoi(tutkittava:TSprite); - kukaan muu kuin kaksi spriteä kekskenään ei voi luotettavasti todeta törmäystä - kukaan muu kuin kaksi spriteä (esim. sukellusvene ja pommi) eivät voi tietää miten reagoidaan törmäsytapauksessa (sukellusvenä räjähtää ja pommi häviää pois maailmasta, kaksi palloa kimpoaa päinvastaisiin suuntiin) - sopii kun spritejä on suhteellisen vähän (esim. alle 20) ja niiden liikkeiden ei tarvitse olla synkronoitua 2) Avaruuden ohjaamat spritet ----------------------------- - jokaisessa spritessa on metodi TeeHomma, joka overridataan kussakin jälkeläisluokassa tarpeen mukaan. Esim. sukellusveneellä liikkuu x-suuntaan, pommi liikkuu y-suuntaan keihäs liikkuu seuraavan askeleensa fysiikan lakien mukaan. Puu ei tee mitään. Liikennevalo vaihtaa väriään. - avaruus käy kellon ohjaaman kaikki spritet läpi ja käskee jokaisen spriten tehdä oman hommansa - tämän jälkeen avaruus käy pareittain kaikki spritet läpi (vähän kuten tapauksessa 1) ja tutkii mitkä spritet ovat törmänneet ja käskeen törmänneiden reagoida keskenään - sopii kun spritejä on paljon tai niiden liikkeiden tulee olla synkronoituja (esim. joukko samaan suuntaan liikkuvia spritejä, synkronoimattomana jonon viimeinen voi saada liikevuoron ensin ja siten törmätä muihin samalla vauhdilla liikkuviin spriteihin) 3) Avaruuden ohjaamat spritet jotka eivät ole kontrolleja --------------------------------------------------------- - jos 1) tai 2) toteutetaan Delphin kontrolleilla, niin liikkeestä tulee helposti nykivää. Liike saada pehmeäksi, jos kunkin askeleen kuva piirretään avaruudessa valmikiisi johonkin bitmappiin ennen kuvan näyttämistä ruudulla (ns. double buffering). - spritet muuten kuten kohdassa 2), mutta niissä on metodi PaintTo(canvas:TCanvas), eli ne eivät piirrä itseään suoraan "itseensä" kuten esim TPaintBox tai TImage tekee, vaan ne ovat komponentteja, joilla ei ole samanlaista Delphi-elämää kuin TImage:lla yms. - avaruus käskee jokaisen spriten toimia kuten versiossa 2) - avaruus käskee jokaisen spriten piirtää itsensä AVARUUDEN sisällä olevan apubitmapin canvakselle. - avaruus kopioi apubitmapin omalle canvakselleen - sopii kun halutaan sujuva liike - haittana on se, ettei spritejä voi raahailla "ilmaiseksi" suunnitteluaikana kuten TControllin jälkeläsisitä. periytyviä komponentteja Nyt jos jokainen tutkisi mihin kategoriaan oma peli liittyy (siis jos tekee "räiskintä"peliä) ja sitten yhdessä määriteltäisiin nuo kantaluokat hyvin. Eli tarvitsemme hyvät nimet 3:lle eri kategorian avaruudelle 3:lle eri kategorian spritelle Sitten tarvitsemme riittävän rajapinnan noille (propertyt, metodit). Sen jälkeen toivottavasti jokaisen pelin komponentit saadaan perittyä jostakin noista eri kategorioiden komponenteista. Ja sitten tarvitsemme vapaaehtoiset toteuttamaan noita kantaluokkia. Eli yleiskäyttöinen komponentti ei olekkaan se lopullinen sukellusvene, vaan kantaluokka TSprite tai siitä peritty TLiikkuvaSprite. Avaruuden reunatkin voidaan kategoriasta riippumatta toteuttaa paikallaa olevina spriteinä. Silloin ei tarvitse tehdä erikseen mitään erikoistarkasteluja avaruuden ulkopuolelle joutumisesta. Putoava sprite törmää automaattisesti TMaa-komponenttiin ja nämä keskenään reagoivat kuten niden tulee (joko pallo kimpoaa ilmaan tai pommi räjähtää pois ja häviää). Koko systeemissä vaikeinta tulee olemaan overridattavat metodit TSprite.Osuiko(tutkittava:TSprite) ja TSprite.Reagoi(tutkittava:TSprite) Miksikö? Koska keskinäiset reagoinnit voivat olla hyvin erilaisia: - TMaa ja TPallo => pallo pomppaa - TMaa ja TPommi => pommi räjähtää ja häviää - TSukellusvene ja TPommi => sukellusvene räjähtää ja molemmat häviävät - TLiikkevalo ja TAuto => auto pysähtyy jos punainen valo jne... Näitä pareittaisia reagointeja voi tulla useita erilaisia ja kuka ne hoitaa? Jos verratan reaalimaailmaan, niin esim. TAuton tapauksessa mekaaniset törmäykset hoitelee auto kuskista riippumatta, mutta "törmäyksen" liikkennevalon kanssa hoitelee TAuton äly (esim. TIhminen). Jos yksinkertaistetaan ja unohdetaan mitä on Auton sisällä (tummenetut lasit), niin voidaan ajatella että tuo "äly" kuuluu auton rajapintaan ja Auto reagoi mekaaniseen törmäykseen toisen auton kanssa ruttaantumalla ja "törmäyksellä" liikennevaloon pysähtymällä punaiseen. Kuitenkin Auton "äly" on se, joka oppii. Liikkennevalo on tyhmä ja vain välkkyy. Eli jos kukaan ei ole kertonut minulle mitä liikennevalo tarkoittaa, niin silloin minä en sitä tiedä. Voisi siis perustella, että TAutossa olisi (iljettävää yäk, mutta parempaakaan en tähän hätään keksi): function TAuto.ReagoiToiseen(tutkittava:TSprite):???? begin if ( tutkittava is TLiikennevalo ) then begin // hommaile liikennevalon kanssa end; if ( tutkittava is TElain ) then begin // luttaa elukka, oli se sitten TKoira tai TKissa :-( end; end; function TAuto.Reagoi(tutkittava:TSprite):???? begin Result := ReagoiToiseen(tutkittava); end; Vastaavasti liikennevalossa voisi olla (toteutusta täytyy miettiä ettei tule rekursiota): function TLiikennevalo.ReagoiToiseen(tutkittava:TSprite):???? begin // mene lyttyyn ja mutkalla ja tee kuoppa vastustajaan... end; function TLiikennevalo.Reagoi(tutkittava:TSprite):???? begin Result := tutkittava.ReagoiToiseen(self); end; eli annetaankin vastuu toiselle osapuolelle, kun ei itse tiedetä miten menetellään. Näin ei kaikissa "törmäyspareissa" tarvitse toistaa samoja operaatioita. Eli "osaavampi" osapuoli kutsuu omaa Reagoi-metodissa omaa ReagoiToiseen-metodia (jossa on varsinainen reagointi) ja tyhmempi kutsuu naapurin ReagoiToiseen, eli vaihtaa osat keskenään. Tuota metodia ReagoiToiseen, ei kutusta sitten spritejen ulkopuolelta (esim. avaruudesta), vaan aina tuota metodia Reagoi. ReagoiToiseen on se, jossa ainakin pitää homma hoitaa. Kantaluokassa TSprite tämä metodi voi vaikkapa hoitaa fysikaalisen liikemäärien vaihtoon perustuvan kimmoisan (?) törmäyksen (seiniin kannattaa silloin laittaa ääretön massa, niin ne eivät liiku pois alta). Nyt äkkiä keskustelemaan asiasta, kohta tulee kiire! Muistakaa ettei tällä kurssilla olla tekemässä omaa peliä, vaan oma peli on vain hyvien komponenttien sivutuote! Vastaava "analyysi" kannattaa tehdä lautapelien puolella (lauta, ruudut, nappulat) ja korttipelien puolella (poyta, pakka, kortti). Eli nyt peruskomponenttien rajapinnat kiinni ja sen jälkeen jos kaveri tekee pareman peruskomponentin, voidaankin se vaihtaa käyttöön, koska sillä on sama rajapinta! Vesa