Hlavní navigace

Knihovna Clanlib (4)

Petr Kavánek 31. 5. 2004

V dnešním dílu se podíváme na to, jak nám ClanLib může usnadnit práci při psaní programů konfigurovatelných zvenku. Takové programy nebudeme muset kompilovat pokaždé, když se rozhodneme změnit nějakou konstantu, cestu k souboru s obrázkem a podobně. Vše si předvedeme na příkladu jednoduché hry, kterou se budeme snažit během tohoto seriálu naprogramovat.

Úvodem

Po dokončení minulého článku jsem hodně přemýšlel, jakým směrem se v tomto seriálu ubírat dál. Jedna možnost by byla věnovat se postupně jednotlivým částem knihovny odděleně na malých příkladech. Takový přístup je poměrně typický a často velmi účelný. Přesto jsem se v tomto případě nakonec rozhodl, že se pokusím postupovat jinak. Malé příklady mají totiž tu nevýhodu, že jsou velice umělé. ClanLib je objektová knihovna a C++ je objektově oriantovaný jazyk a jejich síla se projevuje obvykle až u rozsáhlejších projektů. Jelikož bych rád vám čtenářům ukázal sílu ClanLibu, rozhodl jsem se společně s vámi vytvořit jednoduchou hru inspirovanou hrami jako je Dynablaster či AtomicBomberman. Doufám, že to tak bude i větší zábava.

Pokud se však časem ukáže, že nás tento přístup odvádí od ClanLibu samotného (což by nebyla chyba ani ClanLibu ani OOP, ale moje), vrátíme se ke krátkým příkladům.

Začněme tedy se slibovanými soubory pro definici zdrojů (resource definition files, dále jen RDF ). Řekněme, že se naše hra bude jmenovat „Onion Bomberman“. Ještě nevíme, zda bude nakonci běžet v okně, nebo ve fullscreenu, ale během vývoje bude pohodlnější, když poběží v okně. Nemůžeme si však nyní být ani jistí, jaká bude velikost tohoto okna a i název hry v titulku by se mohl změnit.

Přesto něco víme :-). Víme, že budeme potřebovat okno a nástroj jak měnit výše uvedené parametry co nejpohodlněji. A přesně k tomuto účelu využijeme RDF.

Vytvoříme tedy soubor zdroje.xml s následujícím obsahem v adresáři zdroje:

<resources>

 <section name="HlavniOkno">

  <string  name="Titulek"    value="Onion Bomberman"/>

  <integer name="SirkaOkna"  value="640"/>
  <integer name="VyskaOkna"  value="480"/>
  <boolean name="FullScreen" value="false"/>

 </section>

</resources>

To je formát přímo podporovaný knihovnou. Jednotlivé položky jsou umístěny mezi tagy <resources> </resources> a mohou (nemusí) být navíc organizovány do pojmenovaných sekcí. Podporovanými typy jsou string (CL_String), integer (CL_Integer), float (CL_Float), boolean (CL_Boolean) a mnoho dalších poněkud specifičtějších typů, ke kterým se dostaneme v příštích dílech, např. Sprite (CL_Sprite).

Jak ale takto zapsaný string nebo integer získáme v našem programu? Jednoduše tak, že nejprve vytvoříme správce těchto našich zdrojů (resource manager):

CL_ResourceManager SpravceZdroju("zdroje/zdroje.xml");

Zde uvádíme těžko konfigurovatelnou cestu, a proto je třeba mít ji dobře rozmyšlenou.

Nyní už je získání údajů ze souboru zdroje.xml velice jednoduché, jak ukazuje příklad:

int SirkaOkna = CL_Integer("HlavniOkno/SirkaOkna", &SpravceZdroju); 

Podíváme-li se nyní na program ahojsvete z minulého dílu, zjistíme, že ho můžeme lehce předělat na základ naší nové hry. Třídu T_AhojSveteApp přejmenujeme na třídu T_Aplikace. V hlavičkovém souboru to bude vlastně vše. Ve zdrojovém souboru ještě uděláme několik úprav.

Je velice pravděpodobné, že Správce zdrojů budeme potřebovat na spoustě různých míst, a proto ukazatel na něj umístíme do globálního jmnenného prostoru Konfigurace. Vytvořit ho však můžeme teprve po inicialicaci knihovny (před deinicializací ho zrušíme):

...
CL_SetupGL::init();

// vytvorime SpravceZdroju definovanych v zdroje/zdroje.xml
SpravceZdroju = new CL_ResourceManager("zdroje/zdroje.xml");

V metodě main() řádky

// vytvorime okno 640 x 480 pixelu s titulkem Ahoj, Svete!
const string Titulek = "Ahoj, Svete!";
const int SirkaOkna = 640;
const int VyskaOkna = 480;
const bool FullScreen = false;

nahradíme následujícím kódem:

// vytvorime okno s parametry uvedenými ve zdroje/zdroje.xml
const string Titulek = CL_String::load("HlavniOkno/Titulek", SpravceZdroju);
const int SirkaOkna = CL_Integer("SirkaOkna", SpravceZdroju);
const int VyskaOkna = CL_Integer("VyskaOkna", SpravceZdroju);
const bool FullScreen = CL_Boolean("FullScreen", SpravceZdroju); 

Abychom se ke třídě T_Aplikace již nemuseli vracet a aby řešila jen to nejnutnější, dohodneme se, že vlastní chod naší hry obstará metoda Run() nové třídy T_Hra. Do metody Run() prozatím přesuneme vytvoření Loga a while smyčku.

Na jejich místo dodáme do metody main() následující kód:

// spustime hru
T_Hra Hra;
Hra.Run();

Aby jsme se neupsali při includování hlavičkových souborů ClanLibu, přesuneme příslušné direktivy do souboru hlavicky.h.

Zamysleme se nyní, co bychom mohli chtít po třídě T_Hra. Je docela pravděpodobné, že metoda Run() bude obsahovat smyčku potobnou té, kterou v ní už máme.

Hry se obvykle skládají z několika poměrně nezávislých částí. V našem případě by to přinejmenším mohlo být jakési úvodní menu, na kterém si budeme moci předvést tvorbu GUI, a vlastní hra dvou hráčů proti sobě, říkejme jí bitva. Při programování bitvy se zase hlouběji seznámíme s partiemi, jako jsou sprite v ClanLibu a detekce kolizí.

Opět je však možné, že se v budoucnu rozhodneme dodat ještě další módy, jako třeba úvodní animaci využívající OpenGL a podobně.

Tyto módy budou mít i něco společného. Při každém kroku metody Run() naší hry bude aktuální aktivní mód nějakým způsobem aktualizovat svou logiku a následně se vykreslí.

To by nás do budoucna mohlo přivést k myšlence odvozovat všechny módy od základní třídy T_ModHryZaklad s virtuálními metodami AktualizujLogiku() a VykresliSe(), volanými třídou T_Hra v každém kroku pro aktuální mód.

Každopádně se nám tu opět rýsuje docela pěkný příklad na využití RDF. Tentokrát však budeme potřebovat trochu silnější nástroje, než ty, s nimiž jsme se setkali doposud.

Řekněme, že bychom chtěli z RDF získat seznam všech módů, které chceme v konkrétním okamžiku zahrnovat do programu, a tento seznam bychom opět chtěli mít možnost pohodlně modifikovat.

Takový seznam by mohl být v souboru zdroje.xml v sekci „T_Hra“. Zde by bylo vhodné poznamenat také jméno módu, který má T_Hra nastavit jako aktivní při své konstrukci. Mohlo by to vypadat třeba takto:

<section name="T_Hra">

   <string     name="UvodniMod"    value="Menu"/>

   <ModHry     name="Menu"/>

   <ModHry     name="Bitva"/>

</section>

Získat jméno úvodního módu už umíme. Podívejme se tedy na to, jak získat seznam všech módu. Přesně řečeno, chceme seznam všech XXX ve výrazech <ModHry name=„XXX“/>. Řešení může vypadat třeba takto:

//-----------------------------------------------
// NactiSeznamModu()
//-----------------------------------------------
void T_Hra::NactiSeznamModu() {

  using namespace std;
  using namespace Konfigurace;

  // nejprve smazeme aktualni seznam
  SeznamModu.clear();

  // vytvorime seznam vsech jmen polozek typu ModHry
  // v sekci T_Hra ve zdroje.xml
  typedef list<string> TTmpList;
  TTmpList TmpList = SpravceZdroju->get_resources_of_type("ModHry", "T_Hra");

  // vytvorime seznam jmen pripustnuch modu
  typedef TTmpList::iterator TTmpListIt;

  TTmpListIt eIt = TmpList.end();
  for (TTmpListIt It = TmpList.begin(); It != eIt; ++It) {

    // vytvorime mod prislusneho nazvu
    CL_Resource ModHry = SpravceZdroju->get_resource(*It);

    // nahrajeme jeho data
    ModHry.load();

    // nyni jiz muzeme pristupovat k jeho slozkam pres CL_DomElement
    CL_DomElement Element = ModHry.get_element();

    // zjistime tedy jeho jmeno
    string JmenoModu = Element.get_attribute("name");

    // toto jmeno pridame do vytvareneho seznamu
    SeznamModu.push_back(JmenoModu);

  }

} // NactiSeznamModu() -------------------------- 

SeznamModu je nějaký kontejner stringů, například vector nebo list. Jeho vyprázdnění před načítáním asi nikoho nepřekvapí. Mnohem zajímavější je řádek

TTmpList TmpList = SpravceZdroju->get_resources_of_type("ModHry", "T_Hra"); 

Metoda get_resources_of_ty­pe() vrací list<string>. Jedná se vlastně o seznam cest ke všem položkám typu „ModHry“ v sekci „T_Hra“ vzhledem ke stromové struktuře celého RDF, tj. v našem případě souboru zdroje.xml. Zde bude list obsahovat dva stringy, a to „T_Hra/Bitva“ a „T_Hra/Menu“. Z tohoto příkladu je vidět, že atribut name má poněkud výsadní postavení. Opravdu je tomu tak. Na formát tagů se totiž můžeme dívat následovně:

<Typ name="jmeno" atribut1="retezec1" ... atributN-1="retezecN-1"/> 

Máme zde N atributů, z nichž ten nultý name má to výsadní postavení, že je součástí cesty k tagu.

V cyklu přes položky TmpList tj. přes výše popsané cesty nejprve vytvoříme konkrétní reprezentaci tagu pro udanou cestu. Jedná se o instance třídy CL_Resource:

// vytvorime mod prislusneho nazvu
CL_Resource ModHry = SpravceZdroju->get_resource(*It);

// nahrajeme jeho data
ModHry.load(); 

Děláme to za pomoci metody get_resource(), jejímž parametrem je výše popsaná cesta. Návratovou hodnotou je tedy objekt CL_Resource, do kterého ještě nahrajeme jeho data metodou load().

Přístup k jednotlivým atributům zprostředkovává třída CL_DomElement, kterou vrací metoda get_element() třídy CL_Resource. Konečně se tedy dozvíme to co potřebujeme vědět:

// zjistime tedy jeho jmeno
string JmenoModu = Element.get_attribute("name");

// toto jmeno pridame do vytvareneho seznamu
SeznamModu.push_back(JmenoModu);

Tento postup může na první pohled vypadat trochu komplikovaně, ale když se nad ním chvíli zamyslíte, a nebo ještě lépe, provedete několik experimentů, zjistíte, že se nejedná o nic hrozného. Tato drobná komplikovanost je asi nezbytnou cenou za univerzálnost práce s RDF.

Je jasné, že pro seznam o dvou položkách se taková práce snad ani nevyplatí. Pokud však máte podezření, že by se mohl seznam v budoucnu rozšiřovat a obětujete těch pár minut navíc, bude to investice, která se vám mnohonásobně vrátí.

Na závěr jsem si nechal ještě malou ukázku využití RDF pro vestavěné ClanLibovské typy. K rozsáhlejším příkladům se dostaneme, až se těmto typům budeme věnovat podrobně. Jedním takovým typem je CL_Surface, kterého se bude ukázka týkat.

Přidejme do zdroje.xml následující sekci:

<section name="Obrazky">

  <surface name="Pozadi" file="obrazky/pozadi/pozadi.png"/>

