Optimalizácia vykresľovanie v 2D hrách
Dneska sa pokúsim popísať niekoľko možných spôsobov, ako optimalizovať vykresľovanie v 2D hrách.
Načrtnutie príkladové situácia
Predstavte si jednoduchú strategickú hru, ktorá má vykresľovať nejakú mapu. Pre jednoduchosť sa zatiaľ bude kresliť iba terén. Veľkosť políčka mapy bude treba 64 x 64 pixelov, veľkosť mapy treba 256 x 256 políčok.
V hre je pole textúr s názvom Textúry a v ňom načítaných niekoľko textúr pre rôzne terény, zatiaľ postačujú potrebné textúry pre trávu a vodu. Ďalej je v hre trieda políčka, ktorá má vlastnosť Teren, ktorá určuje, či sa na tom mieste vykreslí tráva alebo voda. Pole políčok (poličky) s názvom Mapa, do ktorého sú náhodne vygenerované jednotky a nuly (tráva alebo voda). Dve číselné premenné, ktoré nám hovoria, o koľko je kamera (pohľad) posunutá doprava / dole. Ak začnete bez rozmýšľania písať vykresľovanie mapy, najskôr skončíte s kódom podobným tomuto:
for (int y=0; y<256; y++) for (int x=0; x<256; x++) { drawTexture(x*64-PosunMapyX, y*64-PosunMapyY, Textury[Mapa[x,y].Teren]); // vykreslí políčko mapy, bere v potaz posun mapy (kamery) }
Ak sa nepoužíva vykresľovací engine, ktorý by sám vykonával test, či textúra bude vidieť na obrazovke, tak s najväčšou pravdepodobnosťou sa v tejto chvíli fps nedostane ani cez 100 (väčšina kresliacich enginov ho nepoužíva).
To je na primitívne 2D hru celkom málo. Problém je samozrejme v tom, že sa kreslí 65536 (256x256) políčok, čo už je celkom vysoké číslo. A hlavný problém je v tom, že veľa z nich ani nie je vidieť, takže sa kreslí zbytočne.
Optimalizácia prvý - Nekreslit mimo obrazovku
Optimalizovať tento kód je zatiaľ celkom triviálne, riešením je vykresľovať len políčka, ktoré majú šancu byť vykreslené, takže po chvíli premýšľania skončíte treba s niečím takým: (v premenných Screen.Width a Screen.Height je rozlíšenie obrazovky):
int minx=(posunMapyX/64); // X index nejlevějšího políčka, záleží jen na tom, // jak je posunutá mapa int maxx=minx+(Screen.Width/64)+1; // X index nejpravějšího políčka je index // nejlevějšího políčka + maximální počet // políček vedle sebe (což záleží pouze na rozlišení) int miny=(posunMapyY/64); int maxy=miny+(Screen.Height/64)+1; // osetreni indexu, aby se necetlo mimo mapu if (minx<0) minx=0; if (miny<0) miny=0; if (maxx>=256) minx=255; if (maxy>=256) miny=255; for (int y=miny; y<maxy; y++) for (int x=minx; x<maxx; x++) { drawTexture(x*64-PosunMapyX, y*64-PosunMapyY, Textury[Mapa[x,y].Teren]); // vykreslí políčko mapy, bere v potaz posun mapy (kamery) }
Teraz už sa vykresľujú len políčka, čo sú vidieť na obrazovke, fps už použiteľné, ale stále to môže byť lepšie.
Optimalizácia druhá - Textúry
Optimalizáciou, ktorá vás (ak netušíte, ako vo vnútri funguje vykresľovanie cez grafickú kartu) najskôr vôbec nenapadne je zníženie počtu prepínanie textúr pri kreslení - ak kreslíte stále rovnakú textúru, fps sú vysoko, ak sa striedajú aj napríklad len dve textúry, fps klesajú dosť nízko , čo je spôsobené réžiou prepnutie textúry na grafickej karte. Ak sa textúry striedajú napríklad po každom druhom vykreslenie, tak je to značne neefektívne.
Varianta 1 - tadiaľ radšej nie
Riešenie je niekoľko, na prvý pohľad najjednoduchšie je asi prejsť celý cyklus viackrát - pri prvom prechode kresliť len vodu, pri ďalšom trávu a pod., Ale ak by počet textúr rástol, tak by to bolo pomalšie a pomalšie, až by to bolo pomalšie, než bez tejto optimalizácie.
Variant 2 - lepšia
Druhým riešením by bolo pri priechode mapu poľa hneď nekreslit, ale hádzať ich do nejakého zoradeného poľa / zoznamu a vykresliť potom ten - už zoradený podľa textúr, takže by sa réžie znížila. Napr. XNA niečo podobné ponúka - nastavením SpriteSortMode.Texture v metóde Begin u SpriteBatch sa toto vykonáva interne, ale výsledok je stále pomalší než vykresľovanie iba jednej textúry.
Varianta 3 - ideálny variant
Najoptimálnejším riešením je namiesto niekoľkých textúr použiť jednu veľkú, v ktorej sú všetky možné terény a typ terénu len ovplyvňuje, z ktorej časti tej textúry sa má čítať. Tento spôsob vykreslenia je rovnako rýchly, ako keď sa kreslí stále len jeden typ terénu.
Všetko?
Možno to vyzerá, že tým možnosti optimalizácie vykresľovanie končí, ale to je omyl, existuje ešte minimálne jedna veľká optimalizácia vykresľovanie, napríklad v mojej stratégii dokáže zvýšiť fps až na trojnásobok (!).
Táto optimalizácia stavia na tom, že terén sa neanimuje a pohľadom hráč posúva len občas a vykreslenie jednej veľkej textúry je oveľa rýchlejší, než vykreslenie hŕbu malých textúr.
Túto metódu som si pre seba nazval "cachovanie terénu" a funguje tak, že si vytvorím pomocnú textúru veľkú ako obrazovka a terén vykresľujú len do nej a to len ak hráč pohol od minulého snímke výrezom mapy. Táto pomocná textúra sa medzi jednotlivými snímkami nemaže, takže jej obsah zostáva. Pri kreslení celej scény potom kreslím len tú pomocnú textúru a potom samostatne ešte objekty, tie už priamo do scény.
Kreslenie celej scény - terén aj objekty sa potom rieši teda nejako takto:
if (pohnuloSeKamerouOdPOslednihoSnimku)
{
VykreslitTerenDoPomocneTextury();
}
VykresliPomocnouTexturu();
VykresliZbyleObjekty();
Táto metóda sa dá samozrejme ešte byť upravená - napríklad do pomocnej textúry vykresliť väčšiu časť terénu, než je veľkosť obrazovky a potom stačí pomocnú textúru prekresliť až keď sa pohľad mapy posunie o viac, než o jeden pixel, ale zase zakaždým prekreslíte väčšiu časť mapy, ako je nutné.
Zhrnutie
- Vykreslujte len textúry, ktoré majú šancu sa vyskytnúť na obrazovke.
- Snažte sa minimalizovať striedanie vykreslovaných textúr, ideálne všetky obrázky napchajte do jednej textúry a kreslite to z nej.
- Vykreslenie jednej veľkej plochy je rýchlejší, než vykreslenie hŕbu malých. Ak sa nejaká vrstva / časť obrazovky nemení príliš často, tak ju cachujte - vykreslite si ju do pomocnej textúry a miesto hŕbu volania kreslenie malých plôch vykreslujte jednu veľkú.