.[ ČeskéHry.cz ].
Návrhový vzor pro třídu hlavního okna hry
Jdi na stránku 1, 2  Další
 
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> C / C++
Zobrazit předchozí téma :: Zobrazit následující téma  
Autor Zpráva
TeaTime



Založen: 17. 06. 2011
Příspěvky: 264

PříspěvekZaslal: 18. listopad 2013, 23:23:16    Předmět: Návrhový vzor pro třídu hlavního okna hry Odpovědět s citátem

Ahoj, potřeboval bych poradit v oblasti softwarového inženýrství a návrhu aplikací a tak. Vždy když chci udělat do hry nějakou funkčnost (grafický engine, obsluha gui knihovny, správa oken etc.), tak se vždy zamyslím, jak bych to měl co nejlépe navrhnout, aby to bylo co nejjednodušší, aby to splňovalo moje požadavky i požadavky, které mohou časem přijít a aby to bylo snadné na používání a pochopení. Prostě aby to bylo dobře - aby to mělo kvalitní vnitřní návrh a API a jasně definovaný účel. Mám na mysli návrh rozhraní tříd a způsoby komunikace mezi třídami a tak. Prostě klasické problémy softwarového inženýrství.

Snad jsem vám teď trochu přiblížil, čeho se bude týkat můj dotaz.

Právě navrhuji jeden 'systém' nebo spíše rozhraní a potřeboval bych poradit, jak to děláte vy a na co bych si měl dávat pozor - co bych neměl podcenit.

Vezmu to zpříma - v aplikaci chci mít třídu starající se o herní menu a druhou třídu starající se o právě načtený level (plus tam bude možná ještě druhá instance té třídy s přednačteným následujícím levelem). To je takový základ, dále tam budou takové menší třídy jako třída starající se o konzoli (na zadávání textových příkazů). Jde mi to to, jak plánovat, která z těchto tříd se má vykreslovat a která má přijímat vstupy (klávesnice, myš). Jasně, tohle by šlo udělat hodně jednoduše - prostě mít ve třídě hlavního okna příznak 'bool menuActive', který by říkal, jestli je menu zapnuté a podle toho by se vstup a výstup posílal buď do menu nebo do levelu. Ale říkal jsem si, že kdybych to udělal trochu robustněji, tak by mi to mohlo ulehčit další práci.

Představoval jsem si to tak, že bych měl třídu hlavního okna (Window) a pak třídu modulů okna (WindowModule). Moduly by se jednak mohly vykreslovat do okna, jednak by mohly zpracovávat vstup od uživatele a jednak by mohly poskytovat nějaké služby sobě navzájem. Například by tam byl modul, který by sloužil jako mezivrstva pro vstupy z klávesnice mezi přímým vstupem z klávesnice a WindowModulu levelu. Takže by se v menu šly předělávat key-bindings pro hru. Moduly by mohly mít třeba danou hloubku a takže by vstup dostával jen ten nejvíc na vrchu (pokud není nastaven jako skrytý). Vykreslovány by byly také v pořadí podle hloubky. Otázka pak je, jestli by se mi pak třeba nehodilo, aby více modulů přijímalo vstup najednou. Také je otázka, jak to pak vyřešit s tím, když chci, aby nějaký zobrazený modul neřešil vstup, ale bral si ho už 'předkousaný' od jiného modulu (a jestli je to vůbec potřeba).

No prostě by mě zajímalo, jestli tohle řešíte nějak sofistikovaněji nebo spíše jednodušeji a jaké nástrahy by mě tam mohly potkat. Už o tom přemýšlím asi tři dny a mám pocit, že potřebuji navést, jakým směrem se mám dál ubírat.

