Jak vyzrát na GUI - console - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

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.

class C_CONSOLE
{
public:
  int x,y,width,height;
  int line_width;

  // BACKGROUND IMAGE
  int bkg_image_ID;
  int img_x1,img_y1,img_x2,img_y2;
  float img_tx1,img_ty1,img_tx2,img_ty2;
  T_COLOR_RGBA img_color;

  // COLORS
  T_COLOR_RGBA bkg_color;
  T_COLOR_RGBA line_color;
  T_COLOR_RGBA text_color;

  float font_size;
  int max_display_lines;
  int max_display_chars;

C_CONSOLE();  - constructor pro tridu c_console
~C_CONSOLE(); - destructor pro tridu c_console
};

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

class C_CONSOLE
{
...
...
  char lines[CONSOLE_LINE_HISTORY_MAX][CONSOLE_CHAR_PER_LINE];
  int act_line;  -nastavime na 0
  int total_line_count; -nastavime na 0
  int line_count;  -nastavime na 0

  // FUNCTIONS
  int Add_Line(char line[]);  -pridame radek
  int Update_Line(int line_ID,char line[]);  -aktualizujeme radek
...
...
};


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.

int C_CONSOLE::Add_Line(char line[])
{
  int i;

  line_count++;  -zvysime pocty radek
  total_line_count++;  - ----||----

  if(line_count >= CONSOLE_LINE_HISTORY_MAX) line_count = CONSOLE_LINE_HISTORY_MAX; -pokud je pocet radek v historii vetsi nez je jeji kapacita, musime to oriznout

  //we will move the lines
  if(line_count >= 2)   -pokud je v historii vice nez 1 radka (ma index 2), tak posuneme radky o 1 nahoru (v poli)
  {
     for(i = line_count - 1; i >= 1; i--)
     {
        strcpy(lines[i],lines[i - 1]);
     };
   }

   strcpy(lines[0],line);  -ulozime nas retezec do pozice 0

   return(total_line_count);  -vratime celkovy pocet vlozenych radek
};

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.

int C_CONSOLE::Update_Line(int line_ID,char line[])
{
  if(total_line_count - line_ID >= 0 && total_line_count - line_ID < CONSOLE_LINE_HISTORY_MAX) -zjistime, zda dany radek jeste existuje v poli historie
  { - exituje
    strcpy(lines[total_line_count - line_ID],line); -updatujeme pozici [ total_line_count - line_ID ] - vzdalenost mezi posledni vlozenou radkou a nasi radkou
  }
  else -neexistuje
  return FCE_ERROR;  -vratime hodnotu pro chybu (1)

  return FCE_OK; -vratime hodnotu pro uspesne dokonceni fce (0)
};

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

typedef int(*T_CONSOLE_FUNC)(char * params);  -- datovy typ ukazatele (T_CONSOLE_FUNC) na fci., která vraci int a má parametr char * (napriklad retezec)

