2 Uživatelské rozhraní
2.1 Úvod
Uživatelské rozhraní(GUI), stejně tak jako u většiny her, je nejsložitější součástí 8 Kingdoms. Je zároveň nejnáročnější, co se týká procesorového času, a to během chodu celého programu, ne jenom nárazově. Návrh uživatelského rozhraní byl veden v duchu moderních počítačových her. Jádro uživatelského rozhraní spoléhá na standardně zavedené, multiplatformní renderovací API OpenGL, jehož prostřednictvím maximálně využívá renderovací potenciál grafického akcelerátoru. V současné implementaci jsme se rozhodli nejít za hranice OpenGL verze 1.1, díky čemuž lze projekt 8 Kingdoms efektivně provozovat i na poměrně starém hardware(nVidia TNT).
Díky použití OpenGL a další podpůrných multiplatformních knihoven, z nichž s uživatelským rozhraním nejvíce souvisí SDL a SDL_Mixer, je 8 Kingdoms snadno portovatelné na každou platformu, kterou tyto knihovny podporují. Vývoj probíhal na OS Linux a Windows.
Soubory zdrojového kódu ke všem prvkům uživatelského rozhraní se nacházejí v adresáři gui/ hlavního adresáře projektu. Jsou rozděleny do čtyř podadresářů se zdrojovým kódem čtyř tématicky odlišných okruhů:
2.2 Životní cyklus
Díky složitosti, komplexnosti a provázanosti uživatelského rozhraní s ostatními částmi programu je uživatelské rozhraní celkem bohatě rozčleněno do hierarchie objektů. Některé jsou pouze zastřešující a jejich funkce je veskrze organizační. Cílem bylo vytvořit základní rámec pro ostatní funkční moduly, jakožto veskrze nezávislé stavební kameny. Diagram 1 ukazuje základní strukturu objektů uživatelského rozhraní.
Základní struktura uživatelského rozhraní.
Diagram 2.1: Základní struktura uživatelského rozhraní.
Uživatelské rozhraní běží celé především na hlavním vlákně, protože renderovací knihovna OpenGL smí být na většině platforem volána pouze z hlavního vlákna procesu, respektive vlákna odpovídajícího renderovacího kontextu(aplikačního okna v gui OS). To jak program vykonává kód různých metod různých tříd se dá chápat jako životní cyklus programu. Mezi nejdůležitější třídy patří TGUI, TScreen a TContext. Jejich popis bude následovat.
2.3 Třída TGUI
Na nejvyšší úrovni leží instance třídy TGUI. Tato instance je jediná instance této třídy. Pro její alokaci a dealokaci používají statické metody třídy TGUI a ukazatel na ni je statický člen TGUI. Toto schéma je použito u více tříd.
Tato instance je viditelná a čitelná pro všechny prvky GUI. Její členy jsou hlavně pointery na globální data, struktura reprezentující transceiver Message Systému a "kontexty" uživatelského rozhraní.
Obsahuje zmíněné statické metody volané z hlavního programu(main). Její konstruktor rovnou inicializuje různé moduly jako TextureManager, Screen, SoundInterface, atd. a registruje transceiver v Message Systému. Také alokuje sadu kontextů, které tvoří nezávislé stavební bloky GUI.
Nejdůležitější nestatickou metodou je main(), která je volána z hlavního programu, tzn. na hlavním vlákně a program se z ní vrací až při svém ukončení. Uvnitř této metody se odehrává běh celého programu.
V neposlední řadě je nutné zmínit metody, které umožňují nahrávat a uvolňovat data v systému Resource Manageru(RM). Úkol dynamického nahrávání a uvolňování dat je velmi těžké efektivně a hlavně jednoduše předem naplánovat. Postupné nahrávání dat považujeme za nutné proto, abychom se vyhnuli tomu nejjednoduššímu řešení, nahrávat všechna data při spuštění programu. Je to tedy řešeno rozfázováním běhu programu. Při vstupu a výstupu z fáze jsou volány příslušné metody(enterResourcePhase, leaveResourcePhase) instance TGUI. Tyto metody pak efektivně, podle potřeby, nahrávají a uvolňují data držená RM.
2.4 Obrazovka
Obrazovka je jedním z hlavních podsystému uživatelského rozhraní. Jejím reprezentantem je právě třída TScreen.
Nejpodstatnější metodou je enterMainLoop(). Obsahuje hlavní smyčku, která přijímá vstup ze vstupních zařízení(klávesnice a myš), zpracovává je do vnitřní reprezentace typu INPUT a posílá aktuálnímu kontextu. Pokud není na vstupu nic ke zpracování, předa jednou za DELAY_WORK pouze prázdnou strukturu INPUT aktuálnímu kontextu. Dále pokud uběhlo víc jak DELAY_FRAME od posledního překreslení překreslí aktivní kontext. Aby se šetřilo časem procesoru a zabránilo se zbytečným prázdným cyklům, tak pokud není splněna ani jedna z předchozích podmínek, vlákno se uspí na dobu než bude splněna podmínka pro překreslení. Diagram 2 ukazuje práci hlavní smyčky.
Hlavní smyčka
Diagram 2.2: Hlavní smyčka.
Další funkcí třídy TScreen je správa množiny kontextů. Ty tvoří nezávislé, oddělené, stavební kameny, které se starají o samotný vystup na obrazovku a interpretaci vstupu uživatele. Hlavní operací nad kontexty je přepínání mezi nimi. To lze realizovat pomocí jména kontextu - textového řetězce. Hlavní program při spuštění přepne na kontext "MAINMENU". Další přechody už jsou plně pod kontrolou samotných kontextů.
2.5 Kontexty - stavební kameny GUI
Kontext zodpovídá za konkrétní výstup na obrazovku a vstup od uživatele. Kontexty jsou potomky třídy TContext. Každý kontext definuje povinně vlastní metody activateContext, deactivateContext. Život kontextu je znázorněn na Diagramu 3.
Život kontextu
Diagram 2.3: Život kontextu.
Projekt 8 Kingdoms obsahuje sedm kontextů: MainMenu, LocalMulti, MultiHost, MultiJoin, LoadingScreen, Game a FinalStatistics. Nejvýznamnějšími kontexty jsou MainMenu a Game. Přechody mezi jimi, tak jak je může vidět uživatel, jsou vidět v diagramu 4. V následujících podkapitolách jsou popsány všechny existující kontexty, jejich stavba a funkce.
Přechody mezi kontexty
Diagram 2.4: Přechody mezi kontexty.
Tento kontext je aktivní při spuštěni hry. Nejdřív uživatel vybere svůj profil, tedy svoje jméno a s ním spojené nastavení. Pak se dostane do hlavní nabídky, odkud může měnit nastavení a vybrat typ hry (viz obrázek).
Mapa kontextu MainMenu.
Diagram 2.5: Mapa kontextu MainMenu.
Významnější Okna kontextu:
2.5.2 Kontext LocalMulti
Tento kontext obsahuje pouze jedno okno. Je přístupný z hlavního menu po výběru mapy. Okno kontextu obsahuje informace o vybrané mapě (její název, velikost, počet hráčů pro který je určena a stručný slovní popis) a ovládací prvky pro nastavení hry. Je to především seznam rolí, které se v mapě vyskytují. Pro každou roli je potřeba nastavit, kdo ji bude ovládat (hlavní hráč, tedy vlastník aktivního profilu, další hráč se zadaným jménem a nebo umělá inteligence jedné ze tří obtížností). Druhým nastavením je typ ukončení hry (vyvraždění všech nepřátel/obsazení měst viz. pravidla). Okno bylo navrženo podle zkušeností z jiných her.
2.5.3 Kontext MultiHost
MultiHost je obdoba kontextu LocalMulti pro serverovskou část síťové hry. Přibyl tu tudíž seznam hráčů přihlášených k tomuto serveru. Informace o mapě a typ ukončení hry je totožný. Zůstává také seznam rolí ve hře. K rolím se ale přiřazují připojení hráči nebo umělá inteligence. Okno se chová z části automaticky. (Nově připojený hráč nahrazuje první neobsazenou roli.)
Kontext MultiHost se tedy kromě nastavování síťové hry stará také o aktualizaci seznamu připojených hráč (klientů) a o rozesílání informací jednak o nastavení hry a jeho změnách a také o připojených hráčích všem klientům. K tomu slouží zpráva MSG_NET_ROLESETTINGDATA (viz. kapitola 2.6 Zprávy).
V oknech kontextů pro nastavení síťové hry je také chatbox. (Seznam došlých zpráv a krátký řádek pro napsání zprávy). Má sloužit jednak na komunikaci mezi hráči a také na výpis zpráv typu, že se někdo připojil nebo odpojil.
Chatbox posílá a přijímá zprávu MSG_NET_CHAT se jménem odesílatele a textem zprávy.
2.5.4 Kontext MultiJoin
Kontext MultiJoin je kopií kontextu MultiHost s rozdílem, že prvky pro nastavení nepřijímají vstup od uživatele, ale pouze informují o nastaveních, které provedl server.
Kontext zpracovává zprávu MSG_NET_ROLESETTINGDATA , která obsahuje informace o změnách v nastavení.
2.5.5 Kontext LoadingScreen
Tento kontext obsahuje také pouze jedno okno, které navíc nepřijímá od uživatele vstup. Je to mezistupeň mezi kontexty LocalMulti, MultiHost a MultiJoin a kontextem Game. V době kdy se na pozadí načítají resources a inicializují se datové struktury, okno tohoto kontextu informuje o tom, co se děje. Pro zobrazení těchto informací slouží metody: showStateMsg() a setProgress() .
2.5.6 Kontext Game
Game je samozřejmě nejkomplexnější kontext programu. Vedle hlavního okna s mapou obsahuje ještě malá okna s nastavením, uložení hry, diplomacií a okno s informacemi (viz diagram 6).
Mapa kontextu Game.
Diagram 2.6: Mapa kontextu Game.
Součásti hlavního okna:
Ukázka kontextu Game.
Obrázek 2.1: Ukázka kontextu Game.
Z funkčního hlediska pracuje kontext TGame jako stavový stroj. Stavu odpovídá zvolená akce/jednotka/budova a to zdali je hráč na tahu, či ne. Stav definují atributy actionsPanelState, infoPanelState, iGUIOwner, a další(viz dokumentace zdrojového kódu). Změna stavu je zapřičiněna třemi typy událostí. První z nich je vstup od uživatele (volba akce, jednotky, cíle, budovy,...). Druhou jsou pak odpovědi herního enginu přicházející prostřednictvím zpráv Message Systému. Třetí jsou pak události animačního enginu, které mají zajistit, aby grafika zůstala konzistentní s hrou. Aby se zabránilo nekonzistencím je nutné příjem a zpracování těchto událostí serializovat. K tomu slouží jednoduché zamykání(vstupu uživatele) a řazení do front(zprav z herního enginu).
2.5.7 Kontext FinalStatistics
se stává aktivním na konci hry. Obsahuje statistiky bitvy. Cesta odsud vede do hlavního menu.
2.6 Zprávy
Uživatelské rozhraní je přímo závislé na příjmu a odesílání zpráv. Hlavně jeho herní část(kontext Game). Příjem zpráv probíhá v handleru GUI_MSG_HANDLER. Zde se zpracovávají zprávy:
Herní zprávy nejsou hned zpracovány, ale řadí se do fronty. Kontext Game si je vyzvedává v okamžiku, kdy není zamčený z důvodu zpracování předchozí zprávy.
2.7 Výjimečné stavy
Vyšší úrovně uživatelského rozhraní generují v případě chybových stavů výjimky typ E_8K_GUI a jejich potomků. Potomek E_8K_ASE_READER je generován parserem souborů s modely formátu ASE. E_8K_TGA_READER je generován načítadlem TGA souborů. Více informací je v první kapitole programátorské dokumentace 1.7 Výjimky
Fatální chyby vzniklé a zachycené během chodu programu jsou reportovány prostřednictvím funkce CONSOLE(). K takovým chybám by v ideálním případě nemělo docházet. Další informace v podkapitole 2.15 Příkazový řádek.
2.8 Matematická knihovna
Celý funkční aparát uživatelského rozhraní, který má něco společného s výpočtem zobrazeni se opírá o matematickou knihovnu mymath.h. Tato knihovna implementuje nejdůležitější matematické operace potřebné pro výpočet transformací zobrazovaných objektů. Hlavními datovými typy jsou:
P2F, P2D struktury pro 2D souřadnice(x,y) ve float a double
P3F, P3D struktury pro 3D souřadnice(x,y,z) ve float a double
P4F, P4D struktury pro 4D homogenní souřadnice(x,y,z,w) ve float a double
MX4pole typu float velikosti 16 pro reprezentaci matic 4x4
MX3pole typu float velikosti 9 pro reprezentaci matic 3x3
V4D, V4F pole typu double a float délky 4 pro reprezentaci vektorů
V3D, V3I pole typu double a int délky 3 pro reprezentaci vektorů
V2D, V2I pole typu double a int délky 2 pro reprezentaci vektorů
QUATERstruktura pro reprezentaci kvaternionu
AXISANGstruktura pro reprezentaci rotace kolem osy o jistý úhel
Tabulka 2.1: Výčet datových typů mymath.
Některé struktury jsou ve skutečnosti uniony a díky tomu nabízejí svoje data v několika různých reprezentacích. Například P2F obsahuje položku float p[2]. Díky této vlastnosti se typy pro vektory VnD/F/I téměř nepoužívají.
Zbytek knihovny je tvořen makry a funkcemi, které provádějí na těchto typech operace. Pro základní a jednoduché operace typu nastavení, součet, součin, velikost vektoru jsou zvoleny spíše makra, aby se ušetřilo jedno volání funkce, což může být na některých kritických místech kódu znatelné urychlení. Jiné složitější a nepříliš kritické operace jsou vykonávány funkcemi. Mezi hlavní skupiny operací patří:
Rozšíření matematické knihovny mymath_ext.h, obsahuje objektovou verzi vektoru(TVector), s kterou se lépe a efektivněji pracuje. Nepoužívá se ovšem jako typ pro ukládání dat.
Reprezentace matic v mymath je opačná než v OpenGL, avšak operace násobení vektoru a matice a matice s maticí se provádějí v opačném pořadí. Takže výsledek je stejný a dá se tak přecházet z OpenGL a mymath bez speciálních konverzi.
2.9 Křivky a plochy
Křivky a plochy jsou používány v počítačové grafice a pro počítačovou animaci. V projektu jsou použity pro:
Křivky jsou implementovány v modulu AdvGeom.cpp/h. Každá křivka je potomkem třídy TGenericCurve. Důležitým úkolem, vedle samotného vypočítání bodů křivky, je způsob parametrizace. Pro pohyb objektů po křivce je nutné mít možnost kontrolovat rychlost pohybu a její absolutní pozici na křivce. Klasicky definované křivky mají nerovnoměrnou parametrizaci. Třídy implementující křivky tento problém řeší předpočítáním pozic na křivce a následné lineární interpolací parametrů mezi těmito body. Metoda není přesná, ale poskytuje uspokojivé výsledky.
Parametrizace křivky
Obrázek 2.2: Parametrizace křivky.
Tabulka 2 shrnuje všechny typy implementovaných křivek.
TřídaPopis
TGenericCurvePředek pro všechny křivky. Obsahuje základní datové struktury, jako seznam řídících bodů a metody na jeho zpracování. Popisuje základní rozhraní, které křivka musí podporovat, jako hodnota v bodě, první derivace v bodě, druhá derivace, nebo matice lokální soustavy.
TJoinQuadricBezierCurve Spojená Bezierova křivka. Umožňuje vytvořit libovolné množství hladce napojených Bezierových kubik. Řeší jejich parametrizaci.
TJoinLinearCurveLomená čára
THermiteanCubic Hermitovská kubika.
TJoinHermiteanCubic Spojené Hermitovské kubiky. Umožňuje vytvořit libovolně mnoho hladce napojených Hermitovských kubik.
Tabulka 2.2: Seznam tříd implementujících křivky.
Spojené křivky
Obrázek 2.3: Spojené křivky (kvadriky, kubiky).
2.10 Rastrová data.
Projekt 8 Kingdoms načítá rastrová data, obrázky, ze souborů ve formátu TGA, nebo BMP. TGA mají tu výhodu, že podporují osmibitový alfa kanál. Tzn. průhledné a poloprůhledné obrázky. Načítání provádí RM. Pro načítání BMP se používá část knihovny SDL. Pro načítání TGA se používá modul TGA_lib.cpp/h, obsahující funkci LoadTGA. Tato funkce umí načítat nekomprimované obrázky ze souboru TGA do datové struktury SDL_Surface stejně jako ekvivalent pro načítání BMP.
Rastry lze v projektu použít pouze jako textury. Renderovací knihovna OpenGL klade na textury omezení, že jejich velikost smí být pouze mocnina dvou. Proto jsou všechny rastry čtvercové a mají velikost 128x128, 256x256, nebo 512x512. Pro kontrolu a nahrávání rastrů do paměti grafické karty v prostoru identifikátorů OpenGL byl navržen Texture Manager(TM). Ten spravuje seznam rastrů načtených do texturovací paměti. TM je implementován v modulu TextureManager.cpp/h. Je reprezentován jedinou globální instanci třídy TTextureManager, která se inicializuje při inicializaci GUI.
Každá část aplikace, která chce použít texturu musí požádat Texture Manager o její načtení metodou loadTextureQuery. Je možné vytvořit texturu bez obsahu načítaného z externího souboru. Požadavky na načtení se ukládají do zásobníku a k načtení textur dochází až ve chvíli, kdy se zavolá metoda loadTextures. Pří načítání textur se požádá RM o rastr a prostřednictvím volání OpenGL se vytvoří textura v texturovací paměti. Je nutné připomenout, že OpenGL se smí volat pouze na hlavním vlákně. Stejně tak i loadTextures. Zde se může zdát kumulované načítání textur jako špatný nápad.
Fungování Texture Manageru
Diagram 2.7: Fungování Texture Manageru.
2.11 Fonty
Pro psaní textu používáme fonty pevné velikosti (8x16 a 8x13), které jsou ve formě binárních dat uloženy přímo v kódu programu. Pevná velikost se nám zdála výhodná jednak kvůli snadné implementaci a také kvůli zaručené velikosti textu na obrazovce.
Pokryto je prvních 384 znaků unicode sady. Jsou mezi nimi všechny české a německé znaky. Do budoucna by bylo dobré fonty rozšířit například o azbuku a doplnit o chybějící francouzské znaky.
Výhodnější by pravděpodobně bylo používat fonty nainstalované v systému. Přístup k fontům v jednotlivých operačních systémech je ale odlišný. Proto jsme se rozhodli pro snazší řešení, ale do budoucna by tohle byla správná cesta.
Při textovém vstupu od uživatele se některé znaky špatně zobrazují. To není chyba fontů ale překladu vstupu do unicodu knihovnou SDL (viz. komentář k INPUT_KEY_DOWN v kapitole 2.12 2D Engine). Řešení by bylo implementovat tento překlad sami. Postup by byl opět na obou OS odlišný. Částečným řešením by také bylo zakázat na vstupu znaky s kódem vyšším než 127.
2.12 2D Engine
Především kvůli přenositelnosti mezi platformami jsme v našem projektu implementovali vlastní 2d grafické rozhraní pro komunikaci s uživatelem.
Jednak jsme si mysleli, že si uděláme rozhraní přesně na míru a také dostupné systémy jsou často pouze pro jeden OS. Prvotní návrh jsme ale podcenili a přidávání nových prvků a funkcí vedlo ke značné nepřehlednosti systému.
Náš 2D engine Je systém tzv. ActiveObjectů (viz. níž), který uživateli umožňuje klikání na tlačítka, vybírání položek ze seznamů a podobně.
Ukázka 2D engine.
Obrázek 2.4: Ukázka 2D engine.
2.12.1 ActiveObjecty
ActiveObject je 2D entita čtvercového tvaru, která je schopná se nakreslit a zpracovávat vstup z klávesnice nebo od myši (tlačítko, okno, ...).
              
    class TActiveObject {
    public:
      int x, y, width, height;
      int state;

      virtual void show();
      virtual void hide();

      virtual void drawObject()=0;
      virtual int workInput(INPUT *input)=0;

      /* ... */
    };
  
ActiveObjecty je možné rozdělit do několika skupin:
Zpracování vstupu: Vzhledem k tomu, že 2d objektů se v každém našem kontextu vyskytuje relativně málo pohromadě, rozhodli jsme se, že každý vstup z klávesnice a od myši bude dostávat každý ActiveObject. Tedy ne jenom ten nad kterým je myš, nebo který má focus. Toto řešení je sice náročnější na výkon, ale na druhou stranu je každý prvek autonomní. Také to usnadňuje situaci, kdy chtějí dva prvky reagovat na stejný vstup.
Každý ActiveObject má svůj "stav", který obsahuje informaci o tom, zda je kurzor myši právě nad ním, zda má objekt focus a podobně. K zjišťování stavu pak slouží makra (CLICKED, PUSHED, FOCUSED, MOUSEOVER, ...)
              
    if CLICKED(mybutton) { /*...*/ }
  
ActiveObject je o vstupu informován metodou workInput. Jako parametr je mu předána struktura INPUT. Tato struktura obsahuje především typ události, která nastala a dále parametry, které s touto událostí souvisí (která klávesa byla zmáčknuta...). Každý input navíc obsahuje informaci o pozici myši v době události.
              
    struct MOUSEPOS{
      Sint16 x,y;
    };
    struct KEY {
      SDLKey sym;
      Uint16 unicode;
    };
    struct INPUT {
      int type;
      MOUSEPOS mousepos;
      union {
        KEY key;
        MOUSEPOS relpos;
        Uint8 button; 
      };
    };
  
Možné typy vstupů jsou:
INPUT::typePopis
INPUT_MOUSE_DOWNBylo zmáčknuté tlačítko myši. button obsahuje které tlačítko
INPUT_MOUSE_UPBylo puštěno tlačítko myši. button obsahuje které tlačítko
INPUT_MOUSE_MOTIONPohnula se myš. relpos udává relativní pohyb od předchozí pozice
INPUT_KEY_DOWNZmáčknutá klávesa. sym říká, která to byla. unicode je znak, kterému to odpovídá

Pozn.: na přeložení klávesy do unicodu používáme rozhraní knihovny SDL. V něm je ale chyba a správně překládá pouze unicody menší než 256. To má za následek že vstup od uživatele je v některých případech špatně reprezentován. To se projevuje především u písmen s háčky a čárkami.
INPUT_KEY_UPPuštěná klávesa.
INPUT_NOPo dobu DELAY_WORK nedošlo k žádné události
Tabulka 2.3: Typy vstupu.
Naše ActiveObjecty si nedělají ambice být úplným okénkovacím systémem. Vznikaly postupně podle toho, co jsme potřebovali. Implementovány jsou:
TřídaPopis
Jednoduché pasivní.
TLabelJeden řádek textu.
TMultiLabelVíceřádkový text
TIconJednoduchý obrázek (výřez z textury).
TIconExtRozšíření TIcon. Obrázek s rámečkem.
Tlačítka
TButtonobdélník, který mění svůj vzhled když je stisknut (popřípadě i když je nad ním kurzor). Má dvě verze: standart, toolbar.
TTextButtonPotomek TButton, který navíc obsahuje TLabel.
TSoundedTextButtonPotomek TTextButton, který, když se na něj klikne, přehraje zvuk.
TIconButtonPotomek TButton, který má navíc TIcon
Ostatní aktivní
TRadioBoxPřepínátko. Maximálně jeden TRadioBox v jednom TGroupBaru (viz níž) může být vybrán.
TCheckBoxZaškrtávátko.
TLineTextField Jednořádkové políčko pro textový vstup.
TScrollBarObdélník s dvěma šipkami a jezdcem mezi nimi. Dvě verze: vertikální, horizontální.
Kontejnery
TBarPředek pro všechny kontejnery. Má metodu Add(TActiveObject *) na přidání objektu do kontejneru. Metoda DrawObject() pak kreslí všechny takto přidané objekty. Stejně tak workInput(INPUT *) předá všem těmto objektům vstup od uživatele.
TWindowTBar na nejvyšší úrovni z hlediska stromu vykreslování (hned pod kořenem). Může být pohyblivé/nepohyblivé. Typicky má nějakou texturu jako pozadí, ale může být i poloprůhledné.
TGroupBarPotomek TBaru, který graficky zvýrazňuje určitou oblast. Stará se o přepínání radioboxu. (tj. aby v něm byl vybrán maximálně jeden TRadioBox.)
TListBarSeznam s vertikálním scrollbarem. Lze do něho přidávat TItemy. ListBar> se pak stará o skrolování seznamem. Mezi TItemy lze vybírat podobně jako mezi radioboxy. Pouze jede item muže být vybrán.
TItemPoložka TListBaru a zároveň kontejner
Speciální
TCursorJe to speciální ActiveObject. Současně existuje pouze jednou. Jeho poloha odpovídá poloze kurzoru myši. Je to potomek TIcon, ale jeho obrázek se může měnit v závislosti na poloze (na okraji obrazovky se ve hře mění na šipku v daném směru).
TFrameTaké TFrame existuje vždy jen jednou. Typicky je to předek kontextu (viz výše). Je to vlastně kořen hierarchie vykreslování a zpracování vstupů ActiveObjectů. Stará se také o překrývání oken.
Tabulka 2.4: Seznam ActiveObjectů.
Kromě víceméně standardních obecných ActiveObjectů, jsme potřebovali i nějaké speciálnější. Jsou to většinou potomci některého z výše uvedených objektů, rozšířené o další možnosti, nebo úplně nové objekty, které se ale nepoužívají běžně.
TřídaPopis
TConsoleWindowKonzole, tedy řádek na zadávání textů (příkazů) a seznam historie.
TOSDPrůhledné okno, kterému se metodou showMsg dají posílat textové zprávy. TOSD tyto zprávy zobrazuje. Dále se stará o jejich uspořádání podle priority a času příchodu.
TWallpaperVelký obrázek na pozadí
TScrollingTitles Skrolující titulky
TIconTextButtonTlačítko s obrázkem i textem zároveň
TProgressBarobdélník, který mění svůj rozměr a tím naznačuje míru pokroku probíhající akce
Tabulka 2.5: Seznam rozšiřujících (konkrétních) ActiveObjectů.
2.13 3D Engine
8 Kingdoms obsahuje vlastní 3D engine pro zobrazování dynamického trojrozměrného světa, terénu, překážek, modelů vojáčků atd. Implementuje některé standardní postupy, které se v počítačových hrách běžně používají. Skládá se ze statické části, která drží data v reprezentaci vhodné pro rychlé vykreslování pomoci API OpenGL a dynamické části, která simuluje pochody ve virtuálním světě v průběhu času. 3D engine sám o sobě je umístěn ve vykreslovacím systému jakožto potomek TActiveObject, konkrétně potomek TFrame. Tímto je zařazen do stromu TActiveObjectů jak je uvedeno v části o 2D enginu. Pokaždé když se volá metoda drawObject() zděděna po TActiveObjectu vykreslí se jeden frame do oblasti dané zděděnými parametry TActiveObjectu. Poté se provede update dynamické části a řízení se vrátí zpátky do hlavní smyčky.
2.13.1 Graf scény
Hlavním objektem 3D enginu je TScene. TScene si pamatuje objekty, které jsou v dané scéně, pozice a parametry světelných zdrojů a aktuální mlhu.
TScene
Diagram 2.8: Objekt TScene
Mlha slouží především k tomu, aby se nemusely kreslit objekty, které jsou ve scéně ve výřezu, který vidí kamera, ale které jsou daleko. Navíc změnou její barvy simulujeme změny počasí.
Nejdůležitější součástí scény jsou ale TSceneObjecty. Obecně to může být cokoli, co má nějakou pozici a orientaci. Pro zrychlení výpočtu je každý TSceneObject obalen osově orientovaným kvádrem (TAABB). Při výpočtech (kolize dvou TSceneObjectů, kolize TSceneObjectu s paprskem, ...) se potom objekt nahrazuje svým TAABB.
2.13.2 Rendering
Objekty nejsou ve scéně v jednom seznamu, ale jsou uloženy ve stromu. Když se má pak z pohledu nějaké kamery scéna nakreslit, nekreslí se všechny objekty ale jen objekty z nějakých uzlů. Protože naše scéna má dvourozměrný charakter (ve směru osy z jsou nad sebou typicky maximálně dva objekty), rozhodli jsme se pro datovou strukturu Quad-tree. Jedná s v podstatě o rekurzivní dělení scény na čtvrtiny, tak aby ve výsledku každý uzel obsahoval přibližně stejný počet TSceneObjectů.
QuadTree
Diagram 2.9: Quad-Tree
Obrázek popisuje, jak by mohl vypadat Quad-tree pro jednoduchou scénu. Na počátku je ve scéně moc objektů, tak se rozdělí na čtvrtiny. Pak už je to lepší až na první kvadrant, který se dále dělí. Pokud je nějaký na rozhraní Quad-nodů, uložíme odkaz na něj do obou.
TScene je potomkem objektu TQuadNode. Na začátku se do scény umístí všechny objekty, pak se spustí tohle dělení. Když se potom objekt přesouvá z jednoho quad-nodu do druhého, struktura se už nepřepočítává.
Při zobrazování scény se nejprve spočítá jehlan kamery. Kreslí se ty quad-nody do kterých tento jehlan zasahuje. Každý TSceneObject si pamatuje, jestli byl v tomto průchodu již nakreslen, tím zabraňujeme, aby se objekt na rozhraní dvou quad-nodů kreslil dvakrát.
2.13.3 Model světa
Ke grafické reprezentaci světa slouží třída TguiMap je to potomek obecné scény (TScene). Oproti TScene obsahuje navíc seznam hexů, jednotek a budov. Má svojí vlastní kameru. Stará se o stupně viditelnosti objektů. Zpracovává vstupy od myši a z klávesnice.
2.13.4 Import 3D Modelů
Struktura scény je hierarchická. Na nejnižších úrovních jsou body a trojúhelníky. Body a trojúhelníky jsou sdruženy do komplexnějších objektů, které reprezentují tuhá tělesa ve virtuálním světě. Těmto tělesům se říká modely. Modely jsou předvytvořené odděleně během vývoje hry. Jelikož jsou celkem komplikované, používá se k jejich vytvoření sofistikovaný modelovací software. Například:
Součástí modelů, jsou i předpřipravené animace, jako chůze, mávání mečem, upadnutí na zem, apod. Tyto animace se také vytvářejí v modelovacím software.
Vytvořený model z modelovacího programu vyexportuje do souboru formátu .ASE (Ascii Scene Export). Daly by se použít i jiné soubory s 3D daty, pro které by bylo třeba napsat příslušnou importovací knihovnu. Soubor ASE je textový a obsahuje geometrii a animace jedné scény vytvořené v modelovacím software. Některé jeho části jsou povinné, a některé nepovinné. Pro použití v 8 Kingdoms potřebujeme především geometrii objektů a jejich animace. Jak bylo řečeno, soubor obsahuje jednu animaci pro celou scénu. Proto bylo nutné uložit různé animace jednoho objektu do více souborů. Máme tedy pro jeden objekt primární soubor s geometrií a sekundární soubory s animacemi. Je důležité aby si tyto soubory odpovídaly. Soubory s animací objektů obsahují redundantně i geometrii, kvůli tomu, aby byly sami o sobě konzistentní. Tato geometrie se při načítaní sekundárních souborů ignoruje. Považuje se za už načtenou z primárního souboru. Z animačních souborů se tedy nahrávají pouze animace nad stejnou geometrii. Odkazy na geometrii musí sedět.
Pro načítání modelů ze souborů ASE se používá knihovna reader_ase.cpp/h, kterou si volá Resource Manager, když potřebuje nahrát požadované zdroje. Protože je ASE soubor textový, je jeho parsování pomalejší, než tomu je u binárních formátů. Přesto je dostatečně rychlé na běžném počítači, aby stačilo nahrát všechny modely, se kterými 8 Kingdoms pracuje. Výhodou textového formátu je jednodušší práce během vývoje. Detailní popis souboru ASE se dá nalézt na těchto adresách:
Knihovna reader_ase.cpp exportuje dvě funkce load_ASE. Které nahrávají geometrii a animace do objektu typu TModel. První funkce nahrává přímým způsobem primární verzi objektu, druhá funkce slouží k nahrávání zjednodušených verzí modelů pro použití LOD. LOD není v 8 Kingdoms použito.
Je nutné zmínit, že každý model se nahraje do programu pouze jednou. To že je vidět na scéně vícekrát, je způsobeno instanciováním modelu. To znamená, že se vytvoří jednoduchá unikátní konstrukce, instance, odkazující se v klíčových bodech na geometrii v TModel. Více informací v podkapitole o grafu scény a o hierarchické animaci.
2.13.5 Kontroléry
Kontroléry tvoří jádro dynamické části 3D engine. Zajišťují změny obsahu statické scény v čase. Jinými slovy pohyb. Oživují scénu animacemi a speciálními efekty. Jsou hierarchicky uspořádané a mají plug-in charakter. Snadno se vyvíjí a přidávají k již hotovému systému a tím přidávají nové a další zajímavé efekty a funkcionalitu.
Architektura kontrolérů je inspirována principy na kterých fungují nástroje na vytváření 3D animací, také trendy ve vývoji moderních 3D engine.(V neposlední řadě přednáškou Antonína Hildebranda: Návrh animačního systému pro herní engine.)
Kontrolér je objekt, který modifikuje nějaká data v závislosti na čase. Jeho struktura vypadá zhruba následovně:
              
    class controller
    {
      private:
        void* data;
      public:
        void update(float time);
    };
  
Základní návrh lze modifikovat mnoha způsoby: hierarchická struktura, závislosti, updatování podle přírůstku, spoolovaná alokace, LOD, atd. V 8 Kingdoms kontroléry implementují některé tyto vlastnosti. Pro stručnost a přehlednost jsou shrnuty v následujícím výčtu:
Kontroléry jsou svázány čistě pouze s 3D scénou. Jediná 3D scéna je v kontextu TGame a typ teto scény se jmenuje TguiMap. Jeden její člen je speciální potomek třídy TController, TControllerManager, který tvoří kořen hierarchie všech kontrolérů na scéně. Diagram 10 zachycuje práci kontrolérů.
Struktura kontrolérů
Diagram 2.10: Struktura kontrolérů.
Typů kontrolérů je mnoho a jejich počet poměrně rychle naroste. Následující tabulka 6 obsahuje nejdůležitější z nich rozdělené podle funkce.
TřídaPopis
Obecné
TController Předek všech kontrolérů. Viz výše.
TAdvController Vylepšený kontrolér. Přidány funkce pro počítání přírůstků a relativního času.
TControllerManager Tvoří kořen hierarchie kontrolérů. Obsahuje metodu destroyAllControllers a metodu setSpeed pro nastavení rychlosti cele hierarchie
TSceneObjectManipulator Umožňuje pohybovat s objektem po scéně pomocí vstupu z klávesnice. Nepoužívá se.
Hierarchická animace
TTRSController Základní kontrolér pro hierarchickou animaci. Modifikuje parametr P3F a AXISANG v závislosti na čase. Obsahuje prostředky pro interpolaci.
TNodeAnimationController Svazuje TRSController s animační tabulkou pro přehrávání animací importovaných z 3D modelovacího software. Viz hierarchická animace.
TModelAnimationController Tvoří kořen hierarchie kontrolérů pro jednu animační tabulku. Tzn. pro jednu konkrétní animaci. Obsahuje metody play() a stop() pro aktivaci a přehrávání
TPuppetController Tvoří zastřešující kontrolér pro všechny animace na jedné instanci modelu. Obsahuje metody addAnimation() pro přidání nového stromu kontrolérů pro animaci instance modelu a metody playAnimation(aid) a stopAnimation(aid) pro přehrání a zastavení konkrétní animace.
Částicové efekty
TParticles Předek pro všechny částicové animace. Obsahuje datové složky pro pole pozic a směrů částic, metodu addPoint pro přidávání bodů, virtuální
TBloodSplash Efekt "výstřiku krve". Vkládá se do scény statickou metodou installSmallBloodSplash(), která přijímá jen nezbytné parametry, jako je poloha a směr.
TExplosion Efekt výbuchu je téměř identický s TBloodSplash, jen částice jsou jinak velké a barevné. Vkládá se pomocí statické metody installExplosion().
TSmoke Trvalý, nebo dočasný efekt stoupajícího kouře. Vkládá se statickou metodou installSmoke().
TDistantAttack První verze střely. Rozlišuje dva druhy podle vnořeného typu TDistantAttackType na střelu z katapultu a střelu z luku a magickou střelu. Nepoužívá se.
TDistant_Attack Druhá verze střeleckých útoků. Předek pro všechny. Obsahuje hlavně datové položky.
TProjectileAttack Střela reprezentovaná 3D modelem. Obsahuje update pro let hmotného bodu.
TArrowAttack Šípový útok. Pouze využívá speciální typ modelu.
TCatapultAttack Útok katapultu kombinující částicový efekt stopy za letící střelou s 3D modelem projektilu
TMagicMissileAttack Většinou stejná jako u střely z katapultu jen optimalizováno pro magickou střelu. Přidána tlakové vlna po dopadu.
TDestruction Exploze při zničení budovy. Je tvořena pomocí potexturované koule a disku vytvořené funkcemi glu.
TFlyAway Není to vlastně částicový efekt, ale funguje podobně, vyletí s objektem scény do vzduchu a pak ho odstraní ze scény a smaže.
TSparkle Efekt jiskření. Pro zdůraznění události na mapě. Je zároveň potomkem částic i billboardů(jiskry se otáčejí na kameru).
Počasí
TAtmosphericEffect Předchůdce pro částicové atmosférické efekty. Implementuje efektivní algoritmus pro rozmístění částic jen v okolí kamery. Ve stručnosti jde o devět krychlí, které se vyměňují podle toho přes který okraj prostřední krychle kamera přešla.
TSnow Potomek TAtmosphericEffect, který simuluje pád vloček - tetelení, a vykresluje je jako jednoduché body.
TRain Potomek TAtmosphericEffect. Simuluje dešťové kapky, zvukový efekt hromu a záblesky. Nastavení časových prodlev je možné pomocí konstant v Effects.h
TWeatherController Zastřešující kontrolér pro ovládání tří stavů počasí (Léto, Zima, Podzim) a jejich plynulého přechodu. Změna barvy pozadí a mlhy.
Plánovací
TScheduler Předek pro plánovací kontorolery. Slouží pro načasování spouštění akcí. Akce je reprezentována datovou strukturou TSheduledAction. Pokud je akce naplánována pomocí metod linkActionAfterLast(act), insertActionAfter(act1, act2) a insertAction(act) spouští se ve stanoveném intervalu updateAction(rtime, act) pokaždé, když se zavolá metoda kontroléru update(time). Akce mohou samy řídit svoji dobu běhu. Z toho důvodu není zaručeno, kdy akce končí a nedá se tak plynule navázat na druhou pouhým nastavením počátku následující za konec předchozí. Proto se dají akce zřetězit explicitně. Platí, že když akce skončí aktivuje se akce která je na ni zřetězená. Dají se tak vytvořit sekvence činností: běž dokud se nedostaneš na místo->bojuj dvě vteřiny->umři.
TBuildingController Plánovací kontrolér pro budovy. Umožňuje naplánovat zničení, spuštění scénáře a odstranění z mapy
TUnitMemberController Plánovací kontrolér jednoho modelu z jednotky. Umožňuje naplánovat pohyb scénou po křivkách, nebo za použití steeringu(viz Jednotky). Dále umí naplánovat spuštění scénáře, zmizení a objevení modelu a odstranění modelu ze scény.
Mapové
TMapControllerManager Potomek TControllerManager tvořící kořen hierarchie v TguiMap. Drží ukazatel na stav vstupních zařízení, který updatuje při každém updatu hierarchie. Spolupracuje s TInputController .
TInputController Při vytvoření získá ukazatel na TMapControllerManager, kterého se ptá na stav vstupů. Dovoluje tak odvozeným kontrolérům pracovat kromě času i se vstupem z klávesnice.
TMainCameraController Kontrolér ovládající pohyb kamery po scéně v závislosti na vstupu z myši a klávesnice. Realizuje pohyb uživatele po scéně.
TMovingCameraController Implementuje automatický pohyb kamery po scéně z místa na místo. Zaostření na událost na mapě, atd.
TEventPlayer Přehrávač událostí. Dostane jako vstup vektor událostí typu TEventInfo* a přehraje je uživateli. Události se týkají herních dějů. Například smrt zraněné jednotky, dostavění budovy. Události jsou sbírány v průběhu hry, od konce posledního hráčova tahu do začátku následujícího. Tak se dozví, co se odehrálo důležitého.
TVisibilityScheduler Plánovač skrývání a odkrývání mapy. Spolupracuje se scénáři. Například soubojem, sebevraždou, nebo autodestrukcí. Dovoluje naplánovat zmizení chvíli potom, co scénář skončí.
Steering
TSteeringKernelController Udržuje mapu pro steering jednotek. Viz Jednotky. Mapa je reprezentována v quadtree pro vyhledávání v logaritmickém čase. Zastřešuje třídu TQuadrant. Na čase a updatování hierarchie není přímo závislý.
TSteeringController Jako potomek TAgent reprezentuje virtuálního agenta pro steering. Viz Steering.
Ostatní
TSoundInterfaceController Updatuje hlasitost u zvuků se specifikovaným zdrojem v prostoru podle vzdálenosti k asociované kameře pozorovatele(posluchače).
TUnitController Nic nedělá.
Tabulka 2.6: Seznam typů kontrolérů.
Některé z kontrolérů budou podrobněji popsaný ve zbytku dokumentace. Implementační detaily je lepší dohledat v dokumentaci zdrojového kódu.
2.13.6 Objekty na scéně
Z popisu grafu scény vyplynulo. že nejmenší jednotkou, která se samostatně vykresluje jsou modely a jejich instance. Jako model je zvlášť reprezentován každý hex na mapě. Jako model je reprezentován každý typ jednotky, budovy a terénní překážky. Jejich vsazení do scény je pak instance modelu. Instance modelu je objekt typu TModelInstance. Existuje více instancí(instancí objektu TModelInstance) jednoho modelu(instance objektu TModel). Tím se zabrání redundanci dat na úrovni vrcholů, trojúhelníků a ploch.
To jak zachytit v grafu scény, jehož listy jsou instance modelů, herní objekty podle pravidel 8 Kingdoms je řešeno pomocí objektů typu THex a TProxyObjekt. Tyto objekty jsou specifické pro scénu typu TguiMap.
Tato scéna je speciální tím, že zachycuje mapu bitevního pole 8 Kingdoms skládající se z šestiúhelníkových buněk, hexů. Jeden hex je reprezentován potomkem TModelSceneObjektu THex. TguiMap obsahuje explicitně pole ukazatelů na tyto objekty, kromě toho, že jsou někde v grafu scény. Hex obsahuje jedinou instanci modelu povrchu. Každý hex si drží ukazatel na scénu TguiMap. Hex je navíc potomkem THighlighting, která mu dává možnost zvýraznění se například po přejetí myší. Stejně tak jako v pravidlech hry, každý hex je prostorem pro výskyt ostatních herních objektů, i zde každý hex obsahuje odkazy na reprezentace herních objektů, na potomky třídy TProxyObjekt.
Potomci TProxyObjekt sdružují modely scény do množin, které reprezentují herní objekt. Proxyobjekty jsou kategorizovány typem TProxyType, který může nabývat hodnot ptStatic, ptDynamic, ptTerrain, ptBuilding. Konkrétní od TProxyObjekt odvozené typy jsou:
Každý proxyobjekt si zároveň drží ukazatel na hex, na kterém se nachází. Umisťování proxyobjektů na hexy se řeší obecnými metodami TProxyObjectu addToHex(), moveToHex(), které si každý potomek může předefinovat. Každý proxyobjekt ukazatel na scénu TguiMap. Vztahy scény a objektů na ní ukazuje diagram 11.
Vztahy scény a objektů na ní
Diagram 2.11: Vztahy scény a objektů na ní.
Následující odstavce popisují specifické vlastnosti konkrétních proxyobjektů.
2.13.6.1 Jednotky
Jednotky vojáčků jsou v GUI projektu reprezentovány třídami TUnit a TUnitMember. TUnit je potomek TProxyObject, který tvoří záštitu vojenského útvaru podle pravidel. Je s ním svázán objekt typu TUnitController, který je potomek TController a sám o sobě nemá zatím žádnou funkci. Je zde jen proto, aby zastřešoval objekty TUnitMemberController, který zase pracuje v tandemu se statickým TUnitMember(potomek TSceneObject). Doplňkovým objektem je TUnitFormation jehož potomci mají řešit speciální požadavky na rozmístění panáčku na mapě(mimo terénní překážky, do různých útvarů a řad,..). Jednotka nese zbarvení hráče, který ji vlastní.
Operace a funkce TUnit víceméně kopírují pravidla hry:
TUnitMember je objekt vojáčka ve virtuálním světě jeho metody a vlastnosti přímo souvisí s grafickým výstupem:
Pohyb jednotek probíhá po křivkách. Neoficiálně je implementován steering, což je pohyb virtuální agenta, nebo agentů ve světě překážek. Metoda je inspirovaná článkem http://www.red3d.com/cwr/steer/ .
2.13.6.2 Budovy
Budovy a jejich herní logika je reprezentována objektem TBuilding potomkem TProxyObject. Konkrétní objekty budov na scéně jsou reprezentovány TSceneBuildingObject. Doplňuje ho TFlag reprezentující geometrii vlajky, která se objevuje na budovách, když jsou v nich jednotky. Budova nese zbarvení hráče, který ji vlastní. Budovy jsou svázány s ovladačem typu TBuildingController, který se stará o spouštění scénářů, výbuch budov a vyvěšení vlajky. Budovy jsou dvou typů:
Budovy k sobě vážou doplňková data:
Akce kopírují pravidla:
2.13.6.3 Terén
Poslední přímý potomek TProxyObjectu, TTerrainEntity je předkem pro všechny terénní specializace podle pravidel 8 Kingdoms. Tyto specializace zastřešují objekty scény(stejně jako TUnit a TUnitMember). Samotné objekty scény(potomci TModelInstance) jsou reprezentovány TSceneTerreainObject a TTownObject. Konkrétní terénní specializace většinou obsahují pouze data o modelech, způsobu rozmístění po hexu, zbarvení a velikosti. Speciální výjimkou je terén TTerrainTown, který si drží barvu hráče, jež ho vlastní a umožňuje ji měnit. Terénní specializace jsou:
Data k terénním objektům jsou inicializovány pomocí statických metod každé specializace a centrálně pak pomocí statických metod TTerrainEntity.
2.13.6.4 Povrch
Herní plán je složen z šestiúhelníků (hexů). Každý hex je určen typem terénu a svou nadmořskou výškou. Různé terény zvýrazňujeme jednak terénními překážkami (stromy, kameny, ...) a jednak různými texturami povrchu. Nadmořská výška je znázorněna vymodelováním povrchu hexů. Kvůli iluzi plynulého přechodu mezi nadmořskými výškami je každý hex rozdělen na několik trojúhelníků.
Dělení hexu
Obrázek 2.5: Dělení hexu.
Souřadnice x a y každého vrcholu v této síti trojúhelníků je určená. Z-ovou souřadnici dopočítáváme metodou spline-ploch. Jeden plát kubické spline plochy je určen šestnácti řídícími body. Za řídící body jsme použili středy hexů. Jejich nadmořská výška je známá.
Spline plochy
Obrázek 2.6: Řídící body spline ploch pro modelování hexů.
Nezávisle na nadmořské výšce je pak do povrchu případné vymodelované koryto řeky.
Každý hex je samostatným objektem scény. Jinou možností je, považovat celý povrch za jeden objekt. Rozdělení povrchu je výhodné pro renderování - každý hex se buď nakreslí nebo nenakreslí podle toho, zda zasahuje do výhledu kamery. Také operace jako změna barvy hexu se provádí snáze. Na druhou stranu po rozdělení povrchu nelze implementovat LOD (různé úrovně detailů modelu v závislosti na vzdálenosti od pozorovatele) - hexy by na sebe nenavazovaly přesně. Implemetace LODu povrchu by zřejmě vedla k výraznému zrychlení renderování scény.
2.13.7 Kamera
Pro renderování je kromě samotné scény potřeba popsat také pozorovatele. Ten je dán parametry tzv. kamery. Jsou to: pozice, směrový vektor pohledu, up-vektor (z-ová souřadnice), zorný úhel a dosah viditelnosti (dál už je jen mlha).
Kamera v našem případě není součást obecné scény, ale je to jedna z položek TguiMap. Používáme jednu kameru, která zabírá vždy výřez scény. Lze s ní pohybovat, natáčet a zoomovat. Možným rozšířením by bylo přidat ještě další kameru a přidat možnost přepínání pohledu z kamery a pohledu jednotky.
2.13.8 Hierarchická animace
Velkým přínosem architektury kontrolérů je podstatné zjednodušení implementace hierarchické animace. Hierarchická animace je animace hierarchického objektu prováděná časovou změnou lokálních transformací v uzlech. Tento princip demonstruje obrázek 7.
Hierarchická animace
Obrázek 2.7: Hierarchická animace.
Jak už bylo zmíněno v kapitole o importu modelů, data popisující změnu lokální transformace jsou vytvářena v 3D modelovacím nástroji. Do programu jsou importována společně s geometrií modelů.
Jak tato data vypadají? Jsou to dlouhé řady čísel a vektorů říkajících, jaké hodnoty mají mít lokální transformace popisující vektory v jistém čase od začátku animace. Tyto data se drží v strukturách uvnitř TModel. Aby se v těchto datech dalo lépe vyhledávat a byla všechna v jednoduché struktuře, vytvářejí se z nich animační tabulky. Pro každou animaci, např. zachycenou v jednom ASE souboru, je jedna tabulka. Animační tabulky se vytvářejí při nahrávaní animací metodou TModel->loadAnimation(). Ta volá po nahrání struktur TModel z ASE souboru funkci CreateTableForModel, která vytvoří tabulku a uloží ji do cache tabulek animací. Tato cache a funkce pro práci s ní jsou implementovány v modulu Animation.cpp.
Jak se data v těchto tabulkách aplikují na instance modelů? Právě díky kontrolérům TTRSController, TNodeAnimationController a TModelAnimationController. TTRSController si drží ukazatele na vektory popisující lokální transformace. TNodeAnimationController ho kombinuje s ukazatelem na tabulku a do příslušných políček tabulky. V TNodeAnimationController vzniká vazba mezi konkrétní lokální transformací (veličinou) a tabulkou pro animaci instance modelu(daty). TModelAnimationController pak zastřešuje všechny kontroléry, které pracují s jednou tabulkou, tzn. jednou animací. Tyto vztahy také popisuje diagram 12.
Struktury a kontroléry pro hierarchickou animaci
Diagram 2.12: Struktury a kontroléry pro hierarchickou animaci.
Tento model hierarchické animace je naprosto základní. Další možná pokročilá rozšíření jako jsou Animation blending, nebo Skeletální animace se dají doimplementovat.
2.13.9 Efekty
Dnešní hry se neobejdou bez nesmírného množství detailní vizuálních efektů, jako jsou oheň, kouř, déšť, stíny. Dočasné objekty pohybující se scénou, například kameny, nebo šípy. A mnoho jiných doplňků, které jsou nad rámec základní funkčnosti objektů na scéně.
8 Kingdoms implementuje tuto skupinu objektů v modulech Effects.cpp/h. Efekty jsou vlastně speciální kontroléry, protože jsou skoro vždy závislé na čase a v čase mění svůj stav definující vzhled. Jde tedy o malé, procedurálně generované animace. Hlavní třídy zde jsou TEffectEngine, TParticles a TBillBoard.
TEffectEngine je zastřešující třída, pro kterou existuje v programu jediná instance. Zatím se neuvažuje o tom, že by najednou bylo víc scén, které by potřebovaly svůj vlastní EffectEngine. Tento objekt se stará o hromadnou žádost o nahrání příslušných zdrojů pro všechny efekty, které to potřebují. Drží některé atributy společné pro všechny efekty. Jedním takovým je například iPRP, což je perioda obnovení pro částicové systémy. Efekty se jeho prostřednictvím umisťují a odstraňují ze scény a ze systému kontrolérů přes atributy scene a ctrlmng.
Velkou skupinu efektů tvoří tzv. částicové systémy. Pomocí nich lze namodelovat chování složitějších animací jako je oheň, kouř, výbuch a jiné. Částicové systémy fungují tak, že vytvoří, většinou náhodně, velké množství vrcholů v prostoru a pravidelně mění jejich polohu, rychlost, velikost a jiné parametry podle zadání. To se děje v update fázi kontrolérů. Proto jsou částicové systémy potomky TController. Aby se daly částicové systémy renderovat, musí být zároveň potomky TSceneObject. Při fázi vykreslování je možné na jejich pozici umísti body, trojúhelníky, nebo celé plochy, potažené texturou. Všechny částicové systémy jsou potomky třídy TParticles, která obsahuje základní atributy jako pointery na 3D souřadnice pro ukládání pozice a směru pohybu částic.
Příklad částicových systémů
Obrázek 2.8: Příklad částicových systémů.
Dalším užitečným efektem jsou billboardy. Princip billboardů je následující. Struktura některých objektů je příliš složitá na to jak jsou tyto objektu pro pozorovatele nezajímavé, proto stačí ukazovat jen jejich 2D obrázek a pří pohybu scénou ho natáčet čelem k uživateli. Tato technika byla v jistých dobách jediným způsobem jak uživateli rychle zobrazit nějaký objekt na scéně.
Příklad billboardů
Obrázek 2.9: Příklad billboardů.
Billboardy nejsou závislé na čase, nejsou proto potomky TController. Musí však obsahovat odkaz na kameru, aby se při vykreslení mohly zobrazit pokaždé správně natočené k pozorovateli. Billboardy jsou často kombinovány s částicovými systémy. Všechny billboardy jsou potomky TBillBoard.
Posledním zmiňovaným efektem je TSimpleShadow. Ten umožňuje, pokud je předkem nějakého modelového objektu, zobrazit kolem tohoto objektu jednoduchý stín, který dává lepší dojem ze scény. Je nutné po vykreslování scény volat statické metody TSimpleShadow, aby proběhly další běhy víceprůchodového algoritmu na vykreslení stínů. Ty pracují se stencillbufferem kam si ukládají příznaky o příjemci stínu, tak aby se stín vykreslil pouze na něj i při vypnutém z-bufferu. Tento postup je naznačen na obrázku 4.
Princip vykreslování stínů
Obrázek 2.10: Princip vykreslování stínů.
Třída efektuPopis
TEffectEngine Správce pro všechny efekty. Obsahuje některá společná data pro souhru efektů na jedné scéně. Počítá jen s jednou instancí scény.
TParticles Předek pro všechny částicové efekty. Obsahuje základní datové struktury a rozhraní pro implementované metody, jako přidávání částic(addPoint()), inicializace( setUp()) a základní vykreslení částic jako bodů(drawObject())
TSimpleSpray Jednoduchý efekt určený pro testování. Vystřikuje částice ve výseči v náhodném směru, náhodnou rychlostí.
TAtmosphericEffect Předek pro atmosférické efekty jako je déšť nebo sníh. Stará se o efektivní využívání množiny částic, aby to budilo dojem, že částice jsou na celé scéně. Přitom jsou jen v okolí kamery. Velikost toho to okolí se ovlivňuje konstantou AE_CELL_SIZE.
TSnow Sníh. Částice se mihotavě pohybují zhruba ve směru gravitace. Velikost, barva a hustota částic se dá řídit konstantami SNOWFLAKE_SIZ, SNOWFLAKE_COL a SNOWFLAKE_DENSITY .
TRain Efekt deště. Dešťové kapky padají zhruba nakloněny ve směru vanoucího větru. Délka, velikost, barva a hustota se dá řídit konstantami RAINDROPS_LEN, RAINDROPS_SIZ, RAINDROPS_COL a RAINDROPS_DENSITY . Občas se i zableskne. Blesk využívá světelný zdroj 1. Interval, délka blesku a odstup zahřmění se dá ovlivnit LIGHTING_INTERVAL_MIN, LIGHTING_INTERVAL_MAX, LIGHTING_INTERVAL_LEN, THUNDER_TIMING .
TWeatherController Umožňuje plynulé přepínání počasí mezi zimou (wsWinter, sníh), létem ( wsSummer) a podzimem (wsAutumn, déšť) s postupnou změnou barvy pozadí
TBloodSplash Implementuje výstřik krve v průběhu soubojů. Krev vystříkne ve výseči v požadovaném směru, po oblouku dopadne na definovanou nulovou úroveň a po chvilce zmizí a automaticky se odstraní ze scény.
TExplosion Výbuch je podobný výstřiku krve, liší se jen velikostí a barvou částic a v budoucnu možná ještě něčím. Používá se při útoku na budovu.
TBillBoard Předek pro všechny billboardy. Obsahuje ukazatel na kameru.
TSmoke Kouřový efekt. Je kombinací částic i billboardů. Každá částice je čtyřúhelník potažený poloprůhlednou texturou. Tvoří tak dohromady obláček dýmu. Pomalu stoupa k nebi. Dá se spustit v nekonečně dýmající smyčce, nebo aby dýmal jen chvíli a pak se sám odstranil ze scény.
TSimpleShadow Staticky implementuje ad-hoc algoritmus pro vykreslení jednoduchých stínů. Objekt sám slouží jako předek pro vrhače stínů. Je to tříkrokový algoritmus:
  • Vykreslení scény z pohledu kamery. Zachycení všech objektů co vrhají stíny. Do stencil bufferu se zapíše 0 tam kde je vrhač a 1 tam kde je příjemce stínu.
  • Vykreslení stínu na místa označená 1 jen do z-bufferu s drobnou modifikací. Posunutí směrem k pozorovateli o magickou konstantu (SIMPLESHADOW_MAGIC_CONSTANT) a inkrementuje stencil buffer tam kde projde z-test
  • Vykreslí stíny při normální projekci tam kde je stencil buffer dost velký