Díky.
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
]semo[



Založen: 29. 07. 2007
Příspěvky: 1481
Bydliště: Telč

PříspěvekZaslal: 19. listopad 2013, 11:28:38    Předmět: Odpovědět s citátem

ad bool menuActive
Zatim nejlepší co používám je obyčejný stavový automat. V nejjednodušším případě (který ale naprosto dostačuje) jde o obyčejný velký switch, který ošetřuje stavy hry (enum: Init, Preloading, LoadingMenu, Menu, Game, InGameMenu, ...). Na papír si nakresli, který stav může vést do kterého stavu a přepntí implementuj v nějaké funkci "SetGameState". Když vydržíš psát to čistě a nebudeš si tam dělat hacky, tak uvidíš, že to je uplně super.

Velké plus je, že nejsi omezen nějakou hloubkou zobrazených modulů a podobně. Každý stav se může kreslit i přijímat input uplně jinak (například ve stavu InGameMenu chceš kreslit jak hru na pozadí, tak menu, ale input je jen pro menu...). Zároveň v SetGameState máš jasně definovaný místo, kam lze napsat přechody mezi stavama (např. pokud byl stav Game a chceš InGameMenu, tak Game.Pause()).

Je to prostě čisté. A přidání fadeout mezi menu a hrou je hračka :-)

ad Window-WindowModule
Pokud přistoupíš na to, co jsem napsal, tak patrně zjistíš, že tě abstraktní WindowModule zbytečně omezuje. Nebo jinak: u toho stavového automatu to už nedává smysl. Samozřejmě můžeš odvodit více subsystémů od jednoho předka, ale neměla by to být vyžadovaná nutnost.

ad Input
Prostě klasicky zapouzdřit a podle aktuálního stavu zpracovavávat. Ve hře třeba:
Game.ProcessInput(InputManager)
v InGameMenu
GameMenu.ProcessInput(InputManager)
....


Celkově radím, neomezovat se nějakým zkonstnatělým návrhem, který by se snažil všechno abstrahovat. Naopak, na takto vysoké úrovni (řízení běhu hry), je lepší být velice konkrétní :-). Možná ti bude připadat, že je to o pár řádků víc, ale nakonec se to vyplatí a kód ještě ušetříš.
_________________
Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
TeaTime



Založen: 17. 06. 2011
Příspěvky: 264

PříspěvekZaslal: 19. listopad 2013, 13:51:02    Předmět: Odpovědět s citátem

Díky, ještě si to musím nechat projít hlavou. Už mám za sebou fázi, kdy jsem se snažil vše naprosto dokonale abstrahovat. Teď se snažím brát to prakticky - vždy se zamyslet, jaká abstrakce se ještě vyplatí a jaká už je nesmysl.

Takže pokud bych se snažil o nějaké WindowModuly, tak by se to muselo pojmout dost konkrétně.

Jak by se u toho stavového automatu třeba řešilo zobrazení skóre hry (to co se běžně zobrazuje pokud držíš TAB v multiplayerových hrách)?

Jak jsem si přečetl, co píšeš, tak mě ještě něco napadlo: WindowModule by byl interface, který má metodu ProcessInput a metodu Render. Třída Window by měla tu metodu SetGameState, ale ta by neměla jako argument enum, ale seznam modulů, které budou přijímat vstup a seznam modulů, které budou vykreslovat (včetně hloubky). Případně by se tam přidaly ještě nějaké jemnější parametry (ale zas nevím, jestli by něco zmohly). Tahle metoda by se volala z těch metod Game.Pause() a tak, takže z vnějšku by už člověk nemusel řešit, že se nejedná o enum. Výhodou by bylo, že by se snáze přidávaly další stavy a nemusely by tam být switche.

Taky ještě přemýšlím nad tím, jestli je rozumné, aby mohly dvě tyhle třídy (á la WindowModuly) přijímat vstup najednou. Nebo jestli to prostě udělat tak, že když nějaké dvě části budou chtít přijímat vstup najednou, tak je budu muset prostě sloučit do jedné.