typedef struct  -- datovy typ prikazu, ze ktereho budeme tvorit pole
{
  T_CONSOLE_FUNC func;  -- fce., ktera se zavola (v programu pak jen napiseme pikaz.func("ahoj");
  char command[CONSOLE_CHAR_PER_LINE];  -- retezec prikazu
} T_CONSOLE_COMMAND;

Do objektu C_CONSOLE přibudou asi tyto věci :
class C_CONSOLE
{
  ...
  ...
  T_CONSOLE_COMMAND commands[CONSOLE_COMMAND_MAX];  --pole prikazu
  char command_history[CONSOLE_COMMAND_HISTORY_MAX][CONSOLE_CHAR_PER_LINE]; --historie prikazu
  char command[CONSOLE_CHAR_PER_LINE];  -- aktualni prikaz, ktery prave tukame do klavesnice
  int act_command_history;  -- index zobrazovaneho prikazu z historie
  int command_count; //set to 0 = no commands  -- pocet prikazu, kdyz je 0 ... zadne prikazy
  int command_history_count;  -- pocet prikazu v historii
  
  // FUNCTIONS
  int Add_Command(char command[], T_CONSOLE_FUNC func); -- fce. ktera prida prikaz do seznamu s parametry ( retezec prikazu, fce. ktera se ma zavoalt )
  int Run_Command(char command[],char params[]);  -- spusti prikaz napsany v command[] a prida k nemu parametry (zada je fci. do char * param)
  ...
  ...
};

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();

int C_CONSOLE::Add_Command(char command[], T_CONSOLE_FUNC func)
{
  command_count++;  - zvetsime pocet prikazu o 1

  if(command_count >= CONSOLE_COMMAND_MAX)  -- pokud je pole prikazu jiz plne
  {
   command_count--;  -zase pocet prikazu odecteme
   return FCE_ERROR;  -a rekneme, ze nastala chyba
  };

  strcpy(commands[command_count-1].command,command); --do volneho mista v seznamu ulozime nazev funkce a nasledne i ukazatel na funkci, ktera se ma spustit
  commands[command_count-1].func = func;

  return FCE_OK;  -vse probehlo v poradku, tak to take oznamime
};

int C_CONSOLE::Run_Command(char command[],char params[])
{
  int i;
  char temp[CONSOLE_CHAR_PER_LINE] = "";  -docasny retezec, pouzijeme ho k oddeleni jmena funkce a jejiho parametru

  if(command_count <= 0)  -pokud je pocet ulozenych prikazu roven 0, tak neni co spoustet
  {
    Add_Line("No commands binded");
    return FCE_ERROR;
  };

  sscanf(command,"%s",temp); -nacteme jmeno fce

  //prohledame seznam prikazu a pokud najdeme shodu, sputime fci.
  for(i = 0; i < command_count;i++)
  {
    if(strcmp(temp,commands[i].command) == 0)
    {
      commands[i].func(params);
      return FCE_OK;
    };
  };

  Add_Line(command);  - pokud jsme nenasli shodna jmena funkci, vypiseme jmeno fce, ktere jsme napsali a oznamime, ze je nezname
  Add_Line("Unknown command");

  return FCE_ERROR; -vratime 1 (nastala chyba)
};

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 :

GLvoid glPrint(GLint x, GLint y, char *string, float size)
{
  glBindTexture(GL_TEXTURE_2D, 1);
  glDisable(GL_DEPTH_TEST);
1 -  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glOrtho(0,640,0,480,-100,100);
2-  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
  glLoadIdentity();
  glTranslated(x,y,0);
  glScalef(size, size, 1.0);  - aby jsme mohli menit velikost fontu bez pouziti znakovych sad
  glListBase(base);
  for (int i = 0; i < strlen(string); i++)
    glCallList(FindString.Pos(string[i]));
3-  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
4-  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();
  glEnable(GL_DEPTH_TEST);
}

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

int text_x, text_y;  -tyto promenne nadefinujeme jako globalni

int MoveText(int x, int y)
{
  text_x = x;
  text_y = y;
  
  return FCE_OK;
};

int OutText(char text[], float size, float r, float g, float b, float alpha)
{
  if(alpha <= 0.0f) return FCE_OK;  -- kdyz je 100% pruhlednost, neni co vykreslovat
  
  if(alpha < 1.0f) engine.renderer.Enable_Blending(); else engine.renderer.Disable_Blending(); -zapneme/vypneme pruhlednost

  glColor4f(r,g,b,alpha) -nastavime barvu

  glPrint(text_x, text_y, text, size); -toto je fukce ze clanku Michala Svobody, upravena pro glSizef()

  text_y += (int) (vyska_fontu * size);  -vyska fontu je v pixelech (velikost v bitmape) * size je tam proto, ze muzeme font vykreslovat treba polovicni nebo 2x veci
};

Tímto jsme provedli nezbytné úpravy na vypisovací funkci a můžeme se vrhnout na vykreslení console :

int C_CONSOLE::Draw(void)
{
  int i,j,len,len2;
  int lines_left = max_display_lines;
  char temp[CONSOLE_CHAR_PER_LINE] = "";
  char temp2[CONSOLE_CHAR_PER_LINE] = "";

  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); -nastaviem mod blendingu

  if(visible == FALSE) return FCE_OK; -pokud neni console viditelna, neni co vykreslovat

  //draw background
  if(bkg_color[3] > 0.0f) -pokud je pruhlednost pozadi vetsi ne 0.0 (nebylo by videt), tak ho vykreslime
  {
    if(bkg_color[3] <1.0f) engine.renderer.Enable_Blending(); else engine.renderer.Disable_Blending(); -pokud je pruhlednost pozadi mensi nez 1.0 (nebyla by pruhlednost), pruhlednost zapneme, pokud je vetsi nebo rovna 1.0 , pruhlednost vypneme

    glColor4fv(bkg_color); - nastavime barvu pozadi
    glRectd(x,480 - y - height,x+width,480 - y); - vykreslime obdelnik, je to optimalizovane pro 640x480. 480-y je tu proto, ze OpenGL nema y = 0 nahore, ale dole
  };

  Draw_Background_Image();  -vykreslime bitmapu na pozadi (popiseme si dale)

  //vykreslime okrajovou caru
  if(line_color[3] > 0.0f) -pokud je pruhlednost pozadi vetsi ne 0.0 (nebylo by videt), tak ho vykreslime
  {
    if(line_color[3] < 1.0f) engine.renderer.Enable_Blending(); else engine.renderer.Disable_Blending(); -pokud je pruhlednost pozadi mensi nez 1.0 (nebyla by pruhlednost), pruhlednost zapneme, pokud je vetsi nebo rovna 1.0 , pruhlednost vypneme
    glDisable(GL_TEXTURE_2D); - vypeneme texturovani

    glColor4fv(line_color); -nastaviem barvu
    glLineWidth((float)line_width); -nastavime tloustku cary

    glBegin(GL_LINES); -vykreslime cary
        glVertex3d(x,480 - y - height,0);
        glVertex3d(x + width,480 - y - height,0);
        glVertex3d(x,480 - y,0);
        glVertex3d(x + width,480 - y,0);
        glVertex3d(x,480 - y,0);
        glVertex3d(x,480 - y - height,0);
        glVertex3d(x + width - 1,480 - y,0);
        glVertex3d(x + width - 1,480 - y - height,0);
    glEnd();
};

//vykreslime text
  if(line_count > 0 && text_color[3] > 0.0f) -- pokud je text videt a pokud je pocet radek vetsi nez 0, vypiseme
  {
    i = max_display_lines + act_line;
    /zapneme textury
    glEnable(GL_TEXTURE_2D);

    engine.canvas.MoveText(x,y); - posuneme vypisovay text do pozice [x,y]

    while(lines_left >= 0) - projizdime cyklus, dokud mame jeste moznost vypisovat radky do console (lines_left se na zacatku rovna poctu zobrazitelnych radek
    {
        len2 = strlen(lines[i]);  - zjsitime delku prave vypisovane radky

        //zjsitime, zda se retezec vejde cely do okna console, pokud ne, tak ho dovykreslime na dalsi radce
        if(len2 < max_display_chars) - vejde se
        {
            engine.canvas.OutText(lines[i],font_size,text_color[0],text_color[1],text_color[2],text_color[3]);
            lines_left--; -odpocitame si jednu vykreslenou radku
        }
        else -nevejde se
        {
            //string is too long
            for(j = 0; j < (len2 / max_display_chars) + 1; j++)  -zjistime si na kolik radek text bude
            {
                len = max_display_chars; -nastavime maximalni moznou delku vypsatelneho retezce
                if(len + (j * max_display_chars) >= len2) len = len2 - (j * max_display_chars) + 1; -pokud jsme uz mimo hranici retezce (napr. delka = 20 a my chceme vykreslit 100), tak to urizneme

                strncpy(temp,lines[i] + (j * max_display_chars),len);  -zkopirujeme len znaku z temp od pozice j*max_display_chars
                engine.canvas.OutText(temp,font_size,text_color[0],text_color[1],text_color[2],text_color[3]); - vypiseme radku (parametry viz. vyse)
                lines_left--; -odpocitame si jednu vykreslenou radku
                if(lines_left < 0) break; -pokud uz nejsou volne radky v okne console, zkocime
            }
        }
        i--;  -posuneme se s indexem vypisovane radky nahoru
    }

    //vypiseme aktualne zadavany prikaz do prikazove rakdy
    engine.canvas.MoveText(x,y + (max_display_lines + 1) * 12); -posuneme text na zacatek prikazove radky (zde je 12 velikost defaultniho fontu pro nasi consoli, tedy 11 + 1 mezera  pixelu)
    sprintf(temp,">%s",command); -pred prikaz pridame znak prikazove radky > (napr. testuj =   >testuj)
    len2 = strlen(temp); - zjsitime delku prikazove radky

    //pokud je prikaz prilis dlouhy, nebudeme ho zalamovat na dalsi radky, ale zobrazime maximalni pocet znaku o konce, tzn. budeme zobrazovat to co piseme a zacatek nas nezajima
    len = len2 - max_display_chars;
    if(len <= 0) engine.canvas.OutText(temp,0.5f); -vejde se cely do okna console, tak ho vykreslime
    else  -nevejde se
    {
        strncpy(temp2,temp + len,max_display_chars);
        engine.canvas.OutText(temp2,0.5f);
    };

    glDisable(GL_ALPHA_TEST); -pokud jsme pouzivali Alpha-testing, tak ho vypneme
};

glBlendFunc(GL_SRC_ALPHA,GL_ONE); -nastavime puvodni blend mod

return FCE_OK; -vse probehlo v poradku, tak to oznamime
};

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 :

int C_CONSOLE::Draw_Background_Image(void)
{
  int x1,y1,x2,y2; -souradnice vykreslovane bitmapy (levy horni a pravy dolni roh)

  if(bkg_image_ID < 0) return FCE_ERROR;  -toto je pro OpenGL, pokud je ID (vygenerovane pomoci glGenTextures()) mensi nez 0, textura neexistuje
  if((img_color[0] <= 0.0f && img_color[1] <= 0.0f && img_color[2] <= 0.0f) || img_color[3] <= 0.0f) return FCE_ERROR; -pokud by byl obrazek cerny nebo mel 100% pruhlednos, nevykreslime ho

  x1 = (img_x1 < 0)?x:img_x1;  -pokud je img_x1 mensi nez 0, nastavime x1 (pozice vykreslovaneho obrazku) na x console, jinak nechame nami urcnou pozici
  y1 = (img_y1 < 0)?480-y:480-img_y1;
  x2 = (img_x2 < 0)?x+width:img_x2;
  y2 = (img_y2 < 0)?480-y-height:480-img_y2;

  glEnable(GL_TEXTURE_2D); -zapneme texturovani
  if(Texture_Have_Alpha(bkg_image_ID) == TRUE) glEnable(GL_ALPHA_TEST); -zjistime, zda ma textura alpha-masku (TGA) a eventuelne zapneme alpha-testing
  Texture_Bind(GL_TEXTURE_2D,bkg_image_ID); -nabindujeme texturu (to same jako glBindTexture2D(GL_TEXTURE_2D, bkg_image_ID); )

  if(img_color[3] > 0 && img_color[3] < 1.0) engine.renderer.Enable_Blending(); else engine.renderer.Disable_Blending();  - pokud je nejaka pruhlednost (0 < alpha < 1), tak ji zapneme, jinak ji vypneme

  glColor4fv(img_color); -nastavime barvu (pro pripad, ze chceme mit texturu napriklad do cervena, zelena, atd)

  glBegin(GL_TRIANGLE_STRIP); -vykreslime ctyruhlenik (pomoci GL_TRIANGLE_STRIP je to rychlejsi)1
  //Top right
    glTexCoord2f(img_tx2,img_ty1);
    glVertex3f( x2, y1, 0);
  //Top left
    glTexCoord2f(img_tx1,img_ty1);
    glVertex3f( x1, y1, 0);
  //Bottom right
    glTexCoord2f(img_tx2,img_ty2);
    glVertex3f( x2, y2, 0);
  //Bottom left
    glTexCoord2f(img_tx1,img_ty2);
    glVertex3f( x1, y2, 0);
  glEnd();

  if(img_color[3] > 0 && img_color[3] < 1.0) engine.renderer.Disable_Blending();  -pokud jsme zapli pruhlednost, tak ji zase vypneme

  return FCE_OK; -vse probehlo v poradku, tak to oznamime
};

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):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: