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.
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.