Diskuze: znovupoužitelný kód pro vývoj 2D her
V předchozím kvízu, Online test znalostí C++, jsme si ověřili nabyté zkušenosti z kurzu.


Patrik Valkovič:29.10.2015 18:22
1 - OS skutečně uvolní všechnu paměť, kterou program používal. Z toho
plyne, že smaže i Singleton. Eventuelně si můžeš udělat nějakou
statickou metodu, která ti singleton smaže, ale není to moc podle návrhu
singletonu.
2 - AZT nemusí nutně definovat pouze rozhraní. V C++ je možné (na rzdíl od
ostatních programovacích jazyků) definovat tělo i abstraktním metodám.
Vždy je lepší programovat proti "rozhraní", i když se bude jednat o
abstraktní třídu. Souřadnice můžeš určitě vymyslet jinak. Já jsem to
řešil tak, že jsem ze třídy, která objekty vykreslovala, vkládal
souřadnice vykreslení jako parametr. Je to nejefektivnější řešení.
3 - Pokud nadefinuješ nějaký kontruktor (s parametrama), tak implicitní se
sám nedefinuje, tudíž jej nemusíš skrývat.
4 - Pro Linux můžeš zkompilovat programy i pro Linux (Cygwin + g++). Ale
nemůžeš použít kompilátor pro Windows. Také si musíš dát pozor, abys
nepoužíval knihovny specifické pro platformu ("windows.h").
Lukáš Hruda:29.10.2015 19:30
1. Nepíšeš tu přesně na co ten singleton potřebuješ, ale obecně
platí, že používat singleton nebývá dobrý nápad.
Pokud ho použiješ, co se týká úklidu daného objektu, záleží jakým
způsobem ho vytvoříš. Pokud objekt přímo deklaruješ jako atribut, např.
takto
class Singleton
{
static Singleton singleton;
.
.
.
};
pak se s koncem programu zavolá destruktor singletonu a ten bude korektně
zničen.
Pokud ale objekt vytvoříš dynamicky takto:
class Singleton
{
Singleton* singleton;
.
.
.
};
Singleton::singleton = new Singleton;
pak ti objekt v programu zůstane viset a úklid provede až operační systém, na což sice u moderních OS lze spoléhat, ale obecně je to prasárna.
2. Udělat abstraktní třídu jako rozhraní pro vykreslovatelné obejekty
je určitě dobrý nápad. Ideálně aby neměla žádné atributy ani
implemetaci, tedy sloužila skutečně pouze jako rozhraní. Nevadí, když bude
mít jenom jednu metodu.
Nikdy nevíš, kdy budeš potřebovat objekt, který žádnou pozici mít nebude
a přesto půjde vykreslit, například pozadí.
Snaž se ale nepřehánět to s tou dědičností (obrovký strom dědičnosti,
kde každý další objekt přidává jednu funkci nebo kde se člověk musí
prokousat několika abstraktními úrovněmi než narazí a třídu, která jde
instancovat, je spíš matoucí), obzvlášť s tou vícenásobnou a když, tak
si o ní nejdřív něco nastuduj.
3. Osobně bych implicitní konstruktor vytvořil. Jeho absence totiž může
způsobit problémy. Pokud budeš chtít například vytvořit pole objektů
nebo vkládat objekty do kolekce (např. vektor), nepůjde to. Samozřejmě to
lze vždy udělat tak, že použiješ pole/kolekci pointerů a budeš pak
objekty vytvářet dynamicky, což bývá i často rozumnější, ale někdy se
víc vyplatí ukládat tam přímo objekty a ne jen ukazatele, nemusíš se pak
například starat o jejich dealokaci a neprovádíš tolik alokací na haldě,
které mohou někdy být relativně pomalé.
Možná to vůbec nevyužiješ, ale jelikož píšeš, že děláš framework,
což je něco, co by mělo být poměrně obecné, nepřijde mi jako dobrý
nápad tohle prostě zakázat, obzvlášť, když vytvoření implicitního
konstruktoru je jeden řádek. Stejně tak by každá třída ideálně měla
mít korektně definovaný kopírovací konstruktor a úplně ideálně i
operátor =. Nikdy nevíš, co se s tou třídou bude jednou dělat.
Díky Lukáš Hruda a Patrik Valkovič za vaše odpovědi, pokusím se tedy ještě upřesnit pár věcí
1. Z toho, co jsem vygooglil, se singleton většinou definuje tak, jak píše Luckin (ta 2. varianta), proto i já jej zatím definoval takto. Ten memory leak je tam naprosto zjevný, ale nikde na něj nijak neupozorňují (což mě znepokojuje). Jelikož se většinou singleton (nebo aspoň v mém případě) používá vždy od začátku do konce programu, tak potřebuji uvolnit onu paměť opravdu až ke konci. Tedy těsně předtím, než to stejně udělá OS. Nevím tedy, jestli je zodpovědností programátora rešit, že uživatel má nějaký velmi starý nebo buglý OS (kolik takových uživatelů asi je?). Jenže už zase slyším ty nepříjemné dotazy od učitelů. Je to jen teorie... Bude tedy lepší držet se spíše té 1. varianty?
Jinak, já používám jako singleton např. třídu, která obstarává práci s texturami. Tzn. obsahuje pole textur a metody pro jejich přidávání a vykreslování. Je potřeba jen jedna v průběhu celé hry (od jejího začátku až do konce) a metody jsou volány na různých místech, takže ten globální přístup je nutný. (pozn.: to vykreslování je nejdřív pouze na backbuffer, výsledné překreslení na frontbuffer se provede v herní smyčce, o kterou se stará třída "hra")
Třídu pro hru jsem zatím udělal jako singleton taky, což je možná kontroverzní. Je to hlavně proto, že obsahuje front i back buffer, ale backbuffer potřebuje i ta třída pro textury (když je tam vykresluje). Nikdy jsem nepoužíval globální proměnné a nevím jak je to lepší. Teď mě ale napadlo, že dám ten backbuffer do třídy pro textury, takže třída hra už nemusí být singleton ...
4. Já používám MinGW a pouze standardní knihovny C++ a multiplatformní SDL. Moc jsem nepochopil, jak ten Cygwin má fungovat. Jde mi o to, že když to teda zkompiluju na Windows s pomocí MinGW, tak *.exe nepojede klasicky třeba na Macu. Pokud ovšem vezmu zdrojáky a rebuildnu to pomocí nějakého kompilátoru pro Mac na Macu, tak by se to mělo zkompilovat a měl bych tak získat vlastně spustitelnou verzi pro Mac. To bych potom považoval za multiplatformnost. No a jestli to tak skutečně, to zatím nevím.
2., 3. Pokusím se tu zítra přidat kousek nějakého kódu pro lepší ilustraci. Jelikož je to jen školní projekt (k maturitě), tak to stejně kromě mě dál nikdo nevyužije, proto to nechci moc přehánět s tou obecností (nikdo to neocení a akorát si zkomplikuji život).
Jinak ještě díky patrik.valkovic k 3. bodu, to jsem netušil.
Patrik Valkovič:29.10.2015 22:29
Memory leak ti tam nemůže vzniknout už z principu OS. Pokud nepracuješ s
real-time systémy, musí se starat OS o správu paměti. Tedy nemůže stejnou
pamět přiřadit několika procesům. Když program skončí, tak se
automaticky (ve všech systémech) paměť uvolní. Kdyby se paměť neuvolnila
po ukončení programu, tak ti bude na RAM ležet kus paměti, kterou OS
nemůže použít ani pro jiné procesy, což je samozřejmě blbost.
V real-time OS to funguje na trošku jiném principu, ale základ je v tom, že
ti běží pouze jediný proces, který má všechny prostředky. Jakmile se
proces ukončí, tak příjde další proces, který má zase stejné
prostředky. Co se může maximálně stát je, že další proces bude mít
někde v paměti uložené data předchozího procesu, ale pokud neřešíš
věci jako hesla, šifrování a podobné věci, kde ti teoreticky můžou
zůstat "citlivé" informace uložené v RAM, tak tě to vůbec nemusí
trápit.
Prostě řekni, že OS si automaticky paměť uvolní, a nemusíš se o to tedy
jako programátor starat.
Cygwin ti prakticky přenese příkazovou řádku Linuxu do Windows. Máš tam bash a podobné věci, které můžeš používat. Můžeš si tam stáhnout g++ kompiler, který funguje prakticky stejně jako MSBuild a podobně. Co vím, tak MinGW funguje na stejném principu.
V g++ pomocí přepínače nastavuješ možnosti
g++ Source.cpp -mcall-linux # měl by se zkompilovat pro Linux
pomocí přepínače -b nastavíš architekturu a tak. Viz http://linux.die.net/man/1/g++
Ale nemyslím si, že by tě to mělo zajímat, když se jedná jen o školní
projekt
Lukáš Hruda:30.10.2015 0:08
Memory leak ti na beznem modernim OS jako windows skutecne nevznikne, alespon
ne na urovni OS, vznikne ti jen na urovni programu, ale u singletonu to bude az
v dobe, kdy uz je ti to jedno.
Problem ale bude, pokud by ten objekt pracoval s necim mimo ten program, treba v
konstruktoru oteviral soubor, socket nebo pripojeni k databazi. To uz za tebe OS
nezavre (ten soubor jeste nejspis ano, ale ne hned). Tyhle uklidove akce budes
idealne delat v destruktoru, ale v pripade te dynamicke alokace se destruktor
sam od sebe nezavola. Proto je dobre si nezvykat to takhle delat. Proste je to
prasarna - ke kazdemu new musi patrit jeden delete.
Opět děkuji za vaše odpovědi. Otázka multiplatformnosti opravdu není nijak horká, jen by byla škoda ji nevyužít (když můžu). Singletony rovněž považuji za vyřešené. Teď se pokusím nastínit v čem mám problém s tou AZT.
Mám klasickou herní smyčku, která 1. načte vstup, 2. provede update, 3. render - všechno vykreslí na obrazovku.
Hra se může současně nacházet v dané chvíli v jednom "stavu" (menu, game over, ...). Abych tedy nemusel použít u updatu a renderování nějaký switch, chci implementovat FSM. Ta by měla pointer na jakousi obecnou třídu "stav" a při volání update a render by se tedy využil polymorfismus. Třída stav by pak obsahavola pole pointerů na jakousi obecnou třídu "objekt" (pro menu tlačítka, pro samotnou hru postavy, ...), tudíž update a render se provede opět s pomocí polymorfismu.
No a já si nejsem jistý, zda-li bych měl použít pro ony obecné třídy "objekt" a "stav" AZT. Taky tápu nad tím, jak moc obecně bych to měl navrhnout? Např.:
class azt_objekt {
public:
virtual void update() = 0;
virtual void render() = 0;
virtual ~objekt() {}
};
Pak konkrétní třída by mohla vypadat třeba takto:
class had : public azt_objekt {
private:
vector2d *p_telo;
public:
virtual void update() {...}
virtual void render() {...}
};
Mám pak v plánu naprogramovat hada...
Analogicky bych vytvořil třeba třídu pro "potravu". Je to opravdu
poprvé, co se snažím programovat objektově (znovupoužitelný kód), kdybych
to dělal procedurálně, tak by to až takový problém nebyl. Holt se stejně
někdy to OOP budu muset naučit. (možná by to chtělo ty objekty navrhnout
ještě nějak jinak )
Ahoj, jelikož už nejspíš nikdo nemá zájem přispět k tomuto tématu
... Označím jej tedy za
vyřešené. Na AZT jsem nakonec nějak vyzrál, udělal řadu dalších změn a
začínám to vidět docela nadějně.
Naposledy ještě jednou díky za všechny vaše postřehy a rady
+5 Zkušeností

Zobrazeno 9 zpráv z 9.