Díky.
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
]semo[



Založen: 29. 07. 2007
Příspěvky: 1481
Bydliště: Telč

PříspěvekZaslal: 19. listopad 2013, 14:34:06    Předmět: Odpovědět s citátem

TeaTime napsal:
Třída Window by měla tu metodu SetGameState, ale ta by neměla jako argument enum, ale seznam modulů, které budou přijímat vstup a seznam modulů, které budou vykreslovat (včetně hloubky).


Tim si to akorát zkomplikuješ. Existujou herní subsystémy, který vůbec nemají render ani input. SetGameState má mít pouze jeden parametr a tím je ID stavu.

citace:
Už mám za sebou fázi, kdy jsem se snažil vše naprosto dokonale abstrahovat.

Cesta do pekel. Člověk při abstrakci myslí většinou jen na ten jeden konkrétní případ (no dobře, několik případů) a když za čas přijdou nové požadavky, hádej co selhá? Abstrakce. Bude to uplně jinak a nepude to tam napasovat. Takže abstarkce sice jo, ale opatrně. To co sem popisoval je spíš kompozice více objektů, což je flexibilnější vzor.


Jinak, tomu, co si psal, se asi nejvíc podobá tohle: http://gamedevgeek.com/tutorials/managing-game-states-in-c/ . Ale je to něco jiného, než sem měl namysli. Vlastně mi to přjde v něčem dost blbý (třeba kvůli těm singletonům, u kterých píše "good idea" :) ). No ale třeba se ti to bude líbit. Lepší odkaz se mi nedaří najít.
_________________
Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Poky



Založen: 29. 06. 2009
Příspěvky: 182
Bydliště: Písek / Plzeň

PříspěvekZaslal: 19. listopad 2013, 16:56:02    Předmět: Odpovědět s citátem

Když už se to tady řeší, musím se taky zeptat na jeden návrhový vzor.

Mějme něco takového:

Abstraktní třída (dejme tomu že AbstractGameState) s metodou
kód:

int do(void) = 0;


Pak v nějaké hlavní třídě Game bude vektor pointerů na tuto abstraktní třídu (gameStatesPtr) a ukazatel(uint) gameStateId, ukazující ve kterém prvku toho vektoru se právě nacházím.

Pak bych měl něco takového:

kód:

void mainGameLoop()
{
  while(1)
  {
    this->gameStateId = gameStatesPtr[this->gameStateId]->do();

    if(this->gameStateId je navalidní (tedy mimo rozsah vektoru, případně by ukazoval na hodnotu definovanou pro konec)) break;
  }
}


A pak následně odděděním AbstractGameState si budu tvořit jednotlivé stavy hry. Funkce do pak může obsahovat vlastní while smyčku + vlastní řešení vykreslování, vstupů a nebo další dělení, případně logiku.

Je možné i takovéto řešení bez "switche"??


Naposledy upravil Poky dne 20. listopad 2013, 16:46:25, celkově upraveno 2 krát
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Mem



Založen: 28. 07. 2007
Příspěvky: 1959
Bydliště: Olomouc

PříspěvekZaslal: 19. listopad 2013, 16:58:20    Předmět: Odpovědět s citátem

Ty stavy jak popisuje semo taky používáme a hodně si tím zjednodušujeme život. Např. v Unity máme skript, který hážeme na většinu objektů, a který má jako jednu property seznam herních stavů, kdy má být objekt viditelný, případně aktivní (klikatelný). To pak stačí naházet na objekty GUI, menu obrazovku, výběr levelu atd. a při změně globálního stavu hry se všechno automaticky popřepíná a člověk nemusí nic řešit.

Samotnou změnu stavu tedy máme ještě mírně složitější, protože tam děláme většinou i přechodové efekty mezi obrazovkami, ale opět je to přes pár globálních proměnných a společnou rutinu, která ty přechodu zrealizuje (odanimuje, prolne alphy apod.) Příklad viz Scarab Tales: http://youtu.be/eWJGimILiUc
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
perry



Založen: 28. 07. 2009
Příspěvky: 870

PříspěvekZaslal: 19. listopad 2013, 18:36:25    Předmět: Odpovědět s citátem

Možná úplně mimo a nepochopil jsem přesně co je problém. Pokud jo, ignorujte můj post Smile

Já mám "stack", kde jsou všechny obrazovky. Každá obrazovka má metody Render, Update, ProcessInput atd. Tím jak leží na stacku, tak postupně procházím a volám příslušné metody. Když změním stav, tak akorát obrazovku na stacku nastavím jako neaktivní (popř. ji vyhodím úplně. Mám tam ještě globální pool se všemi alokovanými obrazovkami). Díky stacku mám pak zajištěné i pořadí objektů, takže vím co se kreslí přes co (podobně by se dal asi řešit blend dvou obrazovek.. ale to nemám). Každá obrazovka má taky nastavenou blokaci, zda má pustit ovládání i do spodní vrtsvy (takže třeba můžu otevřít inventář, nastavím mu stav na blokující a kliky se dál neposílají). Při zavření inventáře ho odeberu z aktivního stacku a dál existuje někde na odložené hromadě.

Samozřejmě obrazovka != musí se to kreslit, metoda Render může být prázdná.

Podobným stylem mám seskládané i GUI... ale to mám celé jako jednu obrazovku i proto, abych urychlil rendering, kdy to celé kreslím najednou s jednou atlasovou texturou.
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
]semo[



Založen: 29. 07. 2007
Příspěvky: 1481
Bydliště: Telč

PříspěvekZaslal: 20. listopad 2013, 09:47:57    Předmět: Odpovědět s citátem

Poky: Proč ne, jde to i takhle :-). Mě switch v main loop nevadí, přijde mi to přehlednější, ale i takto se to dá. Jen pak musíš pro každý stav, dědit tu abstraktní třídu (což neni vždy pohodlné, ale chyba to neni:) ). Možná by ale stálo za zvážení místo vnitřní smyčky v metodě do() nahradit to metodou update() a volat jí z té hlavní smyčky. Díky tomu bys v mainu mohl updatovat systémy společné pro všechny herní stavy (Timer, Render, ...).

