🤔 Para Refletir :
"Todos projetos não são unicamente meu, pois cada sugestão e critica, recursos e sugestões fazem me ver que ele leva um pedaço de cada pessoa nele"
- riquecamarg0

Criando um motor de jogo em C++ e DirectX - Parte 2/Final

Raijenki

Novato
Membro
Membro
Juntou-se
29 de Junho de 2015
Postagens
16
Bravecoins
1
Olá!
Este tutorial tem como objetivo a criação do esqueleto de uma engine 2D utilizando C++/DirectX no Windows. A parte 1, que buscava discutir um pouco de tecnologia (enrolar), encontra-se aqui.

Sem enrolação porque já me arrependi de escrever isso, vamos lá.

Sumário
1. Configurando o Visual Studio
2. Iniciando o Win32
3. Constantes e Tratamento de Erros
4. Inputs
5. Gráficos, Imagens e Texturas
6. Um Jogo Genérico
7. As Regras do Pokémon
8. Adicionando mais coisas e polindo o jogo


Configurando o Visual Studio
Bom, na parte 1, avisei que era necessário ter instalado os kits de desenvolvimento do DX e do Windows 7. Não tem muito mistério, basta baixar e apertar nos botões de seguir em frente. O que eu falo aqui é pro Visual Studio 2010 Express, mas serve também para outras IDEs como Eclipse e tudo mais. O objetivo inicial aqui é inserirmos todas as libs relacionadas ao Windows 7 e DX para o compilador usá-las (afinal, as libs não ficam por padrão inseridas no linker do compilador).

Crie um projeto e vá em Project e, logo em seguida, Properties. No topo da nova janela, sete a configuração para "Active" (posteriormente, repita todo o processo aqui para a Debug também).
Agora expanda a opção Linker e vá para a opção General. Em Additional Library Directories, insira, antes de todos os outros, a linha "$(DXSDK_DIR)\Lib\x86;" (sem aspas). Já na opção "Input", no campo Additional Dependencies, é necessário incluir as seguintes libs (no início de tudo): "d3d9.lib;d3dx9.lib;winmm.lib;xinput.lib;"Agora na opção C/C++ no menu, a qual você deve expandir, siga para General e em Additional Include Directories, insira "$(DXSDK_DIR)\Include;".

Pronto, o Visual Studio está perfeitamente configurado para compilar aplicações que usem o Win32 e o DirectX.  Se você quiser, pode incluir as libs da versão x64 também, mas só recomendo fazer isso se souber as consequências.

Iniciando o Win32
Win32 é uma API que permite você se utilizar de funções para fazer janelas, botões e afins no Windows. Vale lembrar que ela é uma função quase legada, visto que se você quiser um aplicativo Metro, conforme é no Windows 8/10, você utiliza outras tecnologias (como XAML, até onde sei).

De qualquer forma, o primeiro passo aqui é setar as configurações que queremos da janela e depois chamar a função de inicialização.  Como existem muitos headers para adicionar, utilizamos a diretiva WIN32_LEAN_AND_MEAN que contém as seguintes informações:

Código:
#ifndef WIN32_LEAN_AND_MEAN
    #include <cderr.h>
    #include <dde.h>
    #include <ddeml.h>
    #include <dlgs.h>
    #ifndef _MAC
        #include <lzexpand.h>
        #include <mmsystem.h>
        #include <nb30.h>
        #include <rpc.h>
    #endif
    #include <shellapi.h>
    #ifndef _MAC
        #include <winperf.h>
        #include <winsock.h>
    #endif
    #ifndef NOCRYPT
        #include <wincrypt.h>
        #include <winefs.h>
        #include <winscard.h>
    #endif

    #ifndef NOGDI
        #ifndef _MAC
            #include <winspool.h>
            #ifdef INC_OLE1
                #include <ole.h>
            #else
                #include <ole2.h>
            #endif /* !INC_OLE1 */
        #endif /* !MAC */
        #include <commdlg.h>
    #endif /* !NOGDI */
#endif /* WIN32_LEAN_AND_MEAN */

Começando, crie um arquivo .cpp no Visual Studio; pode chamá-lo de como quiser, aqui vamos chamar de winmain.cpp. Vou inserindo o código por partes e discutindo o que cada trecho faz.

Código:
#define _CRTDBG_MAP_ALLOC       // detectar memory leaks
#define WIN32_LEAN_AND_MEAN

#include <Windows.h>
#include <stdlib.h>             // detectar memory leaks
#include <crtdbg.h>             // detectar memory leaks
#include "pokemon.h"

// Protótipos de funções
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int); 
bool CreateMainWindow(HWND &, HINSTANCE, int);
LRESULT WINAPI WinProc(HWND, UINT, WPARAM, LPARAM); 

// Ponteiros
Pokemon *game = NULL;
HWND hwnd = NULL;

A primeira diretiva e a biblioteca <crtdbg.h> tem como funções a detecção de possíveis memory leaks na versão debug do código. Por mais que não tenhamos, no futuro, ao adicionar mais funções, pode ser interessante investigá-los. O pokemon.h trata-se de nosso "Jogo Específico" (discutido mais a frente) e a stdlib.h é a biblioteca padrão da linguagem C que, se você está lendo isso, já a deve conhecer.

Quanto aos protótipos de funções, existem coisas interessantes aqui. Todo programa Windows é inicializado pela função WinMain (diferente do "main" padrão em C ou Java, de forma que esta não existe aí). A WinProc recebe todos os comandos inseridos por nós (cliques, teclas e afins) e a CreateMainWindow é apenas uma função que será mais discutida a frente e tem a finalidade de gerar a janela de fato.

Código:
//=============================================================================
// Ponto inicial para uma aplicação Windows
//=============================================================================
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    // Verificando por memory leaks se estivermos compilando uma versão DEBUG
    #if defined(DEBUG) | defined(_DEBUG)
        _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    #endif

    MSG msg;

    // Crie o jogo, configura o mensageiro
    game = new Pokemon;

    // Cria a janela principal
    if (!CreateMainWindow(hwnd, hInstance, nCmdShow))
        return 1;

    try{
        game->initialize(hwnd);     // Dá GameError

        // Loop principal de mensagem
        int done = 0;
        while (!done) {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                // procurar por uma mensagem de saída
                if (msg.message == WM_QUIT)
                    done = 1;

                // decodar e passar mensagem para o WinProc
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            } else
                game->run(hwnd);    // rodar o loop do jogo
        }
        SAFE_DELETE (game);     // limpar memória antes de sair
        return msg.wParam;
    }
    catch(const GameError &err) {
        game->deleteAll();
        DestroyWindow(hwnd);
        MessageBox(NULL, err.getMessage(), "Error", MB_OK);
    }
    catch(...){
        game->deleteAll();
        DestroyWindow(hwnd);
        MessageBox(NULL, "Erro desconhecido aconteceu no jogo.", "Error", MB_OK);
    }

    SAFE_DELETE (game);     // limpar memória antes de sair
    return 0;
}

Vamos tentar destrinchar o que está acontecendo aqui, mas não vou me aprofundar muito em detalhes sobre a API ou isso daria um livro. O nosso jogo funciona no esquema de loops: enquanto o loop estiver rodando (ou seja, o while(!done) for verdadeiro), as ações continuam sendo sempre monitoradas. Se há uma mensagem (input, pedido de saída, etc), então isso é passado posteriormente para o WinProc que, posteriormente, vai retornar uma função que será executada no jogo.

Existe tratamento de erros aí que é meio intuitivo o que está acontecendo; o GameError é tratado mais a frente.
O código para o Safe_Delete (inserido em um constants.h em breve) é o seguinte:
Código:
#define SAFE_DELETE(ptr)       { if (ptr) { delete(ptr); (ptr)=NULL; } }

Por fim, nosso winmain.cpp termina da seguinte forma:
Código:
//=============================================================================
// Função de callback da janela
//=============================================================================
LRESULT WINAPI WinProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) {
    return (game->messageHandler(hwnd, msg, wParam, lParam));
}

//=============================================================================
// Cria a janela
// Em caso de erro, retorna falso
//=============================================================================
bool CreateMainWindow(HWND &hwnd, HINSTANCE hInstance, int nCmdShow) { 

    WNDCLASSEX wcx; 
 
    // Preenche a janela com a estrutura de parâmetros que descrevem a janela principal
    wcx.cbSize = sizeof(wcx);           // tamanho da estrutura
    wcx.style = CS_HREDRAW | CS_VREDRAW;    // redesenhar se o tamanho mudar na horizontal ou vertical
    wcx.lpfnWndProc = WinProc;          // aponta para o procedimento WinProc 
    wcx.cbClsExtra = 0;                 // sem memória extra de classe 
    wcx.cbWndExtra = 0;                 // sem memória extra de classe  
    wcx.hInstance = hInstance;          // handle to instance 
    wcx.hIcon = NULL; 
    wcx.hCursor = LoadCursor(NULL,IDC_ARROW);   // cursor predefinido
    wcx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);    // bg negro 
    wcx.lpszMenuName =  NULL;           // nome do menu
    wcx.lpszClassName = CLASS_NAME;     // nome da classe da janela 
    wcx.hIconSm = NULL;                 // ícone da janela pequeno
 
    // Registra a classe da janela
    // RegisterClassEx retorna 0 em erro.
    if (RegisterClassEx(&wcx) == 0)    // se erro
        return false;

    // configurar a janela em janelado ou fullscreen?
    DWORD style;
    if (FULLSCREEN)
        style = WS_EX_TOPMOST | WS_VISIBLE | WS_POPUP;
    else
        style = WS_OVERLAPPEDWINDOW;

    // Gerar janela
    hwnd = CreateWindow(
        CLASS_NAME,             // nome da classe da janela
        GAME_TITLE,             // texto da barra de título
        style,                  // estilo da janela
        CW_USEDEFAULT,          // posição horizontal padrão da janela
        CW_USEDEFAULT,          // posição vertical padrão da janela
        GAME_WIDTH,             // largura of window
        GAME_HEIGHT,            // altura of the window
        (HWND) NULL,            // sem janela pai
        (HMENU) NULL,           // sem menu
        hInstance,              // lidar para o parâmetro da aplicação
        (LPVOID) NULL);         // sem parâmetros de janela

    // se houve um erro ao criar a janela
    if (!hwnd)
        return false;

    if(!FULLSCREEN) {            // se janelado 
        // Ajustar tamanho da janela para que a área fique GAME_WIDTH x GAME_HEIGHT
        RECT clientRect;
        GetClientRect(hwnd, &clientRect);   // get size of client area of window
        MoveWindow(hwnd,
                   0,                                           // Esquerda
                   0,                                           // Cima
                   GAME_WIDTH+(GAME_WIDTH-clientRect.right),    // Direita
                   GAME_HEIGHT+(GAME_HEIGHT-clientRect.bottom), // Baixo
                   TRUE);                                       // Redesenhar janela
    }

    // Mostra a janela
    ShowWindow(hwnd, nCmdShow);

    return true;
}
O código está bem comentado e é legível por si só, mas vale explicar alguns pontos. WNDCLASSEX é basicamente uma estrutura que define as janelas do windows por padrão. É necessário preenchê-la de parâmetros e registrá-la para que o Windows a reconheça como um processo em execução (neste caso, como sistema operacional mesmo). O que definirá o estilo da janela em si é a função CreateWindow (do próprio Windows) que está recheado de parâmetros ali, inclusive mostrando sempre que qualquer ação deve ser dirigida ao WinProc, que já foi discutido anteriormente.

Por fim, a função que exibe a janela de fato é a ShowWindow. Se você precisa escondê-la, basta enviar o parâmetro SW_HIDE.

Constantes e Tratamento de Erros
Antes de prosseguirmos, é interessante definir algumas constantes que usaremos mais a frente e funções para tratamento de erros. Lembrando sempre que a necessidade dessas constantes, diferente aqui, vai aparecendo com o tempo; porém, o "passo-a-passo" ficaria demasiado longo. O objetivo aqui é entender o que há por trás de um motor de jogo, afinal.

Criemos agora um arquivo constants.h.
Começamos com as prediretivas, includes e macros para evitar repetições no compilador:
Código:
#ifndef _CONSTANTS_H   
#define _CONSTANTS_H
#define WIN32_LEAN_AND_MEAN

#include <windows.h>

//-----------------------------------------------
// Macros úteis
//-----------------------------------------------
// Deletar com segurança item (ponteiro) referenciado 
#define SAFE_DELETE(ptr)       { if (ptr) { delete (ptr); (ptr)=NULL; } }
// Liberar com segurança item (ponteiro) referenciado 
#define SAFE_RELEASE(ptr)      { if(ptr) { (ptr)->Release(); (ptr)=NULL; } }
// Deletar com segurança array de ponteiro referenciado
#define SAFE_DELETE_ARRAY(ptr) { if(ptr) { delete [](ptr); (ptr)=NULL; } }
// Chamar com segurança onLostDevice
#define SAFE_ON_LOST_DEVICE(ptr)    { if(ptr) { ptr->onLostDevice(); } }
// Chamar com segurança onResetDevice
#define SAFE_ON_RESET_DEVICE(ptr)   { if(ptr) { ptr->onResetDevice(); } }
#define TRANSCOLOR  SETCOLOR_ARGB(0,255,0,255)  // cor transparente (magenta)

O código em si é autoexplicativo; queremos evitar que memória se acumule sem ser descartada e acabe gerando um overflow. O interessante está na TRANSCOLOR, que basicamente vai definir que cor queremos para a transparência na hora de renderizar nossos sprites/tiles e afins; nesse caso, é a magenta. Se mudássemos tudo para 0, teríamos que preto é equivalente.

Ainda em nosso constants.h:
Código:
//-----------------------------------------------
//                  Constantes
//-----------------------------------------------
// imagens
const char MAP_IMAGE[] = "pictures\\bg.jpg";  // background 
const char CHAR_IMAGE[]   = "pictures\\red.png";   // RED

// configurações da janela
const char CLASS_NAME[] = "Pokemon";
const char GAME_TITLE[] = "Pokemon - Tech Demo 1.0";
const bool FULLSCREEN = false;              // janelado ou fullscreen
const UINT GAME_WIDTH =  640;               // largura do jogo em pixels
const UINT GAME_HEIGHT = 480;               // altura do jogo em pixels
const int  CHAR_START_FRAME = 0;            // frame inicial da animação
const int  CHAR_END_FRAME = 2;              // último frame da animação do personagem
const float CHAR_ANIMATION_DELAY = 0.2f;    // tempo entre frames
const int  CHAR_COLS = 3;                   // textura dos personagens tem duas colunas
const int  CHAR_WIDTH = 16;                 // largura da imagem do personagem
const int  CHAR_HEIGHT = 32;                // altura da imagem do personagem
const float ROTATION_RATE = 180.0f;         // graus por segundo
const float SCALE_RATE = 0.2f;              // % mudança por segundo
const float CHAR_SPEED = 100.0f;            // pixels por segundo
const float CHAR_SCALE = 1.5f;              // escala inicial do personagem

// jogo
const double PI = 3.14159265;
const float FRAME_RATE  = 200.0f;               // o framerate alvo (frames/sec)
const float MIN_FRAME_RATE = 10.0f;             // framerate mínimo
const float MIN_FRAME_TIME = 1.0f/FRAME_RATE;   // tempo mínimo desejado para 1 frame
const float MAX_FRAME_TIME = 1.0f/MIN_FRAME_RATE; // tempo máximo usado nos cálculos

// Teclas
// Usamos simples constantes para o mapeamento de teclas. Se variáveis fossem usadas, seria possível salvar e
// restaurar o mapeamento de teclas de um arquivo de dados.
const UCHAR ESC_KEY      = VK_ESCAPE;       // Esc
const UCHAR ALT_KEY      = VK_MENU;         // Alt 
const UCHAR ENTER_KEY    = VK_RETURN;       // Enter 
const UCHAR CHAR_LEFT_KEY    = VK_LEFT;     // Direcional esquerda
const UCHAR CHAR_RIGHT_KEY   = VK_RIGHT;    // Direcional direita
const UCHAR CHAR_UP_KEY      = VK_UP;       // Direcional cima
const UCHAR CHAR_DOWN_KEY    = VK_DOWN;     // Direcional baixo

#endif

Nosso jogo consiste apenas em um background e um personagem se movimentando - nada mais. De forma a facilitar nossa vida, simplesmente definimos os locais dos arquivos em uma constante. Pode-se fazer isso lendo um arquivo de dados ou similar, por exemplo.

As configurações da janela foram todas chamadas na função CreateWindow, no tópico anterior; tanto faz setá-las como constantes ou invocá-las diretamente pela função, passando-as como parâmetro. Isso é apenas para facilitar o trabalho. Aproveitamos e definimos também a estrutura do sprite de nosso personagem (3 colunas e 4 linhas) e detalhes como FPS e demos nomes mais simples às teclas para o input.

Para fins de informação, este é o sprite que usaremos para o nosso personagem:
i7TfP74yn9gZE.png
O background pode ser qualquer um, mas dei preferência por um em 640x480.

Criemos agora um gameError.h.

Código:
// Classe de Erro jogada pela engine

#ifndef _GAMEERROR_H            
#define _GAMEERROR_H          
#define WIN32_LEAN_AND_MEAN

#include <string>
#include <exception>

namespace gameErrorNS {
    // Códigos de erros
	// Números negativos são erros fatais que podem necessitar que o jogo seja fechado
	// Números positivos são alertas que não requerem que o jogo seja desligado
    const int FATAL_ERROR = -1;
    const int WARNING = 1;
}
Primeiro, um namespace que indica a gravidade do erro; juntamente à biblioteca <exception> do C++.

A partir daqui, de forma a facilitar nosso trabalho, simplesmente criamos uma classe GameError (que herda de exception) e fazemos os construtores e dando overloading nos operadores.

Código:
 class GameError : public std::exception {
private:
    int     errorCode;
    std::string message;
public:
    // construtor padrão
    GameError() throw() :errorCode(gameErrorNS::FATAL_ERROR), message("Erro indefinido no jogo.") {}
    // construtor cópia
    GameError(const GameError& e) throw(): std::exception(e), errorCode(e.errorCode), message(e.message) {}
    // construtor com argumentos
    GameError(int code, const std::string &s) throw() :errorCode(code), message(s) {}
    // operador de taxa
    GameError& operator= (const GameError& rhs) throw() {
        std::exception::operator=(rhs);
        this->errorCode = rhs.errorCode;
        this->message = rhs.message;
    }
    // destrutor
    virtual ~GameError() throw() {};

    // substituir o what da classe base
    virtual const char* what() const throw() { return this->getMessage(); }

    const char* getMessage() const throw() { return message.c_str(); }
    int getErrorCode() const throw() { return errorCode; }
};

#endif

Inputs
Os inputs nada mais são que as entradas do jogo - pode ser mouse, teclado ou mesmo joysticks. A XInput é o componente do DirectX responsável por as entradas. No nosso caso, para demonstrar o poder dele, estaremos adicionando entradas de teclado, mouse e controle de Xbox 360 - o jogo em si só vai chamar o teclado, mas as funções pros outros dos tipos existem.

Criemos um input.h.

Código:
#ifndef _INPUT_H               
#define _INPUT_H               
#define WIN32_LEAN_AND_MEAN

class Input;

#include <windows.h>
#include <WindowsX.h>
#include <string>
#include <XInput.h>
#include "constants.h"
#include "gameError.h"


// para mouse de alta definição
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC      ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE     ((USHORT) 0x02)
#endif

namespace inputNS {
    const int KEYS_ARRAY_LEN = 256;     // tamanho do array de teclas
    
    // what tem valor de clear(), flag de bit
    const UCHAR KEYS_DOWN = 1;
    const UCHAR KEYS_PRESSED = 2;
    const UCHAR MOUSE = 4;
    const UCHAR TEXT_IN = 8;
    const UCHAR KEYS_MOUSE_TEXT = KEYS_DOWN + KEYS_PRESSED + MOUSE + TEXT_IN;
}

const DWORD GAMEPAD_THUMBSTICK_DEADZONE = (DWORD)(0.20f * 0X7FFF);    // padrão 20% de intervalo em zona morta
const DWORD GAMEPAD_TRIGGER_DEADZONE = 30;                      // range de ativação de 0-255
const DWORD MAX_CONTROLLERS = 4;                                // Número máximo de controles suportados

// Bits correspondentes aos botões do gamepad em  state.Gamepad.wButtons
const DWORD GAMEPAD_DPAD_UP        = 0x0001;
const DWORD GAMEPAD_DPAD_DOWN      = 0x0002;
const DWORD GAMEPAD_DPAD_LEFT      = 0x0004;
const DWORD GAMEPAD_DPAD_RIGHT     = 0x0008;
const DWORD GAMEPAD_START_BUTTON   = 0x0010;
const DWORD GAMEPAD_BACK_BUTTON    = 0x0020;
const DWORD GAMEPAD_LEFT_THUMB     = 0x0040;
const DWORD GAMEPAD_RIGHT_THUMB    = 0x0080;
const DWORD GAMEPAD_LEFT_SHOULDER  = 0x0100;
const DWORD GAMEPAD_RIGHT_SHOULDER = 0x0200;
const DWORD GAMEPAD_A              = 0x1000;
const DWORD GAMEPAD_B              = 0x2000;
const DWORD GAMEPAD_X              = 0x4000;
const DWORD GAMEPAD_Y              = 0x8000;

struct ControllerState {
    XINPUT_STATE        state;
    XINPUT_VIBRATION    vibration;
    float               vibrateTimeLeft;    // mSec
    float               vibrateTimeRight;   // mSec
    bool                connected;
};

class Input {
private:
    bool keysDown[inputNS::KEYS_ARRAY_LEN];     
    bool keysPressed[inputNS::KEYS_ARRAY_LEN];  
    std::string textIn;                         
    char charIn;                               
    bool newLine;                              
    int  mouseX, mouseY;                        
    int  mouseRawX, mouseRawY;                  
    RAWINPUTDEVICE Rid[1];                      
    bool mouseCaptured;                         
    bool mouseLButton;                          
    bool mouseMButton;                         
    bool mouseRButton;                          
    bool mouseX1Button;                         
    bool mouseX2Button;                         
    ControllerState controllers[MAX_CONTROLLERS];    

public:
    Input();
    virtual ~Input();
    void initialize(HWND hwnd, bool capture);
    void keyDown(WPARAM);
    void keyUp(WPARAM);
    void keyIn(WPARAM);
    bool isKeyDown(UCHAR vkey) const;

