Hlavní navigace

Knihovna ClanLib (11)

26. 7. 2004
Doba čtení: 7 minut

Sdílet

V tomto dílu seriálu jsem se rozhodl dočasně přerušit povídání o tvorbě GUI. Důvodem je to, že jsem dokončil ucelenější část hry, kterou se snažím psát jako ukázku síly ClanLibu. Uděláme si proto takové malé opakování toho, co již známe, a na reálných ukázkách kódu uvidíme, jak se dosud získané znalosti dají využít v praxi. S výkladem o GUI budeme pokračovat až po této malé vsuvce.

Třída T_Krokovatelny

Začněme třeba s popisem třídy T_Krokovatelny:

class T_Krokovatelny {
public:
  // metoda Run implicitne nic nedela, ale je pritomna
  virtual void Run() {};
};

Jedná se o velice jednoduchou třídu, která má pouze jedinou virtuální metodu Run(). Některé z vás nyní možná napadla otázka: „Může mít napsání takovéto třídy vůbec nějaký smysl?“. Pokud se opravdu tací našli, troufám si tvrdit, že má dokonce smysl zabývat se takovou třídou v tomto článku :-).

Pokud si totiž vzpomeneme na hlavní smyčku naší hry, která je implementována obyčejným while cyklem s uspáváním a probouzením programu, tj. řešením typickým pro většinu ClanLibovských programů, zjistíme, že život většiny našich objektů se bude skládat z opakujících se diskrétních kroků (často tak rychle se opakujících, že jimi budeme simulovat spojité děje).

Moje zkušenost je taková, že tříd, které budou mít nějakou periodicky volanou metodu obstarávající jejich životní cyklus, bude při psaní takovéhoto programu opravdu mnoho. Nejhorší, co by se nám pak mohlo stát, by bylo, kdybychom tyto metody začali pojmenovávat různými jmény, například Run(), run(), krok(), update(), krokuj(), aktualizujSe() a podobně.

Osobně zastávám názor, že je mnohem rozumnější vytvořit si takovouto třídu představující rozhraní všech „krokovatelných“ tříd a tyto od ní odvozovat. Rozhodně bych se nebál ani použití vícenásobné dědičnosti, kterou tento přístup bude nejspíš vyžadovat, myslím si, že zde je její použití na místě. Můžeme totiž mít například objekt, který je zároveň krokovatelný a zároveň umístěný (viz následující odstavec). Příslušnou třídu pak odvodíme jak od třídy T_Krokovatelny, tak od třídy T_Umisteny, jejíž popis následuje v dalším odstavci.

Třída T_Umisteny

class T_Umisteny {

// konstrukce
public:
  T_Umisteny(int X = 0, int Y = 0) : XX(X), YY(Y) {};

// atributy
public:
  // vrati x-ovou souradnici
  int X() const { return XX; };
  // vrati y-ovou souradnici
  int Y() const { return YY; };

// operace
public:
  // nastavi x-ovou souradnici (implicitne na nulu)
  void set_X(int X = 0) { XX = X; };
  // nastavi y-ovou souradnici (implicitne na nulu)
  void set_Y(int Y = 0) { YY = Y; };

// implementace
protected:
  // x-ova souradnice
  int XX;
  // y-ova souradnice
  int YY;
};

Třída T_Umisteny představuje rozhraní všech objektů, které mají souřadnice X a Y. Takovým objektem může být třeba panáček nebo příšera, jejichž polohu na obrazovce si potřebujeme pamatovat.

Třídy odvozené od T_Umisteny zaručují, že jejich objekty budou umět svoje souřadnice nastavovat metodami set_X(), set_Y(). Hodnotu těchto souřadnic budou vracet metody X() a Y().

Již zmíněný panáček, jak uvidíme, bude příkladem objektu, který je krokovatelný i umístěný.

Třída T_Animovatelny

