Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Olio-ohjelmoinnissa piirtäminen

Touho [10.02.2008 11:48:26]

#

Aloitan strategiapelin ohjelmoimisen C++:lla. Edellisessä C++ projektissa jokainen olio (puut, lentokoneet, leveli) osasivat piirtää itsensä, mutta se tuntui sekavalta.
Jos Game-luokassa on level-olio ja units-olioiden linkitetty lista, kannattaako Game-luokkaan laittaa vielä Draw-olio? Tässä kaikki objektit joutuisi antamaan argumenttina, kun ruvetaan piirtämään, eikä sekään mielestäni ole kovin kätevän kuuloista.

Onko jollain paremmalla peliarkkitehdilla ehdotusta piirron suorittamiseen?

Metabolix [10.02.2008 12:24:00]

#

Minun näkemykseni olisi tällainen:

class objekti; // Sisältää normaalien kappaleiden tiedot (muoto, väri, sijainti, ...)
class pelaaja: public objekti;
class mika_lie: public objekti;

// Ja muut oleellisesti erilaiset tyypit; esimerkiksi partikkelilla ei välttämättä ole samalla tavalla muotoa ja sen piirto hoidetaan usein muutenkin aivan omalla tavallaan, joten se täytyy määritellä erikseen.
class partikkeli;

class piirtoluokka;
class peli;

void peli::piirto()
{
  piirtaja->tyhjenna();
  piirtaja->kamera(pelaajat[0].paikka, pelaajat[0].suunta);
  piirtaja->tausta(kauas, hieno);

  vector<pelaaja>::iterator i;
  vector<palikka>::iterator j;
  vector<partikkeli>::iterator k;

  // Kaikki objekti-tyypistä perityt toimivat samalla funktiolla
  for (i = pelaajat.begin(); i != pelaajat.end(); ++i) {
    piirtaja->objekti(*i);
  }
  for (j = palikat.begin(); j != palikat.end(); ++j) {
    piirtaja->objekti(*j);
  }
  // Muilla oleellisesti erilaisilla tyypeillä on omat funktionsa
  for (k = partikkelit.begin(); k != partikkelit.end(); ++k) {
    piirtaja->partikkeli(*k);
  }
}

Kaikilla samantapaisilla objekteilla olisi samanlaiset tiedot piirtämistä varten ja niiden piirtäminen onnistuisi kätevästi yhdellä funktiolla joka tyypille. Optimoinnit siitä, mitä oikeasti piirretään (esim. kaukaiset ja taakse jäävät kohteet), voitaisiin valinnan mukaan hoitaa peliluokassa tai piirtoluokassa. Itse tekisin tarkistuksen peliluokassa (tai lisäisin väliin vielä yhden wrapperin), jotta piirtoluokka pysyisi mahdollisimman yksinkertaisena.

Tämän suunnittelun taustalla on ajatus, että piirtäminen on helppo muuttaa jälkeenpäin käyttämään eri rajapintaa, kun pelin puolelta on käytössä vain omia funktioita, joiden lopputulos on selvästi tiedossa. Myös tekstuurien lataaminen pitäisi siis jättää erillisen grafiikkajärjestelmän hoidettavaksi. Objekti voisi vaikkapa sisältää erillisen osoittimen piirtodataan, jonka varaaminen ja vapauttaminen hoidettaisiin grafiikkajärjestelmän kautta. Tähän dataan kuuluisi siis esimerkiksi ladatun tekstuurin tunniste ja mahdollisesti joitakin optimointitietoja muttei kuitenkaan itsestäänselvyyksiä kuten verteksidataa.

Piirtoluokan sijainti sinänsä on sivuseikka.

Näihin kysymyksiin on aina kiva vastata, kun joutuu kirjoittamaan ajatuksensa ymmärrettävään muotoon ja näkee, onko niissä merkittäviä aukkoja. :)

os [10.02.2008 14:55:17]

#

Ihan kokemuksesta voin vinkkinä sanoa, että piirron abstraktio hyvään OO-tyyliin kannattaa suosiolla unohtaa. Tässä nimittäin päätyy siihen, että piirtoluokasta on periytetty yhden käden sormilla laskettava määrä erilaisia luokkia, jolla ei ole mitään yhteistä, ja joiden instanssit on joka tapauksessa eroteltu. Minkä takia puu, kartta ja lentokone pitäisi pystyä piirtämään samalla metodilla, jos niillä ei grafiikan eikä pelimekaniikan kannalta ole mitään yhteistä?

Grafiikka kannattaa erottaa pelimekaniikasta, ja oikea paikka grafiikkadatalle on oma piirtoluokka.

Kannattaa etukäteen suunnitella mahdollisimman tarkkaan, minkälaisia olioita pelissä tarvitaan. Jos tarvitaan ainoastaan yksiköitä ja partikkeleita, niin näiden piirtoa ei ole mitään järkeä abstrahoida, vaan, kuten Metabolix sanoi, kannattaa tehdä piirtoluokkaan oma funktio jokaisen pelin kannalta olennaisesti erilaisen objektin piirtoon.

Strategiapelissä yksiköillä on yleensä jokin tyyppi (tankki, lentokone, auto...) ja saman tyyppisiä yksiköitä on useita. Tyypille kannattaa tehdä oma luokka, joka sisältää muun muassa pointterin tämän tyyppisten objektien piirtodataan. Esimerkiksi:

struct YksikonKuva; // määritellään piirtoluokkien yhteydessä (sisältää esim. kuvadatan)

class Yksikko
{
  public:
    struct Tyyppi
    {
       const YksikonKuva *piirtodata;

       std::string nimi; // esim. "auto", "lentokone" ...
       float nopeus;
       float koko;
       float tulivoima; // ...
    };

    const Tyyppi &hae_tyyppi() const { return *tyyppi; }
    // ...

  private:
    /**
     * Oma elämä helpottuu huomattavasti, jos oliolla on default copy-constructori,
     * koska yksiköt voi silloin tallentaa suoraan tyyliin std::vector<Yksikko>
     * tämä ei kuitenkaan onnistu, jos tässä on referenssi (vaihtoehtoisesti
     * kannatta myös tutustua boost-kirjastoon)
     */
    const Tyyppi *tyyppi;

    Vektori paikka, nopeus;
    float kunto;
    // ...
};

Piirto toimisi sitten esimerkiksi näin:

void Piirtoluokka::piirra(const Yksikko &yks)
{
  const Yksikko::Tyyppi &tyyppi = yks.hae_tyyppi();
  const YksikonKuva &pdata = *tyyppi.piirtodata;

  Vektori paikka = yks.hae_paikka();
  // haetaan yksikön suunta, tila yms...
  // piirretään näiden ja pdata:n avulla kuva (2D tai 3D) ruudulle
}

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta