DirectX (15.)

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.

15.1. Knihovna Engine

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:

15.2. T°φda CSprite

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.

12.1.1. Detekce kolize

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∙).

12.1.2. CSpriteState

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.

15.2. NovΘ t°φdy

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:

  1. Je t°eba zadat nulovΘ ID, proto₧e podle tohoto ID zjistφme, ₧e objekt nebyl inicializovßn.

  2. Dßle je pot°eba vynulovat pozici a stav, do kterΘho se sprite dostane po skonΦenφ aktußlnφho stavu.

  3. 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.


15.3. Zßv∞r

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.

Ji°φ Formßnek