Piccolo approfondimento per chi nell’era del 3D vuole realizzare qualcosa di 2D

Voglio fare 2D


Guardando sugli scaffali dei negozi non si possono avere dubbi, il 3D domina il mercato, rilegando i poveri titoli bidimensionali all’angolino meno visitato. Tuttavia non tutto è perso, alcuni titoli, 2D da capo a piedi, si confondono in mezzo agli antagonisti con una dimensione in più.
Eh si, perché non tutti i giochi sono adatti ad essere rappresentati in tre dimensioni, soprattutto per problemi di giocabilità; i giochi d’abilità, gli shotem’up, le simulazioni alla SimCity risultano sicuramente più gradevoli in 2 dimensioni.
Riguardo a SimCity, c’è da dire che l’ultimo episodio (4), è una miscela ben riuscita delle due tecnologie; al terreno tridimensionale si affiancano delle costruzioni 2D, che possiedono un dettaglio veramente eccezionale, giocare per credere…
Prima di andare sul tecnico, è d’obbligo aggiungere che il 2D torna utile anche nei videogiochi 3D, non vorrete mica lasciare il giocatore senza un misero pannello che lo informi sul suo stato, quello della sua nave, ecc..

Nel mondo del 3D
Poco meno di un mese fa (dall’inizio del tutorial…) decido di imparare (finalmente) un po’ di C/C++, così, contento come uno scolaretto al primo giorno di scuola (uhm, non è che fossi tanto contento al primo giorno di elementari..), recupero la confezione del VC++ abbandonata nell’armadio dall’università, scarico il mega SDK delle DirectX ed installo il tutto.
Tra me e me penso, dopo tanti anni di Delphi e Java sarà uno scherzo impararlo e fare il porting di qualche giochetto che ho da parte, niente di complicato; un simil Tetris, un clone di Arkanoid, un altro giochetto stile Worms, tutti accomunati dalla grafica 2D.
Istallato il tutto incomincio a familiarizzare con l’ambiente ed il linguaggio, faccio le prime prove ed incomincio a cercare un esempio nel SDK, che mi dia le basi per disegnare sullo schermo con DirectDraw, ma… come mai non ci sono esempi ? Uhm, affiorano alla mente vaghi ricordi di una rivoluzione copernicana iniziata con la versione 8 delle DirectX, mi fiondo su google ed incomincio a cerca informazioni, giro un paio di siti, guardo quelli di settore e… ecco !
La Microsoft ha detto addio al supporto nativo per il 2D, il simil-comunicato in inglese che ho d’avanti dice più o meno “le schede grafiche di oggi sono fatte per muovere 3D, il 2D te lo simuli”.

Iniziamo bene, mi hanno appena soffiato una facile conversione da sotto il naso, ma non mi arrendo per così poco. Torno un po’ in dietro con la mente, quando con un gruppo di amici conosciuti su IRC stavo realizzando il motore che doveva muovere un RPG; uno dei primissimi problemi che incontrammo riguardava la tecnologia da utilizzare, meglio il vecchio è consolidato 2D, nel vero senso del termine, o simulare il 2D appiccicando le “tile” a mo di “texture”, come avevano iniziato a fare in alcuni giochi di allora (Diablo II e NOD se non sbaglio).

All’epoca la scelta, per vari motivi cadde sul 2D, probabilmente non eravamo(ero) pronti ad un utilizzare una tecnologia simile, la documentazione era davvero nulla, e chi doveva fare la grafica impallidiva al pensiero di avere una dimensione in più, anche se alla fine non cambiava poi molto.

La soluzione
Come suddetto, il 2D può essere simulato via 3D, ed oggi, se sviluppate con le DirectX, diventa pressoché obbligatorio, non vorrete mica usare un’interfaccia (quella delle DirectDraw) deprecata ? Naaa…

Prima di spiegare come avviene la simulazione, ragioniamo un momento su come funziona il 2D “classico”; il frame visualizzato viene composto in un area di memoria non visibile (buffer), appiccicando delle immagini o pezzi di esse, provenienti da file sul disco o meglio da altri buffer (per questioni di velocità), alla fine il contenuto del buffer viene “blittato" sull’area di memoria che la scheda video proietta sullo schermo.
Insomma, il concetto “classico” di 2D è alla portata di tutti, l’unica unità di misura con cui si combatte è il pixel (correggetemi se sbaglio), e la materia trattata sono le immagini.

Utilizzando le tecnologie 3D moderne, tutti questi concetti se ne vanno al creatore, primo fra tutti quello di “immagine”, che viene soppiantato da quello di “texture”. Per spiegare ad un grafico abituato al 2D il concetto di “texture”; direi che essa è “una superficie dove si trovano una o più immagini affiancate tra loro, ed eventualmente la relativa mappa Alpha”, ovvero qualcosa di simile nell’utilizzo pratico ai buffer secondari che si utilizzavano nel 2D, per caricare in memoria le immagini utilizzate nel gioco.
Avrete sicuramente notato nella descrizione di “texture”, l’accenno alla mappa Alpha, contenuta nell’omonimo canale; questa serve per gestire le trasparenze, permettendo di avere sino a 256 livelli (8 bit) diversi di trasparenza su ogni pixel della texture. Per chiarire le idee, immaginate di avere sul canale Alpha una copia in scala di grigi della texture, dove i pixel neri danno all’immagine sottostante piena opacità e quelli bianchi piena trasparenza, ovviamente le sfumature rappresentano i valori intermedi.

Volendo si potrebbe usare un singolo colore per rappresentare la trasparenza, nel 2D “classico” si utilizza solitamente il magenta (RGB 255,0,255) od il nero, ma volete mettere il vantaggio di avere a disposizione ben 256 livelli diversi di trasparenza ???

Se siete ancora vivi, andatevi a prendere un caffè, perché ora incominciamo ad entrare nella parte più consistente della questione.

Un po’ di codice
Finita la lunga introduzione teorica, possiamo incominciare a buttare giù il codice necessario per costruire le basi del nostro videogioco 2D in D3D. Tutti gli esempi che riporterò di seguito sono pensati per il Microsoft VC++, per motivi di tempo (e di esperienza) non posso scrivere i cambiamenti necessari per farli funzionare su altri ambienti/compilatori, comunque non è un’operazione difficile da eseguire, anche per chi è alle prime armi.
Se potete, scaricate il SDK delle DirectX 9 (o una versione più recente), contiene la documentazione ufficiale, esempi utili per iniziare ed un framework già pronto, che non fa sicuramente male a chi non può o non ha il tempo per realizzarne uno proprio da zero.

Inizializzazione
Prima di tutto dobbiamo attivare una superficie D3D sulla quale poter disegnare, nel SDK trovate un esempio pronto (Tut01_CreateDevice), ma vi consiglio di rifarlo seguendo il prossimo paragrafo, in modo da capire passo per passo tutto il codice in esso contenuto.

Includiamo il file con gli header di D3D e creiamo le variabili necessarie ad avviarlo:



  1. #include <d3d9.h>
  2. LPDIRECT3D9             g_pD3D       = NULL; // Usata per creare il device D3D
  3. LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; // Device di rendering


Adesso la funzione di inizializzazione vera e propria:



  1. HRESULT InitD3D( HWND hWnd )
  2. {
  3.    if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) return E_FAIL;
  4.    D3DPRESENT_PARAMETERS d3dpp;
  5.    ZeroMemory( &d3dpp, sizeof(d3dpp) );
  6.    d3dpp.Windowed = TRUE;
  7.    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  8.    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
  9.    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
  10.                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
  11.                                      &d3dpp, &g_pd3dDevice ) ) )
  12.    {
  13.        return E_FAIL;
  14.    }
  15.    return S_OK;
  16. }


Il primo IF tenta di inizializzare il device D3D, se non vi riesce termina la funzione restituendo E_FAIL. La funzione Direct3DCreate9 restituisce NULL in caso di fallimento o un puntatore all’interfaccia IDirect3D9 in caso di successo. D3D_SDK_VERSION serve al compilatore per verificare se si stanno utilizzando gli header corretti, per ulteriori informazioni:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/functions/direct3dcreate9.asp

Di seguito troviamo i parametri che verranno passati alla funzione CreateDevice, per creare il device di rendering vero è proprio.

D3DPRESENT_PARAMETERS è una struttura capace di contenere tutte le informazioni riguardanti il device che andremo a creare, per conoscerla in dettaglio:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/structures/d3dpresent_parameters.asp

La funzione ZeroMemory serve a riempire di zeri l’area di memoria occupata da una variabile, in questo caso quella di d3dpp, per ulteriori informazioni:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/memory/base/zeromemory.asp

Le tre linee di codice successivo settano dei parametri di d3dpp, che come detto contiene i dati (D3DPRESENT_PARAMETERS) dl nostro device, la creazione di un device in finestra, il comportamento sui buffer ed infine il formato del buffer, in questo caso con D3DFMT_UNKNOWN gli diciamo che non lo conosciamo, lasciando che se ne occupi D3D. In futuro potremo stabilire a priori il formato da utilizzare, per ulteriori informazioni:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/enums/d3dformat.asp

Infine, passiamo tutto a CreateDevice che a scanso di errori, creerà finalmente un device sul quale poter renderizzare quello che più ci aggrada ! Velocemente i arametri non visti:

Per informazioni sulla funzione CreateDevice:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3d9/CreateDevice.asp

Rispettando la sequenza nell’esempio fornito da Microsoft, sequenza che tutto sommato mi sembra utile ai fini dell’apprendimento, ecco la procedura per “chiudere” il device che abbiamo creato con tanti sforzi:



  1. VOID Cleanup()
  2. {
  3.    if( g_pd3dDevice != NULL)
  4.        g_pd3dDevice->Release();
  5.    if( g_pD3D != NULL)
  6.        g_pD3D->Release();
  7. }


Mi sembra che non ci sia molto da dire, controlliamo se le due principali variabili (per il momento abbiamo solo quelle…) esistono ed eventualmente passiamo loro il metodo Release(); che si occupa per noi di compiere le operazioni necessarie per ripulire la memoria utilizzata.



  1. VOID Render()
  2. {
  3.    if( NULL == g_pd3dDevice ) return;
  4.    // Cancella il BackBuffer ed inposta il colore blue
  5.    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
  6.    
  7.    // Inizio della scena
  8.    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
  9.    {
  10.        // Disegnamo allegramente
  11.    
  12.        // Fine della scena 9
  13.        g_pd3dDevice->EndScene();
  14.    }
  15.    // Flippa il contenuto del BackBuffer sulla superficie primaria (va sullo schermo…)
  16.    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
  17. }


La funzione Render() controlla che esista un device inizializzato, cancella il contenuto del BackBuffer, prova a creare una nuovo frame della scena (in questo caso vuota) e quindi copia il contenuto del BackBuffer sulla superficie primaria, che corrisponde all’immagine visibile sullo schermo.
Ci troviamo di fronte a quattro nuovi metodi del device (LPDIRECT3DDEVICE9), esaminiamoli uno per uno, velocemente:

Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
Cancella l’intera area del buffer o una porzione rettangolare dello stesso, attribuendogli un colore nel formato RGBA (Red Green Blue Alpha), per conoscere meglio il metodo:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3ddevice9/Clear.asp

BeginScene() / EndScene();
rispettivamente aprono e chiudono l’area in cui inserire il codice per disegnare la scena.

Present( NULL, NULL, NULL, NULL );
presenta (è educato) il contenuto del backbuffer sullo schermo, o meglio, per essere precisi lo copia nella porzione di memoria della scheda video a cui è associata l’area visibile sullo schermo.
I parametri sono nell’ordine:

Per approfondire:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3ddevice9/Present.asp

Per ulteriori informazioni sull’interfaccia IDirect3DDevice9:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3ddevice9/_idirect3ddevice9.asp

Il nostro videogioco, o meglio, la base da cui potremo svilupparlo, funziona dentro una “normale” finestra di Windows, di conseguenza è necessaria la funzione per decifrare i messaggi:



  1. LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
  2. {
  3.    switch( msg )
  4.    {
  5.        case WM_DESTROY:
  6.            Cleanup();
  7.            PostQuitMessage( 0 );
  8.            return 0;
  9.        case WM_PAINT:
  10.            Render();
  11.            ValidateRect( hWnd, NULL );
  12.            return 0;
  13.    }
  14.    return DefWindowProc( hWnd, msg, wParam, lParam );
  15. }


Per il momento consideriamo i messaggi di uscita e disegno, all’interno di questo ultimo troviamo un richiamo alla funzione Render() precedentemente esaminata. Dato che questo tutorial è rivolto all’uso di D3D9 non vado oltre nella spiegazione, se no diventa un libro sul C++ :-P

Concludiamo la nostra applicazione di prova con WinMain che, come saprete è il punto di inizio (entry point) per le applicazioni Windows, per ulteriori informazioni:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/dnw98bk/html/thewinmainprocedure.asp

Ed ecco il codice:



  1. INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
  2. {
  3.    // Registro la classe
  4.    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
  5.                      GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
  6.                      \"D3D Tutorial\", NULL };
  7.    RegisterClassEx( &wc );
  8.    // Creo la finestra dell'applicazione
  9.    HWND hWnd = CreateWindow( \"D3D Tutorial\", \"D3D Tutorial 01: CreateDevice\",
  10.                              WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
  11.                              GetDesktopWindow(), NULL, wc.hInstance, NULL );
  12.    // Provo ad inizializzare Direct3D
  13.    if( SUCCEEDED( InitD3D( hWnd ) ) )
  14.    {
  15.        // Mostro la finestra
  16.        ShowWindow( hWnd, SW_SHOWDEFAULT );
  17.        UpdateWindow( hWnd );
  18.        // Avvio il loop dei messaggi, bruttissimo per un videogame, poi lo miglioreremo…
  19.        MSG msg;
  20.        while( GetMessage( &msg, NULL, 0, 0 ) )
  21.        {
  22.            TranslateMessage( &msg );
  23.            DispatchMessage( &msg );
  24.        }
  25.    }
  26.    UnregisterClass( \"D3D Tutorial\", wc.hInstance );
  27.    return 0;
  28. }


L’unica cosa interessante hai fini dell’articolo è InitD3D, che tenta di inizializzare le DirectX. Come da commento, il ciclo per la gestione dei messaggi non può andare bene per un videogioco, dato che non garantisce uno sviluppo lineare del tempo, vedremo in seguito come sostituirla con una più adeguata, per il momento possiamo accontentarci.

A questo punto, siamo pronti per provare ad avviare la nostra applicazione di prova. Avviamo il VC++, selezioniamo file->new e scegliamo di creare una nuova Win32 Application, andiamo avanti nella procedura con Empty Application.

Di nuovo selezioniamo file- new ed aggiungiamo un file sorgente C++ Source File alla nostra applicazione, su cui copieremo il codice sin ora esaminato.

Proviamo a compilare il tutto, se il SDK delle DirectX è stato istallato correttamente, il compilatore non dovrebbe restituire errori, possiamo quindi provare ad avviare la nostra applicazione e vedere che… il linker restituisce diversi errori.

Andiamo in Project->Settino clicchiamo su Link ed aggiungiamo d3d9.lib all’inizio della riga contrassegnata da Object/Library Modules. Controlliamo inoltre che siano inseriti i path corretti ai file del SDK, in tools->options->directories, in “Include files” deve esserci una voce tipo C:\DXSDK\INCLUDE e C:\DXSDK\LIB sotto “Library files”.

Proviamo nuovamente ad eseguire l’applicazione, che adesso dovrebbe partire senza problemi, mostrando una finestra riempita di blu al suo interno.

Trovate maggiori informazioni (in inglese, come il resto del MSDN), sull’esempio di inizializzazione che vi ho spiegato, al seguente indirizzo:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/programmingguide/ tutorialsandsamplesandtoolsandtips/tutorials/1/tutorial1.asp

Setup dello schermo
Ora che la base del nostro videogioco è funzionante, possiamo finalmente passare alla parte più pratica, dove vedremo come impostare lo schermo, caricare una texture in memoria ed usare parti di essa per comporre una scena nel BackBuffer.

Dobbiamo innanzi tutto, dire a D3D che vogliamo usare un certo formato per visualizzare la scena, vediamo il codice:



  1. void SetupScreen(float WindowWidth, float WindowHeight)
  2. {
  3.    D3DXMATRIX matOrtho;
  4.    D3DXMATRIX matIdentity;
  5.    
  6.    D3DXMatrixOrthoLH(&matOrtho, WindowWidth, WindowHeight, 0.0f, 1.0f);
  7.    D3DXMatrixIdentity(&matIdentity);
  8.    g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matOrtho);
  9.    g_pd3dDevice->SetTransform(D3DTS_WORLD, &matIdentity);
  10.    g_pd3dDevice->SetTransform(D3DTS_VIEW, &matIdentity);
  11.    // Disabilitiamo lo z-buffer e le luci, non ci servono
  12.    g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
  13.    g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
  14. }


In questa funzione incontriamo la struttura D3DXMATRIX, che contiene i dati descrittivi della matrice 3D usata da D3D, per avere un’idea di quante cose possa contenere:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/structures/d3dxmatrix.asp

Le due matrici che abbiamo dichiarato (matOrtho e matIdentity), vengono quindi popolate dalle due funzioni:

D3DXMatrixOrthoLH(&matOrtho, WindowWidth, WindowHeight, 0.0f, 1.0f);
Costruisce una matrice di proiezione ortogonale left-handed, popolando la struttura D3DXMATRIX appena vista. I parametri sono, per l’appunto un puntatore ad una struttura D3DXMATRIX, larghezza e altezza del volume visibile (quella su cui disegneremo), valore minimo e massimo sull’asse zeta del suddetto volume. Per ulteriori informazioni fate riferimento a:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/functions/math/d3dxmatrixortholh.asp

D3DXMatrixIdentity
Crea una identity matrix (matrice identità), ovvero una matrice in cui gli elementi sulla diagonale hanno valore 1 e gli altri 0. Tale matrice ha la caratteristica di non mutare la scena a cui viene applicata, ci tornerà utile al momento di effettuare trasformazioni sui vertici, per informazioni vedi:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/functions/math/d3dxmatrixidentity.asp


Il codice seguente setta alcuni parametri fondamentali della visualizzazione, rivediamolo:



  1. g_pd3dDevice->SetTransform;(D3DTS_PROJECTION, &matOrtho;);
  2.  g_pd3dDevice->SetTransform;(D3DTS_WORLD, &matIdentity;);
  3.  g_pd3dDevice->SetTransform;(D3DTS_VIEW, &matIdentity;);


Il metodo SetTransform, modifica il modo in cui D3D applicherà certe trasformazioni, nel nostro caso:


SetTransform
http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3ddevice9/SetTransform.asp

D3DTRANSFORMSTATETYPE
http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/enums/d3dtransformstatetype.asp

Il metodo SetRenderState, modifica i parametri con cui D3D eseguirà il rendering, nel nostro caso abbiamo cambiato lo stato di D3DRS_ZENABLE su D3DZB_FALSE per disattivare lo z-buffer, e D3DRS_LIGHTING su FALSE per disattivare l’illuminazione di D3D.

SetRenderState
http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/interfaces/idirect3ddevice9/SetRenderState.asp

D3DRENDERSTATETYPE
http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3d/enums/d3drenderstatetype.asp


Caricare una texture
Lo schermo è pronto, ma non abbiamo niente da disegnarci, vediamo quindi di creare una funzione per poter caricare in modo pratico una texture in memoria:



  1. LPDIRECT3DTEXTURE9 LoadTexture(char *filename,D3DCOLOR colorkey = 0xFF000000)
  2. {
  3.     LPDIRECT3DTEXTURE9 pd3dTexture;
  4.     D3DXIMAGE_INFO SrcInfo; // Opzionale
  5.     D3DXCreateTextureFromFileEx(g_pd3dDevice, filename, 0, 0, 0, 0,
  6.          D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT,
  7.          colorkey, &SrcInfo , NULL, &pd3dTexture);
  8.     // Controlli, prossimamente…
  9.     return pd3dTexture;
  10. }


Per creare una nuova texture da un file, come si vede, chiamiamo la funzione D3DXCreateTextureFromFileEx, passandogli I seguenti argomenti:

D3DXCreateTextureFromFileEx Function
http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/functions/texture/d3dxcreatetexturefromfileex.asp


Per compilare il codice appena visto è necessario includere D3DX9Math.h, quindi aggiungiamo all’inizio del codice:



  1. #include <D3DX9Math.h>


Bisogna inoltre aggiungere la libreria d3dx9dt.lib al clinker, esattamente come abbiamo fatto all’inizio per d3d9.lib.


La funzione Blit
Il nostro “piccolo motore” è quasi pronto, manca la funzione più importante, quella per spostare le texture sullo schermo (non è proprio così, ma il risultato finale è questo), e la funzione per gestire il rendering.

Iniziamo quindi con la funzione per emulare il blit di DirectDraw:




  1. HRESULT BltSpriteEx( RECT *pDestRect, LPDIRECT3DTEXTURE9 pSrcTexture,
  2.               RECT *pSrcRect, DWORD mFlag, D3DCOLOR modulate = 0xFFFFFFFF,
  3.               float rotation = 0, POINT *prcenter = NULL )
  4. {
  5.     D3DXVECTOR2 scaling(1,1), rcenter(0,0), trans(0,0);
  6.     // Traslazione (posizione sullo schermo)
  7.     if (pDestRect) {
  8.          trans.x = (float) pDestRect->left;
  9.          trans.y = (float) pDestRect->top;
  10.     }
  11.     // Centro per la rotazione
  12.     if (prcenter) {
  13.          rcenter.x = (float) prcenter->x;
  14.          rcenter.y = (float) prcenter->y;
  15.     } else if (pSrcRect) {
  16.   // Setto il centro della rotazione al centro dell'oggetto
  17.          rcenter.x = (float) (pSrcRect->right - pSrcRect->left) / 2;
  18.          rcenter.y = (float) (pSrcRect->bottom - pSrcRect->top) / 2;
  19.     }
  20.     // Scala - Se le area di sorgente/destinazione hanno dimensioni diverse,
  21.     // provvedo a scalare l'imamgine
  22.     if (pDestRect && pSrcRect) {
  23.          scaling.x = ((float) (pDestRect->right - pDestRect->left)) /
  24.               ((float) (pSrcRect->right - pSrcRect->left));
  25.          scaling.y = ((float) (pDestRect->bottom - pDestRect->top)) /
  26.               ((float) (pSrcRect->bottom - pSrcRect->top));
  27.     }
  28.     // Eseguo l'eventuale mirror
  29.     if (pSrcRect&&mFlag) {
  30.          if (mFlag&MIRRORLEFTRIGHT) {
  31.               scaling.x = -scaling.x;
  32.               trans.x += (float) (pDestRect->right - pDestRect->left);
  33.     }
  34.          if (mFlag&MIRRORUPDOWN) {
  35.               scaling.y = -scaling.y;
  36.               trans.y += (float) (pDestRect->bottom - pDestRect->top);
  37.          }
  38.     }
  39.     return pd3dxSprite->Draw( pSrcTexture, pSrcRect, &scaling, &rcenter, rotation, &trans, modulate );
  40. }


La funzione inizia con la dichiarazione di tre variabili di tipo D3DXVECTOR2, struttura che contiene le due coordinate x/y in formato float:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/structures/d3dxvector2.asp

Le operazioni che seguono sono relativamente semplici sino a quando non incontriamo mFlag, un attributo che si rende utile per indicare se l’immagine deve essere specchiata sul piano orizzonatale (MIRRORLEFTRIGHT) e/o su quello verticale (MIRRORUPDOWN). Non essendo parte delle DirectX, dobbiamo dichiarare noi stessi i valori possibili per mFlag, prima di poterli usare:



  1. #define MIRRORLEFTRIGHT 0x01
  2.  #define MIRRORUPDOWN 0x02


Infine, arriviamo al punto chiave di questa funzione, il metodo Draw dell’interfaccia ID3DXSprite:



  1. HRESULT Draw(      
  2.    LPDIRECT3DTEXTURE9 pSrcTexture,
  3.    CONST RECT *pSrcRect,
  4.    CONST D3DXVECTOR2 *pScaling,
  5.    CONST D3DXVECTOR2 *pRotationCenter,
  6.    FLOAT Rotation,
  7.    CONST D3DVECTOR2 *pTranslation,
  8.    D3DCOLOR Color
  9. );


Abbiamo già esaminato in precedenza le strutture utilizzate da questo metodo, pertanto non mi dilungo ulteriormente e vi rimando per un’ulteriore approfondimento ad MSDN:

http://msdn.microsoft.com/library/default.asp?url= /library/en-us/directx9_c/directx/graphics/reference/d3dx/interfaces/id3dxsprite/Draw.asp

Prima di continuare una nota “ottimizzativa”, il metodo Draw deve essere richiamato tra i metodi Begin & End, sempre della struttura ID3DXSprite. Se non si procede in tal senso, vengono richiamati automaticamente ad ogni utilizzo del metodo Draw, con un conseguente spreco di risorse, la funzione Render() diverrà quindi:



  1. VOID Render()
  2. {
  3.    if( NULL == g_pd3dDevice ) return;
  4.    // Cancella il BackBuffer ed inposta il colore blue
  5.    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
  6.    
  7.    // Inizio della scena
  8.    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
  9.    {
  10.        // Disegnamo allegramente
  11. D3DXCreateSprite(g_pd3dDevice,&pd3dxSprite);
  12. SetRect(&rect1,1,1,256,256);
  13. SetRect(&rect2,1,1,256,256);
  14. pd3dxSprite->Begin();
  15. BltSpriteEx(&rect1, g_pTexture, &rect2, NULL);
  16. // Blt...
  17. // Blt...
  18. // Blt...
  19. pd3dxSprite->End();
  20.    
  21.        // Fine della scena
  22.        g_pd3dDevice->EndScene();
  23.    }
  24.    // Flippa il contenuto del BackBuffer sulla superficie primaria (va sullo schermo…)
  25.    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
  26. }



Gestiamo meglio il tempo
Come suddetto, il loop per smistare i messaggi di Windows, così com’è non fa proprio al caso nostro, quindi commentiamolo ed aggiungiamo una nuova chiamata:



  1. /*
  2. MSG msg;
  3. while( GetMessage( &msg, NULL, 0, 0 ) )
  4. {
  5. TranslateMessage( &msg );
  6. DispatchMessage( &msg );
  7. }
  8. */
  9.  GameLoop();


Il problema fondamentale è che il “loop standard” è pensato per attendere smistando i messaggi, sino a quando non ne viene intercettato uno diretto all’applicazione stessa. Questo sistema funziona perfettamente per le applicazioni, che ragionano in termini di “eventi”, per un videogioco però, come già detto non può funzionare, perché basato non sugli eventi ma sul tempo, vediamo quindi la funzione GameLoop():



  1. void GameLoop()
  2. {
  3.    MSG msg;
  4.    BOOL fMessage;
  5. PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
  6. while(msg.message != WM_QUIT)
  7. {
  8. fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
  9. if(fMessage)
  10. {
  11. // Processo i messaggi
  12. TranslateMessage(&msg);
  13.              DispatchMessage(&msg);
  14.         }
  15.   else
  16. {
  17. // Non ci sono messaggi, via al rendering !
  18. Render();
  19. }
  20. }
  21. }


Come potete vedere, la differenza sostanziale è nell’impiego del tempo in cui il precedente loop rimaneva in attesa, per effettuare il nostro rendering. Questo è solo un piccolo passo nella gestione del tempo, più che sufficiente comunque per i nostri scopi.


Concludendo
Se non ho commesso troppi errori, questo breve tutorial, che più che altro è un “diario di bordo”, scritto in contemporanea con lo studio delle DirectX stesse, dovrebbe risultarvi d’aiuto per cominciare a scrivere il vostro primo gioco 2D in 3D.
Vi invito a continuare in questa direzione visitando i link che troverete di seguito, e consultando l’ottima MSDN di Microsoft, che al dire il vero, purtroppo, difetta un po’ sull’argomento trattato da questo testo.

Buon divertimento !


Bibliografia & link utili

page top



RSS XML ATOM