wiki:Pong/Vaihe7

Version 2 (modified by tekrjant, 9 years ago) (diff)

--








Pong-peli, vaihe 7

Tässä vaiheessa lisäämme peliin pistelaskun. Pong-pelissä pelaaja saa pisteen kun pallo ohittaa toisen pelaajan mailan.

1. Aliohjelma laskureita varten

Pisteiden laskemiseksi tarvitsemme jonkinlaisen laskurin, jossa pitää yllä pelaajan pisteitä. Lisäksi laskuri täytyisi nollata pelin alussa. Tässä olisi yksi kokonaisuus pelin alustamisessa. Tehdäänpä siitä aliohjelma (toteutetaan se hiukan myöhemmin):

        void LisaaLaskurit()
        {
            // ...
        }

Kutsutaan tuota aliohjelmaa LoadContent-aliohjelmasta:

        protected override void LoadContent()
        {
            LuoKentta();
            AsetaOhjaimet();
            LisaaLaskurit();
            AloitaPeli();
        }

2. Laskurin luominen

Laskureita tulee peliin kaksi, yksi kummallekin pelaajalle. Koska molemmat laskurit luodaan arvatenkin lähes samalla tavalla, kannattaa laskurin luomisesta tehdä aliohjelma:

        Meter<int> LuoPisteLaskuri()
        {
            Meter<int> laskuri = new Meter<int>( 0, 0, 10 );
            return laskuri;
        }

Aliohjelman paluuarvon tyyppi on Meter<int>, mikä tarkoittaa laskuria joka laskee kokonaisluvuilla (kokonaisluvun tyyppi on int). Laskurin luomisessa ensimmäinen parametri on alkuarvo, mikä pistelaskun ollessa kyseessä on luonnollisesti nolla. Toinen ja kolmas parametri ovat vähimmäisarvo ja enimmäisarvo. Tällä laskurilla voi siis kerätä korkeintaan kymmenen pistettä.

Pisteiden laskemiseen riittäisi toki pelkkä kokonaisluku, mutta Meter<int>-tyyppiä käyttämällä päästään helpommalla kun pisteitä halutaan esittää ruudulla, kuten kohta nähdään.

3. Pisteiden esittäminen

Pelkkä Meter<int>-olio ei vielä näytä pisteitä, vaan ainoastaan säilyttää lukuarvon muistissa muistissa. Pisteiden esittämiseksi ruudulla tarvitaan vielä näyttö-olioita. Kokonaislukujen esittämiseen sopii tyyppi nimeltä ValueDisplay. Tehdäänpä sellainen laskurin ohessa. Koska pelaajien laskurit tulee eri kohtaan ruutua, viedään ruutukoordinaatit parametrina aliohjelmalle. Näillä muutoksilla aliohjelma näyttää tältä:

        Meter<int> LuoPisteLaskuri( double x, double y )
        {
            Meter<int> laskuri = new Meter<int>( 0, 0, 10 );
            ValueDisplay naytto = new ValueDisplay( this );
            naytto.BindTo( laskuri );
            naytto.X = x;
            naytto.Y = y;
            naytto.ValueColor = Color.White;
            Add( naytto );
            return laskuri;
        }

ValueDisplay ei itse sisällä yhtään lukua, mutta se osaa näyttää luvun. Tätä varten sille pitää kertoa, minkä laskurin lukua se näyttää, kutsulla naytto.BindTo( laskuri ). Kun näyttö tietää laskurin, näytössä näkyy aina laskurin oikea arvo silloinkin kun laskurin arvoa jossakin kohtaa muutetaan. Koska lukunäytön teksti on oletuksena musta, se ei erottuisi kovin hyvin mustasta taustasta. Niinpä asetetaankin näytön väri (naytto.ValueColor) valkoiseksi (Color.White).

Kun pistenäyttö on luotu, täytyy se vielä lopuksi lisätä peliin. Tämä tapahtuu yksinkertaisesti aliohjelman Add (suom. lisää) kutsulla. Huomaa, että näyttöä ei lisätä kenttään. Se siis pysyy paikoillaan, vaikka kenttä vaihtuisikin.

4. Laskureiden lisääminen peliin

Koska laskureihin täytyy päästä käsiksi kohta, kun pisteitä lasketaan, tehdään laskureista attribuutteja (attribuutti näkyy kaikille saman luokan aliohjelmille) muiden attribuuttien seuraksi:

namespace Pong
{
    public class Peli : PhysicsGame
    {
        PhysicsObject pallo;
        PhysicsObject maila1;
        PhysicsObject maila2;

        Vector2D nopeusYlos = new Vector2D( 0, 200 );
        Vector2D nopeusAlas = new Vector2D( 0, -200 );

        Meter<int> pelaajan1Pisteet;
        Meter<int> pelaajan2Pisteet;

        protected override void LoadContent()
        {
            Level = LuoKentta();
            AsetaOhjaimet();
            LisaaLaskurit();
            AloitaPeli();
        }

        // ...

Kun meillä on aliohjelma laskurin luomiseksi, voidaan pistelaskurit tehdä kutsumalla sitä.

