A je tu dalÜφ lekce o DirectX. Minule jsme p°ipojili knihovnu Input.dll do naÜeho projektu a dnes budeme pokraΦovat dalÜφ mo₧nß nejpodstatn∞jÜφ knihovnou Engine.dll. Povφme si, o co se tato knihovna bude starat a velmi brzy zjistφte, k Φemu nßm budou °ßdky napsanΘ v minul²ch lekcφch.
D°φve ne₧ vytvo°φme novou knihovnu bych rßd jeÜt∞ jednou ukßzal cel² projekt graficky:
ÄlutΘ rßmeΦky oznaΦujφ knihovny, kterΘ jsme u₧ n∞jak²m zp∙sobem dod∞lali. To ovÜem nevyluΦuje, ₧e je Φasem nebudeme dßle upravovat podle pot°eby. Dßle si vÜimn∞te, ₧e to co jsme do te∩ vymysleli a napsali bude vyu₧φvat knihovna Engine, o kterΘ bude dnes °eΦ. Je to jakßsi dalÜφ vrstva. Co ale bude v tΘto knihovn∞? Dß se °φci, ₧e bude obsahovat zbytek funkcionality naÜeho menu. Jist∞, ₧e n∞co p°ipφÜeme i do Game, ale to hlavnφ bude v Engine.
Nap°φklad vytvo°φme t°φdu CSprite, kterß bude zßkladnφ t°φda pro vÜechny ostatnφ objekty, kterΘ majφ grafickou reprezentaci (nap°. tlaΦφtka). I tlaΦφtka ovÜem budou mφt svoji vlastnφ t°φdu CGButton. SystΘm menu by mohl mφt nap°φklad takovou strukturu:
BφlΘ linky p°edstavujφ vazbu gen-spec neboli rodiΦ-potomek tj. d∞diΦnost. Naproti tomu ₧lutΘ linky p°edstavujφ vazbu celek-Φßst. To v praxi znamenß, ₧e t°φdy CGPage obsahuje pole prvk∙ CGItem. Je takΘ vid∞t, ₧e menu bude obsahovat dva prvky CGLabel a CGButton. Nenφ problΘm si vytvo°it dalÜφ prvky nap°φklad CGListBox nebo CGEditBox. Mo₧nß se divφte proΦ jsem odd∞lil CSprite a CGItem. Je to op∞t kv∙li mo₧nosti rozÜφ°enφ. Φasem t°eba budete mφt prvky, kterΘ budou grafickΘ, ale nebudou souΦßstφ ₧ßdnΘ strßnky menu. Tento objekt pak zd∞dφte od CSprite stejn∞ jako jsem jß zd∞dil CGItem. T°φdu CSprite jsme ji₧ p°ed Φasem vytvo°ili v naÜφ prvotnφ DirectDraw aplikaci. Novß t°φda bude velmi podobnß.
ZaΦneme tφm, ₧e p°idßme nov² projekt. Postup je standardnφ. Bude se jednat o MFC projekt typu DLL. Vlo₧te nov² projekt z menu Project. Vyberte polo₧ku Add to project a New...:
Dßle nastavte, ₧e chcete rozÜφ°enou knihovnu MFC:
PotΘ nastavφme Dependencies pro nov² projekt:
Projekt Engine bude zßvisl² na Display a Input (obr. vlevo). Zßrove≥ musφte nastavit zßvislost mezi Game a Engine (obr. vpravo).
Pot°ebujeme, aby se knihovna Engine.dll vytvß°ela ve spoleΦnΘm adresß°i Debug a Release. To nastavφme na kart∞ Settings...:
Je takΘ samoz°ejm∞ pot°eba vlo₧it knihovnu Common.dll jako v p°edchozφch projektech.
TIP: Pokud se chcete podφvat jakΘ knihovny vy₧adujφ n∞kterΘ programy Φi
jinΘ knihovny, vyzkouÜejte aplikaci Depencency Walker, kterß je souΦßst
instalace Visual C++ 6.0.
Standardn∞ ji najdete v menu Start->Programy->Microsoft
Visual Studio 6.0->Microsoft Visual Studio 6.0 Tools->Depends. V
programu lze otev°φt libovoln² program .exe nebo
.dll a uvidφte na jak²ch knihovnßch je zßvisl².
ClassView by po t∞chto ·pravßch m∞lo vypadat n∞jak takto:
ZaΦneme rovnou s t°φdou s CSprite, proto₧e je to zßkladnφ t°φda pro prvky menu. Jak jsem p°edeslal v²Üe, t°φda bude velmi podobnß tΘ, co jsme definovali v lekci 6 a 7. Ne vÜechny atributy bude t°φda CGItem vyu₧φvat, ale budeme poΦφtat s mo₧n²m rozÜφ°enφm, tak₧e je tam nechßme.
V uvedenΘm schΘmatu knihovny jsem ₧lut∞ vyznaΦil dalÜφ t°φdu, kterß bude ·zce spolupracovat s CSprite. Op∞t by to Ülo pro jednoduch² p°φpad menu ud∞lat jednoduÜeji, ale pokud vyu₧ijete t°φdu i jinak, m∙₧e se vßm to hodit. Jednß se o t°φdu CSpriteState, kterß p°edstavuje stav objektu CSprite. V tomto stavu se sprite chovß podle objektu CSpriteState, nap°φklad nadefinujeme pro ka₧d² stav jinou bitmapu a kdykoliv se p°epnete stav, zm∞nφ se bitmapa (v naÜem p°φpad∞ toto vyu₧ijeme u tlaΦφtek, proto₧e ka₧dΘ tlaΦφtko bude mφt 4 stavy - normßlnφ, stisknutΘ, s fokusem a nep°φstupnΘ). Ka₧d² stav bude moci animovat urΦitou sekvenci (bude mφt definovanΘ vlastnφ animaΦnφ rychlosti apod.). Dßle budeme moci nastavit, zda-li se stav bude opakovat po p°ehrßnφ celΘ sekvence nebo se nastavφ jin² atribut objektu CSprite (nap°φklad jin² stav, poloha). VÜechny tyto mo₧nosti nebudeme v p°φpad∞ menu pln∞ vyu₧φvat a takΘ je samoz°ejm∞ vaÜe v∞c jak to upravφte podle vlastnφch pot°eb.
Proto₧e ob∞ t°φdy jsou k sob∞ ·zce vßzßny, popφÜeme se ve spoleΦnΘm souboru.
Podφvejme se nßvrh t°φdy CSprite:
Atributy |
||
Typ atributu | Nßzev prom∞nnΘ | Popis |
protected: |
||
CPoint | m_ptPos | Aktußlnφ pozice spritu. |
int | m_iVelX | Rychlost v horizontßlnφm sm∞ru. |
int | m_iVelY | Rychlost ve vertikßlnφm sm∞ru. |
int | m_iMaxVel | Maximßlnφ rychlost. |
CPtrArray | m_arStates | Pole stav∙ pro tento objekt CSprite. |
CSpriteState | m_pCurState | Ukazatel na aktußlnφ stav. |
CRect | m_rcRealRect | SkuteΦn² obdΘlnφk spritu, viz. dßle |
private: |
||
int | m_iLastUpdate | ╚as poslednφ zm∞ny animace |
int | m_iCurrentPhase | Aktußlnφ fßze animace |
Metody | ||
Typ nßvratovΘ hodnoty | JmΘno a parametry | Popis |
public: | ||
HRESULT | DrawSprite(void) | Nakreslφ sprite na svΘm mφst∞ |
HRESULT | MoveSprite(void) | P°iΦte k aktußlnφ pozici hodnoty rychlosti v obou sm∞rech |
HRESULT | ChangeAnimationPhase(void) | Inkrementuje aktußlnφ fßzi animace a kontroluje hornφ mez |
virtual void | UpdateSprite() | Obnovφ sprite, postupn∞ volß tyto metody: ChangeAnimationPhase(), MoveSprite() a DrawSprite() |
void | AddState(CSpriteState * pNewState) | P°idß stav objektu do pole stav∙ spritu |
BOOL | DetectCollision(CRect * pCollisionRect) | Vracφ TRUE pokud je pr∙nik obdΘlnφku z parametru a reßlnΘho obdΘlnφku spritu nenulov², jinak FALSE. |
CSpriteState* | GetState() | Vracφ aktußlnφ stav spritu, vracφ ukazatel na stav |
void | SetState(DWORD dwState) | Nastavφ aktußlnφ stav spritu podle ID stavu |
void | SetVelX(int VelX) | Zm∞nφ rychlost v horizontßlnφm sm∞ru |
int | GetVelX(void) | Vrßtφ rychlost v horizontßlnφm sm∞ru |
void | SetVelY(int VelY) | Zm∞nφ rychlost ve vertikßlnφm sm∞ru |
int | GetVelY(void) | Vrßtφ rychlost ve vertikßlnφm sm∞ru |
void | SetMaxVel(int iMaxVel) | Nastavuje maximßlnφ rychlost |
int | GetMaxVel() | Vracφ maximßlnφ rychlost |
void | SetPosition(CPoint ptPos) | Zm∞nφ pozici spritu. Tato funkce m∙₧e mφt dv∞ varianty. |
CPoint* | GetPosition(void) | Vrßtφ aktußlnφ pozici spritu. |
void | SetRealRect(int, int, int, int) | Nastavuje reßln² obdΘlnφk spritu |
void | SetRealRect(CRect) | Nastavuje reßln² obdΘlnφk spritu |
void | GetRealRect(CRect*) | Vracφ reßln² obdΘlnφk na obrazovce (reßln² obdΘlnφk + pozice) |
void | GetDestin(CRect*) | Funkce vracφ ukazatel na obdΘlnφk, do kterΘho se bude vykreslovat sprite. |
void | GetSource(CRect*) | Narozdφl od p°edeÜlΘ metody, tato metoda vracφ zdrojov² obdΘlnφk, ze kterΘho se bude kreslit. |
Nap°φklad hodnoty rychlosti m_iVelX, m_iVelY a m_iMaxVel zde v∙bec nevyu₧ijeme. Sprite musφ mφt definovan² alespo≥ jeden stav, jinak je zcela nepou₧iteln². Z objektu stavu CSpriteState Φte vÜechny pot°ebnΘ informace pro vykreslenφ spritu.
SkuteΦn² obdΘlnφk se pou₧ije p°i detekci kolize dvou objekt∙ CSprite. V∞tÜina spritu toti₧ bude mφt pr∙hlednΘ okraje a tyto okraje se nesmφ brßt v ·vahu p°i detekci kolize. Nap°φklad se podφvejme na obrßzek:
╚ernΘ okraje nastavφme jako pr∙hlednΘ, tak₧e vlastn∞ nejsou souΦßstφ spritu (jako sprite bereme pouze letadlo). Kdybychom zkoumali detekci kolize dvou t∞chto sprit∙ a brali bychom v ·vahu obdΘlnφk spritu vΦetn∞ okraj∙ a nikoliv reßln² obdΘlnφk, funkce DetectCollision() by vrßtila TRUE, u₧ kdy₧ by se oba objekty dotkly Φern²mi okraji a to je bezpochyby Üpatn∞. Nastavφme proto skuteΦn² obdΘlnφk spritu, kter² m∙₧ete vid∞t na dalÜφm obrßzku:
Ani tento obdΘlnφk nenφ ·pln∞ p°esn² a u slo₧it∞jÜφch objekt∙ si moc nepom∙₧eme. M∙₧ete tedy definovat slo₧it∞jÜφ strukturu pro popsßnφ skuteΦnΘho tvaru spritu. V MFC existuje t°φda CRgn pomocφ kterΘ lze vytvo°it jak²si region zadan² nap°φklad n∞kolika body. SkuteΦn² region pro v²Üe uveden² sprite by mohl vypadat nßsledovn∞:
U grafickΘho menu budeme takΘ vyu₧φvat detekci kolize. Nap°φklad kdy₧ u₧ivatel najede kurzorem na tlaΦφtko, tlaΦφtko dostane fokus apod. Budeme ale uva₧ovat naprosto obdΘlnφkovΘ tlaΦφtka, tak₧e skuteΦn² obdΘlnφk bude stejn² jako obdΘlnφk spritu. Pokud byste ovÜem pou₧ili nap°φklad kruhovΘ prvky, u₧ byste museli definovat skuteΦn² obdΘlnφk slo₧it∞ji (p°φpadn∞ pomocφ region∙).
Nynφ si proberme t°φdu CSpriteState. Objekt tohoto typu bude uchovßvat vlastnosti spritu (nap°φklad. poΦet animaΦnφch sekvencφ nebo rychlost animace). Poka₧dΘ, kdy₧ metodou SetState() nastavφte stav, zm∞nφ se chovßnφ a t°eba i vzhled spritu. U₧ jsem psal, ₧e ka₧dΘ tlaΦφtko bude mφt 4 stavy. Ka₧d² tento stav bude definovßn pomocφ stavu spritu. Podrobn∞ji si o tom povφme, a₧ budeme deklarovat t°φdu CGButton.
Nßvrh t°φdy CSpriteButton by mohl vypadat nßsledovn∞:
Atributy |
||
Typ atributu | Nßzev prom∞nnΘ | Popis |
private: |
||
DWORD | m_dwID | ID objektu. Musφ b²t unikßtnφ v∙Φi ostatnφm stav∙m jednoho spritu. |
int | m_iAnimationSpeed | Rychlost animace v milisekundßch. |
int | m_iPhasesCount | PoΦet animaΦnφch krok∙. |
CSurface | m_surSource | Zdrojov² povrch pro tento stav. Pokud bude nastaven, sprite se bude vykreslovat z tohoto povrchu. |
DWORD | m_dwDoAfterAnimationEnds | V tΘto prom∞nnΘ budou ulo₧eny informace o tom, co se mß stßt, kdy₧ stav skonΦφ tj. kdy₧ se p°ehraje celß sekvence. |
CPoint | m_ptPoint | Tato prom∞nnß oznaΦuje mφsto, kam se p°esune sprite po skonΦenφ sekvence. |
DWORD | m_dwState | Dßle m∙₧eme vyu₧φt tuto prom∞nnou k tomu, abychom nastavili jin² stav spritu po skonΦenφ sekvence. |
int | m_iWidth | èφ°ka jednoho polφΦka spritu. VypoΦteme p°i inicializace z Üφ°ky celΘho povrchu a poΦtu animaΦnφch krok∙. |
int | m_iHeight | V²Üka jednoho polφΦka je rovna v²Üce povrchu, proto₧e jednotlivΘ animace jsou v bitmap∞ posklßdßny horizontßln∞. |
Metody | ||
Typ nßvratovΘ hodnoty | JmΘno a parametry | Popis |
public: | ||
void | CreateState(DWORD, LPCSTR, int, int ) | InicializaΦnφ metoda objektu, kterß definuje stav spritu. PoslΘze lze vyu₧φt ukazatel tohoto objektu jako parametr metody AddState() t°φdy CSprite. Prvnφ parametr p°edstavuje ID stavu, dßle je tu °et∞zec povrchu stavu, pak mßme animaΦnφ rychlost a nakonec poΦet sekvencφ. Jednß-li se o statick² sprite (statick² stav), poslednφ dv∞ hodnoty nechßme implicitn∞. |
void | SetDoAfterAnimationEnds(DWORD, DWORD, int, int) | Nastavuje vlastnosti spritu, kterΘ se nastavφ po odezn∞nφ animace. Prvnφ parametr urΦuje jakou vlastnost nastavujeme, dalÜφ parametry jsou konkrΘtnφ vlastnosti (v naÜem p°φpad∞ je to pozice a stav objektu). |
DWORD | GetID() | Vracφ ID objektu stavu. |
int | GetAnimationSpeed() | Vracφ animaΦnφ rychlost. |
int | GetPhasesCount() | Vracφ poΦet animaΦnφch krok∙. |
CSurface* | GetSourceSurface() | Vracφ ukazatel na zdrojov² povrch. |
DWORD | GetDoAfterAnimationEnds() | Vracφ hodnotu, kterß urΦuje, co se bude dφt po skonΦenφ animace. |
CPoint | GetPosAF() | Vracφ pozici spritu po skonΦenφ sekvence. |
DWORD | GetStateAF() | Vracφ stav spritu po skonΦenφ sekvence. |
int | GetHeight() | Vracφ v²Üku jednoho polφΦka spritu. |
int | GetWidth() | Vracφ Üφ°ku polφΦka spritu. |
Metody probereme podrobn∞ji v dalÜφ Φßsti.
Nejprve se ale podφvejme na deklaraci obou t°φd. U₧ jsem zmφnil, ₧e t°φdy spolu velice ·zce spolupracujφ a proto je vlo₧φme do jednoho souboru. Vlo₧me nejprve t°φdu CSprite a potΘ k nφ p°idßme t°φdu CSpriteState. Na nßsledujφcφm obrßzku vidφte, jak vyplnφme dialog pro p°idßnφ novΘ t°φdy:
PotΘ do toho samΘho souboru vlo₧φme t°φdu CSpriteState:
Stiskem tlaΦφtka Change... vyvolßte nßsledujφcφ dialog, kde nastavφte hlaviΦkovΘ a implementaΦnφ soubory pro t°φdu:
V ClassView se vßm tedy objevφ dv∞ novΘ t°φdy, ale ve FileView jen soubory Sprite.h a Sprite.cpp.
V tΘto lekci jeÜt∞ stihneme deklarovat a definovat t°φdy CSprite a CSpriteState. Podφvejme se tedy na deklarace t°φd:
#define DAS_NONE 0x0001
#define DAS_POSITION 0x0002
#define DAS_STATE 0x0004
class AFX_EXT_CLASS CSpriteState
{
DWORD m_dwID;
int m_iAnimationSpeed;
int m_iPhasesCount;
CSurface m_surSource;
// Tile dim
int m_iWidth;
int m_iHeight;
// What to do
after animation ends
DWORD m_dwDoAfterAnimationEnds;
CPoint m_ptPoint;
DWORD m_dwState;
public:
void CreateState(DWORD dwID, LPCSTR szSurface, int
iAnimationSpeed = 0, int iPhases = 1);
void SetDoAfterAnimationEnds(DWORD dwWhatToDo, DWORD dwState,
int x, int y);
//
// Inline
DWORD GetID() {return
m_dwID;}
int GetAnimationSpeed() {return m_iAnimationSpeed;}
int GetPhasesCount() {return m_iPhasesCount;}
CSurface* GetSourceSurface() {return &m_surSource;}
DWORD GetDoAfterAnimationEnds() {return m_dwDoAfterAnimationEnds;}
CPoint* GetPosAF() {return &m_ptPoint;}
DWORD GetStateAF() {return m_dwState;}
int GetHeight() {return m_iHeight;}
int GetWidth() {return m_iWidth;}
public:
CSpriteState();
~CSpriteState();
};
class AFX_EXT_CLASS CSprite
{
protected:
CPoint m_ptPos;
int m_iVelX;
int m_iVelY;
int m_iMaxVel;
CPtrArray m_arStates;
CSpriteState* m_pCurState;
CRect m_rcRealRect;
private:
int m_iLastUpdate;
int m_iCurrentPhase;
public:
HRESULT DrawSprite(void);
HRESULT MoveSprite(void);
HRESULT ChangeAnimationPhase(void);
void AddState(CSpriteState * pNewState);
BOOL DetectCollision(CRect * pCollisionRect);
//
// State depent methods
void SetState(DWORD
dwState);
void GetDestin(CRect* rcDestin);
void GetSource(CRect* rcSource);
void SetRealRect(int iLeft, int iTop, int iRight, int iBottom);
void SetRealRect(CRect * pRealRect);
void GetRealRect(CRect* pRealRect);
//
// Inline
CSpriteState*
GetState() {return m_pCurState;}
void SetVelX(int VelX) {m_iVelX = VelX;}
int GetVelX(void) {return m_iVelX;}
void SetVelY(int VelY) {m_iVelY = VelY;}
int GetVelY(void) {return m_iVelY;}
void SetMaxVel(int iMaxVel) {m_iMaxVel = iMaxVel;}
int GetMaxVel() {return m_iMaxVel;}
void SetPosition(CPoint ptPos) {m_ptPos = ptPos;}
CPoint* GetPosition(void) {return &m_ptPos;}
//
// Virtual
virtual void
UpdateSprite();
public:
CSprite();
~CSprite();
};
Na zaΦßtku definuji symbolickΘ konstanty pro metodu SetDoAfterAnimationEnds() pomocφ nich₧ °φkßte, kter² parametr je platn². Kombinaci t∞chto hodnot takΘ vracφ metoda GetDoAfterAnimationEnds(). Dßle si vÜimn∞te makra AFX_EXT_CLASS, kterΘ exportuje celou t°φdu z knihovny.
Dßle si proberme implementaci obou t°φd. ZaΦneme u t°φdy CSpriteState, proto₧e CSprite je na nφ zßvislß.
Jako prvnφ tu mßme konstruktor a destruktor t°φdy. Zde je d∙le₧itΘ inicializovat n∞kterΘ atributy, abychom zajistili sprßvnou funkci programu:
Je t°eba zadat nulovΘ ID, proto₧e podle tohoto ID zjistφme, ₧e objekt nebyl inicializovßn.
Dßle je pot°eba vynulovat pozici a stav, do kterΘho se sprite dostane po skonΦenφ aktußlnφho stavu.
Nakonec je zde nulovßnφ prom∞nnΘ m_dwDoAfterAnimationEnds, co₧ zajistφ, ₧e po skonΦenφ aktußlnφ animace nenastane ₧ßdnß zm∞na ve spritu a animace zaΦne znovu.
Naopak p°i uvol≥ovßnφ objektu bychom takΘ m∞li uvolnit povrch stavu, i kdy₧ to nenφ nutnost (objekt CSurface se automaticky uvol≥uje p°i destrukci).
CSpriteState::CSpriteState()
{
m_dwID = 0;
m_dwState = 0;
m_ptPoint = CPoint(0, 0);
m_dwDoAfterAnimationEnds = 0;
}
CSpriteState::~CSpriteState()
{
//
// Release surface
if(m_dwID != 0) {
m_surSource.Release();
}
}
T°φda CSpriteState obsahuje pouze dv∞ metody. Za
prvΘ je to metoda CreateState(), kterß inicializuje
objekt. Prvnφm parametrem urΦφte ID objektu, kterΘ musφ b²t unikßtnφ v∙Φi
ostatnφm stav∙m danΘho objektu CSprite. DalÜφ
parametr je ukazatel na °et∞zec, ve kterΘm je ulo₧ena cesta k bitmap∞. Z tΘto
bitmapy se bude vykreslovat, pokud bude nastaven tento stav. DalÜφ dva parametry
se t²kajφ pouze animovan²ch stav∙. UrΦujφ rychlost animace a poΦet animaΦnφch
krok∙.
void CSpriteState::CreateState(DWORD dwID,
LPCSTR szSurface, int iAnimationSpeed, int iPhases)
{
if(m_dwID == 0) {
// Fill structure
m_dwID = dwID;
m_surSource.Create(szSurface);
m_iAnimationSpeed = iAnimationSpeed;
m_iPhasesCount = iPhases;
//
// Compute dim of one tile
if(iPhases != 0) {
m_iWidth =
int(m_surSource.Width() / iPhases);
}
else {
m_iWidth = m_surSource.Width();
}
// Height is same as surface
m_iHeight = m_surSource.Height();
}
else {
DXTRACE("State is already initialized.");
}
}
Metoda je velice jednoduchß. Pouze inicializuje n∞kterΘ atributy t°φdy. VÜimn∞te si testovßnφ nulovΘho ID a vzpome≥te si na konstruktor t°φdy. V²poΦet Üφ°ky jednoho polφΦka u animovan²ch stav∙ se provßdφ velice podobn∞ jako tomu bylo u kurzoru.
Druhß a poslednφ metoda nastavuje chovßnφ po skonΦenφ animace. Prvnφm parametrem urΦφme, co se mß d∞lat. M∙₧e to b²t kombinace hodnot DAS_POSITION a DAS_STATE. Prvnφ z nich urΦφ, ₧e pozice ulo₧enß ve t°etφm a ΦtvrtΘm parametru jsou platnΘ. Naopak hodnota DAS_STATE °φkß, ₧e je platn² druh² parametr. U₧ivatel volß tuto funkci pouze pokud chce nastavit chovßnφ spritu po skonΦenφ animace. Pokud metodu v∙bec nezavolß, vznikne nekoneΦn² stav, kter² se opakuje do tΘ doby, dokud u₧ivatel nenastavφ jin² stav explicitn∞.
void CSpriteState::SetDoAfterAnimationEnds(DWORD
dwWhatToDo, DWORD dwState, int x, int y)
{
m_dwDoAfterAnimationEnds = dwWhatToDo;
m_ptPoint = CPoint(x, y);
m_dwState = dwState;
}
Nynφ se podφvejme na implementaci t°φdy CSprite, kterß bude o n∞co zajφmav∞jÜφ a slo₧it∞jÜφ.
Op∞t se nejprve podφvßme na konstruktor a destruktor. U₧ jsme si °φkali v²Üe, ₧e objekt spritu je nepou₧iteln² pokud nemß nadefinovan² alespo≥ jeden stav. VÜimn∞te si, ₧e v konstruktoru nastavujeme ukazatel na aktußlnφ stav na NULL. Tφm zaruΦφme nesprßvnΘmu pou₧itφ objektu CSprite (v∞tÜina metod testuje tento ukazatel). Implicitn∞ takΘ nemß sprite ₧ßdn² pohyb, tak₧e vÜechny rychlosti jsou vynulovßny. Pro jistotu nulujeme i pozici spritu. Kdyby toti₧ u₧ivatel zapomn∞l tento atribut nastavit, sprite by se vykreslil kdesi v nekoneΦnu. Dßle je velice d∙le₧itΘ vynulovat aktußlnφ fßzi. Zdrojov² obdΘlnφk se odvozuje prßv∞ od aktußlnφ fßze a kdybychom nechali p∙vodnφ hodnotu, op∞t by jsme vykreslovali odn∞kud z nekoneΦna a program by jisto jist∞ spadl. Dßle nulujeme hodnotu doby od poslednφ zm∞ny animaΦnφho kroku. To nenφ zase tak d∙le₧itΘ, ale vyvarujeme se neidentifikovatelnΘmu chovßnφ na zaΦßtku programu. Nakonec inicializujeme skuteΦn² obdΘlnφk spritu. Hodnotu -1 na prvnφm mφst∞ si vysv∞tlφme o trochu pozd∞ji.
CSprite::CSprite()
{
m_pCurState = NULL;
m_iVelX = 0;
m_iVelY = 0;
m_ptPos = CPoint(0, 0);
m_iCurrentPhase = 0;
m_iLastUpdate = 0;
m_rcRealRect = CRect(-1, 0, 0, 0);
}
CSprite::~CSprite()
{
m_pCurState = NULL;
}
PokraΦujme dßle a rozeberme metodu DrawSprite(),
kterß je pro sprite mo₧nß nejd∙le₧it∞jÜφ a m∞la by b²t co nejjednoduÜÜφ:
HRESULT CSprite::DrawSprite(void)
{
DWORD dwRet = S_FALSE;
CRect rcDestin, rcSource;
//
// State must be selected
if(m_pCurState) {
//
// Get rectangles
GetDestin(&rcDestin);
GetSource(&rcSource);
//
// Draw sprite
dwRet = disBlt(rcDestin, rcSource, m_pCurState->GetSourceSurface());
}
return dwRet;
}
Za prvΘ otestujeme hodnotu m_pCurState, proto₧e
musφ b²t nastaven n∞jak² stav. PotΘ zφskßme zdrojov² a cφlov² obdΘlnφk spritu
pomocφ metod GetSource() a
GetDestin() (viz. dßle). Nakonec zavolßme znßmou funkci
disBlt() a sprite vykreslφme. VÜimn∞te si pou₧itφ
objektu CSpriteState.
DalÜφ velice jednoduchß metoda je MoveSprite():
HRESULT CSprite::MoveSprite(void)
{
DWORD dwRet = S_OK;
//
// Standard sprite movement
m_ptPos += CPoint(m_iVelX, m_iVelY);
//
return dwRet;
}
Metoda jen posune sprite podle jeho rychlosti v obou sm∞rech x a y. Metoda vracφ
poka₧dΘ hodnotu S_OK (0).
KoneΦn∞ je tu trochu zajφmav∞jÜφ metoda, kterß se starß o zm∞nu animaΦnφho
kroku.
HRESULT CSprite::ChangeAnimationPhase(void)
{
DWORD dwRet = S_FALSE;
//
// State must be selected
if(m_pCurState) {
//
// Get Current phase that will be
changed
int iPhase = m_iCurrentPhase;
//
// Get time from last update and from
windows start
int newTime = GetTickCount();
int oldTime = m_iLastUpdate;
//
// If is time to update, increment
phase
if((newTime - oldTime) > m_pCurState->GetAnimationSpeed())
{
//
// Increment
phase
iPhase++;
//
// If phase
is maximum, reset it and do after segment sequention
if(iPhase >=
m_pCurState->GetPhasesCount() - 1) {
iPhase = 0;
// Set pos or state after animation
ends
if(m_pCurState->GetDoAfterAnimationEnds() & DAS_POSITION) {
m_ptPos = *m_pCurState->GetPosAF();
}
if(m_pCurState->GetDoAfterAnimationEnds() & DAS_STATE) {
SetState(m_pCurState->GetStateAF());
}
}
//
// Change
phase
m_iCurrentPhase
= iPhase;
//
// Remember
time of this update
m_iLastUpdate
= newTime;
}
}
return dwRet;
}
V naÜφ prvnφ aplikaci s DirectDraw jsme m∞li podobnou metodu, vzpomφnßte? Dnes ji malinko upravφme. Na zaΦßtku op∞t testujeme hodnotu m_pCurState, proto₧e metoda pracuje s objektem stavu. Dßle si ulo₧φme aktußlnφ fßzi do pomocnΘ prom∞nnΘ (to je v celku zbyteΦnΘ). PotΘ zφskßme aktußlnφ Φas a Φas, kter² ub∞hl od poslednφ zm∞ny animace. Tyto dva Φasy od sebe odeΦteme a zkoumßme, zda-li jejich rozdφl je v∞tÜφ nebo roven animaΦnφ rychlosti. Pokud ano, je Φas posunout animaci vp°ed. Pokud po tΘto ·prav∞ je aktußlnφ fßze v∞tÜφ ne₧ celkov² poΦet fßzφ, je sekvence u konce a je pot°eba fßzi vynulovat, p°φpadn∞ nastavit n∞kterΘ vlastnosti spritu. K tomu vyu₧ijeme objekt stavu, proto₧e ten "vφ", co se mß d∞lat po skonΦenφ animace (v p°φpad∞ nekoneΦnΘho stavu se ned∞je nic, jen se vynuluje aktußlnφ fßze). Nakonec zp∞tn∞ upravφme atribut t°φdy a ulo₧φme stav, kdy se udßla zm∞na animace. Je to snadnΘ, ₧e?
Dßle je tu velice jednoduchß funkce, kterß jen zkracuje zßpis p°i zpracovßnφ
spritu. Postupn∞ toti₧ volß metody ChangeAnimationPhase(),
MoveSprite() a DrawSprite().
void CSprite::UpdateSprite()
{
ChangeAnimationPhase();
MoveSprite();
DrawSprite();
}
Velmi d∙le₧itß metoda je AddState(). Je to vlastn∞
jedin² zp∙sob jak p°idat p°edem vytvo°en² stav objektu
CSprite. Metoda mß jedin² parametr a to je ukazatel na objekt
CSpriteState:
void CSprite::AddState(CSpriteState *
pNewState)
{
CSpriteState *pState;
//
// Check input pointer
if(!pNewState) {
DXTRACE("Invalid state poitner has
been passed.");
return;
}
//
// Check if the state is not alredy in states array
for(int i = 0; i < m_arStates.GetSize(); i++) {
pState = (CSpriteState*) m_arStates[i];
if(pState->GetID() == pNewState->GetID())
{
//
// new state
is found in states array
DXTRACE("State
with id: %d is alredy added to this sprite.", pNewState->GetID());
// Exit
return;
}
}
//
// Else add this state
m_arStates.Add(pNewState);
//
// If not is set, set new state as current
if(!m_pCurState) {
SetState(pNewState->GetID());
}
}
Nejprve zkontrolujeme platnost vstupnφho ukazatele. PotΘ projdeme celΘ pole stav∙ a zjistφme, zda-li ji₧ nenφ vlo₧en stav se stejn²m ID. Pokud vÜechny tyto kontroly projdou bez problΘm∙, jednoduÜe vlo₧φme ukazatel stavu do pole. Nakonec testujeme hodnotu aktußlnφho stavu a pokud je stßle NULL, nastavφme prßv∞ p°idan² stav. U₧ivatel tak nemusφ explicitn∞ volat metodu SetState(), aby nastavil prvnφ stav.
Nßsledujφcφ metoda slou₧φ k explicitnφmu nastavenφ stavu spritu. P°ijφmß jeden
parametr a to ID stavu, kter² chceme nastavit.
void CSprite::SetState(DWORD dwState)
{
CSpriteState *pState;
//
// Check if the state is in states array
for(int i = 0; i < m_arStates.GetSize(); i++) {
pState = (CSpriteState*) m_arStates[i];
if(pState->GetID() == dwState) {
//
// State has
been found, set it
m_pCurState =
pState;
return;
}
}
// If state is
not valid, set NULL as current state
m_pCurState =
NULL;
}
Metoda je trivißlnφ. Op∞t projdeme celΘ pole stav∙ a hledßme po₧adovan² stav. Pokud ho najdeme, nastavφme ho jako aktußlnφ stav jinak se ukazatel nastavφme na NULL.
Jak u₧ bylo °eΦeno v²Üe, metoda DetectCollision()
vracφ TRUE pokud se skuteΦn² obdΘlnφk spritu a
obdΘlnφk urΦen² parametrem metody p°ekr²vajφ, jinß vrßtφ
FALSE.
BOOL CSprite::DetectCollision(CRect *
pCollisionRect)
{
CRect FirstRect;
// Get real
rectangle of this sprite
GetRealRect(&FirstRect);
// Check
intersection both of rectangles
return FirstRect.IntersectRect(&FirstRect
, pCollisionRect);
}
Pou₧ijeme k tomu metodu IntersectRect(), kterß pracuje p°esn∞ tak jako metoda DetectCollision().
Nßsledujφcφ metody slou₧φ funkci DrawSprite().
Prvnφ z nich vracφ cφlov² obdΘlnφk tj. urΦuje mφsto, kam se bude kreslit. Op∞t
samoz°ejm∞ otestuje aktußlnφ stav a potΘ p°φmo naplnφ vstupnφ obdΘlnφk daty.
Hornφ lev² roh je urΦen² pozicφ spritu a dolnφ lev² roh je posunut² o Üφ°ku a
v²Üku jednoho polφΦka. Pokud je n∞co v nepo°ßdku, metoda naplnφ obdΘlnφk
hodnotami -1, tak₧e m∙₧eme testovat sprßvnost metody.
void CSprite::GetDestin(CRect* rcDestin)
{
if(m_pCurState || rcDestin) {
*rcDestin = CRect( m_ptPos.x,
m_ptPos.y,
m_ptPos.x + m_pCurState->GetWidth(),
m_ptPos.y + m_pCurState->GetHeight() );
}
else {
*rcDestin = CRect(-1, -1, -1, -1);
}
}
Druhß metoda vracφ zdrojov² obdΘlnφk pro vykreslenφ tj. urΦuje mφsto odkud se
bude vykreslovat. Po otestovßnφ aktußlnφho stav inicializujeme vstupnφ obdΘlnφk.
Pozice na ose x je zßvislß na aktußlnφ fßzi animace. Pozice na ose y je
konstantnφ. Princip obou metod jsme ji₧ mnohokrßt rozebφrali.
void CSprite::GetSource(CRect* rcSource)
{
if(m_pCurState || rcSource) {
*rcSource = CRect( m_iCurrentPhase
* m_pCurState->GetWidth(),
0,
(m_iCurrentPhase + 1) * m_pCurState->GetWidth(),
m_pCurState->GetHeight() );
}
else {
*rcSource = CRect(-1, -1, -1, -1);
}
}
Poslednφ t°φ metody se zab²vajφ skuteΦn²m obdΘlnφk spritu. Prvnφ z t∞chto metod
jsou vlastn∞ standardnφ SET metody, kterΘ inicializujφ skuteΦn² obdΘlnφk bu∩
pomocφ Φty° celoΦφseln²ch hodnot nebo pomocφ objektu CRect.
void CSprite::SetRealRect(int iLeft, int
iTop, int iRight, int iBottom)
{
m_rcRealRect = CRect(iLeft, iTop, iRight, iBottom);
}
void CSprite::SetRealRect(CRect * pRealRect)
{
m_rcRealRect = *pRealRect;
}
Naopak metoda GetRealRect() je o trochu slo₧it∞jÜφ.
Abychom nemuseli explicitn∞ volat metody SetRealRect(),
je za°φzeno, ₧e po prvnφm volßnφ metody GetRealRect()
je tento obdΘlnφk nastaven standardn∞ tj. na obdΘlnφk spritu. Prvnφ volßnφ
poznßme podle -1, kterou jsme nastavili v konstruktoru. PotΘ ji₧ metoda
pokraΦuje normßln∞ a vracφ skuteΦn² obdΘlnφk, kter² je posunut² do mφsta spritu.
void CSprite::GetRealRect(CRect* pRealRect)
{
// Check validity
of real rect
if(m_rcRealRect.left == -1 && m_pCurState) {
//
// Init standard real rect from size
of tile
m_rcRealRect = CRect(0, 0, m_pCurState->GetWidth(),
m_pCurState->GetHeight());
}
*pRealRect = m_rcRealRect + m_ptPos;
}
V dneÜnφm p°φkladu tedy najdete novou knihovnu Engine.dll, kterß ji₧ obsahuje t°φdy CSprite a CSpriteState. P°φklad si m∙₧ete stßhnout v sekci Downloads.
P°φÜt∞ se podφvßme na ostatnφ t°φdy naÜeho grafickΘho menu CGMenu atd. K≤d z dneÜnφ lekce si m∙₧ete libovoln∞ upravit podle vlastnφch pot°eb a m∙₧ete ho vyu₧φt i v jin²ch grafick²ch aplikacφch. Prßv∞ proto je d∙le₧itΘ psßt obecnΘ t°φdy, kterΘ postupn∞ budete specializovat ve t°φdßch odvozen²ch.
Doufßm, ₧e se vßm dneÜnφ lekce lφbila a t∞Üφm se p°φÜt∞ nashledanou.