Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!
Zadejte Vaši e-mailovou adresu:
Kamarád fotí rád?
Přihlas ho k odběru fotomagazínu!
Zadejte e-mailovou adresu kamaráda:
C/C++
Jak vyzrát na GUI - console
GUI
11. července 2002, 00.00 | Tímto článkem bych rád začal seriál zabývající se programováním GUI v počítačovývh hrách. A jelikož jsou console velice důležitou součástí her (zejména pro programátory), tak se jim podíváme na zoubek.
Tímto článkem by jsem rád začal seriál o programování GUI. Co to GUI vlastně je? Je to zkratka pro Graphical User Interface, čili grafické uživatelské rozhraní. Dnes je již součástí našeho života v podobě známých Windows a jejich tlačítek, přepínačů, okýnek. Samozřejmě nejenom Windows se pyšní touto vymožeností, ale setkáme se s ní i u Linuxu, BeOSu, MacOSu a dalších.
O co tu půjde?Přirozeně se nebudeme snažit naprogramovat takové GUI, které vídáme ve výše zmíněných operačních systémech. Půjde nám zejména o to, naprogramovat takové GUI, aby jsme byli schopni napsat někam jméno hráče, vybrat mu IQ, ze seznamu položek vybrat například meč a ještě zaškrtnout, že má modré oči. Ano, budeme se pokoušet naprogramovat jednoduchý a snadno pochopitelný systém tlačítek (button), zaškrtávacích políček (checkbox), popisků (label), posuvných lištiček (scrollbar), políček určených ke vkládání textu (editbox) a okýneček s možností vybrání položek ze seznamu (listbox). Časem přibudou i další věci jako skupiny tlačitek, obměna listboxu za použití obrázků. To můžeme například využít k výběru zbraně (obrázky jsou hezčí než text). Nesmíme opomenout ani bitmapy (image), které velmi celé GUI zpestří.
Console
Další součástí GUI by se z našeho hlediska mohla stát i console. Console dnes bývá skoro ve všech akčních i jiných hrách. Někdy bývá hráči ukryta, někdy ne (např. Quake). Slouží k zadávání příkazů, vypisování potřebných věcí (většinou při programování a odlaďování), můžeme ji použít ke vkládání cheatů nebo pomocí ní zprostředkovat chat mezi hráči na síti. Zkusme tedy navrhnout takovou základní consoli.
Co by měla asi obsahovat :
-okno, kde se budou vypisovat reakce na zadané příkazy (např. help)
-řádek kam budeme příkazy zadávat
Tak toto byl opravdu hrubý základ a teď trochu podrobněji :
-historii naposledy zobrazených výpisů (budeme posouvat nahoru a dolů např. pomocí kláves PgUP a PgDown)
-historii naposledy použitých příkazů (budeme posouvat nahoru a dolů např. pomocí kláves. Nahoru a Dolu)
-možnost updatovat jednotlivé řádky v historii výpisů
-ke psaní je zapotřebí fce.,která odfiltruje klávesy (použijeme knihovnu SDL www.libsdl.org)
Další věci rozebereme v jednotlivých souvislostech, koho zajímá telefonní seznam?!
Základní definice
Jeden z hlavních (viditelných) parametrů console je její šířka (width),
výška (height) a pozice (x,y). Také by se hodila barva
pozadí (bkg_color), textu (text_color) a okrajové čáry (line_color).
Na obrázku je barva pozadí tmavě zelená s 50ti procentní průhledností,
text a čára má barvu bílou s nulovou průhledností a tloušťkou 1.0f (line_width).
Pokud budeme chtít consoli obohatit obrázkem (bitmapou), v našem případě
modrý pruh s nápisem "SpaceDroids3D CONSOLE", měli by jsme si
zadefinovat další proměnné jako : bitmapu obrázku (bkg_image_ID),
která bude pod OpenGL typu int, pod SDL typu SDL_Surface, atd. K
vykreslení potřebujeme pozici (levý horní a pravý dolní bod) (img_x1,
img_y1, img_x2, img_y2), popřípadě texturové koordináty
(pod SDL stačí SDL_Rect source, jinak pod OpenGL typ float)
(img_tx1, img_ty1, img_tx2, img_ty2). Jelikož můžeme
použít i bitmapu ve stupních šedi a za běhu měnit její barevné odstíny,
popřípadě chceme-li mít bitmapu průhlednou, nadefinujeme si proměnou (img_color),
která bude typu(stejně tak i ostatní barvy) :
#define float T_COLOR_RGBA[4] (r,g,b,alpha)
Jelikož chceme zajisté také vypisovat nějaký text, budeme potřebovat proměnnou do které uložíme velikost fontu v procentech normální bitmapy (font_size). Způsob načítání fontů zde nebudu popisovat, rád se odkážu na článek Michala Svobody (OpenGL 9. - Bitmap Font), akorát opravíme menší chybičku, ktreá se do článku vloudila a přidáme si menší vylepšovák. Ale to až dále. V dalších dílech budeme tuto funkci již používat automaticky, bez jakéhokoliv rozepisování. Jelikož OpenGL vykresluje písmenka jako polygony, můžeme na ně používat stejné fce. jako na normální objekty. Proto jsme si nadefinovali proměnnou font_size, kterou potom vložíme do fce. glScale3f(); Pokud bude mít hodnotu 1.0f, velikost bísma bude stejná jako je na bitmapě, pokud její hodnota bude 0.5f, písmo bude poloviční, atd. Určitě také nechceme vypisovat text mimo hranice console, proto si nadefinujme tyto dvě proměnné, které udávají maximální počet písmen na řádek a maximální počet řádků (max_display_chars, max_display_lines). Poslední z "viditelných" vlastností console je její viditelnost (visible), pokud je FALSE, nevykreslujeme ani nefiltrujeme klávesnici, pokud je rovna TRUE, vykreslujeme a klávesnici filtrujeme.
|
Vždyť to nic neumí
To je sice pravda, ale musíme to brát hezky popořádku. Aby jsme mohli pokračovat, musíme si říci, jak bude vypadat historie výpisů (to je v podstatě to, co se do console vypíše.) Půjde tedy o pole řetězců s počtem položek, který nám bude vyhovovat (5 je málo, 100000 je moc). Každý řetězec musí mít ale i určenou délku, to si také určíme. Ideální je např. 128 znaků a 200 řádků (řetězců) v historii (výpisů).
#define CONSOLE_CHAR_PER_LINE 128
#define CONSOLE_LINE_HISTORY_MAX 200
Nyní můžeme do objektu C_CONSOLE přidat pole řetězců, které bude sloužit jako ona historie. Dále si přidáme proměnnou, do které uložíme počet zaplněných řádek historie (line_count) a proměnnou, do které budeme zaznamenávat celkový počet vložených řádků (total_line_count). Do proměnné act_line budeme ukládat index řádky, kterou právě zobrazujeme jako první (použijeme to k posouvání se v historii). Do pole historie budeme ukládat položky tak, že na pozici 0 bude ta nejaktuálnější, přičemž při každém vložení nové řádky posuneme všechny řádky o 1 směrem k dolní hranici pole (tj. CONSOLE_LINE_HISTORY_MAX - 1).
|
Toto by byly pouze definice proměnných, které by bez funkcí byly k ničemu. Úplně tou nejzákladnější funkcí bude int Add_Line( char [] ); Této fci. zadáme jako parametr řetězec, který chceme vložit do historie. Funkce posune řádky o jednu dolů, zvětší počet použitých řádek v historii a celkový počet vložených řádek o 1 (line_count, total_line_count) a do místa v poli (lines[][]) s indexem 0 uloží náš řetězec. Po úspěšném dokončení vrátí celkový počet vložených řádek (total_line_count), který použijeme pro updatování řádek.
|
Někdy se může stát, že budeme chtít aktualizovat nějakou položku v historii (aby jsme ji nemuseli stale dokola vypisovat), jako např. FRAMES : na obrázku. K tomu použijeme fci. int Update_Line(int Line_ID, char line[] ); Kde Line_ID je index řádky, kterou chceme updatovat (pozor je to index v celkovém počtu uložených řádek a ne z počtu řádek historie). Tento index vrací fce. int Add_Line( char [] ); Stačí si ho tedy zapamatovat a máme vystaráno.
|
Příkazy
Bez možnosti zadávat příkazy by byla console jen velmi hezký kousek informačního štítku v elektronickém provedení. Pokud budeme chtít aby zpracovávala nějaké příkazy, musíme si ujasnit, jak by to asi mělo fungovat. Nevím, jestli znáte odkazy na funkce (C++) nebo ne. V podstatě jde o to, že vytvoříme datový typ, který bude ukazatel na funkci. Potom do tohoto ukazatele můžeme uložit jakoukoliv funkci, které má stejné typy parametrů a vrací stejný datový typ. Výhodou tohoto způsobu práce s funkcemi je to, že když chceme pro daný úkon změnit funkci, nemusíme měnit její název, ale stačí pouze změnit ukazatel. To se dá hojně využít v případě, že voláme v programu stále jednu a tu samou fci. (odkaz), ale v závislosti na běhu programu se tato funkce mění (přepíná se na jiné fce.). K definování příkazu budeme tedy potřebovat ukazatel na funkci, která se má provést po jeho zadání (func) a řetězec po jehož zadání se fce. spustí (command). Z těchto dvou proměnných utvoříme jednu strukturu (T_CONSOLE_COMMAND) a z ní pak vytvoříme pole příkazů. Situace je zde obdobná jako u historie výpisů. V jednom poli budeme mít uloženy příkazy (commands[]) (jeho velikost si určíme) a v druhém poli uchováme historii použitých příkazů (command_history). Pro tuto historii nemusíme používat znovu datový typ T_CONSOLE_COMMAND, ale bohatě postačí pole řetězců. Při vyvolání příkazu z historie se budeme chovat stejně, jako by jsme příkaz právě napsali, ale to až později. Opět si tedy nadefinujeme pár věciček :
#define CONSOLE_COMMAND_MAX 300
#define CONSOLE_COMMAND_HISTORY_MAX 50
|
Do objektu C_CONSOLE přibudou asi tyto věci :
|
Aby jsme si popsali fci. Add_Command(), udělejme si takovou modelovou
situaci. Chceme, aby při zadání Testuj_fci v příkazové řádce
console se např. spustila fce. Testovaci_Fce(), která musí mít shodné
parametry jako fce. T_CONSOLE_FUNC. Dále budeme chtít aby ona funkce
vypsala libovolné tři řádky do console.
int Testovaci_Fce(char * params)
{
console.Add_Line( "Radka1" );
console.Add_Line( "Radka 2" );
console.Add_Line( "Radka
3" );
};
console.Add_Command( "Testuj_fci", Testovaci_Fce);
Teď tedy fce. Add_Command() vloží do pole C_CONSOLE::commands[]
náš příkaz, ale musíme dávat pozor na jednu věc. Tou věcí jsou podtržítka
místo mezer. Pokud by jsme napsali název fce. bez podtržítka jako "Testuj
fci" stala by se taková nepříjemná věc a to tato. Po zadání "Testuj
fci" do příkazové řádky console bude název funkce "Testuj"
a spustí se s parametrem "fci". Je to proto, že vstup do příkazové
řádky ukládáme do pole command[], ze kterého pomocí funkce sscanf(command,"%s
%s",func_name, parametr); vytáhneme název funkce, kterou chceme
spustit a na druhém místě její parametr. Jelikož načítáme dva řetězce,
bere se jako oddělovací znak mezera!!! Func_name a parametr se
zadají funkci Run_Command() jako paramtery. Tato funkce tedy prohledá
seznam příkazů / funkcí a pokud najde shodu ve jménu (zadané / uložené),
spustí funkci func(), která v tom okamžiku ukazuje na funkci Testovaci_Fce();
|
Vykreslování
Je sice moc hezké, že umíme vypisovat řetězce, zadávat příkazy, atd. Ale
k čemu, když nic nevidíme. Asi to bude chtít i nějakou tu vykreslovací
funkci. Na začátku jsme si řekli, že budeme používat tři barvy (pozadí,
text a okrajová čára). Jelikož máme možnost používat i blending (průhlednost),
měli bychom si ujasnit, jaký mód použijeme (týká se OpenGL, u SDL nemáme
na výběr). Můžeme použít aditivní mód, ale v případě, že bude zrovna
pod consolí nějaká světlá barva, nic neuvidíme, protože daná oblast bude
nejspíše bílá. Nejlepším módem pro blending bude mix obou barev (console
: podklad) v poměru Alpha : 1 - Alpha. Mějme na paměti, že Alpha nabývá
hodnot 0.0f - 1.0f. Tento mód nastavíme takto : glBlendFunc(GL_SRC_ALPHA,
GL_ONE_MINUS_SRC_ALPHA); Ale hlavně nezapomeňte vrátit zpět aditivní mód
(zejména když vykreslujete částice glBlendFunc(GL_SRC_ALPHA,
GL_ONE); ). Použijeme fci. engine.renderer.Enable_Blending(),
která zapne depth-buffer pro čtení (glDepthMask(GL_FALSE)), vypne
alpha-testing a osvětlování (glDisable(GL_ALPHA_TEST) glDisable(GL_LIGHTING))
a zapne blending (glEnable(GL_BLEND)). Pokud alpha-testing nepoužíváte,
není třeba ho vypínat. Funkce engine.renderer.Disable_Blending() udělá
přesně pravý opak, zapne osvětlování, nastaví depth-buffer (glDepthMask(GL_TRUE)
pro zápis a vypne blending. Jak jsem se již zmínil na začátku tohoto článku,
použijeme bitmapový font, jako ve článku Michala Svobody. Nejdříve musíme
opravit onu chybičku, která se vloudila do operací s maticemi, přesněji přepínání
mezi GL_MODEL_VIEW_MATRIX a GL_PROJECTION_MATRIX
:
|
Kdeže je ta chyba? Vezmeme to popořádku. Matrix-stack funguje pod OpenGL jako komínek knížek, když zavoláme fci. glPushMatrix(), uložíme aktuáílní matici(1) nahoru, zavoláme-li ho znovu, další matice(2) se uloží opět na první místo a ta původní je na místě druhém. Při zavolání fce. glPopMatrix() se nahraje zpět z komínku ta matice, která je nejvýše, v našem případě matice 2 a po ní matice 1 (při opětovném zavolání fce.). A zde je ten problém. Na řádce označené číslicí 1 nastavíme projekční matici a následně ji uložíme do stacku, na řádce 2 nastavíme transformační matici a také ji uložíme do stacku. Takže trensofrmační matice je na místě prvním, kdežto projekční matice na místě druhém. Na řádku 3 nastavíme projekční matici a zavoláme fci. glPopMatrix(), tedy do projekční matice nahrajeme první matici ve stacku (transformační) a ne matici druhou (projekční). Na řádku 4 nahrajeme do transformační matice matici projekční (kterou jsme si uložili do stacku). To znamená, že matice prostě prohodíme a to není dobře! :-) Aby funkce správně fungovala, musíme prohodit řádek 3 za řádek 4. Aby jsme se nemuseli starat o pozici kursoru (textu, který vypisujeme), nadefinujme si proměnné text_x a text_y. Pomocí fce. MoveText(), nastavíme pero do pozice [x,y] a při následném zavolání fce. OutText() se proměnná text_y zvětší o výšku vypsané řádky. Tím si ušetříme starosti s počítáním pozice textu při vypisování řádku pod sebe. Bude pouze stačit jednou zavolat fci. MoveText() a pak už jen volat fci. OutText(). Text bude hezky vypsán pod sebe a my budeme mít o starost méně.
|
Tímto jsme provedli nezbytné úpravy na vypisovací funkci a můžeme se vrhnout na vykreslení console :
|
Teď dovedeme vykreslit okno console, ledasco do něj i napsat, ale stále nám chybí obrázek na pozadí, o kterém jsem se zmiňoval již na začátku. Funkce, která ho vykreslí je velice jednoduchá, tak se na ní pojďme podívat :
|
Tímto by jsme měli za sebou vykreslování celé console, vypisování do historie a systém příkazů. Zbývá nám filtrování kláves (použijeme knihovnu SDL, neboť je velice jednoduchá na ovládání a hlavně je přenositelná mezi jednotlivými systémy jako Win32, Linux, Solaris, BeOS, MacOS, atd.) a funkce, pomocí kterých budeme měnit vlastnosti console. Pro dnes už toho bylo ale dost, musíme si také nechat něco na příště. V dalším článku dokončíme programování console a podíváme se na začátky opravdového GUI (tlačítka, scripty, ...). Těším se na vás v příštím dílu.
Obsah seriálu (více o seriálu):
- Jak vyzrát na GUI - console
- Jak vyzrát na GUI - začátky GUI
- Jak vyzrát na GUI 3.díl - Tlačítka, menu, atd.
-
25. listopadu 2012
-
30. srpna 2002
-
10. října 2002
-
4. listopadu 2002
-
12. září 2002
-
25. listopadu 2012
-
28. července 1998
-
31. července 1998
-
28. srpna 1998
-
6. prosince 2000
-
27. prosince 2007
-
4. května 2007