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().
V datovΘ Φßsti t°φdy bude urΦit∞ poloha spritu na obrazovce. Dßle by m∞l mφt sprite, kter² se mß pohybovat po obrazovce, aktußlnφ rychlost ve vertikßlnφm a horizontßlnφm sm∞ru. S t∞mito dv∞ma atributy se pojφ maximßlnφ rychlost, kterΘ m∙₧e sprite dosßhnout. M∙₧e zde b²t aktußlnφ fßze animace, celkov² poΦet animaΦnφch kroku a rychlost animace. Poslednφ dva atributy mohou b²t platnΘ jen pokud se jednß o animaΦnφ sprite. Navφc si m∙₧ete p°idat aktußlnφ stav spritu, podle kterΘho urΦφte, co mß ten kter² sprite zrovna d∞lat.

Kdy₧ to celΘ dßme dohromady, vznikne takov²to nßvrh t°φdy CSprite:
 

Atributy

 

 

Typ atributu

Nßzev prom∞nnΘ

Popis

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

int

m_iCurrentPhase

Aktußlnφ fßze animace

int

m_iPhasesCount

PoΦet animaΦnφch krok∙

int

m_iAnimationSpeed

Rychlost animace

DWORD

m_dwLastUpdate

╚as poslednφ zm∞ny animace

DWORD

m_dwState

Aktußlnφ stav spritu

int

m_iWidth

èφ°ka jednoho animovanΘho polφΦka v pixelech

int

m_iHeight

V²Üka jednoho animovanΘho polφΦka v pixelech

CDisplay*

m_pDisplay

Ukazatel na nßÜ objekt CDisplay, kter² jsme vytvo°ili minule

CSurface

m_surSpriteSurface

Objekt zdrojovΘho povrchu pro sprite

Metody

 

 

Typ nßvratovΘ hodnoty

JmΘno a parametry

Popis

HRESULT

CreateStaticSprite(LPCSTR szSpriteBitmap, CPoint ptInitPos)

Inicializuje objekt statickΘho spritu

HRESULT

CreateAnimatedSprite(LPCSTR szSpriteBitmap, CPoint ptInitPos)

Inicializuje objekt animovanΘho spritu

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

DWORD

GetState()

Vracφ aktußlnφ stav spritu

void

GetState(DWORD dwState)

Nastavφ aktußlnφ stav spritu

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

SetPosition(CPoint ptPos)

Zm∞nφ pozici spritu. Tato funkce m∙₧e mφt dv∞ varianty.

CPoint

GetPosition(void)

Vrßtφ aktußlnφ pozici spritu.

Nynφ m∙₧eme p°istoupit k deklaraci t°φdy:


class CSprite
{
    CPoint m_ptPos;
    int m_iVelX;
    int m_iVelY;
    int m_iMaxVel;
    int m_iCurrentPhase;
    int m_iPhasesCount;
    int m_iAnimationSpeed;
    DWORD m_dwLastUpdate;
    DWORD m_dwState;
    int m_iWidth;
    int m_iHeight;

    CDisplay *m_pDisplay;
    CSurface *m_psurSpriteSurface;

public:
    HRESULT CreateStaticSprite(LPSTR szSpriteBitmap, CPoint ptInitPos);
    HRESULT CreateAnimatedSprite(LPSTR szSpriteBitmap, CPoint ptInitPos, int iPhasesCount, int iAnimationSpeed);
    HRESULT DrawSprite();
    HRESULT MoveSprite();
    HRESULT ChangeAnimationPhase();
    //
    // Inline functions

    DWORD GetState() {return m_dwState;}
    void SetState(DWORD dwState) {m_dwState = dwState;}
    void SetVelX(int VelX) {m_iVelX = VelX;}
    void SetVelY(int VelY) {m_iVelY = VelY;}
    void SetPosition(CPoint ptPos) {m_ptPos = ptPos;}
    void SetPosition(int x, int y) {m_ptPos = CPoint(x, y);}
    CPoint GetPosition() {return m_ptPos;}
    int GetVelX() {return m_iVelX;}

public:
    CSprite();
    ~CSprite();
};

Vidφte, ₧e staΦφ implementovat pouze 5 metod, proto₧e ostatnφ jsou inline funkce. Pus¥me se tedy do nich.

1) CreateStaticSprite()

HRESULT CSprite::CreateStaticSprite(LPSTR szSpriteBitmap, CPoint ptInitPos)
{
    DWORD dwRet = ERROR_SUCCESS;
    DDSURFACEDESC2 dsdesc;
    dsdesc.dwSize = sizeof(DDSURFACEDESC2);
    //
    // Set position

    m_ptPos = ptInitPos;
    //
    // Create surface for sprite

    dwRet = m_pDisplay->CreateSurfaceFromBitmap(&m_psurSpriteSurface, szSpriteBitmap, 0, 0);
    if(dwRet != DD_OK) {
        TRACE("Cannot create sprite surface due %d\n", dwRet);
        return dwRet;
    }
    //
    // Set color key on surface. Black transparent color.

    dwRet = m_psurSpriteSurface->SetColorKey(0);
    if(dwRet != DD_OK) {
        TRACE("Cannot set color key on surface due %d\n", dwRet);
        return dwRet;
    }
    //
    // Get width and height of created surface

    dwRet = m_psurSpriteSurface->GetDDrawSurface()->GetSurfaceDesc(&dsdesc);
    if(dwRet != DD_OK) {
        TRACE("Cannot get surface info due %d\n", dwRet);
        return dwRet;
    }
    //
    // Set width and height members of sprite

    m_iWidth = dsdesc.dwWidth / m_iPhasesCount;
    m_iHeight = dsdesc.dwHeight;

    return dwRet;
}

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()

HRESULT CSprite::CreateAnimatedSprite(LPSTR szSpriteBitmap, CPoint ptInitPos, int iPhasesCount, int iAnimationSpeed)
{
    DWORD dwRet = ERROR_SUCCESS;
    //
    // Set animaiont atributes

    m_iPhasesCount = iPhasesCount;
    m_iAnimationSpeed = iAnimationSpeed;
    //
    // Create static sprite

    dwRet = CreateStaticSprite(szSpriteBitmap, ptInitPos);
    if(dwRet != DD_OK) {
        TRACE("Cannot create static part of sprite due %d\n", dwRet);
    }
    return dwRet;
}

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()

HRESULT CSprite::DrawSprite()
{
    DWORD dwRet = ERROR_SUCCESS;
    CRect rcSrc;
    //
    // Set source rect

    rcSrc.left  = m_iCurrentPhase * m_iWidth;
    rcSrc.right = (m_iCurrentPhase + 1) * m_iWidth;
    rcSrc.top = 0;
    rcSrc.bottom = m_iHeight;
    //
    // Draw sprite at specified location

    dwRet = m_pDisplay->ColorKeyBlt(m_ptPos.x, m_ptPos.y, m_psurSpriteSurface->GetDDrawSurface(), &rcSrc);
    if(dwRet != DD_OK) {
        TRACE("Cannot render sprite due %d\n", dwRet);
    }
    return dwRet;
}

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()

HRESULT CSprite::MoveSprite()
{
    DWORD dwRet = ERROR_SUCCESS;

    m_ptPos += CPoint(m_iVelX, m_iVelY);

    return dwRet;
}

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()

HRESULT CSprite::ChangeAnimationPhase()
{
    DWORD dwRet = ERROR_SUCCESS;
    DWORD dwNewTime = GetTickCount();
    //
    // Wait for right time to change phase

    if(int(dwNewTime - m_dwLastUpdate) > m_iAnimationSpeed) {
        //
        // Increment phase

        m_iCurrentPhase++;
        //
        // Check upper bound

        if(m_iCurrentPhase == m_iPhasesCount) {
            m_iCurrentPhase = 0;
        }
        // Remember last time of update
        m_dwLastUpdate = dwNewTime;
    }
    return dwRet;
}
 

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°φklad

I 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∞r

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


T∞Üφm se p°φÜt∞ nashledanou.

Ji°φ Formßnek