DirectX (14.)

Ve dvou p°edeÜl²ch lekcφch jsme rozebφrali celou knihovnu Display.dll, kterß obsahuje t°φdy CDisplay a CSurface. Tyto dv∞ t°φdy jsou jßdrem naÜeho grafickΘho systΘmu. Dnes na to navß₧eme a p°idßme novou knihovnu Input.dll. Tuto knihovnu jsme ji₧ napsali, staΦφ tedy pouze vlo₧it existujφcφ k≤d do naÜeho projektu.

1. Knihovna Input.dll

Tuto knihovnu jsme podrobn∞ rozebφrali v 9. a 10. lekci kurzu DirectX. Mimo jinΘ jsme si °ekli, ₧e bude nutnΘ p°ed∞lat systΘm vykreslovanφ kurzoru. A prßv∞ to nßs Φekß v dneÜnφ lekci.

V prvnφm kroku tedy p°idejme knihovnu Input.dll do stßvajφcφho projektu z minul²ch lekcφ. Zkopφrujte tedy adresß° projektu Input do adresß°e Game. V tomto adresß°i budeme mφt tedy Common, Display, Input, Game a adresß°e Debug a Release. PotΘ z Visual C++ upravφme nßÜ projekt. Z menu Project vyberte polo₧ku Insert Project into Workspace. Vyberte soubor Input.dsp v adresß°i Input a v Classview se objevφ naÜe znßmß knihovna Input.dll.

Nynφ nastavme zßvislosti projekt∙ tak, aby projekt Input mohl vyu₧φt Display a projekt Game mohl vyu₧φt Input. Nastavte dialog Dependencies takto:

Pokud te∩ oznaΦφte jako aktivnφ projekt Game a zkompilujete ho, zkompilujφ  se projekty postupn∞ v po°adφ Display, Input a Game.

Abychom mohli volat funkce u Input.dll musφte vlo₧it hlaviΦkov² soubor Input1.h do hlavnφho souboru projektu Game:

#include "..\Input\Input1.h"
 

Samoz°ejm∞ musφme brßt v ·vahu to, ₧e projekt Input je ve svΘm adresß°i.

Nynφ do funkce WinMain() dopl≥te volßnφ funkce inpCreateDirectInputSystem() a p°edejte si nßsledujφcφ parametry:

Pokud si vzpomφnßte, tak p°i tvorb∞ Input.dll jsme nepou₧φvali knihovnu Common.dll. Kdy₧ se podφvßte na k≤d, tak zjistφte, ₧e mφsto makra DXTRACE pou₧φvßme obyΦejnΘ TRACE apod. Pro sprßvnou funkci nenφ t°eba toto p°ed∞lßvat, ale bude dobrΘ, kdy₧ t∞chto pßr drobnostφ takΘ upravφme. Musφme proto jen vlo₧it Common.h do souboru Input1.cpp. Dßle musφte v nastavenφ projektu p°ilinkovat knihovnu Common.dll. Ud∞lßme to ·pln∞ stejn∞ jako u ostatnφch projekt∙ (nezapome≥te na Release):

Nynφ tedy p°epiÜte makra TRACE na makra DXTRACE nebo DXERR podle toho, jestli chceme rozluÜtit nßvratovou hodnotu nebo ne. Jß jsem vÜude krom∞ jednoho p°φpadu pou₧il makro DXERR. Nezapome≥te vymazat znaky \n, proto₧e od°ßdkovßnφ se provßdφ automaticky a takto byste od°ßdkovali 2x.

2. ┌prava vykreslenφ kurzoru

Nynφ se budeme v∞novat hlavnφmu tΘmatu dneÜnφ kapitoly. Jsme nßroΦnφ a chceme pou₧φvat i animovanΘ kurzory. CelΘ je to o trochu slo₧it∞jÜφ a v²sledek vypadß lΘpe.

Pou₧ijeme k tomu zcela novou t°φdu CCursor, kterß bude pracovat s DirectDraw. Tato t°φda se bude starat o vytvo°enφ a vykreslenφ kurzoru na obrazovku. Zde je deklarace t°φdy:

class CCursor
{
    BOOL m_bInit;
    int m_iWidth;
    int m_iHeight;
    CString m_csIntPath;

    CSurface m_surCursor;
    int m_iPhase;
    int m_iSpeed;
    int m_nPhases;
    DWORD m_dwOldTime;

public:
    HRESULT Create(CString csIntPath, int cx, int cy);
    HRESULT Create(CString csIntPath, int cx, int cy, int nSpeed);
    HRESULT Update(CPoint ptCursor);

public:
    CCursor();
    ~CCursor();

};

Je to velice jednoduchΘ. V nßsledujφcφ tabulce vidφte popis prom∞nn²ch.
 
Typ prom∞nnΘ JmΘno prom∞nnΘ Popis
int m_iWidth èφ°ka jednoho polφΦka kurzoru
int m_iHeight V²Üka jednoho polφΦka kurzoru
CString m_csIntPath Cesta k bitmap∞ kurzoru
CSurface m_surCursor Povrch kurzoru
int m_iPhase Aktußlnφ sekvence animovanΘho kurzoru
int m_iSpeed AnimaΦnφ rychlost v milisekundßch
int m_nPhases Celkov² poΦet animaΦnφch krok∙
DWORD m_dwOldTime Uplynul² Φas od poslednφho vykreslenφ


Dßle si proberme metody:
 
JmΘno metody a parametry Popis
Create(CString csIntPath, int cx, int cy) Tato verze metody Create() vytvß°φ statick² kurzor z bitmapy csIntPath a velikosti cx a cy.
Create(CString csIntPath, int cx, int cy, int nSpeed) Druhß verze je ji₧ zajφmav∞jÜφ. Vytvß°φ animovan² kurzor z bitmapy csIntPath a velikosti jednoho polφΦka cx a cy. AnimaΦnφ rychlost je urΦena parametrem nSpeed.
Update(CPoint ptCursor) Tato funkce vykreslφ kurzor na po₧adovan²ch sou°adnicφch.

Konstruktor a destruktor t°φdy v seznamu neuvßdφm, ale to je samoz°ejmost. T°φdu m∙₧ete p°idat pomocφ Visual Studia nebo m∙₧ete vyu₧φt p°φkladu u lekce.

Animovan² kurzor je ulo₧en v bitmap∞ v tomto formßtu:

╚ernß okolnφ barva se oznaΦφ jako pr∙hlednß. JednotlivΘ sekvence se m∞nφ po uplynutφ doby urΦenΘ parametrem iSpeed. Dßle parametry cx a cy urΦujφ velikost jednoho polφΦka. Z t∞chto hodnot se takΘ vypoΦφtß, kolik mß kurzor animaΦnφch sekvencφ. Rozeberme si nynφ detailn∞ji co se odehrßvß v metodßch t°φdy CCursor.

Nejprve nßsleduje prvnφ verze metody Create():

HRESULT CCursor::Create(CString csIntPath, int cx, int cy)
{
    DWORD dwRet = 1;
    if(!m_bInit) {

        m_iWidth = cx;
        m_iHeight = cy;
        m_csIntPath = csIntPath;

        dwRet = m_surCursor.Create(csIntPath, DDFLG_COLORKEY);
        if(dwRet != ERROR_SUCCESS) {
           DXERR("Cannot create surface for cursor due", dwRet);
           return dwRet;
        }
        m_bInit = true;
        dwRet = ERROR_SUCCESS;
    }
    return dwRet;
}

Metoda je krßtkß a srozumitelnß. Ulo₧φme si vstupnφ parametry a vytvo°φme povrch pro bitmapu csIntPath. To je celΘ.

Druhß verze je jen o mßlo slo₧it∞jÜφ:

HRESULT CCursor::Create(CString csIntPath, int cx, int cy, int iSpeed)
{
    DWORD dwRet = 1;
    if(!m_bInit) {
      
 //
        // Call standard creation

        dwRet = Create(csIntPath, cx, cy);
        if(dwRet == ERROR_SUCCESS) {
            m_iPhase = 0;
            m_iSpeed = iSpeed;
           
//
            // Compute number of phases

            m_nPhases = (int)m_surCursor.Width() / cx;
           
//
            // Check validity of phases

            if((m_nPhases * cx) > (int)m_surCursor.Width()) m_nPhases--;
          
 //
            // Ok

            m_bInit = true;
        }
    }
    return dwRet;
}

Nejprve zavolßme prvnφ verzi metody Create() (tφm vytvo°φme povrch pro bitmapu a ulo₧φme parametry kurzoru). Dßle objekt p°ipravφme pro spuÜt∞nφ animace tj. vynulujeme poΦφtadlo aktußlnφ sekvence, nastavφme animaΦnφ rychlost a vypoΦteme, kolik sekvencφ mß naÜe bitmapa. To provedeme jednoduÜe: vyd∞lφme Üφ°ku bitmapy Üφ°kou jednoho polφΦka. Proto₧e pot°ebujeme celΘ Φφslo a bitmapa nemusφ b²t p°esn∞ d∞litelnß (m∞la by b²t), musφme zkontrolovat zda-li poΦet sekvencφ odpovφdß i zp∞tn∞. Zkrßtka vynßsobφme vypoΦtenou hodnotu Üφ°kou jednoho polφΦka a zjistφme zda-li tato hodnota nep°ekraΦuje povrch kurzoru, to by toti₧ zp∙sobilo fatßlnφ chybu p°i vykreslovßnφ. Pokud tomu tak skuteΦn∞ je, tak jen snφ₧φme poΦet sekvencφ o jedniΦku (horÜφ u₧ to b²t toti₧ nem∙₧e).

Nakonec si popφÜeme vykreslovacφ metodu Update(), kterß je u₧ slo₧it∞jÜφ:

HRESULT CCursor::Update(CPoint ptCursor)
{
    CRect rcDest, rcSrc;
    DWORD dwRet = 1, newTime;
    if(m_bInit) {
       
//
        // For static cursors

        if(m_nPhases == 0) {
            //
            // One static place
            rcSrc.top = 0;
            rcSrc.left = 0;
            rcSrc.right = m_iWidth;
            rcSrc.bottom = m_iHeight;
        }
     
  //
        // Compute move source rectangle

        else {
           
//
            // Compute phase, increment phase if is time

            newTime = GetTickCount();
           
//
            // Get time from last update

            if((newTime - m_dwOldTime) > (DWORD)m_iSpeed) {
              
 //
                // If it right time, increment phase

                m_iPhase++;
               
//
                // Check to overflow phases

                if(m_iPhase == m_nPhases) {
                    m_iPhase = 0;
                }
               
//
                // Save time of last update

                m_dwOldTime = newTime;
            }
            //
          
 // Compute rectangle from current phase
            //
            // Static vertical postion

            rcSrc.top = 0;
            rcSrc.bottom = m_iHeight;
            // Variable horizontal pos.
            rcSrc.left = m_iPhase * m_iWidth;
            rcSrc.right = rcSrc.left + m_iWidth;
        }
     
  //
        // Destionation rectangle is same for both cursors
     
  rcDest.left = ptCursor.x;
        rcDest.top = ptCursor.y;
        rcDest.bottom = rcDest.top + m_iHeight;
        rcDest.right = rcDest.left + m_iWidth;
 
       //
        // Blit cursor to back surface
 
      dwRet = disBlt(rcDest, rcSrc, &m_surCursor);
    }
    return dwRet;
}

 

Metoda je vlastn∞ jednoduchß jen pro statickΘ kurzory. Pokud je toti₧ poΦet animaΦnφch krok∙ nulov², jednß se o statick² kurzor. Je-li tomu tak, staΦφ jen zinicializovat zdrojov² a cφlov² obdΘlnφk pro funkci disBlt(). Zdrojov² je p°esn∞ velk² jako bitmapa. Rozm∞ry jsme si ulo₧ili do prom∞nn²ch m_iWidth a m_iHeight. Cφlov² obdΘlnφk je poka₧dΘ stejn² a je urΦen mφstem, kde mß b²t kurzor vykreslen. Hornφ lev² roh je p°φmo to mφsto a dolnφ prav² je jen posunuty o velikost polφΦka.

U animovanΘho kurzoru je jin² v²poΦet zdrojovΘho obdΘlnφka, proto₧e ten se musφ pohybovat po bitmap∞. V Φßsti urΦenΘ jen pro animovanΘ kurzory tedy za prvΘ modifikujeme aktußlnφ fßzi a za druhΘ inicializujeme tento pohybliv² obdΘlnφk. Fßzi v₧dy m∞nφme po uplynutφ Φasu m_iSpeed, dßle musφme kontrolovat hornφ mez urΦenou prom∞nnou m_iPhases. P°i ka₧dΘ zm∞n∞ navφc musφme ulo₧it aktußlnφ Φas, abychom mohli poΦφtat Φasov² rozdφl do dalÜφ zm∞ny. Za druhΘ tedy vypoΦteme zdrojov² obdΘlnφk. Ve vertikßlnφm sm∞ru je vlastn∞ stejn² jako u statickΘho kurzoru, proto₧e polφΦka jsou v bitmap∞ ulo₧eny horizontßln∞. Horizontßlnφ pozice zßle₧φ na aktußlnφ fßzi animace. Lev² okraj je tedy jen vynßsobenφ tΘto fßze a Üφ°ky polφΦka. Prav² okraj je oproti levΘmu posunut² o Üφ°ku polφΦka. Zbytek metody je toto₧n².

Nynφ pouze staΦφ vytvo°it ve t°φd∞ CInput jeden objekt typu CCursor a zavolat jeho metodu Create(). Dßle musφte upravit metodu UpdateCursor():


HRESULT CInput::UpdateCursor()
{
    HRESULT dwRet = 1;
    if(m_bInit && m_bShowCursor) {

        m_theCursor.Update(m_ptCursor);
    }
    return dwRet;
}

Nynφ je metoda velice jednoduchß. Prost∞ jen zavolßme metodu kurzoru Update(), ale jen je-li kurzor viditeln².

TIP: P°edeÜl²m postupem jsme vytvo°ili jeden kurzor, co₧ je dosti nepru₧nΘ. Zkuste si ud∞lat dynamickΘ pole kurzor∙, potΘ vytvo°te metodu CInput::LoadCursor(DWORD, CString), kterß nahraje kurzor do tohoto pole. Parametr DWORD je n∞jakΘ ID kurzoru. Dßle ud∞lejte metodu SetCursor(DWORD), kterß vybere kurzor z tohoto pole podle ID a nastavφ ho jako aktußlnφ kurzor, kter² se bude vykreslovat.

Nakonec jeÜt∞ malinko upravφme projekt Game, aby bylo vid∞t, ₧e systΘm DirectInput pracuje:

void UpdateFrame()
{
    disUpdateBackground();

   
inpProcessInput();

   
// Pri stisknuti klavesy Esc ukoncime aplikaci
    if(inpIsKeyDown(DIK_ESCAPE, FALSE)) {
        PostMessage(g_hWnd, WM_DESTROY, 0, 0);
    }


   
inpUpdateCursor();

    disPresent();
}

Do funkce UpdateFrame() p°idejte barevn² k≤d. Nynφ se vßm bude zobrazovat animovan² kurzor a budete moci pou₧φt vÜechny exportovanΘ funkce t°φdy CInput. Pro p°φklad vyu₧φvßm klßvesu Esc jako v²stupnφ klßvesu z programu.

Poznßmka:  JeÜt∞ nezapome≥te vymazat nepot°ebnΘ slo₧ky t°φdy CInput. Je to atribut HICON m_hCursor a metoda SetCursor(HICON).

3. Zßv∞r

DneÜnφ lekce byla pro zm∞nu trochu kratÜφ, ale urΦit∞ byla velk²m p°φnosem pro nßÜ projekt. Mo₧nß by se mohlo zdßt, ₧e p°φÜt∞ u₧ nebudeme mφt co d∞lat, ale opak je pravdou, proto₧e p°φÜt∞ ji₧ koneΦn∞ zaΦneme s tφm grafick²m menu. Doufßm, ₧e to bude zajφmav∞jÜφ.

DneÜnφ p°φklad si samoz°ejm∞ budete moci stßhnout v rubrice Downloads.

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

Ji°φ Formßnek