Grafické aplikace ve Visual C++ (7.) Na konci minulé lekce, jsem vám slíbil, že si této lekci vytvoříme třídu CSprite, která bude představovat pohybující se obrázky, kterým říkáme sprity. Že nevíte co jsou to sprity? Právě od toho je tu dnešní lekce. Opět je k dispozici funkční příklad i zdrojový kód, který zkompilujete pod VC++ s nainstalovaným DirectX SDK 8.0. 7.1 Co je to sprite?Možná je to první otázka, která vám jako první vyvstane v mysli, ale spíš bych řekl, že pokud se již nějakou dobu věnujete grafice, určitě víte co je sprite. Sprite z anglického překladu znamená duch nebo přízrak. V terminologii programování (konkrétně programování grafických aplikací) je sprite jakýkoliv pohybující se nebo statický "obrázek". Vezmeme si příklad z jakékoliv netextové hry. Veškerým grafickým objektů jako například grafická tlačítka, budovy, jednotky, stromy, ale i texty na obrazovce, můžeme říkat sprity. 7.2 Typy spritůSprity jdou dále rozdělit na statické (statické tlačítko) a animované (animovaná postava). Dále na sprity, které jsou stále na stejném místě obrazovky a na sprity, které se pohybují během běhu programu. Pří dalším, ještě podrobnějším dělení, můžeme vytvořit sprity, které ovládá umělá inteligence, a které ovládá uživatel-hráč. 7.3 Třída CSprite
Základem aplikace, která chce používat sprity je třída
CSprite, která uchovává data potřebná k vykreslení spritu, k posunutí
spritu a ke změně animační fáze spritu. Tyto tři operace budou zajišťovat tři
členské funkce: DrawSprite(), MoveSprite() a
ChangeAnimationPhase().
Když to celé dáme dohromady, vznikne takovýto návrh třídy CSprite:
Nyní můžeme přistoupit k deklaraci třídy:
Vidíte, že stačí implementovat pouze 5 metod, protože ostatní jsou inline funkce. Pusťme se tedy do nich. 1) CreateStaticSprite()
Funkce přijímá dva parametry a to sice ukazatel na řetězec, ve kterém je uložena cela cesta k bitmapě spritu a počáteční poloha spritu. Tuto počáteční hodnotu ihned uložíme do členské proměnné třídy. V dalším kroku vytvoříme povrch naplněný bitmapou. K tomu použijeme odkaz na třídu CDisplay, který jsme inicializovali v konstruktoru. Funkci CreateSurfaceFromBitmap() jsme probírali minule takže jen zkráceně: funkce má tři parametry: ukazatel na adresu budoucího povrchu, řetězec jména bitmapy a požadovanou výšku a šířku. V dalším kroku nastavíme Colorkey pro povrch. Nastavíme natvrdo černou barvu jako průhlednou, ale můžete tento parametr udělat variabilní (většinou totiž pracujete s černým pozadím). Dále potřebujeme zjistit šířku a výšku jednoho políčka případně šířku a výšku spritu, to vlastně znamená zjistit velikost vytvořeného povrchu (velikost je stejná jako bitmapa). Parametry povrchu zjistíme funkcí GetSurfaceDesc() rozhraní IDirectDrawSurface7. My ovšem máme ukazatel na CSurface a ne na IDirectDrawSurface7. CSurface ovšem obsahuje funkci, která vrací příslušný ukazatel, pomocí kterého můžeme zavolat zmiňovanou funkci. Funkce GetSurfaceDesc() přijímá ukazatel na strukturu DDSURFACEDESC2. U této struktury je potřeba nejdříve inicializovat velikost, to znamená atribut dwSize. Nakonec vezmeme potřebnou šířku a výšku. Všimněte si, že šířka je dělená počtem snímků animace (u statického spritu je počet snímků 1, takže je výsledná šířka vlastně stejná). Potřebujeme totiž zjistit šířku jednoho políčka. 2) CreateAnimatedSprite()
Tuto funkci použijete chcete-li vytvořit animovaný sprite (tj. sprite který má více jak jeden animační krok). První dva parametry má úplně stejné jako předchozí funkce. Další dva určují počet snímku animace a rychlost přehrávání animace v milisekundách. V prvním kroku uložíme vstupní parametry do členských proměnných třídy. Pak stačí za zavolat funkci pro vytvoření statického spritu, postup je totiž stejný. 3) DrawSprite()
Tato funkce nepřijímá žádné parametry a vrací to co vrátí blitovací funkce.
Funkce ColorKeyBlt() potřebuje zdrojový obdélník tzn. odkud
má kopírovat zdrojová data. Takže v prvním kroku tento obdélník sestavíme.
Postup sestavení osvětlí následující obrázek: Nyní už najdete spojitost s tím co vidíte v kódu a s tím co vidíte na obrázku. Z tohoto vyplývá, že snímky v bitmapě musí být uloženy horizontálně za sebou jak je vidět na obrázku. Takže proměnná m_iCurrentPhase musí běhat u animovaného spritu v rozmezí 0 až (m_iPhasesCount - 1). To zařídí funkce ChangeAnimatioSprite(). Takže teď už víte jak sestavit zdrojový obdélník a nyní již stačí jen zavolat správnou blitovací funkci. Používáme Colorkey, takže zavolejte ColorKeyBlt(). Ta přijímá za prvé pozici kam se bude kreslit (to je naše poloha spritu), dále ukazatel na povrch (opět se jedná o rozhraní IDirectDrawSurface7) a jako poslední parametr posíláme ukazatel na zdrojový obdélník (ten který jsme před chvilkou sestavili). 4) MoveSprite()
Funkce MoveSprite() je úplně nejsnazší. Prostě jen přičte hodnoty aktuální rychlosti (v obou směrech) k aktuální pozici a tím posune sprite. Jak vidíte, vrací vždy 0. Do této funkce by případně přibyl clipping spritu. To jest ochrana proti tomu, aby sprite nepřesáhl okraj obrazovky. K tomu potřebujeme rozlišení obrazovky, takže se pravděpodobně budete muset přidat nějaké funkce, které rozlišení vrací. Princip je snadný, po každé změně polohy zkontrolujte jestli tato nová poloha nepřekračuje mimo obrazovku, pokud ano, změňte polohu spritu tak, aby vyhovovala předchozí podmínce. 5) ChangeAnimationSprite()
Funkce ChangeAnimationPhase() je možná naopak nejsložitější. Fáze se nesmí měnit po každém zavolání této funkce, ale jen jednou za dobu určenou proměnnou m_iAnimationSpeed. Princip je v tom, že si pamatujeme čas, kdy jsme naposledy měnili fázi a od té doby kontrolujeme, kdy doba od poslední změny překročí zmiňovanou animační rychlost. Pak opět měníme fázi a opět si zapamatujeme čas změny. Zároveň kontrolujeme, aby fáze nepřekročila maximální počet snímků (pokud se k této hodnotě přiblíží, je aktuální fáze nastavena na 0 a sekvence jede znovu). Funkce GetTickCount() vrací čas (v milisekundách) od startu systému (vrací celkem obludné číslo). Spočítáme rozdíl mezi předchozím (čas předchozí animace) a současným časem a když tento rozdíl je větší než animační rychlost, inkrementujeme aktuální fázi (přitom kontrolujeme horní mez fáze) a opět si zapamatujeme čas, kdy k této změně došlo. A to je vlastně vše. Poznámka: Kdyby animační fáze překročila mez určenou počtem snímků, došlo by ve funkci DrawSprite() k fatální chybě, protože bychom se pokoušeli kopírovat bitmapu z neexistující oblasti. Poznámka: Nezapomeňte napsat konstruktor, kde budou inicializované proměnné tak, že se vytvoří implicitně statický sprite (počet snímku musí být 1). Důležitým krokem v konstruktoru je také inicializace odkazu na třídu CDisplay, který je vytvořen v CControl. Takže budete muset přidat pár funkcí do třídy aplikace a CControl, aby jste tento odkaz dostali (v příkladu, který je na CD, je vše vidět). 7.4 PříkladI tentokrát jsem pro vás připravil příklad, který využívá znalosti z dnešní lekce. Opět se jedná o upravený projekt z minulé lekce. Za prvé musíte vložit novou třídu CSprite a upravit ji tak, jak je ukázáno výše. Dále musíte někde vytvořit objekty třídy CSprite. Nejlépe uděláte, když vytvoříte sprity přímo v objektu CControl. V příkladu vidíte dva ukázkové sprity. Jeden je statický a druhý animovaný. Dále musíte tyto dva sprity inicializovat ve funkci DDInit(). Pak stačí volat z funkce UpdateFrame() členské funkce CSprite, tak aby se sprite vykresloval případně pohyboval nebo měnil animační fáze. To je vše. Když zkompilujete projekt přiložený na CD, uvidíte dva sprity, z nichž jeden je statický, ale pohybuje se a druhý je animovaný, ale stojí na stále stejném místě. Příklad můžete stáhnout v sekci Downloads. 7.5 ZávěrProblematika spritů je velice rozsáhlá. Je možno vytvořit velice složitou strukturu na sobě závislých tříd, které dohromady tvoří engine. Na ukázku vám ukážu jednoduchý obrázek, na kterém je vidět podobná struktura (velice zjednodušená) ze hry Age of Empires 2. Vidíte, že se zde hojně využívá dědičnosti a polyformismu jazyka C++, což je dobré si uvědomit a zamyslet se nad tím, které znaky jednotlivých spritů jsou podobné a generalizovat tyto znaky do základní třídy (BaseObject). Dále vytvoříte třídu statických spritů (podobně jako jsme to dělali v této lekci), která je odvozená od základní třídy (StaticObject). Dále chceme pohybující se objekty (např. jednotky). To zajišťuje třída MovingObject. Nakonec tu máme třídu MissileObject, který vytváří sprity munice (šípy). Všimněte si virtuální funkce update(). Na úplný závěr vám prozradím, co nás čeká v příští lekci. Jsme prakticky na konci kurzu DirectDraw. V příští lekci vám ještě povím něco uvolňování objektů DirectDraw a o "ztráceni povrchů". A tím prakticky zakončíme DirectDraw. Protože vím, že problematika je skutečně rozsáhlá a každého může zajímat něco jiného, je potřeba abyste mi dali vědět o svých problémech. Další velice důležitou komponentou DirectX je DirectInput, která se rovněž velmi hodí pro programování her a jiných multimediálních aplikací. Takže dále budu pokračovat právě komponentou DirectInput, která není zdaleka tak složitá jako DirectDraw.
© 2001
|