        void LisaaLaskurit()
        {
            pelaajan1Pisteet = LuoPisteLaskuri( Screen.Left + 100.0, Screen.Top - 100.0 );
            pelaajan2Pisteet = LuoPisteLaskuri( Screen.Right - 100.0, Screen.Top - 100.0 );
        }

LuoPisteLaskuri-aliohjelmassahan ensimmäinen parametri oli x-koordinaatti ja toinen y-koordinaatti. Ensimmäisen laskurin x-koordinaatti on tässä laskettu laskemalla yhteen ruudun vasemman reunan x-koordinaatti (Screen.Left) sekä vakioarvo 100.0. Näin laskuri tulee ruudun vasemmasta reunasta hiukan oikealle. Y-koordinaatti lasketaan samaan tapaan ruudun yläreunasta (Screen.Top) lukien. Toinen laskuri muuten sama, mutta laskuri tulee ruudun oikeaan reunaan.

source:/trunk/help_symbols/try_to_run.png

Kun nyt ajat ohjelman, pitäisi ruunun yläreunassa näkyä kaksi laskuria, jotka näyttävät arvoa 0.

5. Törmäyksen käsittely

Jotta voisimme kasvattaa pisteitä, täytyisi tietää milloin pallo ohittaa jommankumman mailan. Tämä onnistuu siten, että tarkkaillaan sitä kun pallo osuu johonkin. Jos pallo osuu kentän vasempaan tai oikeaan reunaan (reunathan lisättiin kenttään aikaisemmin), voidaan kasvattaa toisen pelaajan pisteitä.

Törmäyksiin reagoimista varten kirjastossa on aliohjelma nimeltä AddCollisionHandler. Se ottaa kaksi parametria: fysiikkaolio, jonka törmäyksiin halutaan reagoida sekä aliohjelma, jota kutsutaan olion törmätessä. Lisää seuraavanlainen kutsu LuoKentta-aliohjelmaan, sen jälkeen kun pallo on luotu ja lisätty tasoon:

            AddCollisionHandler( pallo, KasittelePallonTormays );

source:/trunk/help_symbols/does_not_work_yet.png

Aliohjelman, jossa törmäys käsitellään, täytyy olla seuraavanlainen: Paluuarvo on void (eli ei palauteta mitään) ja parametreja on yksi, Collision-tyyppinen olio. Aliohjelman voi toki nimetä vapaasti, kunhan sama nimi annetaan parametrina AddCollisionHandler-aliohjelmalle.

        void KasittelePallonTormays( Collision collision )
        {
        }

Aliohjelman collision-parametri pitää sisällään törmäävän kappaleen (collision.CollidingObject), joka tässä tapauksessa on aina pallo. Olio, johon pallo on törmäämässä, saadaan kutsulla collision.OtherObject.

        void KasittelePallonTormays( Collision collision )
        {
            PhysicsObject pallo = collision.CollidingObject;
            PhysicsObject kohde = collision.OtherObject;

            // ...
        }

Kun törmäyksen käsittelevään aliohjelmaan tullaan, tiedetään vasta että pallo on törmännyt johonkin. Jotta selviää mihin se on törmännyt täytyy tehdä hiukan vertailua. Törmäys oikeaan reunaan selviää tarkastamalla onko törmättävä kohde ja oikea reuna yksi ja sama olio. Tämä tapahtuu yhtäsuuruusvertailulla ==-merkintää käyttäen (Muista, yhtäsuuruus on C#:ssa kaksi =-merkkiä!). Kentän oikeaa reunaa vastaava olio saadaan ominaisuudesta Level.RightBorder, kunhan kenttään on ensin tehty reunat CreateBorder-kutsulla. Vasen reuna vastaavasti Level.LeftBorder.

            if ( kohde == Level.RightBorder )
            {
                // ...
            }

Kun törmäyksen kohde on tiedossa, on pisteiden kasvattaminen helppoa. Jos pallo osuu oikeaan reunaan, kasvatetaan pelaajan 1 pisteitä yhdellä. Meter<int>-tyyppisen laskurin sisältämään arvoon pääsee käsiksi sen Value-ominaisuuden kautta.

Vasempaan reunaan törmäys käsitellää samaan tapaan, nyt vain kasvatetaan pelaajan 2 pisteitä. Näin törmäyksen käsittelystä saadaan seuraavan näköinen:

        void KasittelePallonTormays( Collision collision )
        {
            PhysicsObject pallo = collision.CollidingObject;
            PhysicsObject kohde = collision.OtherObject;

            if ( kohde == Level.RightBorder )
            {
                pelaajan1Pisteet.Value += 1;
            }
            else if ( kohde == Level.LeftBorder )
            {
                pelaajan2Pisteet.Value += 1;
            }
        }

source:/trunk/help_symbols/try_to_run.png

Äskeisessä koodissa tuli vastaan vielä yksi uusi asia: toisen if-lauseen edessä on sana else (suom. muuten). Tämä tarkoittaa sitä, että else-sanan jälkeen tuleva if-lause suoritetaan vain, jos sitä edeltävän if-lauseen ehto oli epätosi. Tässä tapauksessa else ei ole aivan välttämätön, sillä jos pallo osuu kentän oikeaan reunaan, ei se voi samalla osua vasempaan reunaan.

Noista kahdesta if-lauseesta ei siis koskaan suoriteta molempia samalla kertaa. On myös hyvin mahdollista, että kumpaakaan niistä ei suoriteta, jos vaikkapa pallo osuu mailaan.

6. Lopputulos

Pong-pelin koodi kaikkine mailoineen ja pistelaskuineen näyttää nyt tältä:

Error: Macro Include(source:/trunk/esimerkit/Pong/Vaihe7/Peli.cs) failed
No node trunk/esimerkit/Pong/Vaihe7/Peli.cs at revision 4058