    bool wasKeyPressed(UCHAR vkey) const;
    bool anyKeyPressed() const;

    void clearKeyPress(UCHAR vkey);
    void clear(UCHAR what);
    void clearAll() {clear(inputNS::KEYS_MOUSE_TEXT);}
    void clearTextIn() {textIn.clear();}

    // Retorna entrada de texto como string
    std::string getTextIn() {return textIn;}

    // Retorna último caractere inserido
    char getCharIn()        {return charIn;}

    // Lê a posição do mouse em X, Y
    void mouseIn(LPARAM);
    void mouseRawIn(LPARAM);

    // Salva estado do botão do mouse 
    void setMouseLButton(bool b) { mouseLButton = b; }
    void setMouseMButton(bool b) { mouseMButton = b; }
    void setMouseRButton(bool b) { mouseRButton = b; }
    void setMouseXButton(WPARAM wParam) {mouseX1Button = (wParam & MK_XBUTTON1) ? true:false;
                                         mouseX2Button = (wParam & MK_XBUTTON2) ? true:false;}
										 
    // Retorna posição X do mouse
    int  getMouseX()        const { return mouseX; }

    // Retorna posição Y do mouse
    int  getMouseY()        const { return mouseY; }

    int  getMouseRawX()     const { return mouseRawX; }
    int  getMouseRawY()     const { return mouseRawY; }

    // Retorna estado do botão esquerdo / meio / direito do mouse
    bool getMouseLButton()  const { return mouseLButton; }
    bool getMouseMButton()  const { return mouseMButton; }
    bool getMouseRButton()  const { return mouseRButton; }

    // Retorna estado do botão X1 / X2 do mouse
    bool getMouseX1Button() const { return mouseX1Button; }
    bool getMouseX2Button() const { return mouseX2Button; }

    // Atualiza status de conexão dos controles.
    void checkControllers();

    // Salva entrada de controles conectados.
    void readControllers();

    // Retorna estado especificado de controle.
    const ControllerState* getControllerState(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return &controllers[n];
    }

    // Retorna estado dos n botões do controle.
    const WORD getGamepadButtons(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return controllers[n].state.Gamepad.wButtons;
    }

    // Retorna estado do D-PAD cima / baixo / esquerda / direita / start / back / etc do controle n
    bool getGamepadDPadUp(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return ((controllers[n].state.Gamepad.wButtons&GAMEPAD_DPAD_UP) != 0);
    }

    bool getGamepadDPadDown(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return ((controllers[n].state.Gamepad.wButtons&GAMEPAD_DPAD_DOWN) != 0);
    }

    bool getGamepadDPadLeft(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return ((controllers[n].state.Gamepad.wButtons&GAMEPAD_DPAD_LEFT) != 0);
    }

    bool getGamepadDPadRight(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_DPAD_RIGHT) != 0);
    }

    bool getGamepadStart(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_START_BUTTON) != 0);
    }

    bool getGamepadBack(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_BACK_BUTTON) != 0);
    }

    bool getGamepadLeftThumb(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_LEFT_THUMB) != 0);
    }

    bool getGamepadRightThumb(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_RIGHT_THUMB) != 0);
    }

    bool getGamepadLeftShoulder(UINT n) { 
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_LEFT_SHOULDER) != 0);
    }

    bool getGamepadRightShoulder(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_RIGHT_SHOULDER) != 0);
    }

    bool getGamepadA(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_A) != 0);
    }

    bool getGamepadB(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_B) != 0);
    }

    bool getGamepadX(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_X) != 0);
    }

    bool getGamepadY(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return bool((controllers[n].state.Gamepad.wButtons&GAMEPAD_Y) != 0);
    }

    BYTE getGamepadLeftTrigger(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.bLeftTrigger);
    }

    BYTE getGamepadRightTrigger(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.bRightTrigger);
    }

    SHORT getGamepadThumbLX(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.sThumbLX);
    }

    SHORT getGamepadThumbLY(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.sThumbLY);
    }

    SHORT getGamepadThumbRX(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.sThumbRX);
    }

    SHORT getGamepadThumbRY(UINT n) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        return (controllers[n].state.Gamepad.sThumbRY);
    }

    // Vibra o motor esquerdo / direito do controle n.
    // Esquerda é vibração de baixa frequência, direito é alta frequência.
    // Velocidade 0=desligado, 65536=100 porcento
    // sec é o tempo para vibrar em segundos
    void gamePadVibrateLeft(UINT n, WORD speed, float sec) {
        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        controllers[n].vibration.wLeftMotorSpeed = speed;
        controllers[n].vibrateTimeLeft = sec;
    }

    void gamePadVibrateRight(UINT n, WORD speed, float sec) {

        if(n > MAX_CONTROLLERS-1)
            n=MAX_CONTROLLERS-1;
        controllers[n].vibration.wRightMotorSpeed = speed;
        controllers[n].vibrateTimeRight = sec;
    }

    void vibrateControllers(float frameTime);
};

#endif

Com isso, podemos ir para o input.cpp e inserir o código:
Código:
#include "input.h"

//=============================================================================
// construtor padrão
//=============================================================================
Input::Input() {
    // limpar teclas seguradas (key down)
    for (size_t i = 0; i < inputNS::KEYS_ARRAY_LEN; i++)
        keysDown[i] = false;
    // limpar teclas apertadas (key pressed)
    for (size_t i = 0; i < inputNS::KEYS_ARRAY_LEN; i++)
        keysPressed[i] = false;
    newLine = true;                     // iniciar nova linha
    textIn = "";                        // limpar textIn
    charIn = 0;                         // limpar charIn

    // mouse data
    mouseX = 0;                         // X da tela
    mouseY = 0;                         // Y de tela
    mouseRawX = 0;                      // X de alta definição
    mouseRawY = 0;                      // Y de alta definição
    mouseLButton = false;               // verdade se o mouse esquerdo está apertado
    mouseMButton = false;               // verdade se o botão do meio está apertado
    mouseRButton = false;               // verdade se o mouse direito está apertado
    mouseX1Button = false;              // verdade se o botão X1 do mouse está apertado
    mouseX2Button = false;              // verdade se o botão X2 do mouse está apertado

    for(int i=0; i<MAX_CONTROLLERS; i++)
    {
        controllers[i].vibrateTimeLeft = 0;
        controllers[i].vibrateTimeRight = 0;
    }
}

//=============================================================================
// destrutor
//=============================================================================
Input::~Input() {
    if(mouseCaptured)
        ReleaseCapture();               // libera mouse
}

//=============================================================================
// Inicializar mouse e input do controle
// Setar capture=true para capturar mouse
//=============================================================================
void Input::initialize(HWND hwnd, bool capture) {
    try{
        mouseCaptured = capture;

        // registrar o mouse de alta definição
        Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; 
        Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; 
        Rid[0].dwFlags = RIDEV_INPUTSINK;   
        Rid[0].hwndTarget = hwnd;
        RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]));

        if(mouseCaptured)
            SetCapture(hwnd);           // capturar mouse

        // Limpar estados do controle
        ZeroMemory( controllers, sizeof(ControllerState) * MAX_CONTROLLERS );

        checkControllers();             // Checar por controles conectados
    }
    catch(...) {
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar sistema de input"));
    }
}

//=============================================================================
// Coloca true nos arrays keysDown e keysPressed para esta tecla
// Pre: wParam contém o código virtual da tecla (0--255)
//=============================================================================
void Input::keyDown(WPARAM wParam) {
    // ter certeza que o código da tecla está dentro do buffer
    if (wParam < inputNS::KEYS_ARRAY_LEN) {
        keysDown[wParam] = true;    // atualizar o array keysDown
        // tecla foi "apertada", limpar com o clear()
        keysPressed[wParam] = true; // atualizar o array keysPressed
    }
}

//=============================================================================
// Coloca falso no array keysDown para este tecla
//=============================================================================
void Input::keyUp(WPARAM wParam) {
    // ter certeza que o código da tecla está dentro do buffer
    if (wParam < inputNS::KEYS_ARRAY_LEN)
        // atualizar tabela de estados
        keysDown[wParam] = false;
}

//=============================================================================
// Salva o caractere inserido na string textIn
// Pre: wParam contém o caractere
//=============================================================================
void Input::keyIn(WPARAM wParam) {
    if (newLine) {                        // se o início de uma nova linha
        textIn.clear();
        newLine = false;
    }

    if (wParam == '\b') {                     // se espaço
        if(textIn.length() > 0)             // se caracteres existem
            textIn.erase(textIn.size()-1);  // apagar o último inserido
    }
    else {
    
        textIn += wParam;                   // adicionar caractere ao textIn
        charIn = wParam;                    // salvar último caractere inserido
    }
    if ((char)wParam == '\r')               // se retornar    
        newLine = true;                     // iniciar nova linha
}

//=============================================================================
// Retorna true se a tecla virtual está pressionada; caso contrário, falso.
//=============================================================================
bool Input::isKeyDown(UCHAR vkey) const
{
    if (vkey < inputNS::KEYS_ARRAY_LEN)
        return keysDown[vkey];
    else
        return false;
}

//=============================================================================
// Retorna true se a tecla virtual especificada foi pressionada no frame
// mais recente. Apertos de tecla são apagados no fim de cada frame.
//=============================================================================
bool Input::wasKeyPressed(UCHAR vkey) const {
    if (vkey < inputNS::KEYS_ARRAY_LEN)
        return keysPressed[vkey];
    else
        return false;
}

//=============================================================================
// Retorna true se qualquer  tecla virtual especificada foi apertada no frame mais recente.
// Apertos são apagados após o fim de cada frame.
//=============================================================================
bool Input::anyKeyPressed() const {
    for (size_t i = 0; i < inputNS::KEYS_ARRAY_LEN; i++)
        if(keysPressed[i] == true)
            return true;
    return false;
}

//=============================================================================
// Limpar a tecla apertada especificada
//=============================================================================
void Input::clearKeyPress(UCHAR vkey) {
    if (vkey < inputNS::KEYS_ARRAY_LEN)
        keysPressed[vkey] = false;
}

//=============================================================================
// Limpar os buffers de inputs especificados. 
// Veja input.h para quais valores
//=============================================================================
void Input::clear(UCHAR what) {
    if(what & inputNS::KEYS_DOWN) {
        for (size_t i = 0; i < inputNS::KEYS_ARRAY_LEN; i++)
            keysDown[i] = false;
    }
    if(what & inputNS::KEYS_PRESSED) {
        for (size_t i = 0; i < inputNS::KEYS_ARRAY_LEN; i++)
            keysPressed[i] = false;
    }
    if(what & inputNS::MOUSE) {
        mouseX = 0;
        mouseY = 0;
        mouseRawX = 0;
        mouseRawY = 0;
    }
    if(what & inputNS::TEXT_IN)
        clearTextIn();
}

//=============================================================================
// Lê a posição do mouse em mouseX e mouseY
//=============================================================================
void Input::mouseIn(LPARAM lParam) {
    mouseX = GET_X_LPARAM(lParam); 
    mouseY = GET_Y_LPARAM(lParam);
}

//=============================================================================
// Lê o dado cru do mouse em mouseRawX, mouseRawY
// Esta rotina é compatível com um mouse de alta definição
//=============================================================================
void Input::mouseRawIn(LPARAM lParam) {
    UINT dwSize = 40;
    static BYTE lpb[40];
    
    GetRawInputData((HRAWINPUT)lParam, RID_INPUT, 
                    lpb, &dwSize, sizeof(RAWINPUTHEADER));
    
    RAWINPUT* raw = (RAWINPUT*)lpb;
    
    if (raw->header.dwType == RIM_TYPEMOUSE) {
        mouseRawX = raw->data.mouse.lLastX;
        mouseRawY = raw->data.mouse.lLastY;
    } 
}

//=============================================================================
// Procurar por controles conectados
//=============================================================================
void Input::checkControllers()
{
    DWORD result;
    for( DWORD i = 0; i <MAX_CONTROLLERS; i++) {
        result = XInputGetState(i, &controllers[i].state);
        if(result == ERROR_SUCCESS)
            controllers[i].connected = true;
        else
            controllers[i].connected = false;
    }
}

//=============================================================================
// Ler o estado dos controles conectados
//=============================================================================
void Input::readControllers()
{
    DWORD result;
    for( DWORD i = 0; i <MAX_CONTROLLERS; i++) {
        if(controllers[i].connected) {
            result = XInputGetState(i, &controllers[i].state);
            if(result == ERROR_DEVICE_NOT_CONNECTED)    // se desconectado
                controllers[i].connected = false;
        }
    }
}

//=============================================================================
// Vibrar controles conectados
//=============================================================================
void Input::vibrateControllers(float frameTime) {
    for(int i=0; i < MAX_CONTROLLERS; i++) {
        if(controllers[i].connected) {
            controllers[i].vibrateTimeLeft -= frameTime;
            if(controllers[i].vibrateTimeLeft < 0) {
                controllers[i].vibrateTimeLeft = 0;
                controllers[i].vibration.wLeftMotorSpeed = 0;
            }
            controllers[i].vibrateTimeRight -= frameTime;
            if(controllers[i].vibrateTimeRight < 0) {
                controllers[i].vibrateTimeRight = 0;
                controllers[i].vibration.wRightMotorSpeed = 0;
            }
            XInputSetState(i, &controllers[i].vibration);
        }
    }
}

Este módulo de inputs tem suporte a entradas de teclado, mouse e controles de Xbox 360. No entanto, nosso exemplo utilizará apenas teclado comum. De certa forma, o conteúdo do header - contendo declarações sobre bits no nosso controle acaba sendo mais importante. O nosso módulo de Inputs procura ler apenas as informações de X e Y para o mouse e saber quando uma (ou um conjunto) tecla foi pressionada, sem se preocupar em tomar alguma ação quando certa tecla especificamente for pressionada. Algumas teclas estão mapeadas no constant.h, como o Esc, Alt, dentre outras, que poderemos utilizar mais a frente em outras zonas do jogo.
 
(O outro post não aguentou tanto código, então tive que postar outro)

Gráficos, Imagens e Texturas
Precisamos fazer a parte gráfica de nosso game!
Como disse, vamos trabalhar em cima daquela palheta (ou paleta?) que postei mais acima, com fundo rosa (mas facilmente modificável).

Antes de começar a fazer qualquer coisa, precisamos inicializar o nosso Direct3D no graphics.cpp:
Código:
#include "graphics.h"

//=============================================================================
// Construtor
//=============================================================================
Graphics::Graphics() {
    direct3d = NULL;
    device3d = NULL;
    sprite = NULL;
    fullscreen = false;
    width = GAME_WIDTH;    // altura e largura são constantes
    height = GAME_HEIGHT;
    backColor = graphicsNS::BACK_COLOR;
}

//=============================================================================
// Destrutor
//=============================================================================
Graphics::~Graphics() {
    releaseAll();
}

//=============================================================================
// Liberar tudo
//=============================================================================
void Graphics::releaseAll() {
    SAFE_RELEASE(sprite);
    SAFE_RELEASE(device3d);
    SAFE_RELEASE(direct3d);
}

//=============================================================================
// Inicializar Gráficos do DirectX
// Em erro, joga GameError
//=============================================================================
void Graphics::initialize(HWND hw, int w, int h, bool full) {
    hwnd = hw;
    width = w;
    height = h;
    fullscreen = full;

    //inicializar Direct3D
    direct3d = Direct3DCreate9(D3D_SDK_VERSION);
    if (direct3d == NULL)
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar o Direct3D"));

    initD3Dpp();        // iniciar parâmetros do D3D
    if(fullscreen)      // se fullscreen
    {
        if(isAdapterCompatible())   // o adaptador é compatível
            // setar taxa de refresh com um compatível
            d3dpp.FullScreen_RefreshRateInHz = pMode.RefreshRate;
        else
            throw(GameError(gameErrorNS::FATAL_ERROR, 
            "The graphics device does not support the specified resolution and/or format."));
    }
	
	// determinar se a placa grafica suporta texturação e iluminação por hardware e vertex shaders
    D3DCAPS9 caps;
    DWORD behavior;
    result = direct3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
	// Se o dispositivo não suporta T&L por hardware ou 1.1 vertex shades no hardware,
	// mudar para processamento via software
    if( (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == 0 ||
            caps.VertexShaderVersion < D3DVS_VERSION(1,1) )
        behavior = D3DCREATE_SOFTWARE_VERTEXPROCESSING; 
    else
        behavior = D3DCREATE_HARDWARE_VERTEXPROCESSING;

    // criar o device do Direct3D
    result = direct3d->CreateDevice(
        D3DADAPTER_DEFAULT,
        D3DDEVTYPE_HAL,
        hwnd,
        behavior,
        &d3dpp, 
        &device3d);

    if (FAILED(result))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao criar Direct3D device"));
 
    result = D3DXCreateSprite(device3d, &sprite);
    if (FAILED(result))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao criar sprite do Direct3D"));

}

//=============================================================================
// Inicializar parâmetros do D3D
//=============================================================================
void Graphics::initD3Dpp() {
    try{
        ZeroMemory(&d3dpp, sizeof(d3dpp));              // preencher a estrutura com zero
        // preencher com os parâmetros que precisamos
        d3dpp.BackBufferWidth   = width;
        d3dpp.BackBufferHeight  = height;
        if(fullscreen)                                  // se fullscreen
            d3dpp.BackBufferFormat  = D3DFMT_X8R8G8B8;  // 24 bit color
        else
            d3dpp.BackBufferFormat  = D3DFMT_UNKNOWN;   // usar padrão desktop
        d3dpp.BackBufferCount   = 1;
        d3dpp.SwapEffect        = D3DSWAPEFFECT_DISCARD;
        d3dpp.hDeviceWindow     = hwnd;
        d3dpp.Windowed          = (!fullscreen);
        d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;
    } catch(...)
    {
        throw(GameError(gameErrorNS::FATAL_ERROR, 
                "Erro ao inicializar os parâmetros de apresentação do D3D"));
    }
}

Todo o código acima tem como função apenas inicializar o nosso Direct3D, e em boa parte ele procura as configurações do computador em questão (lembre-se do texto da Parte 1! Foi o grande trunfo da Microsoft!). É óbvio que se você sabe que todos os computadores que rodarão seu jogo suportam coisas como HW T&L, então várias coisas podem ser apagadas daí. Ao mesmo tempo que estamos inicializando o Direct3D, fazemos o "amarramento" à janela que criamos anteriormente em nosso winmain (repare o hwnd, é o nome da janela principal!).

Vamos nos preocupar em carregar sprites na memória e mostrar o backbuffer agora. A ideia de backbuffer é bem simples: fazemos todo o cálculo necessário para o frame antes de ele aparecer na tela de fato, e em seguida executamos o "swap", que é a troca do frontbuffer (que é o que está sendo exibido na tela) pelo "backbuffer". Como tudo isso é feito em questão de milisegundos, temos a sensação de animação na tela.

Código:
//=============================================================================
// Carregar textura na memória padrão do D3D (usos normais de textura)
// Para uso interno da engine apenas. O TextureManager lida com as texturas do jogo.
// Pre: filename é o nome da textura.
//      transcolor é a cor transparente
// Post: altura e largura = tamanho da textura
//       texture aponta para texture
// Retorna HRESULT
//=============================================================================
HRESULT Graphics::loadTexture(const char *filename, COLOR_ARGB transcolor,
                                UINT &width, UINT &height, LP_TEXTURE &texture)
{
    // A estrutura para ler arquivos
    D3DXIMAGE_INFO info;
    result = E_FAIL;

    try{
        if(filename == NULL)
        {
            texture = NULL;
            return D3DERR_INVALIDCALL;
        }
    
        // Pegar altura e largura do arquivo
        result = D3DXGetImageInfoFromFile(filename, &info);
        if (result != D3D_OK)
            return result;
        width = info.Width;
        height = info.Height;
    
        // Criar a nova textura carregando do arquivo
        result = D3DXCreateTextureFromFileEx( 
            device3d,           //dispositivo 3D
            filename,           //nome da imagem
            info.Width,         //largura
            info.Height,        //altura
            1,                  //níveis de mip-map
            0,                  //uso
            D3DFMT_UNKNOWN,     //formato de superfície (padrão)
            D3DPOOL_DEFAULT,    //memory class for the texture
            D3DX_DEFAULT,       //filtro de imagem
            D3DX_DEFAULT,       //filtro mip
            transcolor,         //cor de transparência
            &info,              //informação do arquivo de bitmap (carregado)
            NULL,               //paleta de cor
            &texture );         //textura destino

    } catch(...)
    {
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro em Graphics::loadTexture"));
    }
    return result;
}

//=============================================================================
// Mostrar o backbuffer
//=============================================================================
HRESULT Graphics::showBackbuffer()
{
    result = E_FAIL;    // padrão é fail, substituir em sucesso
    // Mostra o backbuffer para a tela
    result = device3d->Present(NULL, NULL, NULL, NULL);
    return result;
}

Por fim, vamos gerar a função de desenhar nossa sprite/textura na tela:

Código:
//=============================================================================
// Desenhar a sprite descrita na estrutura SpriteData
// Color is optional, it is applied like a filter, WHITE is default (no change)
// Pre : sprite->Begin() é chamado
// Post: sprite->End() 
// spriteData.rect define a porção da spriteData.texture para desenhar 
//   spriteData.rect.right deve ser a ponta direita + 1
//   spriteData.rect.bottom deve ser a ponta abaixo + 1
//=============================================================================
void Graphics::drawSprite(const SpriteData &spriteData, COLOR_ARGB color)
{
    if(spriteData.texture == NULL)      // se não há textura
        return;

    // Buscar o centro da Sprite
    D3DXVECTOR2 spriteCenter=D3DXVECTOR2((float)(spriteData.width/2*spriteData.scale),
                                        (float)(spriteData.height/2*spriteData.scale));
    // Posição de tela da Sprite
    D3DXVECTOR2 translate=D3DXVECTOR2((float)spriteData.x,(float)spriteData.y);
    // Scalonar em X, Y
    D3DXVECTOR2 scaling(spriteData.scale,spriteData.scale);
    if (spriteData.flipHorizontal)  // se virada na horizontal
    {
        scaling.x *= -1;            // escala negativa de X para virar
        // Ir para o centro da imagem flipada
        spriteCenter.x -= (float)(spriteData.width*spriteData.scale);
        // Flip ocorre próximo à ponta esquerda, transladar para a direita para colocar
		// a imagem flipada na mesma localização da original.
        translate.x += (float)(spriteData.width*spriteData.scale);
    }
    if (spriteData.flipVertical)    // se virada vertical
    {
        scaling.y *= -1;            // escala negativa em Y para virar
        // Ir para o centro da imagem flipada
        spriteCenter.y -= (float)(spriteData.height*spriteData.scale);
        // Flip ocorre próximo à ponta superior, transladar para a baixo para colocar
        // a imagem flipada na mesma localização da original.
        translate.y += (float)(spriteData.height*spriteData.scale);
    }
    // Criar uma matriz para rotacionar e psicionar nossa sprite
    D3DXMATRIX matrix;
    D3DXMatrixTransformation2D(
        &matrix,                // a matriz
        NULL,                   // manter a origem no topo quando escalonando
        0.0f,                   // sem rotação
        &scaling,               // quantidade de scaling
        &spriteCenter,          // centro de rotacao
        (float)(spriteData.angle),  // angulo de rotacao
        &translate);            // X,Y location

    // Falar o sprite sobre a matriz
    sprite->SetTransform(&matrix);

    // Desenhar o sprite
    sprite->Draw(spriteData.texture,&spriteData.rect,NULL,NULL,color);
}

Repare que tudo se passa pelo tipo D3DXVECTOR2; como nosso jogo é 2D, temos matrizes e vetores envolvidas. O que fazemos nada mais é que ficar transladando e rotacionando estas matrizes.

Finalizando o nosso graphics.cpp, temos a questão de funções que buscam lidar com dispositivos e erros, não carecem de muita explicação porque são autoexplicativas:
Código:
//=============================================================================
//Verifica o adaptador para ver se é compatível com a altura do backbuffer,
//largura e taxa de atualização especificada do d3dpp. Preenche a estrutura do pmode
//com o formato no modo compatível, se encontrado.
// Pre: d3dpp é inicializado.
// Pos: Retorna true se modo compatível encontrado e pMode é preenchido.
//      Retorna falso caso contrário.
//=============================================================================
bool Graphics::isAdapterCompatible()
{
    UINT modes = direct3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, 
                                            d3dpp.BackBufferFormat);
    for(UINT i=0; i<modes; i++)
    {
        result = direct3d->EnumAdapterModes( D3DADAPTER_DEFAULT, 
                                        d3dpp.BackBufferFormat,
                                        i, &pMode);
        if( pMode.Height == d3dpp.BackBufferHeight &&
            pMode.Width  == d3dpp.BackBufferWidth &&
            pMode.RefreshRate >= d3dpp.FullScreen_RefreshRateInHz)
            return true;
    }
    return false;
}

//=============================================================================
// Procurar por dispositivo perdido
//=============================================================================
HRESULT Graphics::getDeviceState()
{ 
    result = E_FAIL;    // padrão em falha, substituir em sucesso
    if (device3d == NULL)
        return  result;
    result = device3d->TestCooperativeLevel(); 
    return result;
}


//=============================================================================
// Resetar o dispositivo gráfico
//=============================================================================
HRESULT Graphics::reset()
{
    result = E_FAIL;    // padrão em falha, substituir em sucesso
    initD3Dpp();                        // inicializar os parâmetros do D3D
    sprite->OnLostDevice();
    result = device3d->Reset(&d3dpp);   // tentativa de resetar dispositivos gráficos

    sprite->OnResetDevice();
    return result;
}

//=============================================================================
// Colocar tela em fullscreen
// Pre: Todas as superfícies criadas em D3DPOOL_DEFAULT são liberadas.
// Pos: Todas as superfícies são recriadas.
//=============================================================================
void Graphics::changeDisplayMode(graphicsNS::DISPLAY_MODE mode)
{
    try{
        switch(mode)
        {
        case graphicsNS::FULLSCREEN:
            if(fullscreen)      // se já em fullscreen
                return;
            fullscreen = true; break;
        case graphicsNS::WINDOW:
            if(fullscreen == false) // se já em janelado
                return;
            fullscreen = false; break;
        default:        // padrão para fullscreen/janelado
            fullscreen = !fullscreen;
        }
        reset();
        if(fullscreen)  // fullscreen
        {
            SetWindowLong(hwnd, GWL_STYLE,  WS_EX_TOPMOST | WS_VISIBLE | WS_POPUP);
        }
        else            // janelado
        {
            SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
            SetWindowPos(hwnd, HWND_TOP, 0,0,GAME_WIDTH,GAME_HEIGHT,
                SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

            // Ajustar o tamanho da tela para GAME_WIDTH x GAME_HEIGHT
            RECT clientRect;
            GetClientRect(hwnd, &clientRect);   // obter tamanho da área da janela do cliente
            MoveWindow(hwnd,
                       0,                                           // Esquerda
                       0,                                           // Topo
                       GAME_WIDTH+(GAME_WIDTH-clientRect.right),    // Direita
                       GAME_HEIGHT+(GAME_HEIGHT-clientRect.bottom), // Baixo
                       TRUE);                                       // Repintar a tela
        }

    } catch(...)
    {
        //Um erro ocorreu, tentar janelado
        SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
        SetWindowPos(hwnd, HWND_TOP, 0,0,GAME_WIDTH,GAME_HEIGHT,
            SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);

        // Ajustar o tamanho da tela para GAME_WIDTH x GAME_HEIGHT
        RECT clientRect;
        GetClientRect(hwnd, &clientRect);   // obter tamanho da área da janela do cliente
        MoveWindow(hwnd,
                    0,                                           // Esquerda
                    0,                                           // Cima
                    GAME_WIDTH+(GAME_WIDTH-clientRect.right),    // Direita
                    GAME_HEIGHT+(GAME_HEIGHT-clientRect.bottom), // Baixo
                    TRUE);                                       // Repintar a janela
    }
}

E, agora, o graphics.h que representa as funções do nosso amado arquivo .cpp e algumas estruturas que mexemos anteriormente, como o spriteData:

Código:
#ifndef _GRAPHICS_H            
#define _GRAPHICS_H            
#define WIN32_LEAN_AND_MEAN

#ifdef _DEBUG
#define D3D_DEBUG_INFO
#endif
#include <d3d9.h>
#include <d3dx9.h>
#include "constants.h"
#include "gameError.h"

// Tipos de texturas do DirectX
#define LP_TEXTURE  LPDIRECT3DTEXTURE9
#define LP_SPRITE   LPD3DXSPRITE
#define LP_3DDEVICE LPDIRECT3DDEVICE9
#define LP_3D       LPDIRECT3D9

// Definição de cores
#define COLOR_ARGB DWORD
#define SETCOLOR_ARGB(a,r,g,b) \
    ((COLOR_ARGB)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))

namespace graphicsNS
{
    // Algumas cores comuns
    // Números ARGB vão de 0 até 255
    // A = Canal alpha
    // R = Vermelho, G = Verde, B = Azul
    const COLOR_ARGB ORANGE  = D3DCOLOR_ARGB(255,255,165,  0);
    const COLOR_ARGB BROWN   = D3DCOLOR_ARGB(255,139, 69, 19);
    const COLOR_ARGB LTGRAY  = D3DCOLOR_ARGB(255,192,192,192);
    const COLOR_ARGB GRAY    = D3DCOLOR_ARGB(255,128,128,128);
    const COLOR_ARGB OLIVE   = D3DCOLOR_ARGB(255,128,128,  0);
    const COLOR_ARGB PURPLE  = D3DCOLOR_ARGB(255,128,  0,128);
    const COLOR_ARGB MAROON  = D3DCOLOR_ARGB(255,128,  0,  0);
    const COLOR_ARGB TEAL    = D3DCOLOR_ARGB(255,  0,128,128);
    const COLOR_ARGB GREEN   = D3DCOLOR_ARGB(255,  0,128,  0);
    const COLOR_ARGB NAVY    = D3DCOLOR_ARGB(255,  0,  0,128);
    const COLOR_ARGB WHITE   = D3DCOLOR_ARGB(255,255,255,255);
    const COLOR_ARGB YELLOW  = D3DCOLOR_ARGB(255,255,255,  0);
    const COLOR_ARGB MAGENTA = D3DCOLOR_ARGB(255,255,  0,255);
    const COLOR_ARGB RED     = D3DCOLOR_ARGB(255,255,  0,  0);
    const COLOR_ARGB CYAN    = D3DCOLOR_ARGB(255,  0,255,255);
    const COLOR_ARGB LIME    = D3DCOLOR_ARGB(255,  0,255,  0);
    const COLOR_ARGB BLUE    = D3DCOLOR_ARGB(255,  0,  0,255);
    const COLOR_ARGB BLACK   = D3DCOLOR_ARGB(255,  0,  0,  0);
    const COLOR_ARGB FILTER  = D3DCOLOR_ARGB(  0,  0,  0,  0);  // usado para especificar o desenho com colorFilter
    const COLOR_ARGB ALPHA25 = D3DCOLOR_ARGB( 64,255,255,255);  // e com cor para pegar 25% de alpha
    const COLOR_ARGB ALPHA50 = D3DCOLOR_ARGB(128,255,255,255);  //  e com cor para pegar 50% de alpha
    const COLOR_ARGB BACK_COLOR = NAVY;                         // cor de background do jogo

    enum DISPLAY_MODE{TOGGLE, FULLSCREEN, WINDOW};
}


// SpriteData: As propriedades requeridas por Graphics::drawSprite para desenhar um sprite.
struct SpriteData
{
    int         width;      // largura em pixels
    int         height;     // altura em pixels
    float       x;          // localização da tela (canto superior esquerdo do sprite)
    float       y;
    float       scale;      // <1 menor, >1 maior
    float       angle;      // ângulo de rotação em radianos
    RECT        rect;       // usado para selecionar uma imagem de textura maior
    LP_TEXTURE  texture;    // ponteiro para textura
    bool        flipHorizontal; // verdade para se flipar a sprite horizontalmente (espelho)
    bool        flipVertical;   // verdade para se flipar verticalmente
};

class Graphics
{
private:
    // Ponteiros do DirectX e afins
    LP_3D       direct3d;
    LP_3DDEVICE device3d;
    LP_SPRITE   sprite;
    D3DPRESENT_PARAMETERS d3dpp;
    D3DDISPLAYMODE pMode;

    // outras variáveis
    HRESULT     result;         // códigos de retorno padrão do windows
    HWND        hwnd;
    bool        fullscreen;
    int         width;
    int         height;
    COLOR_ARGB  backColor;      // cor do background

    // Para uso interno apenas.
    // Inicializar parâmetros do D3D.
    void    initD3Dpp();

public:
    // Construtor
    Graphics();

    // Destrutor
    virtual ~Graphics();
    void    releaseAll();
    void    initialize(HWND hw, int width, int height, bool fullscreen);
    HRESULT loadTexture(const char * filename, COLOR_ARGB transcolor, UINT &width, UINT &height, LP_TEXTURE &texture);
    HRESULT showBackbuffer();
    bool    isAdapterCompatible();
    void    drawSprite(const SpriteData &spriteData,          
                       COLOR_ARGB color = graphicsNS::WHITE); 

    HRESULT reset();
    void    changeDisplayMode(graphicsNS::DISPLAY_MODE mode = graphicsNS::TOGGLE);

    // Funções 
    // Retorna direct3d.
    LP_3D   get3D()             { return direct3d; }

    // Retorna device3d.
    LP_3DDEVICE get3Ddevice()   { return device3d; }

    // Retorna sprite
    LP_SPRITE   getSprite()     { return sprite; }

    // Retorna handle para contexto de dispositivo.
    HDC     getDC()             { return GetDC(hwnd); }

    // Testar para dispositivo perdido.
    HRESULT getDeviceState();

    // Retorna fullscreen
    bool    getFullscreen()     { return fullscreen; }
 
    // Set cor usada para limpar a tela
    void setBackColor(COLOR_ARGB c) {backColor = c;}

    //=============================================================================
    // Limpar backbuffer e BeginScene()
    //=============================================================================
    HRESULT beginScene() 
    {
        result = E_FAIL;
        if(device3d == NULL)
            return result;
        // Limpar o backbuffer para backColor
        device3d->Clear(0, NULL, D3DCLEAR_TARGET, backColor, 1.0F, 0);
        result = device3d->BeginScene();          // iniciar cena para desenhar
        return result;
    }

    //=============================================================================
    // EndScene()
    //=============================================================================
    HRESULT endScene() 
    {
        result = E_FAIL;
        if(device3d)
            result = device3d->EndScene();
        return result;
    }

    void spriteBegin() 
    {
        sprite->Begin(D3DXSPRITE_ALPHABLEND);
    }
    void spriteEnd() 
    {
        sprite->End();
    }
};

#endif

Enquanto o módulo gráfico está preocupado com gerenciamento de memórias, matrizes e afins, estruturamos ele de forma que, ao escrever o código de nosso jogo, não tenhamos que nos preocupar com esse tipo de coisa. Para tal, temos o textureManager onde, para cada textura, criaremos um objeto para cada arquivo no jogo; assim, podemos adicionar e remover tudo muito facilmente.

Façamos um textureManager.cpp:

Código:
#include "textureManager.h"

//=============================================================================
// construtor
//=============================================================================
TextureManager::TextureManager()
{
    texture = NULL;
    width = 0;
    height = 0;
    file = NULL;
    graphics = NULL;
    initialized = false;            // setar true quando inicializado com sucesso
}

//=============================================================================
// destrutor
//=============================================================================
TextureManager::~TextureManager()
{
    SAFE_RELEASE(texture);
}

//=============================================================================
// Carregar a textura do disco.
// Pos: retorna verdade se sucesso, falso se falhar
//=============================================================================
bool TextureManager::initialize(Graphics *g, const char *f)
{
    try{
        graphics = g;                       // o objeto gráfico
        file = f;                           // o arquivo de textura

        hr = graphics->loadTexture(file, TRANSCOLOR, width, height, texture);
        if (FAILED(hr))
        {
            SAFE_RELEASE(texture);
            return false;
        }
    }
    catch(...) {return false;}
    initialized = true;                    // setar verdade quando inicializado com sucesso
    return true;
}

//=============================================================================
// chamado quando o dispositivo gráfico é perdido
//=============================================================================
void TextureManager::onLostDevice()
{
    if (!initialized)
        return;
    SAFE_RELEASE(texture);
}

//=============================================================================
// chamado quando o dispositivo gráfico é resetado
//=============================================================================
void TextureManager::onResetDevice()
{
    if (!initialized)
        return;
    graphics->loadTexture(file, TRANSCOLOR, width, height, texture);
}

O código não tem muito mistério, apenas invocamos as funções do gráfico.
Para o header, temos:

Código:
#ifndef _TEXTUREMANAGER_H       
#define _TEXTUREMANAGER_H    
#define WIN32_LEAN_AND_MEAN

#include "graphics.h"
#include "constants.h"

class TextureManager
{
    // Propriedades do TextureManager
  private:
    UINT       width;       // largura da textura em pixels
    UINT       height;      // altura da textura em pixels
    LP_TEXTURE texture;     // ponteiro para textura
    const char *file;       // nome do arquivo
    Graphics *graphics;     // salvar ponteiro para graphics
    bool    initialized;    // true quando inicializado corretamente
    HRESULT hr;             // tipo padrão de retorno

  public:
    // Constructor
    TextureManager();

    // Destrutor
    virtual ~TextureManager();

    // Retorna um ponteiro para a textura
    LP_TEXTURE getTexture() const {return texture;}

    // Retorna a largura da textura
    UINT getWidth() const {return width;}

    // Retorna a altura da textura
    UINT getHeight() const {return height;}

    // Inicializar o textureManager
    // Pre: *g aponta para o objeto gráfico
    //      *file aponta para o nome da textura para carregar
    // Pos: O arquivo é carregado.
    virtual bool initialize(Graphics *g, const char *file);

    // Liberar recursos
    virtual void onLostDevice();

    // Restaurar recursos
    virtual void onResetDevice();
};

#endif

Façamos um image.cpp para gerenciar as imagens:

Código:
#include "image.h"

//=============================================================================
// construtor padrão
//=============================================================================
Image::Image() {
    initialized = false;            // verdadeiro quando inicializado corretamente
    spriteData.width = 2;
    spriteData.height = 2;
    spriteData.x = 0.0;
    spriteData.y = 0.0;
    spriteData.scale = 1.0;
    spriteData.angle = 0.0;
    spriteData.rect.left = 0;       // usado para selecionar um frame de uma imagem multiframe
    spriteData.rect.top = 0;
    spriteData.rect.right = spriteData.width;
    spriteData.rect.bottom = spriteData.height;
    spriteData.texture = NULL;      // a textura da sprite
    spriteData.flipHorizontal = false;
    spriteData.flipVertical = false;
    cols = 1;
    textureManager = NULL;
    startFrame = 0;
    endFrame = 0;
    currentFrame = 0;
    frameDelay = 1.0;               // padrão para 1 segundo por frame de animação
    animTimer = 0.0;
    visible = true;                 // a imagem é visível
    loop = true;                    // loop nos frames
    animComplete = false;
    graphics = NULL;                // linkar para o sistema gráfico
    colorFilter = graphicsNS::WHITE; // BRANCO para nenhuma mudança
}

//=============================================================================
// destrutor
//=============================================================================
Image::~Image() {
}

//=============================================================================
// Initializar a Imagem.
// Pos: retorna verdadeiro se sucesso; falso se falhar o
// pointer para Graphics
// Largura da Imagem em pixels  (0 = usar a largura total da textura)
// Altura da Imagem em pixels  (0 = usar a altura total da textura)
// Número de colunas da texturas em pixels (1 a n) (0 equivale a 1)
//=============================================================================
bool Image::initialize(Graphics *g, int width, int height, int ncols,
                       TextureManager *textureM) {
    try{
        graphics = g;                               // o objeto graphics
        textureManager = textureM;                  // ponteiro para o objeto da textura

        spriteData.texture = textureManager->getTexture();
        if(width == 0)
            width = textureManager->getWidth();     // usar largura total da textura
        spriteData.width = width;
        if(height == 0)
            height = textureManager->getHeight();   // usar altura total da textura
        spriteData.height = height;
        cols = ncols;
        if (cols == 0)
            cols = 1;                               // se 0 colunas, usar 1

        // configurar spriteData.rect para desenhar o currentFrame
        spriteData.rect.left = (currentFrame % cols) * spriteData.width;
        // borda direita + 1
        spriteData.rect.right = spriteData.rect.left + spriteData.width;
        spriteData.rect.top = (currentFrame / cols) * spriteData.height;
        // borda inferior + 1
        spriteData.rect.bottom = spriteData.rect.top + spriteData.height;
    }
    catch(...) {return false;}
    initialized = true;                                // inicializado com sucesso
    return true;
}


//=============================================================================
// Desenhar a imagem usando cor como filtro
// O parâmetro de cor é opcional, branco é designado como padrão no image.harderr
// Pre : spriteBegin() é chamado
// Pos : spriteEnd() é chamado
//=============================================================================
void Image::draw(COLOR_ARGB color) {
    if (!visible || graphics == NULL)
        return;
	// pegar textura nova em caso de onReset() for convocado
    spriteData.texture = textureManager->getTexture();
    if(color == graphicsNS::FILTER)                     // se desenhar com filtro
        graphics->drawSprite(spriteData, colorFilter);  // usar colorFilter
    else
        graphics->drawSprite(spriteData, color);        // usar cor como filtro
}

//=============================================================================
// Desenhar esta imagem utilizando o SpriteData especificado.
//   O atual SpriteData.rect é usado para selecionar a textura.
// Pre : spriteBegin() é chamado
// Pos : spriteEnd() é chamado
//=============================================================================
void Image::draw(SpriteData sd, COLOR_ARGB color)
{
    if (!visible || graphics == NULL)
        return;
    sd.rect = spriteData.rect;                  // use this Images rect to select texture
    sd.texture = textureManager->getTexture();  // get fresh texture incase onReset() was called

    if(color == graphicsNS::FILTER)             // if draw with filter
        graphics->drawSprite(sd, colorFilter);  // use colorFilter
    else
        graphics->drawSprite(sd, color);        // use color as filter
}

//=============================================================================
// atualizar
// normalmente chamado uma vez por frame
// frameTime é usado para regular a velocidade de movimento e animação
//=============================================================================
void Image::update(float frameTime)
{
    if (endFrame - startFrame > 0)          // se sprite animado
    {
        animTimer += frameTime;             // tempo passado
        if (animTimer > frameDelay)
        {
            animTimer -= frameDelay;
            currentFrame++;
            if (currentFrame < startFrame || currentFrame > endFrame)
            {
                if(loop == true)            // se animação está em looping
                    currentFrame = startFrame;
                else                        // animação não está em loop
                {
                    currentFrame = endFrame;
                    animComplete = true;    // animação completa
                }
            }
            setRect();                      // setar spriteData.rect
        }
    }
}

//=============================================================================
// Setar o frame atual da imagem
//=============================================================================
void Image::setCurrentFrame(int c) {
    if(c >= 0)
    {
        currentFrame = c;
        animComplete = false;
        setRect();                          // setar spriteData.rect
    }
}

//=============================================================================
//  Setar spriteData.rect para desenhar o currentFrame
//=============================================================================
inline void Image::setRect() {
    // configurar spriteData.rect para desenhar o currentFrame
    spriteData.rect.left = (currentFrame % cols) * spriteData.width;
    // borda direita + 1
    spriteData.rect.right = spriteData.rect.left + spriteData.width;
    spriteData.rect.top = (currentFrame / cols) * spriteData.height;
    // borda inferior + 1
    spriteData.rect.bottom = spriteData.rect.top + spriteData.height;       
}

E o image.h:
Código:
#ifndef _IMAGE_H             
#define _IMAGE_H              
#define WIN32_LEAN_AND_MEAN

#include "textureManager.h"
#include "constants.h"

class Image {
    // Propriedades da Imagem
  protected:
    Graphics *graphics;     // ponteiro para graphics
    TextureManager *textureManager; // ponteiro para o texture manager
    // spriteData contem a informação requerida para desenhar a imagem com o Graphics::drawSprite()
    SpriteData spriteData;  // SpriteData é definido em "graphics.h"
    COLOR_ARGB colorFilter; // aplicado como um filtro de cor
    int     cols;           // número de colunas (1 a n) em um sprite multi-frame
    int     startFrame;     // primeiro frame da animação atual
    int     endFrame;       // frame final da animação atual
    int     currentFrame;   // frame atual da animação
    float   frameDelay;     // quanto tempo entre frames de animação
    float   animTimer;      // timer de animação
    HRESULT hr;             // tipo de retorno padrão
    bool    loop;           // true para dar loops em frmes
    bool    visible;        // true quando visível
    bool    initialized;    // true quando inicializado com sucesso
    bool    animComplete;   // true quando loop é falso e endFrame acabou

  public:
    // Construtor
    Image();
    // Destrutor
    virtual ~Image();

    ////////////////////////////////////////
    //           Funções de get           //
    ////////////////////////////////////////

    // Retorna referência para a estrutura SpriteData.
    const virtual SpriteData& getSpriteInfo() {return spriteData;}

    // Retorna visible parameter.
    virtual bool  getVisible()  {return visible;}

    // Retorna posição X.
    virtual float getX()        {return spriteData.x;}

    // Retorna posição Y.
    virtual float getY()        {return spriteData.y;}

    // Retorna fator de escala.
    virtual float getScale()    {return spriteData.scale;}

    // Retorna largura.
    virtual int   getWidth()    {return spriteData.width;}

    // Retorna altura.
    virtual int   getHeight()   {return spriteData.height;}

    // Retorna X central.
    virtual float getCenterX()      {return spriteData.x + spriteData.width/2*getScale();}

    // Retorna Y central.
    virtual float getCenterY()      {return spriteData.y + spriteData.height/2*getScale();}

    // Retorna ângulo de rotação em graus.
    virtual float getDegrees()      {return spriteData.angle*(180.0f/(float)PI);}

    // Retorna ângulo de rotação em radianos.
    virtual float getRadians()      {return spriteData.angle;}

    // Retorna delay between frames of animation.
    virtual float getFrameDelay()   {return frameDelay;}

    // Retorna número do frame inicial.
    virtual int   getStartFrame()   {return startFrame;}

    // Retorna número do frame final.
    virtual int   getEndFrame()     {return endFrame;}

    // Retorna número do frame atual.
    virtual int   getCurrentFrame() {return currentFrame;}

    // Retorna estrutura RECT da Imagem.
    virtual RECT  getSpriteDataRect() {return spriteData.rect;}

    // Retorna estado completo de animação.
    virtual bool  getAnimationComplete() {return animComplete;}

    // Retorna colorFilter.
    virtual COLOR_ARGB getColorFilter() {return colorFilter;}

    ////////////////////////////////////////
    //           Funções de set            //
    ////////////////////////////////////////

    // Setar local do X.
    virtual void SetX(float newX)   {spriteData.x = newX;}

    // Setar local do Y.
    virtual void SetY(float newY)   {spriteData.y = newY;}

    // Setar escala.
    virtual void SetScale(float s)  {spriteData.scale = s;}

    // Setar ângulo de rotação em graus.
    virtual void SetDegrees(float deg)  {spriteData.angle = deg*((float)PI/180.0f);}

    // Setar ângulo de rotação em graus.
    virtual void SetRadians(float rad)  {spriteData.angle = rad;}

    // Setar visibilidade.
    virtual void SetVisible(bool v) {visible = v;}

    // Setar delay entre frames de animação.
    virtual void SetFrameDelay(float d) {frameDelay = d;}

    // Setar frames iniciais e finais de animação.
    virtual void SetFrames(int s, int e){startFrame = s; endFrame = e;}

    // Setar frame atual da animação
    virtual void SetCurrentFrame(int c);

    // Setar spriteData.rect para desenhar o currentFrame
    virtual void SetRect(); 

    // Setar spriteData.rect para r.
    virtual void SetSpriteDataRect(RECT r)  {spriteData.rect = r;}

    // Setar loop de animação.
    virtual void SetLoop(bool lp) {loop = lp;}

    // Setar booleano de animação completa.
    virtual void SetAnimationComplete(bool a) {animComplete = a;};

    // Setar filtro de cor.
    virtual void SetColorFilter(COLOR_ARGB color) {colorFilter = color;}

    // Setar TextureManager
    virtual void SetTextureManager(TextureManager *textureM)
    { textureManager = textureM; }

    ////////////////////////////////////////
    //         Outras funções             //
    ////////////////////////////////////////

    // Inicializar Imagem
    // Pre: *g = ponteiro para o objeto graphics
    //      width = largura da imagem em pixels  (0 = usar largura total da textura)
    //      height = altura da imagem em pixels  (0 = usar altura total da textura)
    //      ncols = número de colunas (1 a n) (0 equivale a 1)
    //      *textureM = ponteiro para o objeto textureManager
    virtual bool Image::initialize(Graphics *g, int width, int height, 
                                    int ncols, TextureManager *textureM);

    // Flipar imagem horizontalmente (espelho)
    virtual void flipHorizontal(bool flip)  {spriteData.flipHorizontal = flip;}

    // Flipar image verticalmente
    virtual void flipVertical(bool flip)    {spriteData.flipVertical = flip;}

    // Desenhar Imagem usando cor como filtro. A cor padrão é WHITE. 
    virtual void draw(COLOR_ARGB color = graphicsNS::WHITE);

    // Desenhar esta imagem usando o SpriteData especificado.
    virtual void draw(SpriteData sd, COLOR_ARGB color = graphicsNS::WHITE);

    // Atualizar a animação. frameTime é usado para regular a velocidade.
    virtual void update(float frameTime);
};

#endif
 
(O segundo post não aguentou tanto código também)


Um Jogo Genérico
(Vou editar depois e botar explicações, segue apenas o código para eu ter uma referência do tamanho)

game.cpp:
Código:
#include "game.h"

//=============================================================================
// Construtor
//=============================================================================
Game::Game() {
    input = new Input();        // inicializar sistema de input imediatamente
    paused = false;             // o jogo não está pausado
    graphics = NULL;
    initialized = false;
}

//=============================================================================
// Destrutor
//=============================================================================
Game::~Game() {
    deleteAll();                // limpar toda a memória reservada
    ShowCursor(true);           // mostrar cursor
}

//=============================================================================
// Window message handler
//=============================================================================
LRESULT Game::messageHandler( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    if(initialized)     // não processar se não inicializado
    {
        switch( msg )
        {
            case WM_DESTROY:
                PostQuitMessage(0);        // falar ao Windows para matar o programa
                return 0;
            case WM_KEYDOWN: case WM_SYSKEYDOWN:    // baixo
                input->keyDown(wParam);
                return 0;
            case WM_KEYUP: case WM_SYSKEYUP:        // cima
                input->keyUp(wParam);
                return 0;
            case WM_CHAR:                           // caractere pressionado
                input->keyIn(wParam);
                return 0;
            case WM_MOUSEMOVE:                      // mouse moveu
                input->mouseIn(lParam);
                return 0;
            case WM_INPUT:                          // entrada de mouse bruta
                input->mouseRawIn(lParam);
                return 0;
            case WM_LBUTTONDOWN:                    // botão esquerdo do mouse pressionado
                input->setMouseLButton(true);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_LBUTTONUP:                      // botão esquerdo do mouse normal
                input->setMouseLButton(false);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_MBUTTONDOWN:                    // botão do meio do mouse pressionado
                input->setMouseMButton(true);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_MBUTTONUP:                      // botão do meio do mouse normal
                input->setMouseMButton(false);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_RBUTTONDOWN:                    // botão direito do mouse pressionado
                input->setMouseRButton(true);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_RBUTTONUP:                      // botão direito do mouse normal
                input->setMouseRButton(false);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_XBUTTONDOWN: case WM_XBUTTONUP: // botão X do mouse pressionado / normal
                input->setMouseXButton(wParam);
                input->mouseIn(lParam);             // posição do mouse
                return 0;
            case WM_DEVICECHANGE:                   // buscar por controles
                input->checkControllers();
                return 0;
        }
    }
    return DefWindowProc( hwnd, msg, wParam, lParam );    // deixar o Windows lidar com isso
}

//=============================================================================
// Inicializa o jogo
//=============================================================================
void Game::initialize(HWND hw)
{
    hwnd = hw;                                  // salva o window handle

    // inicializa graphics
    graphics = new Graphics();
    // chama GameError
    graphics->initialize(hwnd, GAME_WIDTH, GAME_HEIGHT, FULLSCREEN);

    // inicializa input, não captur mouse
    input->initialize(hwnd, false);             // chama GameError

    // tenta setar um timer de alta resolução
    if(QueryPerformanceFrequency(&timerFreq) == false)
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar timer de alta resolução"));

    QueryPerformanceCounter(&timeStart);        // setar tempo inicial

    initialized = true;
}

//=============================================================================
// Renderiza itens do jogo
//=============================================================================
void Game::renderGame()
{
    // começar renderização
    if (SUCCEEDED(graphics->beginScene()))
    {
        render();           // chamar render() em objeto derivado

        // parar renderizaçao
        graphics->endScene();
    }
    handleLostGraphicsDevice();

    // mostrar o backbuffer na tela
    graphics->showBackbuffer();
}

//=============================================================================
// Lidar com dispositivo gráfico perdido
//=============================================================================
void Game::handleLostGraphicsDevice()
{
    // testar para e lidar com o DGP
    hr = graphics->getDeviceState();
    if(FAILED(hr))                  // se o dispositivo gráfico não estiver em um estado válido
    {
        // se o DGP estiver perdido e não disponível para reset
        if(hr == D3DERR_DEVICELOST)
        {
            Sleep(100);             // tempo de cpu (100 milisegundos)
            return;
        } 
        // o dispositivo foi perdido mas está disponível agora para reset
        else if(hr == D3DERR_DEVICENOTRESET)
        {
            releaseAll();
            hr = graphics->reset(); // tentar resetar dispositivo gráfico
            if(FAILED(hr))          // se falhou
                return;
            resetAll();
        }
        else
            return;                 // outros erros do dispositivo
    }
}

//=============================================================================
// Window ou modo Fullscreen
//=============================================================================
void Game::setDisplayMode(graphicsNS::DISPLAY_MODE mode)
{
    releaseAll();                   // liberar todas as superfícies criados por usuários
    graphics->changeDisplayMode(mode);
    resetAll();                     // recriar superfícies
}

//=============================================================================
// Call repeatedly by the main message loop in WinMain
//=============================================================================
void Game::run(HWND hwnd)
{
    if(graphics == NULL)            // se gráficos não foram inicializados
        return;

    // calcular tempo passado no último frame, salvar no frameTime
    QueryPerformanceCounter(&timeEnd);
    frameTime = (float)(timeEnd.QuadPart - timeStart.QuadPart ) / (float)timerFreq.QuadPart;

    // Código de otimização de energia, requer winmm.lib
    if (frameTime < MIN_FRAME_TIME) 
    {
        sleepTime = (DWORD)((MIN_FRAME_TIME - frameTime)*1000);
        timeBeginPeriod(1);         // Pedir 1 ms de resolução para o timer da janela
        Sleep(sleepTime);           // libera cpu para sleepTime
        timeEndPeriod(1);           // Encerra o timer de resolução de 1ms 
        return;
    }

    if (frameTime > 0.0)
        fps = (fps*0.99f) + (0.01f/frameTime);  // fps médio
    if (frameTime > MAX_FRAME_TIME) // se taxa de frames estiver muito lenta
        frameTime = MAX_FRAME_TIME; // limitar frameTime máximo
    timeStart = timeEnd;

    // update(), ai(), e collisions() são funções virtuais puras.
    // Estas funções devem ser mostradas na classe que herda de Game.
    if (!paused)                    // se não estiver pausado
    {
        update();                   // atualiza todos os itens de jogo
        ai();                       // inteligência artifical
        collisions();               // lidar com colisões
        input->vibrateControllers(frameTime); // lidar com vibração de controle
    }
    renderGame();                   // desenhar todos os itens de jogos
    input->readControllers();       // ler estado dos controles

    // se Alt+Enter, alternar entre fullscreen/window
    if (input->isKeyDown(ALT_KEY) && input->wasKeyPressed(ENTER_KEY))
        setDisplayMode(graphicsNS::TOGGLE); // alternar fullscreen/window

    // se Esc, colocar modo de janela
    if (input->isKeyDown(ESC_KEY))
        setDisplayMode(graphicsNS::WINDOW); // modo janela

    // Limpar input
    // Chamar iso após todos as verificações de teclas tiverem finalizadas.
    input->clear(inputNS::KEYS_PRESSED);
}

//=============================================================================
// Dispositivo gráfico foi perdido.
// Liberar toda a memória de vídeo reservada para que ele seja resetado.
//=============================================================================
void Game::releaseAll() {}

//=============================================================================
// Recria todas as superfícies e reseta todas as entidades.
//=============================================================================
void Game::resetAll() {}

//=============================================================================
// Deleta toda memória reservada
//=============================================================================
void Game::deleteAll() {
    releaseAll();               // chama onLostDevice() para todo item gráfico
    SAFE_DELETE(graphics);
    SAFE_DELETE(input);
    initialized = false;
}

graphics.h:
Código:
#ifndef _GAME_H                 
#define _GAME_H                
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <Mmsystem.h>
#include "graphics.h"
#include "input.h"
#include "constants.h"
#include "gameError.h"

class Game
{
protected:
    // common game properties
    Graphics *graphics;         // ponteiro para Graphics
    Input   *input;             // ponteiro para Input
    HWND    hwnd;               // window handle
    HRESULT hr;                 // tipo de return padrão
    LARGE_INTEGER timeStart;    // Valor inicial do Contador de Performance
    LARGE_INTEGER timeEnd;      // Valor final do Contador de Performance
    LARGE_INTEGER timerFreq;    // Frequência do Contador de Performance
    float   frameTime;          // Tempo requerido para o último frame
    float   fps;                // frames per second
    DWORD   sleepTime;          // número de milisegundos para dormir entre frames
    bool    paused;             // true se o jogo está pausado
    bool    initialized;

public:
    // Construtor
    Game();
    // Destrutor
    virtual ~Game();

    // Funções membro

    // Message handler
    LRESULT messageHandler( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );

    // Inicializa o jogo.
    virtual void initialize(HWND hwnd);

    // Chama "run" repetidamente no loop de mensagem principal no WinMain.
    virtual void run(HWND);

    // Chamar quando o dispositivo gráfico foi perdido.
	// Liberar toda a memória de vídeo reservada.
    virtual void releaseAll();

    // Recriar todas as superfícies e resetar todas as entidades.
    virtual void resetAll();

    // Deletar toda memória reservada.
    virtual void deleteAll();

    // Renderizar itens do jogo.
    virtual void renderGame();

    // Lidar com GDP.
    virtual void handleLostGraphicsDevice();

    // Seta modo de display
    void setDisplayMode(graphicsNS::DISPLAY_MODE mode = graphicsNS::TOGGLE);

    // Retorna ponteiro para Graphics.
    Graphics* getGraphics() {return graphics;}

    // Retorna ponteiro para Input.
    Input* getInput()       {return input;}

    // Sair do jogo.
    void exitGame()         {PostMessage(hwnd, WM_DESTROY, 0, 0);}

    // Declarações de funções virtualmente puras.
	// Elas DEVEM ser escritas em qualquer classe que herde de Jogo.

    // Atualizar itens de jogos.
    virtual void update() = 0;

    // Gerar cálculos de AI.
    virtual void ai() = 0;

    // Verificar colisões.
    virtual void collisions() = 0;

    // Renderizar gráficos
    // Chamar graphics->spriteBegin();
    //   desenhar sprites
    // Chamar graphics->spriteEnd();
    //   desenhar não-sprites
    virtual void render() = 0;
};

#endif


As Regras do Pokemon

pokemon.h:
Código:
#ifndef _POKEMON_H             
#define _POKEMON_H             
#define WIN32_LEAN_AND_MEAN

#include "game.h"
#include "textureManager.h"
#include "image.h"

//=============================================================================
// Esta classe é o núcleo do jogo
//=============================================================================
class Pokemon : public Game {
private:
    // game items
    TextureManager mapTexture;   // textura do mapa
    TextureManager charTexture;     // textura do personagem
    Image   map;                 // imagem do mapa
    Image   character;                   // textura do mapa

public:
    // Construtor
    Pokemon();

    // Destrutor
    virtual ~Pokemon();

    // Inicializar o jogo
    void initialize(HWND hwnd);
    void update();      // deve sobrepor a função virtual pura de Game
    void ai();          // "
    void collisions();  // "
    void render();      // "
    void releaseAll();
    void resetAll();
};

#endif

Por fim, vamos ao código do nosso jogo:
Código:
#include "pokemon.h"

//=============================================================================
// Construtor
//=============================================================================
Pokemon::Pokemon()
{}

//=============================================================================
// Destrutor
//=============================================================================
Pokemon::~Pokemon() {
    releaseAll();           // chamar onLostDevice() para cada item gráfico
}

//=============================================================================
// Inicializa o jogo
// Chama GameError em erro
//=============================================================================
void Pokemon::initialize(HWND hwnd) {
    Game::initialize(hwnd); // chama GameError

    // textura do mapa
    if (!mapTexture.initialize(graphics,MAP_IMAGE))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar a textura do mapa"));

    // textura do personagem
    if (!charTexture.initialize(graphics,CHAR_IMAGE))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar a textura do personagem"));

    // mapa
    if (!map.initialize(graphics,0,0,0,&mapTexture))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar mapa"));

    // personagem
    if (!character.initialize(graphics,CHAR_WIDTH, CHAR_HEIGHT, CHAR_COLS, &charTexture))
        throw(GameError(gameErrorNS::FATAL_ERROR, "Erro ao inicializar personagem"));
    character.setX(GAME_WIDTH/4);                    // posição de início
    character.setY(GAME_HEIGHT/4);
    //character.setFrames(CHAR_START_FRAME, CHAR_END_FRAME);   // frames de animação, caso seja loop
    character.setCurrentFrame(CHAR_START_FRAME);     // frame inicial
    character.setFrameDelay(CHAR_ANIMATION_DELAY);

    return;
}

//=============================================================================
// Update all game items
//=============================================================================
void Pokemon::update()
{
    if(input->isKeyDown(CHAR_RIGHT_KEY))            // se mover para a direita
    {
		character.setFrames(10, 11);
		character.update(frameTime);
		character.setFrames(11, 9);
		character.update(frameTime);
		character.setFrames(9, 10);
		character.update(frameTime);
        character.setX(character.getX() + frameTime * CHAR_SPEED);
        if (character.getX() > GAME_WIDTH)               // se fora da tela na direita
            character.setX((float)-character.getWidth());     // ir para a esquerda
    }
    if(input->isKeyDown(CHAR_LEFT_KEY))             // se mover para a esquerda
    {
		character.setFrames(7, 8);
		character.update(frameTime);
		character.setFrames(8, 6);
		character.update(frameTime);
		character.setFrames(6, 7);
		character.update(frameTime);
		character.setX(character.getX() - frameTime * CHAR_SPEED);
        if (character.getX() < -character.getWidth())         // se fora da tela na esquerda
            character.setX((float)GAME_WIDTH);           // ir para a direita
    }
    if(input->isKeyDown(CHAR_UP_KEY))               // se mover para cima
    {
		character.setFrames(4, 5);
		character.update(frameTime);
		character.setFrames(5, 3);
		character.update(frameTime);
		character.setFrames(3, 4);
		character.update(frameTime);
        character.setY(character.getY() - frameTime * CHAR_SPEED);
        if (character.getY() < -character.getHeight())        // se fora da tela em cima
            character.setY((float)GAME_HEIGHT);          // ir para canto inferior
    }
    if(input->isKeyDown(CHAR_DOWN_KEY))             // se mover para baixo
    {
		character.setFrames(0, 2);
		character.update(frameTime);
        character.setY(character.getY() + frameTime * CHAR_SPEED);
        if (character.getY() > GAME_HEIGHT)              // se fora da tela em baixo
            character.setY((float)-character.getHeight());    // ir para canto superior
    }



   //character.update(frameTime);
}

//=============================================================================
// IA
//=============================================================================
void Pokemon::ai() {}

//=============================================================================
// Colisões
//=============================================================================
void Pokemon::collisions() {}

//=============================================================================
// Render game items
//=============================================================================
void Pokemon::render()
{
    graphics->spriteBegin();                // iniciar a desenhar sprites

    map.draw();                          // desenhar mapa
    character.draw();                     // desenhar personagem

    graphics->spriteEnd();                  // finalizar
}

//=============================================================================
// Dispositivo gráfico foi perdido.
// Liberar toda a memória de vídeo reservada para que possa ser resetado.
//=============================================================================
void Pokemon::releaseAll()
{
    charTexture.onLostDevice();
    mapTexture.onLostDevice();

    Game::releaseAll();
    return;
}

//=============================================================================
// Dispositivo gráfico foi resetado
// Recriar todas as superfícies.
//=============================================================================
void Pokemon::resetAll()
{
    mapTexture.onResetDevice();
    charTexture.onResetDevice();

    Game::resetAll();
    return;
}

Se você fez tudo direitinho, nem que seja copiar e colar código, seu resultado final deve ser algo como:
i9KDqiMG7CN8l.png


Adicionando mais coisas e polindo o jogo

Este post está sendo editado... (hoje a noite termino, juro!)
 
Gods be praised! Que tarefa homérica, e o resultado final é show de bola!
Obrigado por postar e por ter tanto trabalho, isto é ouro para o pessoal que quer aprender mais sobre o backstage da maior parte dos videojogos!
 
Voltar
Topo Inferior