Tato metoda nemá s moderními způsoby realtime stínů nic společného. Není přesná a vykazuje chyby např. velké objekty stojící v kopci mají stínovací polygon hodně pod povrchem, odkud ho magická konstanta nedokáže vytáhnout.
TDistantAttack Implementuje střelbu při střeleckém útoku, nebo obraně. Místo částice se používá model projektilu (šíp, balvan). První verze. Zastaralé.
TDistant_Attack Druhá verze střeleckých útoků. Předek pro všechny. Obsahuje hlavně datové položky: souřadnice zdroje a cíle, zvukový efekt a doba trvání
TProjectileAttack Střela reprezentovaná 3D modelem. Dědí navíc TModelSceneObject. Obsahuje update pro let hmotného bodu.
TArrowAttack Šípový útok. Pouze využívá speciální typ modelu.
TCatapultAttack Útok katapultu kombinující částicový efekt stopy za letící střelou s 3D modelem projektilu. Některé parametry se dají nastavit konstantami: CATAPULT_TRACE_DENSITY, CATAPULT_TRACE_PARTICLE_SIZE, CATAPULT_TRACE_PARTICLE_COLOR. Ostatní jsou přímo v kódu.
TMagicMissileAttack Většina stejná jako u střely z katapultu jen optimalizováno pro magickou střelu. Přidána tlakové vlna po dopadu. Parametry střely lze modifikovat podobně jako u střely z katapultu(konstanty začínající MAGIC_)
TDestruction Stylizovaná exploze typu ohnivá koule, tlakové vlna a vyletující částice doprovázena ohlušujícím zvukovým efektem.
TFlyAway Vyhazuje do vzduchu roztočený objekt a automaticky ho po nějaké době zničí a odstraní ze scény.
TSparkle Jiskření. Slouží hlavně k zdůraznění událostí na mapě, které nelze jiným rozumným a viditelným způsobem zobrazit. Změna hodnoty, vlastnosti, atd.
Tabulka 2.7: Seznam efektů.
2.13.10 Scénáře
Scénáře, nebo také skripty, jsou vedle kontrolérů další důležitou částí, která vnáší život do statických scén. Řeší se pomocí nich organizačně složité úlohy jako souboje a pohyb skupin modelů a jakékoliv složitější chování, které zahrnuje více elementů scény.
Scénáře často fungují jako stavové stroje. Předek pro všechny scénáře je třída TSceneScript. Každý scénář musí implementovat metodu decision, která funguje jako přechodová funkce. Stav pak je určen z parametru této funkce a dat, která si konkrétní skript u sebe ukládá. Scénáře nejsou kontroléry, protože nejsou přímo závislé na čase, ale na stavu. Bývají z kontroléru spouštěny, resp. jejich přechod bývá volán z kontorolerů, který tak činí v závislosti na čase. Nejčastěji to bývá kontrolér typu plánovač(TSheduller). Skript má tak možnost naplánovat si vlastní spuštění daným objektem. Princip volání scénářů osvětluje diagram 13.
Scháma spouštění a práci scénářů
Diagram 2.13: Schéma spouštění a práci scénářů.
Jak bylo naznačeno, scénářů je mnoho, pro každý úkol je speciální. Do budoucna se nabízí možnost implementovat scénáře pomocí skriptovacího jazyka. Prozatím jsou scénáře napsány v C++. Jejich úplný seznam s popisem zachycuje tabulka 8. Detaily o fungování přechodové funkce jsou nejlépe vidět ze zdrojového kódu.
ScénářPopis
TSceneScript Předek pro všechny scénáře. Definuje povinné rozhraní pro scénáře, metody decision a getStatusMsg a pamatuje si scénu.
TCombat Předek pro všechny soubojové scénáře. Souboje jsou komplikované tím, kolik možných kombinací existuje. Aby se snáze psali stavové přechody. Byly rozděleny do více tříd. Vstupem pro tento stavový stroj je TSceneObject ke kterému je jednoznačně navázán TCombatant, datová struktura, která si udržuje další speciální informace. TCombat> obsahuje datové položky pro účastníky i výsledky boje a některé metody pro vyhledávaní cílů, nebo například leavescript pro opuštění scénáře
TCloseCombat Implementace boje tváři v tvář.
TDistantCombat Implementace útoku střelbou.
TOpenBuildingCombat Implementace boje v otevřené budově.
TBuildingAttack Implementace útoku na budovu.
TBlastAttack Implementace sebevražedného destrukčního útoku.
TMarchMove Pohyb jednotek z hexu na hex, z budovy, do budovy.
TBuildBuilding Stavba budov.
TReleaseUnit Sebevražda jednotky a odstranění ze scény.
TDemolishBuilding Autodestrukce budovy a odstranění ze scény.
TRepairBuilding Oprava budovy.
TUnitDeserted Dezertující jednotka.
TUnitMemberDied Umře zraněná a neléčená část mužstva.
Tabulka 2.8: Seznam scénářů.
Na závěr je uveden, jako příklad, vývojový diagram analýzy a přechodů stavu nejjednoduššího soubojového scénáře TCloseCombat.
Scháma spouštění a práci scénářů
Diagram 2.14: Analýza stavu a přechody objektu podle scénáře TCloseCombat.
2.14 Zvuk
Zvukový doprovod je součástí většiny her. Jeho implementace je poměrně jednoduchá, na druhou stranu získat, nebo vytvořit zvuková data je úkol obtížnější. Samotné přehrávání zvuku zprostředkovává multiplatformní knihovna SDL. A přehrávání konkrétních zvukových formátů a některé technické detaily obstarává doplňková knihovna SDL_mixer. Zvuková podpora se dělí na přehrávání hudby(music tracks) z jednoho zdroje, která běží neustále na pozadí a přehrávání zvuků(sound samples), které jsou kratší a v jednu chvíli jich může běžet víc najednou. O mixování hudby a zvuků do jednoho datového toku a posílání na zvukové rozhraní SDL se stará právě knihovna SDL_mixer.
V 8 Kingdoms jsou volání SDL_mixeru obalena metodami třídy TSoundInterface případně TSoundInterfaceController. TSoundInterface má pouze jednu instanci, která se inicializuje při inicializaci uživatelského rozhraní. Implementuje metody pro přehrávání zvuků a hudby získané ze Správce zdrojů(Resource Manager). Zároveň si udržuje vlastní přehled o přehrávaných zvucích, které obsazují volné sloty tzv. kanály(channels). Nakonec umožňuje oddělenou regulaci hlasitosti hudby a všech zvuků.
Oživení do prostého přehrávání zvuků v 3D scéně přináší TSoundInterfaceController. Umožňuje právě tu vlastnost prostoru, tlumit zvuky ze vzdálenějších zdrojů. Ve scéně je potřeba pouze jedna jeho instance, kterou stačí vytvořit na začátku scény a asociovat s kamerou na scéně. Tento kontrolér pak při updatu mění hlasitost všech kanálů přehrávajících zvuk, která má specifikovánu polohu zdroje v prostoru podle vzdálenosti tohoto zdroje od kamery. Tato jednoduchá úprava dává aspoň základní pocit trojrozměrného zvuku. Dalším rozšířením by byla regulace hlasitosti zvuku na různých částech reprosoustavy.
Schéma zvukového subsystému
Diagram 2.15: Schéma zvukového subsystému.
Pro přehrávání zvuků slouží metoda playSample(int rid) a pro přehrávání zvuku se specifikovaným zdrojem v prostoru se používá playSampleSpatialConrolled. Zvuk se přehraje okamžitě pokud existuje neobsazený kanál. Počet kanálů, současně přehratelných zvuků, je definován makrem DEFAULT_CHANNELS_COUNT, jehož hodnota se může pohybovat od 8 do 64. Při větším počtu kanálů dochází k většímu zatížení CPU a výsledek není o mnoho realističtější.
Hudba se přehrává metodami playMusic, stopMusic. Vždycky se přehrává maximálně jedna skladba. Dokonce v jednu chvíli smí být v paměti nahrána pouze jedna. To je omezení SDL_mixeru. To je celkem logické, protože čistá data skladeb zabírají mnoho místa. SDL_mixer je online streamuje, dekóduje a přehrává ze souboru na disku. Má smysl pracovat s jedním otevřeným souborem.
2.15 Příkazový řádek
Příkazový řádek patří mezi uživatelské rozhraní, proto je nutné ho zde alespoň krátce zmínit. Zpracování parametrů programu z příkazového řádku probíhá na samotném začátku programu ve funkci main. Podle některých z parametrů se totiž rozhoduje co se bude dál dít s grafickým rozhraním. O zpracování se stará jediná instance objektu TCommandLine. Atributy tohoto objektu jsou všechny možné údaje, které lze z parametrů příkazového řádku získat. Tato jediná instance TCommandLine je se vytváří voláním statické metody parseCommandLine(argv, argc) a ukazatel na ni se drží v statické proměnné cl. Díky tomu je dostupná z každého místa programu od začátku jeho běhu. Jaké chování lze parametry ovlivnit ukazuje tabulka 9.
VlastnostParametrPopis
iRunMap a iMapType==0 -m <n> Spustí mapu číslo n. Má smysl jen pro hru jednoho hráče a pro síťovou hru v roli serveru.
iRunMap a iMapType==1 -l <n> Nahraje mapu číslo n ze savegame/. Má smysl jen pro hru jednoho hráče a pro síťovou hru v roli serveru.
iListMaps -e Vylistuje mapy na standardní výstup.
iPrintHelp -h Vytiskne stručný popis parametrů.
iNoSound - Není implementováno.
iNetworkHost, iTimeOut -s -t <ms> Spustí hru jako Server. Je nutné použít parametr -m pro nastavení mapy. Program pak čeká ms milisekund, dokud se nepřipojí nějací klienti a spustí mapu se zbytek počítačových hráčů.
iNetworkClient, szConnectTo -c <adr> Připojí se na hru na adrese adresa adr jako klient.
iRunAsDemon -d Zatím není implementováno. Nefunguje
iDevelopersConsole -x Dovolí během hry otvírat vývojovou konzoli pro spouštění speciálních příkazů.
iUseProfileName, szProfileName -p <jméno> Spustí hru v profilu jméno. Pokud se použije některý ze zrychlených spuštění hry, a nepoužije tento parametr, spustí se hra v posledním profilu.
Tabulka 2.9: Parametry příkazového řádku.
Výstup je standardně prováděn přes funkci CONSOLE() , která je implementována v závislosti na OS. V Linuxu vypisuje na standardní výstup a ve WIN32 prostřednictvím volání MessageBox(). Některá strukturované vypisy nejsou ve WIN32 dostupné. CONSOLE() se používá i pro výstup chyb během chodu programu. K těm by ovšem nemělo docházet.