perry: Ten stack, jak popisuješ, je dobrej vzor. Kolega ho jednou implentoval a náramě si to pochvaloval. Přijde mi to nejvíc použitelný hlavně na GUI. Možná jsem otázce taky neporozuměl dobře, tak sem popsal ten stavový automat. To je vzor, kterej je ve hrách potřeba na všechno možný (hlavní smyčka, AI, zvuk...).

Mem: v unity se to asi musí dělat takhle, jak píšeš, tam žádnej main loop neni :-)
_________________
Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
Mem



Založen: 28. 07. 2007
Příspěvky: 1959
Bydliště: Olomouc

PříspěvekZaslal: 20. listopad 2013, 15:04:32    Předmět: Odpovědět s citátem

semo: No pomocí nastavení priority skriptů si ho tak můžeš docela dobře nasimulovat (a naopak pokud děláš hru, kde jsou objekty závislé na stavu ostatních, je nevydefinování priority slušný zdroj frustrace proč to občas vůbec nefunguje Wink)
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
TeaTime



Založen: 17. 06. 2011
Příspěvky: 264

PříspěvekZaslal: 21. listopad 2013, 03:17:23    Předmět: Odpovědět s citátem

perry: Jak vkládáš ty obrazovky do stacku? Každá si řekne, jakou má hloubku, nebo je tam na začátku cpeš ve správném pořadí? Pokud obrazovka nemusí kreslit, tak jaké další věci ještě může dělat? Tedy k jaké objekty může ovládat a používat a jaký má interface (co za metody kromě Render ještě má)?

Díky.
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
perry



Založen: 28. 07. 2009
Příspěvky: 870

PříspěvekZaslal: 21. listopad 2013, 09:12:38    Předmět: Odpovědět s citátem

Do stacku je vkládám postupně, tak jak mají "ležet" na sobě. Tzn. hlavní scéna jde jako první, pak třeba titulky, pak jde GUI, pak např. inventář atd.

Většinu "obrazovek" mám tak, že kreslí... ale ten systém je obecný, takže to může např. jenom updatovat fyziku.

Ten interface mám s metodami
- Render
- Update
- HandleInput
- Release
- volitelně SetState

A stack má potom metoda
- AddScreen(Screen )
- RemoveScreen(Screen )
- SetUpdateEnabled(Screen )
- SetRenderEnabled(Screen )
atd pro další metody z interfacu screenu
- Update
- Render
- HandleInput
a tohle pak interně teprve volá ty jednotlivé obrazovky. Tutu část pak volám z hlavní smyčky