class T_Animovatelny : public T_Umisteny {

// typy
public:
  // typ ukazatele na nas animovany sprite
  typedef CL_SharedPtr<CL_Sprite> SpritePointer;

// konstrukce a destrukce
public:
  // vytvori prazdny animovatelny objekt,
  // u nejz je treba nastavit sprite
  T_Animovatelny();
  // vytvori animovatelny objekt dle predaneho
  // jmena sprajtu, prislusne jmeno se bude hledat
  // pres spravce zdroju
  T_Animovatelny(const std::string& Jmeno);
  // destruktor
  virtual ~T_Animovatelny();

// atributy
public:
  // odpovi, zda se animuje, nebo ne
  bool Animovan() const { return AAnimovan; };

// operace
public:
  // vykresli se na zadane souradnice
  void VykresliSe(int X, int Y);
  // vykresli se na svoje souradnice
  void VykresliSe() { VykresliSe(XX, YY); };
  // zapne/vypne animovani (implicitne zapne)
  void set_Animovan(bool Animovan = true);
  // nastavi sprite zadaneho jmena
  void set_Sprite(const std::string& Jmeno);

// konverze
public:
  // konvertuje na true/false podle toho,
  // zda je sprite platny
  operator bool() const { return SSpritePtr; };

// implementace
protected:
  // priznak, zda dochazi k animovani
  bool AAnimovan;
  // ukazatel na sprite tohoto animovatelneho objektu
  // ukazatel proto, aby jej mohli potomci efektivne
  // menit
  SpritePointer SSpritePtr;
  // nastavi zadany sprite
  void set_Sprite(SpritePointer Sprite);

};

Třída T_Animovatelny je konečně třída, která využívá ClanLib takříkajíc naplno. Jedná se totiž o jakousi nadstavbu třídy CL_Sprite. Animovatelný objekt je v jejím pojetí objekt umístěný, tj. pamatuje si svoje souřadnice a dokáže s nimi pracovat.

Můžeme vytvořit buď prázdný animovatelný objekt, nebo zadáme jméno a vytvoříme neprázdný animovatelný objekt. Zde si zjednodušíme syntax konstrukce takového objektu, jelikož na rozdíl od konstrukce CL_Sprite nebudeme nuceni pokaždé udávat odkaz na CL_ResourceManager a jméno bude také kratší (bez cesty do příslušné sekce). Toto zpohodlnění bude umožněno za cenu dohody, že každý animovatelný objekt bude mít definován svůj sprite v RFD spravovaném dohodnutým „globálním“ správcem zdrojů.

Příslušná část souboru zdroje.xml bude vypadat třeba nějak takto:

...
<section name="Animovatelne">
  <sprite     name="Panacek_Zluty_Doleva">

    <image file="obrazky/panacek_zluty/l1.tga"/>
    <image file="obrazky/panacek_zluty/l2.tga"/>
    <image file="obrazky/panacek_zluty/l3.tga"/>

      <animation
        speed="300"
        loop="yes"
        pingpong="yes"
      />

  </sprite>

</section>

Příslušný animovatelný objek pak vytvoříme například takto:

T_Animovatelný Panacek("Panacek_Zluty_Doleva");

Animovatelný objekt pak bude možné vykreslit příkazem VykresliSe() buď přímo na jeho souřadnice, nebo na nějaké jiné. Bude také možné zapnout/vypnout jeho animování tj. volání update() u spritu před jeho vykreslením a podobně.

Samozřejmě se bude hodit i možnost nastavení jiného spritu. Při práci s ukazateli si můžete všimnout použití ClanLibovských smart pointerů, které odstraňují starosti se správou paměti.

Podotýkám, že tato třída není navržena ještě úplně podle mých představ a asi by se dala vylepšit. Na druhou stranu pro naše účely by tento třošičku kostrbatý návrh mohl být poměrně účelný.

Třída T_Panacek

class T_Panacek : public T_Animovatelny, public T_Krokovatelny {

// konstrukce a destrukce
public:
  // vytvori panacka zadane barvy (implicitne zluteho)
  T_Panacek(const std::string& Jmeno);
  ~T_Panacek();

// atributy:
public:
  // rychlost panacka, tj. pocet pixelu, o ktere
  // se posune behem jednoho kroku
  int Rychlost() const { return RRychlost; };

// operace:
public:
  // nastavi rychlost panacka
  void set_Rychlost(int Rychlost);
  // necha panacka jit v zadanem smeru
  void JdiVeSmeru(Uzitecne::E_Smery Smer);
  // zastavi panacka
  void Zastav();
  // jeden krok zivota panacka
  virtual void Run();

// implementace:
private:
  // rychlost panacka
  int RRychlost;

  // kontejner slotu pro signaly, ktera panacek prijima
  CL_SlotContainer Sloty;

  // kontejner spritu, ktere panacek pouziva ke svemu animovani
  typedef CL_SharedPtr<CL_Sprite> SpritePtr;
  typedef std::vector<SpritePtr> TKontejnerSpritu;
  TKontejnerSpritu Sprity;

  // kody klaves, kterymi je panacek ovladan
  int KeyUp;
  int KeyDown;
  int KeyLeft;
  int KeyRight;

  // smer, ve kterem se panacek prave pohybuje
  Uzitecne::E_Smery Smer;
  // akce provedena po stisku klavesy
  void OnKey_down(const CL_InputEvent& Key);
  // akce provedena po uvolneni klavesy
  void OnKey_up(const CL_InputEvent& Key);
}; 

Třída T_Panacek je odvozena od T_Animovatelny a T_Krokovatelny. Stará se o konstrukci panáčka z jeho jména zadaného jako parametr konstruktoru z údajů v RDF. Z RDF zjišťuje například jeho počáteční rychlost:

<section name="Panacci">
  <Panacek name="Zluty" rychlost="1"/>
</section>

K metodám OnKey_down() a OnKey_up() připojuje pomocí slotů signály pro stisknutí a uvolnění kláves. Zmíněné metody tyto události patřičně ošetřují.

Co jsme naprogramovali nového

V praxi napsanou třídu T_Panacek (a tedy i všechny ostatní popsané od nichž je odvozena) zatím využijeme tak, že po spuštění programu se objeví panáček, který se bude pohybovat, jak budeme mačkat šipky na klávesnici. Panáčka vytvoříme jako součást bitvy a v ní ho také budeme aktualizovat a vykreslovat.

Zde využijeme toho, že si v RDF můžeme bitvu nastavit jako počáteční aktivní mód.

Závěrem

Příklad je již příliš rozsáhlý, než abych v článku uváděl každý řádek napsaného kódu. Mou snahou bylo umožnit jakýsi obecný pohled na vytvářenou hru. Ten vám snad pomůže zorientovat se ve zdrojových kódech. Doufám, že možnost jejich pročtení by mohla alespoň některým z vás pomoci. Můžete si proto stáhnout archiv obsahující příslušný projekt (Kdevelop 3.0). Najdete v něm všechny zdrojové texty včetně testovacích tříd využívajících schopností CPPUnit (automatického testování), kterými je definováno chování všech tříd. Například testovací třída třídy T_Panacek je třída T_PanacekTest. Podotýkám, že tyto třídy zakončené příponou Test se nijak nepodílejí na funkčnosti, a pokud nechcete, nemusíte je vůbec číst.

Já osobně píšu testovací skripty dřív než samotné třídy a z takto rozsáhlého projektu už se mi je nechce pokaždé odstraňovat, tak doufám, že pro vás nebude jejich přítomnost na škodu, ale spíše ku prospěchu.

root_podpora

Hra bude v archivu zkompilovaná v adresáři src, takže si ji můžete zkusit spustit i bez pročítání kódu a kompilace. Doporučuji si pohrát také s nastaveními ve zdroje.xml.

Příště se vrátíme k tvorbě GUI.

Byl pro vás článek přínosný?