</section> 

Když jsme v minulém dílu vytvářeli CL_Sprite, abychom nakreslili Logo knihovny, předávali jsme konstruktoru přímo cestu k souboru s obrázkem. Nyní můžeme postupovat s pomocí RDF mnohem výhodněji.

Vytvoříme obrázek pozadí naší hry. Toto pozadí budeme prozatím vykreslovat v metodě Run() třídy T_Hra. Metoda Run() tedy bude vypadat následovně:

//-----------------------------------------------
// Run()
//-----------------------------------------------
void T_Hra::Run() {

  using namespace Konfigurace;

  // vytvorime obrazek pozadi !!!!!!!!!!!!!!!!!!!
  CL_Surface Pozadi("Obrazky/Pozadi", SpravceZdroju);

  // pockame si na stisteni klavesy escape
  while (! CL_Keyboard::get_keycode(CL_KEY_ESCAPE)) {

  // invariant: Dosud nebyl stisten escape pri get_keycode()

  // vykreslime pozadi do leveho horniho rohu !!!!!!!!!!!!!!
  Pozadi.draw();

  // zobrazime nakreslene zmeny (prehozeni
  // predniho a zadniho bufferu)
  CL_Display::flip();

  // uspime aplikaci na deset milisekund, abychom
  // zbytecne neblokovali procesor
  const int DobaObnoveni = 10;
  CL_System::sleep(DobaObnoveni);

  // probudime aplikaci resp. knihovnu aby byla aktualni
  CL_System::keep_alive();

  } // while

} // Run() -------------------------------------- 

Stačí už jen upravit rozměry okna na 660×540 a jsme hotovi.

Zdrojové kódy zahrnující i KDevelopí project file dnes vytvořeného programu si můžete stáhnout zde (včetně použitého obrázku).

Podívat se můžete také na všechny dosud napsané zdrojové kódy v pdf (včetně částí, které se do článku nevešly).

V příštím dílu se pokusíme vyřešit dnes nakousnutou otázků módů hry. Podíváme se přitom na sadu chytrých pointerů, které nám ClanLib nabízí. Ty jsou velice užitečné zejména proto, že velmi usnadňují v C++ jinak dosti složitou správu paměti, mají však i další výhody.

Našli jste v článku chybu?

5. 12. 2004 13:26

C (neregistrovaný)

Nehazi vam to nekomu tudle chybu?

g++ -DHAVE_CONFIG_H -I. -I. -I.. -Wnon-virtual-dtor -Wno-long-long -Wbad-function-cast -Wundef -Wall -pedantic -W -Wpointer-arith -Wmissing-prototypes -Wwrite-strings -ansi -D_XOPEN_SOURCE=500 -D_BSD_SOURCE -Wcast-align -Wconversion -O2 -fno-exceptions -fno-check-new -fexceptions -c -o t_hra.o `test -f 't_hra.cpp' || echo './'`t_hra.cpp
cc1plus: warning: "-Wbad-function-cast" is valid for C/ObjC but not for C++
In file included from /usr/include/ClanLi…



Vitalia.cz: Žloutenka v Brně: Nakaženo bylo 400 lidí

Žloutenka v Brně: Nakaženo bylo 400 lidí

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Lupa.cz: E-shopy: jen sleva už nestačí

E-shopy: jen sleva už nestačí

Vitalia.cz: Jak koupit Mikuláše a nenaletět

Jak koupit Mikuláše a nenaletět

Podnikatel.cz: Přivýdělek u Airbnb nebo Uberu? Čekejte kontrolu

Přivýdělek u Airbnb nebo Uberu? Čekejte kontrolu

Podnikatel.cz: Na poslední chvíli šokuje vyjímkami v EET

Na poslední chvíli šokuje vyjímkami v EET

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Podnikatel.cz: Platební brány a EET? Stále s otazníkem

Platební brány a EET? Stále s otazníkem

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Lupa.cz: UX přestává pro firmy být magie

UX přestává pro firmy být magie

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí

Vitalia.cz: 9 největších mýtů o mase

9 největších mýtů o mase

DigiZone.cz: Další dva kanály nabídnou HbbTV

Další dva kanály nabídnou HbbTV