Kde tedy to screen je buď pointer na instanci, nebo nějaké ID do slovníku / pole. Sice to sežere trochu více místa (mám tam více look-up tabulek), ale zase se s tím pak lépe pracuje
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
]semo[



Založen: 29. 07. 2007
Příspěvky: 1481
Bydliště: Telč

PříspěvekZaslal: 21. listopad 2013, 09:43:37    Předmět: Odpovědět s citátem

Možná jsem to nepochopil, ale mít v takhle top-level záležitosti metodu Render() je špatně. Teda pokud se bavíme o klasickým herním enginu, kde jde o výkon a vůbec typický použití ve hrách.

Co mi tam nesedí: pokud kreslení objektů řídí nějaký stack pro okna (!), tak si uzavířáš cestu k některým optimalizacím a funkcionalitě. Renderer má být jen jeden a má pracovat s nějakýma svýma objektama (Model, GuiSprite, atd...), který si ten či onen herní systém ("obrazovka") vytvoří. Pořadí kreslení má mít v rukou vždy Renderer a z vnějšku se to má řídit jen zprostředkovaně (Z-ko, ID render queue, nějaký flagy a pod.). Vím to ze zkušenosti se svým kódem, ale i z architektury cizích enginů.
_________________
Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
perry



Založen: 28. 07. 2009
Příspěvky: 870

PříspěvekZaslal: 21. listopad 2013, 10:22:05    Předmět: Odpovědět s citátem

Hmm... no je mi jasný, že ztrácím výkon tím že to mám oddělené. Na druhou stranu, většinou mám jednu MainScreen a ta v Render kreslí celý svět. Ty ostatní obrazovky na stacku jsou pak jen GUI věci a tam mi na druhou stranu způsob kreslení "někde mimo" moc netrápí. Tam stejně žádné vylepšení dělat nebudu a ani nevím jak (momentálně GUI kreslím na jeden draw call a jednu texturu). Možná by vylepšení snesl font renderer, ale zase.. proč opravovat něco co funguje a zatím mě neomezuje výkon Smile
(To mě spíš omezuje neexistence instancingu v opengl es, ale to je jiné téme už Smile Wink )
V aktuální hře co dělám, tak ten stack navíc nemám vůbec, protože celá hra má pouze jednu statickou obrazovku viděnou izometricky (žádný LOD apod nepotřebuji).
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
]semo[



Založen: 29. 07. 2007
Příspěvky: 1481
Bydliště: Telč

PříspěvekZaslal: 21. listopad 2013, 10:41:55    Předmět: Odpovědět s citátem

Možná už zajíždíme do offtopicu.
citace:
Hmm... no je mi jasný, že ztrácím výkon tím že to mám oddělené.

Spíš ztrácíš nějaké možnosti optimalizací a efektů, než výkon. Napadá mě příklad: máš na stacku screen s hrou, screen s herním gui a na to všechno ještě přihodíš screen s in game menu. A teď to přijde! :-) řekneš si, že to vypadá hnusně a že bys chtěl udělat blur na pozadí in game menu. Ale jak to budeš dělat, když je Render() pro každý screen zvlášť a navíc se volá z hlavní smyčky? Půjde to vyřešit jen nějakou nesystémovou oklikou.
_________________
Kdo jede na tygru, nesmí sesednout.
---
http://www.inventurakrajiny.cz/sipka/
Aquadelic GT, Mafia II, simulátory
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu
perry



Založen: 28. 07. 2009
Příspěvky: 870

PříspěvekZaslal: 21. listopad 2013, 10:54:27    Předmět: Odpovědět s citátem

Hm.. no to by šlo obtížněji no... ale nějak by to šlo (resp. vím jak bych to tam u sebe relativně čistě napasoval). Ale globálně se mého návrhu tím pádem úplně nedržte Very Happy Určitě si vzpomenu, jak to mám blbě, až to budu potřebovat udělat Very Happy Zatím takhle to bylo nejrychlejší pro mě s ohledem cena / výkon.
_________________
Perry.cz
Návrat nahoru
Zobrazit informace o autorovi Odeslat soukromou zprávu Zobrazit autorovi WWW stránky
Zobrazit příspěvky z předchozích:   
odeslat nové téma   Odpovědět na téma    Obsah fóra České-Hry.cz -> C / C++ Časy uváděny v GMT + 1 hodina
Jdi na stránku 1, 2  Další
Strana 1 z 2

 
Přejdi na:  
Nemůžete odesílat nové téma do tohoto fóra
Nemůžete odpovídat na témata v tomto fóru
Nemůžete upravovat své příspěvky v tomto fóru
Nemůžete mazat své příspěvky v tomto fóru
Nemůžete hlasovat v tomto fóru


Powered by phpBB © 2001, 2005 phpBB Group


Vzhled udelal powermac
Styl "vykraden" z phpBB stylu MonkiDream - upraveno by rezna