Archives pour la catégorie Game programming

Programmation de jeux-vidéo

Calculer le nombre d’images par seconde

Q3_HUD_gameplay

Intro : 

Dans les jeux ou les applications graphiques il peut être utile de savoir combien d’images par seconde sont affichées à l’écran pendant le rendu.

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

Voici le code pour calculer le temps de rafraichissement par seconde, cette méthode devra être appelée à chaque boucle de rendu.
Il est calculé aussi le temps que met, à peu près, le rendu pour afficher une image.


void CalculateFPS(float fTimeSinceLastFrame)
{
    /* Incrèmente le nombre d'images affichées.

       /!\ Doit être déclarée en tant que float et initialisée à 0 au début
       de l'application */
    m_fNumFrames += 1.0f;

   /* Ajoute combien de temps s'est passé depuis la dernière
     image de rendu

     /!\ Doit être déclarée en tant que float et initialisée à 0 au début
     de l'application */
    m_fTimeElapsed += fTimeSinceLastFrame;

    float iFPS = 0;
    float iMilliSecPerFrame = 0;

    // Lorsqu'une seconde s'est écoulée
    if (m_fTimeElapsed >= 1.0f)
    {
        iFPS = m_fNumFrames / m_fTimeElapsed;

        // Temps moyen, en milisecondes, pour qu'une seule image soit rendue
        iMilliSecPerFrame = 1000.0f / iFPS;

        /* Réinitialise le compteur d'images et le compteur de temps entre
           chaque image */
        m_fTimeElapsed = 0.0f;
        m_fNumFrames = 0.0f;

        /* On place les nombres dans une chaîne représentant les statistiques
           formatées */
        static char buffer[256];
        sprintf(buffer, "Images par seconde = %.2f | "
            "Millisecondes par image = %.4f", iFPS, iMilliSecPerFrame);

        // On met à jour le titre de la fenêtre
        SetWindowTextA(m_hWnd, buffer);
    }
}


Résumé :

Nous avons présenté comment calculer le FPS (Images par seconde) dans une application de rendu.

Références : 

Introduction to DirectX 9.0c – A shader approach – Franck D. Luna

Une simple grille en 3D

grid

Intro :

Savoir créer une grille en 3D peut être utile pour :

– un éditeur de map

– générer un terrain à partir d’une height map

– représenter la surface d’une eau en mouvement

– etc…

Prérequis :

– Savoir initialiser DirectX 10.1

Explications :

Le fichier Grid.h :

#ifndef GRID_H
#define GRID_H

#include <vector>

#include <d3d10.h>
#include <d3dx10.h>

#include "Defines.h"
#include "D3D10Renderer.h"

class Grid
{
public:
    Grid();
    virtual ~Grid();
 
    bool Initialize();
 
    void Render(float fTimeSinceLastFrame);

    void GenTriGrid(int iNumVertRows, int iNumVertCols,
        float fDx, float fDz, const D3DXVECTOR3& center,
        std::vector<D3DXVECTOR3>& verts, std::vector<uint32>& indices);

private:
    ID3DX10Mesh* m_pMesh;
 
    D3DXMATRIX m_worldMatrix;
    D3DXMATRIX m_viewMatrix;
    D3DXMATRIX m_projMatrix;

    ID3D10EffectMatrixVariable* m_pWorldVariable;
    ID3D10EffectMatrixVariable* m_pViewVariable;
    ID3D10EffectMatrixVariable* m_pProjVariable;
     
    ID3D10Effect* m_pEffect;
    ID3D10EffectTechnique* m_pTechnique;
     
    ID3D10InputLayout* m_pVertexLayout;
};
 
#endif

 

Le fichier Grid.cpp :

#include "Grid.h"

Grid::Grid() :
m_pMesh(nullptr),
m_pWorldVariable(nullptr),
m_pTechnique(nullptr),
m_pEffect(nullptr),
m_pVertexLayout(nullptr)
{
    D3DXMatrixIdentity(&m_worldMatrix);

    D3DXVECTOR3 Eye(0.0f, 22.0f, -46.0f);
    D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
    D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);
 
    D3DXMatrixLookAtLH(&m_viewMatrix, &Eye, &At, &Up);
 
    D3DXMatrixPerspectiveFovLH(&m_projMatrix, D3DX_PI * 0.25f,
        (float) D3D10_RENDERER->GetViewportWidth() / (float)D3D10_RENDERER->GetViewportHeight(),
        0.1f, 100.0f);
}
 
Grid::~Grid()
{
    SAFE_RELEASE(m_pMesh);
}
 
bool Grid::Initialize()
{
    std::vector<D3DXVECTOR3> vertices;
    std::vector<uint32> indices;
     
    GenTriGrid(12, 12, 5.0f, 5.0f, D3DXVECTOR3(0, 0, 0), vertices, indices);

    D3D10_INPUT_ELEMENT_DESC layout[] =
    {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
    };

    HRESULT hr;
    hr = D3DX10CreateMesh(D3D10_RENDERER->GetDevice(), layout,  1, "POSITION", vertices.size(), indices.size()/3, D3DX10_MESH_32_BIT, &m_pMesh);
 
    hr = m_pMesh->SetVertexData(0, vertices.front());
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->SetIndexData(indices.data(), indices.size());
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    hr = m_pMesh->CommitToDevice();
 
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }
 
    ID3D10Blob* pBlob = nullptr;
 
    hr = D3DX10CreateEffectFromFile(L"Grid.fx", nullptr, nullptr, "fx_4_0",
                                    D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_DEBUG, 0,
                                    D3D10_RENDERER->GetDevice(), nullptr, nullptr, &m_pEffect, &pBlob, nullptr);
    if (FAILED(hr))
    {
        if (pBlob)
        {
            // Affiche l'erreur de compilation du shader
            MessageBoxA(nullptr, (PCSTR)pBlob->GetBufferPointer(), "Erreur dans le fichier shader", MB_ICONHAND | MB_OK);
        }
        else
        {
            ShowMessageBoxDXError(hr);
        }
 
        return false;
    }
 
    m_pTechnique = m_pEffect->GetTechniqueByName("Render");
 
    m_pWorldVariable = m_pEffect->GetVariableByName("World")->AsMatrix();
    m_pViewVariable = m_pEffect->GetVariableByName("View")->AsMatrix();
    m_pProjVariable = m_pEffect->GetVariableByName("Projection")->AsMatrix();
 
    D3D10_PASS_DESC PassDesc;
    m_pTechnique->GetPassByIndex(0)->GetDesc(&PassDesc);
    hr = D3D10_RENDERER->GetDevice()->CreateInputLayout(layout, 1, PassDesc.pIAInputSignature,
                                         PassDesc.IAInputSignatureSize, &m_pVertexLayout);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}
 
void Grid::Render(float fTimeSinceLastFrame)
{
    D3D10_RENDERER->GetDevice()->IASetInputLayout(m_pVertexLayout);

    // Pour faire tourner la grille autour de l'axe Y
    static float r = 0;
    D3DXMatrixRotationY(&m_worldMatrix, r);
    r += fTimeSinceLastFrame * 0.6f;

    m_pWorldVariable->SetMatrix((float*)&m_worldMatrix);
    m_pViewVariable->SetMatrix((float*)&m_viewMatrix);
    m_pProjVariable->SetMatrix((float*)&m_projMatrix);

    D3D10_TECHNIQUE_DESC techDesc;
    m_pTechnique->GetDesc(&techDesc);

    for (uint32 p = 0; p < techDesc.Passes; p++)
    {
        m_pTechnique->GetPassByIndex(p)->Apply(0);

        m_pMesh->DrawSubset(0);
    }
}

void Grid::GenTriGrid(int iNumVertRows, int iNumVertCols,
        float fDx, float fDz, const D3DXVECTOR3& center,
        std::vector<D3DXVECTOR3>& verts, std::vector<uint32>& indices)
{
    int numVertices = iNumVertRows * iNumVertCols;
    int numCellRows = iNumVertRows - 1;
    int numCellCols = iNumVertCols - 1;

    int numTris = numCellRows*numCellCols*2;

    float width = (float) numCellCols * fDx;
    float depth = (float) numCellRows * fDz;

    /* Construction des vertices */

    /* On construit d'abord la grille centrée  à l'origine */

    verts.resize( numVertices );

    /* Valeurs de décalage pour pour déplacer la grille
       dans le centre du système de coordonnées */
    float xOffset = -width * 0.5f;
    float zOffset =  depth * 0.5f;

    int k = 0;
    for (float i = 0; i < iNumVertRows; ++i)
    {
        for (float j = 0; j < iNumVertCols; ++j)
        {
            verts[k].x =  j * fDx + xOffset;
            verts[k].z = -i * fDz + zOffset;
            verts[k].y =  0.0f;

            /* Opère une translation afin que le centre de la grille
               soit positionné en fonction du centre spécifié en paramètre */
            D3DXMATRIX T;
            D3DXMatrixTranslation(&T, center.x, center.y, center.z);
            D3DXVec3TransformCoord(&verts[k], &verts[k], &T);
            
            ++k;  // Prochain vertex
        }
    }

    /* Construction des indices */ 

    indices.resize(numTris * 3);
    
    // Créé les indices pour chaque face
    k = 0;
    for (DWORD i = 0; i < (DWORD)numCellRows; ++i)
    {
        for (DWORD j = 0; j < (DWORD)numCellCols; ++j)
        {
            indices[k]     =   i   * iNumVertCols + j;
            indices[k + 1] =   i   * iNumVertCols + j + 1;
            indices[k + 2] = (i+1) * iNumVertCols + j;
                    
            indices[k + 3] = (i+1) * iNumVertCols + j;
            indices[k + 4] =   i   * iNumVertCols + j + 1;
            indices[k + 5] = (i+1) * iNumVertCols + j + 1;

            // Prochaine face
            k += 6;
        }
    }
}

 

Résumé :

Nous avons expliqué comment générer une grille en 3D avec le paramétrage de sa longueur et de sa largeur puis la dimensions de ses faces.

Voici les fichiers sources : Simple Grille.zip

Références :

– Introduction to DirectX 9.0c – Frank D. Luna

Créer un curseur de souris customisé pour votre jeu

wow_cursors

Intro :

Dans un jeu, il peut être plus joli de changer l’image du curseur de la souris en fonction de ce que pointe la souris.

Prérequis :

– Savoir lire du C++

– Savoir initialiser DirectX 10 à travers la classe System

Explications :

– Téléchargez un curseur (par exemple WoW.cur)

– Créez un fichier « Res.rc » :

#include "Ressource.h"

IDC_CURSOR_ARROW CURSOR DISCARDABLE "WoW.cur"

 

– Créez un fichier « Ressource.h » :

#define IDC_CURSOR_ARROW 4000

Mettez ces fichiers dans le dossier des fichiers de ressources :

ressource

Voici la fonction pour fixer le curseur de la souris sur la fenêtre de rendu :

#include "Ressource.h"

void LoadCustomCursor()
{
    HINSTANCE hInst = (HINSTANCE)GetModuleHandle(NULL);
    HWND hwnd = SYSTEM->GetHwnd();

    HCURSOR WoWCursor = LoadCursor(hInst, MAKEINTRESOURCE(IDC_CURSOR_ARROW));

    SetClassLong(hwnd, GCL_HCURSOR, (LONG)WoWCursor);
}

 

Il vaut mieux utiliser l’appel à la fonction SetClassLong que l’appel à la fonction SetCursor ;
en effet le programme gardera constamment l’image de la souris même si le pointeur de souris sort de la fenêtre.

Résumé :

Nous avons présenté une méthode permettant de changer l’image du curseur de la souris.

Une classe pour afficher des lignes en 3D avec une certaine couleur

3D_lines

Intro :

Il peut être utile d’afficher des lignes dans la surface de rendu ; par exemple pour représenter des normales, des trajectoires, un repère, etc…

Prérequis :

– Savoir lire du C++

– Savoir utiliser la classe MeshSceneNode

– Savoir utiliser la classe ShaderTechnique et les déclarations de vertices.

Explications :

Voici le fichier Vertex3DLine.h :

#ifndef MESH_3D_LINE_H
#define MESH_3D_LINE_H

class Vertex3DLine : public MeshSceneNode
{
public:
    Vertex3DLine(unsigned int bufferBytesSize);
    virtual ~Vertex3DLine();

    bool Initialize();

    void AddLine(D3DXVECTOR3 startPoint, D3DXVECTOR3 endPoint, D3DXCOLOR color);

private:
    void ResizeVertexBuffer(unsigned int iNewBytesSize);

private:
    std::vector<PCVertex> m_vertices;

    unsigned int m_bufferBytesSize;
};

#endif

 

Voici le fichier Vertex3DLine.cpp :

#include "Vertex3DLine.h"

Vertex3DLine::Vertex3DLine(unsigned int bufferBytesSize) :
m_bufferBytesSize(bufferBytesSize)
{
    SetVertexType(VertexLayoutType::PC_VERTEX);
    SetDrawMethod(DrawMethod::DRAW_NORMAL);
    SetTopology(D3D10_PRIMITIVE_TOPOLOGY_LINELIST);

    SetShaderTechnique( SHADER_MANAGER->GetShader("Vertex3DLine") );
}

Vertex3DLine::~Vertex3DLine()
{
    m_vertices.clear();
}

bool Vertex3DLine::Initialize()
{
    HRESULT hr;

    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(PCVertex) * m_bufferBytesSize;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;

    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&bd, 0, &m_pVertexBuffer);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}

void Vertex3DLine::AddLine(D3DXVECTOR3 startPoint, D3DXVECTOR3 endPoint, D3DXCOLOR color)
{
    if (!m_pVertexBuffer)
    {
        return;
    }

    D3D10_BUFFER_DESC bd;
    m_pVertexBuffer->GetDesc(&bd);

    unsigned int iActualBufferBytesSize = bd.ByteWidth;
    unsigned int iNewBufferBytesSize = (m_vertices.size() + 2) * sizeof(PCVertex);

    // Si la taille du buffer est trop petite pour ajouter de nouveaux éléments
    // => on redimensionne
    if (iNewBufferBytesSize > iActualBufferBytesSize)
    {
        ResizeVertexBuffer(iActualBufferBytesSize + 2);
    }

    PCVertex* data = nullptr;

    PCVertex vert1;
    PCVertex vert2;

    vert1.position = startPoint;
    vert1.color = color;

    vert2.position = endPoint;
    vert2.color = color;

    m_vertices.push_back(vert1);
    m_vertices.push_back(vert2);

    m_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&data);

    memcpy(data, m_vertices.data(), sizeof(PCVertex) * m_vertices.size());

    m_pVertexBuffer->Unmap();

    m_iVerticesCount = m_vertices.size();
}

void Vertex3DLine::ResizeVertexBuffer(unsigned int iNewBytesSize)
{
    SAFE_RELEASE(m_pVertexBuffer);

    HRESULT hr;

    D3D10_BUFFER_DESC bd;
    bd.Usage = D3D10_USAGE_DYNAMIC;
    bd.ByteWidth = sizeof(PCVertex) * iNewBytesSize;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    bd.MiscFlags = 0;
    hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&bd, nullptr, &m_pVertexBuffer);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
    }
}

 

Voici le fichier ShaderTechnique_Vertex3DLine.h :

//------------------------------------------------------
// Shader d'affichage de lignes 3D
//------------------------------------------------------
class Vertex3DShaderTechnique : public ShaderTechnique
{
public:
    Vertex3DShaderTechnique();
    virtual ~Vertex3DShaderTechnique();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);

};

 

Voici le fichier ShaderTechnique_Vertex3DLine.cpp :

#include "ShaderTechnique_Vertex3DLine.h"

Vertex3DShaderTechnique::Vertex3DShaderTechnique() :
ShaderTechnique("3DLine.fx", "Render", VertexLayoutType::PC_VERTEX)
{
}

Vertex3DShaderTechnique::~Vertex3DShaderTechnique()
{
}

bool Vertex3DShaderTechnique::Initialize()
{
    ShaderTechnique::Initialize();

    SetupShaderVariables();

    return true;
}

void Vertex3DShaderTechnique::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);
}

void Vertex3DShaderTechnique::Update(float fTimeSinceLastFrame)
{
    SetAutoMatrix(ShaderVariableType::WORLD);
    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier 3DLine.fx correspondant :

matrix World;
matrix View;
matrix Projection;

struct VS_INPUT
{
    float4 Pos : POSITION;
    float4 Color : COLOR0;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float4 Color : COLOR0;
};

PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );
    
    output.Color = input.Color;
    
    return output;
}

float4 PS( PS_INPUT input) : SV_Target
{
    return input.Color;
}

technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

 

Résumé :

Nous avons présenté un système permettant d’afficher des lignes dans la surface de rendu tout en pouvant spécifier leur couleur.

Une classe Skybox pour afficher un ciel autour de votre caméra – 1ère approche

skybox

Intro :

Pour que votre scène soit réaliste, il peut être utile d’afficher un décor de ciel de grande taille autour de la caméra.

Ceci constitue une première approche à l’affichage d’une Skybox.

Prérequis :

– Savoir utiliser la classe ShaderTechnique. Voir cet article.

– Savoir utiliser la classe MeshSceneNode. Voir cet article.

– Savoir un peu utiliser DirectX 10.

Explications :

Tout d’abord la classe dérivant ShaderTechnique :

//------------------------------------------------------
// Une simple effet affichant une SkyBox
//------------------------------------------------------
class ShaderTechnique_SkyBox : public ShaderTechnique
{
public:
    friend class SkyBoxSceneNode;

    ShaderTechnique_SkyBox();
    virtual ~ShaderTechnique_SkyBox();

    virtual bool Initialize();

    virtual void SetupShaderVariables();

    virtual void Update(float fTimeSinceLastFrame);
};

 

Voici ensuite le fichier ShaderTechnique_SkyBox.cpp :

ShaderTechnique_SkyBox::ShaderTechnique_SkyBox() :
ShaderTechnique("SkyBox.fx", "Render", VertexLayoutType::PTN_VERTEX)
{
}

ShaderTechnique_SkyBox::~ShaderTechnique_SkyBox()
{
}

bool ShaderTechnique_SkyBox::Initialize()
{
    ShaderTechnique::Initialize();

    SetupShaderVariables();

    return true;
}

void ShaderTechnique_SkyBox::SetupShaderVariables()
{
    RegisterMatrixVariable("World", ShaderVariableType::WORLD);
    RegisterMatrixVariable("View", ShaderVariableType::VIEW);
    RegisterMatrixVariable("Projection", ShaderVariableType::PROJECTION);

    RegisterTextureVariable("txFace");
}

void ShaderTechnique_SkyBox::Update(float fTimeSinceLastFrame)
{
    SetAutoMatrix(ShaderVariableType::WORLD);
    SetAutoMatrix(ShaderVariableType::VIEW);
    SetAutoMatrix(ShaderVariableType::PROJECTION);
}

 

Voici le fichier SkyBoxSceneNode.h :

#ifndef SKY_BOX_SCENE_NODE_H
#define SKY_BOX_SCENE_NODE_H

class SkyBoxSceneNode : public MeshSceneNode
{
public:
    SkyBoxSceneNode(ID3D10ShaderResourceView* pTop, ID3D10ShaderResourceView* pBottom,
        ID3D10ShaderResourceView* pLeft, ID3D10ShaderResourceView* pRight,
        ID3D10ShaderResourceView* pFront, ID3D10ShaderResourceView* pBack);

    virtual ~SkyBoxSceneNode();

    virtual bool Initialize();
    virtual void Render(float fTimeSinceLastFrame);

protected:
    virtual void OnPreRender(float fTimeSinceLastFrame);
    virtual void OnPostRender(float fTimeSinceLastFrame);

private:
    PTNVertex SetVertex(D3DXVECTOR3 p, D3DXVECTOR2 t, D3DXVECTOR3 n);

private:
    ID3D10ShaderResourceView* m_pFrontTex;
    ID3D10ShaderResourceView* m_pLeftTex;
    ID3D10ShaderResourceView* m_pBackTex;
    ID3D10ShaderResourceView* m_pRightTex;
    ID3D10ShaderResourceView* m_pTopTex;
    ID3D10ShaderResourceView* m_pBottomTex;

    ID3D10Buffer* m_pFrontVB;
    ID3D10Buffer* m_pLeftVB;
    ID3D10Buffer* m_pBackVB;
    ID3D10Buffer* m_pRightVB;
    ID3D10Buffer* m_pTopVB;
    ID3D10Buffer* m_pBottomVB;
};

#endif

 

Voici le fichier SkyBoxSceneNode.cpp :

#include "SkyBoxSceneNode.h"

SkyBoxSceneNode::SkyBoxSceneNode(ID3D10ShaderResourceView* pTop, ID3D10ShaderResourceView* pBottom,
        ID3D10ShaderResourceView* pLeft, ID3D10ShaderResourceView* pRight,
        ID3D10ShaderResourceView* pFront, ID3D10ShaderResourceView* pBack) :
m_pFrontVB(nullptr),
m_pLeftVB(nullptr),
m_pBackVB(nullptr),
m_pRightVB(nullptr),
m_pTopVB(nullptr),
m_pBottomVB(nullptr),
m_pFrontTex(pFront),
m_pLeftTex(pLeft),
m_pBackTex(pBack),
m_pRightTex(pRight),
m_pTopTex(pTop),
m_pBottomTex(pBottom)
{
    SetVertexType(VertexLayoutType::PTN_VERTEX);
    SetDrawMethod(DrawMethod::DRAW_INDEXED);
    SetTopology(D3D_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    SetShaderTechnique( SHADER_MANAGER->GetShader("SkyBox") );
}

SkyBoxSceneNode::~SkyBoxSceneNode()
{
}

PTNVertex SkyBoxSceneNode::SetVertex(D3DXVECTOR3 p, D3DXVECTOR2 t, D3DXVECTOR3 n)
{
    PTNVertex vertex;
    
    vertex.position = p;
    vertex.texture = t;
    vertex.normal = n;

    return vertex;
}

bool SkyBoxSceneNode::Initialize()
{
    std::vector<unsigned short> indices;

    indices.push_back(3);
    indices.push_back(1);
    indices.push_back(0);
    indices.push_back(2);
    indices.push_back(1);
    indices.push_back(3);

    BuildIB(indices);

    // On redecoupe les bords des textures pour éviter
    // d'afficher les arrêtes du cube
    float fTextureWidth = GetShaderTextureViewSize(m_pBottomTex).x;

    float onepixel = 1.0f / (fTextureWidth * 1.5f);
    float t = 1.0f - onepixel;
    float o = 0.0f + onepixel;

    std::vector<PTNVertex> vertices;

    // Front side
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1,-1),  D3DXVECTOR2(o, t), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1,-1),  D3DXVECTOR2(o, o), D3DXVECTOR3(0, 0, 1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 0, 1.0f)));

    BuildVB(vertices);

    m_pFrontVB = GetVertexBuffer();

    // Left side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1,-1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(-1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(-1, 0, 0)));

    BuildVB(vertices);

    m_pLeftVB = GetVertexBuffer();

    // Back side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, 0, -1.0f)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 0, -1.0f)));

    BuildVB(vertices);

    m_pBackVB = GetVertexBuffer();

    // Right side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(o, t), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(o, o), D3DXVECTOR3(1, 0, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(1, 0, 0)));

    BuildVB(vertices);

    m_pRightVB = GetVertexBuffer();

    // Top side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1,-1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1, 1, 1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1, 1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, -1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1, 1,-1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, -1, 0)));

    BuildVB(vertices);

    m_pTopVB = GetVertexBuffer();

    // Bottom side
    vertices.clear();

    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1, 1), D3DXVECTOR2(t, t), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3( 1,-1,-1), D3DXVECTOR2(o, t), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1,-1), D3DXVECTOR2(o, o), D3DXVECTOR3(0, 1, 0)));
    vertices.push_back( SetVertex(D3DXVECTOR3(-1,-1, 1), D3DXVECTOR2(t, o), D3DXVECTOR3(0, 1, 0)));

    BuildVB(vertices);

    m_pBottomVB = GetVertexBuffer();

    return true;
}

void SkyBoxSceneNode::Render(float fTimeSinceLastFrame)
{
    D3D10_RENDERER->EnableZBuffer(false);

    SetVertexBuffer(m_pFrontVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pFrontTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pLeftVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pLeftTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pBackVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pBackTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pRightVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pRightTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pTopVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pTopTex);
    Renderable::Render(fTimeSinceLastFrame);

    SetVertexBuffer(m_pBottomVB);
    GetShaderTechnique()->SetTextureRV("txFace", m_pBottomTex);
    Renderable::Render(fTimeSinceLastFrame);

    D3D10_RENDERER->EnableZBuffer(true);
}

void SkyBoxSceneNode::OnPreRender(float fTimeSinceLastFrame)
{
    Camera* pCamera = SCENE_MANAGER->GetActiveCamera();

    D3DXMATRIX finalMatrix;

    D3DXMATRIX translate;

    D3DXVECTOR3 cameraPos = pCamera->GetPosition();

    // On centre toujours la skybox autour de la position de la caméra
    D3DXMatrixTranslation(&translate, cameraPos.x, cameraPos.y, cameraPos.z);

    float viewDistance = (pCamera->GerNearValue() + pCamera->GetFarValue()) * 0.5f;

    // On agrandie la skybox pour qu'elle est une bonne taille
    D3DXMATRIX scale;
    D3DXMatrixScaling(&scale, viewDistance, viewDistance, viewDistance);

    finalMatrix = scale * translate;

    SCENE_MANAGER->PushAndSetMatrix(finalMatrix);
}

void SkyBoxSceneNode::OnPostRender(float fTimeSinceLastFrame)
{    
    SCENE_MANAGER->PopMatrix();
}

 

Voici le fichier SkyBox.fx :

matrix World;
matrix View;
matrix Projection;

Texture2D txFace;

SamplerState samLinear
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD;
    float3 Norm : NORMAL;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
    float3 Norm : NORMAL;
};

PS_INPUT VS( VS_INPUT input )
{
    PS_INPUT output = (PS_INPUT)0;
    
    output.Pos = mul( input.Pos, World );
    output.Pos = mul( output.Pos, View );
    output.Pos = mul( output.Pos, Projection );

    output.Tex = input.Tex;
    
    output.Norm = mul( input.Norm, World );
    
    return output;
}

float4 PS( PS_INPUT input) : SV_Target
{
    float4 textureColor = txFace.Sample(samLinear, input.Tex);
    
    return textureColor;
}

technique10 Render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );
    }
}

 

Résumé :

Nous avons présenté un énorme cube autour de la caméra qui servira de ciel ou de décor.

Une classe MeshSceneNode pour représenter des meshes dans votre scène

mesh node

Intro :

Pour afficher vos entités / mesh dans la scène 3D, je vous présente une classe qui permettra de les initialiser rapidement.

Elle pourra construire facilement du contenu 3D à partir des méthodes suivantes :

// Construit et remplit le vertex buffer à partir d'un tableau de vertices
bool BuildVB(std::vector<T>& vertices)

// Construit et remplit l'index buffer à partir d'une tableau d'indices
bool BuildIB(std::vector<T>& indices)

// Construit un mesh à partir des tableaux de vertices et d'indices
bool Build(std::vector<T>& vertices, std::vector<unsigned short>& indices)

 

Prérequis :

– Savoir utiliser la classe SceneNode. Voir cet article.

– Savoir utiliser la classe Renderable. Voir cet article.

– Savoir un peu utiliser DirectX 10

– Comprendre la classe SceneNode

 

Explications :

Voici le fichier MeshSceneNode.h :

#ifndef MESH_SCENE_NODE_H
#define MESH_SCENE_NODE_H

class MeshSceneNode : public SceneNode, public Renderable
{
public:
    MeshSceneNode(SceneNode* pParent = nullptr,
        const D3DXVECTOR3& position = D3DXVECTOR3(0, 0, 0),
        const D3DXVECTOR3& rotation = D3DXVECTOR3(0, 0, 0),
        const D3DXVECTOR3& scale = D3DXVECTOR3(0, 0, 0));

    virtual ~MeshSceneNode();

    virtual bool Initialize() { return true; }

    template<class T>
    bool BuildVB(std::vector<T>& vertices)
    {
        unsigned int iVertexSize = sizeof(T);

        D3D10_BUFFER_DESC vertexBufferDesc;
        D3D10_SUBRESOURCE_DATA vertexData;
 
        m_iVerticesCount = vertices.size();

        vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;

        vertexBufferDesc.ByteWidth = iVertexSize * m_iVerticesCount;

        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
        vertexBufferDesc.MiscFlags = 0;

        vertexData.pSysMem = vertices.data();

        // Créé le vertex buffer
        HRESULT hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &m_pVertexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    bool BuildIB(std::vector<T>& indices)
    {
        D3D10_BUFFER_DESC indexBufferDesc;
        D3D10_SUBRESOURCE_DATA indexData;

        HRESULT hr;
 
        m_iIndicesCount = indices.size();

        indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(T) * m_iIndicesCount;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        indexData.pSysMem = indices.data();
    
        // Créé l'index buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &m_pIndexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    bool Build(std::vector<T>& vertices, std::vector<unsigned short>& indices)
    {
        unsigned int iVertexSize = sizeof(T);

        D3D10_BUFFER_DESC vertexBufferDesc;
        D3D10_BUFFER_DESC indexBufferDesc;
 
        D3D10_SUBRESOURCE_DATA vertexData;
        D3D10_SUBRESOURCE_DATA indexData;
 
        HRESULT hr;
 
        m_iVerticesCount = vertices.size();
        m_iIndicesCount = indices.size();

        vertexBufferDesc.Usage = D3D10_USAGE_DYNAMIC;

        vertexBufferDesc.ByteWidth = iVertexSize * m_iVerticesCount;

        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
        vertexBufferDesc.MiscFlags = 0;

        vertexData.pSysMem = vertices.data();

        // Créé le vertex buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&vertexBufferDesc, &vertexData, &m_pVertexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }
 
        indexBufferDesc.Usage = D3D10_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = sizeof(unsigned short) * m_iIndicesCount;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        indexData.pSysMem = indices.data();
    
        // Créé l'index buffer
        hr = D3D10_RENDERER->GetDevice()->CreateBuffer(&indexBufferDesc, &indexData, &m_pIndexBuffer);
 
        if (FAILED(hr))
        {
            ShowMessageBoxDXError(hr);

            return false;
        }

        return true;
    }

    template<class T>
    void UpdateVertices(const std::vector<T> vertices)
    {
        T* data = nullptr;

        m_pVertexBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, (void**)&data);

        memcpy(data, vertices.data(), sizeof(T) * vertices.size());

        m_pVertexBuffer->Unmap();
    }
};

#endif

 

Resumé :

Nous avons présentée une classe qui permettra d’afficher du contenu 3D dans vos scène d’entités (SceneNode).

Une classe gestionnaire de textures pour DirectX 10

texture

Intro :

Pour gérer nos fichiers textures au sein d’un moteur 3D, nous allons utiliser un « TextureManager ».

Prérequis :

– Savoir utiliser la classe Singleton. Voir cet article

Explications :

Voici le fichier TextureManager.h :

#ifndef TEXTURE_MANAGER_H
#define TEXTURE_MANAGER_H

class TextureManager : public Singleton<TextureManager>
{
public:
    TextureManager();
    virtual ~TextureManager();

    ID3D10ShaderResourceView* AddTexture(const std::string& sTextureFileName, const std::string& sTextureAliasName);

    bool HasTexture(const std::string& sTextureAliasName);

    ID3D10ShaderResourceView* GetTexture(const std::string& sTextureAliasName);

    // Charge la texture dans la mémoire et si elle est déjà chargée, elle renvoit le pointeur de la ressource
    ID3D10ShaderResourceView* LoadTexture(const std::string& sTextureFilename);

    void RemoveTexture(const std::string& sTextureAliasName);
    void RemoveTexture(ID3D10ShaderResourceView* pTextureRV);

private:
    std::map<std::string, ID3D10ShaderResourceView*> m_texturesAlias;
};

#endif

 

Voici le fichier TextureManager.cpp :

#include "TextureManager.h"

template<> TextureManager* Singleton<TextureManager>::ms_instance = nullptr;

TextureManager::TextureManager()
{
}

TextureManager::~TextureManager()
{
    std::map<std::string, ID3D10ShaderResourceView*>::iterator it;

    for (it = m_texturesAlias.begin(); it != m_texturesAlias.end(); it++)
    {
        ID3D10ShaderResourceView* pTextureRV = (it->second);
        SAFE_RELEASE(pTextureRV);
    }
}

ID3D10ShaderResourceView* TextureManager::AddTexture(const std::string& sTextureFileName, const std::string& sTextureAliasName)
{
    HRESULT hr;

    // On charge la texture
    ID3D10ShaderResourceView* pTextureRV = nullptr;

    hr = D3DX10CreateShaderResourceViewFromFileA(D3D10_RENDERER->GetDevice(), sTextureFileName.c_str(), nullptr, nullptr, &pTextureRV, nullptr);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return nullptr;
    }

    if (m_texturesAlias.count(sTextureAliasName) == 0)
    {
        m_texturesAlias[sTextureAliasName] = pTextureRV;
    }
    else
    {
        std::cout << Formater(L"Texture déjà enregistrée !") << std::endl;
    }

    return pTextureRV;
}

bool TextureManager::HasTexture(const std::string& sTextureAliasName)
{
    return m_texturesAlias.count(sTextureAliasName) != 0;
}

ID3D10ShaderResourceView* TextureManager::GetTexture(const std::string& sTextureAliasName)
{
    if (m_texturesAlias.count(sTextureAliasName) != 0)
    {
        return m_texturesAlias[sTextureAliasName];
    }
    else
    {
        return nullptr;
    }
}

void TextureManager::RemoveTexture(const std::string& sTextureAliasName)
{
    Assert(m_texturesAlias.count(sTextureAliasName) > 0);

    ID3D10ShaderResourceView* pRV = m_texturesAlias[sTextureAliasName];

    SAFE_RELEASE(pRV);

    m_texturesAlias.erase(sTextureAliasName);
}

void TextureManager::RemoveTexture(ID3D10ShaderResourceView* pTextureRV)
{
    std::string sTextureAliasName = "";

    for (auto it = m_texturesAlias.begin(); it != m_texturesAlias.end(); it++)
    {        
        sTextureAliasName = it->first;

        ID3D10ShaderResourceView* pRV = it->second;

        if (pRV == pTextureRV)
        {
            SAFE_RELEASE(pRV);
        }
    }
        
    m_texturesAlias.erase(sTextureAliasName);
}

ID3D10ShaderResourceView* TextureManager::LoadTexture(const std::string& sTextureFilename)
{
    if (HasTexture(sTextureFilename))
    {
        return GetTexture(sTextureFilename);
    }
    else
    {
        return AddTexture(sTextureFilename, sTextureFilename);
    }
}

 

Résumé :

Nous avons présenté une classe gestionnaire de chargement de textures.

Entrées clavier et souris avec DirectInput

 

souris

Intro :

DirectInput est un composant de DirectX qui permet de gérer les entrées clavier (touches) et de la souris (position).

Bien qu’il soit tendance à n’être plus utilisé, il est toujours utile car il permet de capturer directement les entrées clavier à partir des drivers périphériques correspondants.

Prérequis :

– Savoir un peu lire du C++.

– Savoir initialiser DirectX 10.1.

– Savoir utiliser la classe Singleton.

Explications :

Voici le fichier InputManager.h :

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#define DIRECTINPUT_VERSION 0x0800

#include <dinput.h>
#include <map>
#include <vector>

#include "Singleton.h"
#include "System.h"

class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : key(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int key;
    // La caractère correspondant de la touche
    const wchar_t text;
};

class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void RepetitiveKeyPressed(const KeyEvent &arg) = 0;
    virtual void RepetitiveKeyReleased(const KeyEvent &arg) = 0;

    virtual void SoloKeyPressed(const KeyEvent &arg) = 0;
    virtual void SoloKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    enum Modifier
    {
        SHIFT = 0x0000001,
        CTRL  = 0x0000010,
        ALT   = 0x0000100
    };

    InputManager();
    virtual ~InputManager();

    bool Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight);

    void Frame();

    void GetMouseLocation(int& mouseX, int& mouseY);

    bool IsKeyDown(char key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsModifierDown(Modifier modifier);

    void SetBuffered(bool bBuffered);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie la signification de la clé, ex : "F1" pour la touche F1
    std::string GetKeyCodeAsString(char key);

    // Renvoie la touche correspondante à la clé
    wchar_t GetKeyCodeAsWChar(char key);

private:    
    bool ReadKeyboard();
    bool ReadMouse();
    void ProcessInput();

    void UpdateBuffered();
    void UpdateNonBuffered();

private:
    IDirectInput8* m_pDI;
    IDirectInputDevice8* m_pKeyboard;
    IDirectInputDevice8* m_pMouse;

    unsigned char m_keyboardState[256];

    DIMOUSESTATE m_mouseState;

    int m_iScreenWidth;
    int m_iScreenHeight;

    int m_iMouseX;
    int m_iMouseY;

    wchar_t m_deadkey;

    bool m_bBufferedMode;

    unsigned int m_iModifiers;

    std::map<int, bool> m_pressedKeys;
 
    /* Pourquoi ici un unique objet ?
    Car on a besoin que d'un seul KeyListener car
    c'est soit la console, soit la messagerie qui ont besoin des entrées clavier
    à la fois */
    KeyListener* m_pKeyListener;

    // Variables conçues pour que l'appel à la callback KeyListener soit appelée moins de fois dans le temps
    unsigned long m_iElapsedTime;
    unsigned long m_iLastTime;
    unsigned int m_iWaitTime;

    // Variables conçues pour la touche appuyée soit repétée quand on reste longtemps appuyée dessus
    unsigned long m_iElapsedTime2;
    unsigned long m_iLastTime2;
    unsigned int m_iWaitTime2;

    bool m_bCanRepeatKey;
    unsigned int m_iCurrentKeyCode;
};

#endif

 

Voici le fichier InputManager.cpp :


#include "Console.h"
#include "InputManager.h"

#include <sstream>
#include <DxErr.h>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_pDI(nullptr),
m_pKeyboard(nullptr),
m_pMouse(nullptr),
m_iMouseX(0),
m_iMouseY(0),
m_deadkey('\0'),
m_bBufferedMode(false),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_bCanRepeatKey(false),
m_iWaitTime(INPUT_MANAGER_REPETITIVE_WAIT_TIME),
m_iLastTime(0),
m_iElapsedTime(0),
m_iWaitTime2(INPUT_MANAGER_REPETITIVE_WAIT_TIME_2),
m_iLastTime2(0),
m_iElapsedTime2(0),
m_iCurrentKeyCode(0)
{
    SetBuffered(true);
}

InputManager::~InputManager()
{
    if (m_pMouse)
    {
        m_pMouse->Unacquire();
        SAFE_RELEASE(m_pMouse);
    }

    if (m_pKeyboard)
    {
        m_pKeyboard->Unacquire();
        SAFE_RELEASE(m_pKeyboard);
    }

    if (m_pDI)
    {
        SAFE_RELEASE(m_pDI);
    }

}

bool InputManager::Initialize(HINSTANCE hInstance, HWND hwnd, int iScreenWidth, int iScreenHeight)
{
    HRESULT hr;

    m_iScreenWidth = iScreenWidth;
    m_iScreenHeight = iScreenHeight;
    
    /****** Setup du clavier ******/
    hr= DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_pDI, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pDI->CreateDevice(GUID_SysKeyboard, &m_pKeyboard, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetDataFormat(&c_dfDIKeyboard);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    
    // On configure la taille du buffer qui sert à sauvegarder l'état
    // des touches du clavier entrées
    DIPROPDWORD dipdw;
    dipdw.diph.dwSize  = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj = 0;
    dipdw.diph.dwHow = DIPH_DEVICE;
    dipdw.dwData = INPUT_MANAGER_BUFFER_SIZE;

    hr = m_pKeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);

    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /***********/
    hr = m_pKeyboard->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    /****** Setup de la souris ******/

    hr = m_pDI->CreateDevice(GUID_SysMouse, &m_pMouse, nullptr);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetDataFormat(&c_dfDIMouse);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    hr = m_pMouse->Acquire();
    if (FAILED(hr))
    {
        ShowMessageBoxDXError(hr);
        return false;
    }

    return true;
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsModifierDown(Modifier modifier)
{
    return m_iModifiers & MKF_MODIFIERS;
}

void InputManager::Frame()
{
    ReadKeyboard();

    ReadMouse();

    ProcessInput();
}

void InputManager::GetMouseLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

bool InputManager::ReadKeyboard()
{
    if (m_bBufferedMode)
    {
        UpdateBuffered();
    }
    else
    {
        UpdateNonBuffered();
    }

    return true;
}

/* setcapture & getcapture (essayer) */
void InputManager::UpdateBuffered()
{
    DIDEVICEOBJECTDATA diBuff[INPUT_MANAGER_BUFFER_SIZE];
    DWORD entries = INPUT_MANAGER_BUFFER_SIZE;

    HRESULT hr;
    hr = m_pKeyboard->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), diBuff, &entries, 0);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }

    // Pour la touche soit repétée quand on reste longtemps appuyée dessus
    if (m_iCurrentKeyCode != 0)
    {
        m_iElapsedTime2 = GetTickCount() - m_iLastTime2;
        if (m_iElapsedTime2 > m_iWaitTime2)
        {
            m_bCanRepeatKey = true; 

            m_iLastTime2 = GetTickCount();
        }
    }
    else
    {
        // On reset le timer au-dessus
        m_iLastTime2 = GetTickCount();
    }

    if (!SYSTEM->HasFocus())
    {
        m_bCanRepeatKey = false;
        m_iElapsedTime2 = 0;
        m_iCurrentKeyCode = 0;
    }

    // On parcourt les touches appuyées
    for (unsigned int i = 0; i < entries; i++)
    {
        int iKeyCode = diBuff[i].dwOfs;

        if (diBuff[i].dwData & 0x80)
        {        
            m_pressedKeys[iKeyCode] = true;

            m_iCurrentKeyCode = diBuff[i].dwData;    

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers |= CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers |= SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers |= ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
        else
        {
            m_pressedKeys[iKeyCode] = false;
            m_bCanRepeatKey = false;
            m_iCurrentKeyCode = 0;

            if (iKeyCode == DIK_LCONTROL || iKeyCode == DIK_RCONTROL)
            {
                m_iModifiers &= ~CTRL;
            }
            else if (iKeyCode == DIK_LSHIFT || iKeyCode == DIK_RSHIFT)
            {
                m_iModifiers &= ~SHIFT;
            }
            else if (iKeyCode == DIK_LMENU || iKeyCode == DIK_RMENU)
            {
                m_iModifiers &= ~ALT;
            }

            if (m_pKeyListener)
            {
                m_pKeyListener->SoloKeyReleased( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }
        }
    }

    /********** Entrées répétées ***********/

    // On cherche quelle touche du clavier a été appuyée
    int iKeyCode = 0;
    for (auto it = m_pressedKeys.begin(); it != m_pressedKeys.end(); it++)
    {
        bool bPressed = it->second;
        if (bPressed)
        {
            iKeyCode = it->first;
        }
    }

    if (m_pKeyListener && iKeyCode != 0)
    {
        m_iElapsedTime = GetTickCount() - m_iLastTime;

        // On appel la callback a une certaine fréquence de temps
        if (m_iElapsedTime > m_iWaitTime)
        {
            m_pKeyListener->RepetitiveKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );

            if (m_bCanRepeatKey)
            {
                m_pKeyListener->SoloKeyPressed( KeyEvent(iKeyCode, GetKeyCodeAsWChar(iKeyCode)) );
            }

            m_iLastTime = GetTickCount();
        }
    }
}

void InputManager::UpdateNonBuffered()
{    
    HRESULT hr;

    hr = m_pKeyboard->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            ZeroMemory(m_keyboardState, sizeof(m_keyboardState));
            m_pKeyboard->Acquire();
        }
    }
}

bool InputManager::ReadMouse()
{
    HRESULT hr;

    hr = m_pMouse->GetDeviceState(sizeof(DIMOUSESTATE), (LPVOID)&m_mouseState);

    if (FAILED(hr))
    {
        if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
        {
            m_pMouse->Acquire();
        }
        else
        {
            return false;
        }
    }

    return true;
}

void InputManager::ProcessInput()
{
    m_iMouseX += m_mouseState.lX;
    m_iMouseY += m_mouseState.lY;

    if (m_iMouseX < 0)
    {
        m_iMouseX = 0;
    }

    if (m_iMouseY < 0)  
    {
        m_iMouseY = 0;
    }
    
    if (m_iMouseX > m_iScreenWidth)  
    {
        m_iMouseX = m_iScreenWidth;
    }

    if (m_iMouseY > m_iScreenHeight)
    {
        m_iMouseY = m_iScreenHeight;
    }
    
    return;
}

bool InputManager::IsKeyDown(char key)
{
    return KEYDOWN(m_keyboardState, key);
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return (m_mouseState.rgbButtons[button] & 0x80) != 0;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    std::string sKeyCode;

    char temp[256];

    DIPROPSTRING prop;
    prop.diph.dwSize = sizeof(DIPROPSTRING);
    prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    prop.diph.dwObj = static_cast<DWORD>(key);
    prop.diph.dwHow = DIPH_BYOFFSET;

    if (SUCCEEDED(m_pKeyboard->GetProperty(DIPROP_KEYNAME, &prop.diph)))
    {
        if (WideCharToMultiByte(CP_ACP, 0, prop.wsz, -1, temp, sizeof(temp), nullptr, nullptr))
        {
            return sKeyCode.assign(temp);
        }
    }

    std::stringstream ss;
    ss << "Key_" << (int) key;

    return sKeyCode.assign(ss.str());
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);
    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if(ascii == 1 && m_deadkey != '\0' )
    {
        WCHAR wcBuff[3] = {buff[0], m_deadkey, '\0'};
        WCHAR out[3];
        
        m_deadkey = '\0';
        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == 1)
    {        
        m_deadkey = '\0';
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadkey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadkey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadkey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadkey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadkey = 0x327;
                break;
            default:
                m_deadkey = buff[0];
                break;
        }
    }

    return 0;
}

void InputManager::SetBuffered(bool bBuffered)
{
    m_bBufferedMode = bBuffered;
}

 

Résumé :

Avec l’interface DirectInput, on peut élaborer un système d’entrée clavier souris tout simple.

Entrées clavier et souris avec la boucle de message WndProc

keyboard

todo : remplacer les scancodes par les virtual codes

Intro :

A l’inverse de l’utilisation du composant DirectInput de DirectX, nous pouvons directement utiliser les réponses de la boucle de message WndProc en tant qu’entrées clavier-souris.

Cet article fournira la classe InputManager utilisée dans d’autres articles.

Explications :

Voici le code pour le fichier InputManager.h

#ifndef INPUT_MANAGER_H
#define INPUT_MANAGER_H

#include <windows.h>
#include <map>
#include <vector>
#include <sstream>
#include <iostream>

#define BIT(x) 1 << x

#include "Singleton.h"

// Objet d'entrée clavier qui sera envoyé au KeyListener enregistré
// dans la classe InputManager
class KeyEvent
{
public:
    KeyEvent(int kc, wchar_t txt) : keyCode(kc), text(txt) {}
    virtual ~KeyEvent() {}

    // L'ID de la touche
    const int keyCode;

    // La caractère correspondant de la touche
    const wchar_t text;
};

// Classe KeyListener qui permet à n'importe quel objet héritant de
// cette classe de recevoir les entrées clavier
class KeyListener
{
public:
    virtual ~KeyListener() {}

    virtual void OnKeyPressed(const KeyEvent &arg) = 0;
    virtual void OnKeyReleased(const KeyEvent &arg) = 0;
};

class InputManager : public Singleton<InputManager>
{
public:
    // Touches spéciales
    enum KeyModifier
    {
        SHIFT = BIT(0),
        CTRL  = BIT(1),
        ALT   = BIT(2)
    };

    // Bouttons de la souris
    enum MouseButton
    {
        MouseButtonLeft,
        MouseButtonRight,
        MouseButtonMiddle,
        MouseButtoNone
    };

    InputManager();
    virtual ~InputManager();

    // Méthodes d'enregistrements des entrées clavier de la boucle
    // de message Win32 WndProc
    void InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode);
    void InjectKeyModifierDown(KeyModifier modifier);
    void InjectKeyModifierUp(KeyModifier modifier);

    // Méthodes d'enregistrements des entrées souris de la boucle
    // de message Win32 WndProc
    void InjectMousePosition(int iMouseX, int iMouseY);
    void InjectMouseButtonDown(MouseButton button);
    void InjectMouseButtonUp();

    // Méthodes de récupérations de la position de la souris
    void GetMouseAbsoluteLocation(int& iMouseX, int& iMouseY);
    void GetMouseRelativePosition(float& fX, float& fY);

    // Méthodes de récupérations des entrées clavier
    bool IsKeyDown(unsigned int key);
    bool IsKeyUp(unsigned int key);
    bool IsMouseButtonDown(unsigned int button);
    bool IsKeyModifierDown(KeyModifier modifier);

    // Enregistre le receveur de touches clavier
    void SetKeyListener(KeyListener* pKeyListener);

    // Renvoie le caractère wchar_t correspondant à la clé spécifié
    wchar_t GetKeyCodeAsWChar(char key);

    // Renvoie une chaîne de caractère correspondant à la clé spécifié
    std::string GetKeyCodeAsString(char key);

    // Met le curseur au centre de la fenêtre
    void CenterMouseCursor();

    // Je place cette fonction utilitaire au sein même
    // de la classe, mais sa place serait plus juste ailleurs
    std::string Formater(const std::wstring& src)
    {
        char outString[512];
        CharToOemW(src.c_str(), outString);

        return std::string(outString);
    }

private:
    // Coordonnées de la souris
    int m_iMouseX;
    int m_iMouseY;

    // Variable représentant les combinaison de touches
    // de modification
    unsigned int m_iModifiers;

    // Touches préssées
    std::map<unsigned int, bool> m_pressedKeys;
 
    // L'écouteur d'entrée clavier
    KeyListener* m_pKeyListener;

    // Le boutton de la souris^^
    MouseButton m_iMouseButton;

    // variable ppour gérer les accents comme :
    // û, ì, ò, ô, etc...
    wchar_t m_deadKey;
};

#endif

 

Voici le code pour le fichier InputManager.cpp :

#include "InputManager.h"
#include "System.h"

#include <sstream>

template<> InputManager* Singleton<InputManager>::ms_instance = nullptr;

InputManager::InputManager() :
m_iMouseX(0),
m_iMouseY(0),
m_iModifiers(0),
m_pKeyListener(nullptr),
m_iMouseButton(MouseButton::MouseButtoNone),
m_deadKey('\0')
{
}

InputManager::~InputManager()
{
}

void InputManager::SetKeyListener(KeyListener* pKeyListener)
{
    assert(pKeyListener);

    m_pKeyListener = pKeyListener;
}

bool InputManager::IsKeyModifierDown(KeyModifier modifier)
{
    return m_iModifiers & modifier;
}

void InputManager::InjectKeyModifierDown(KeyModifier modifier)
{
    m_iModifiers |= modifier;
}

void InputManager::InjectKeyModifierUp(KeyModifier modifier)
{
    m_iModifiers &= ~(modifier);
}

void InputManager::InjectKeyDown(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = true;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyPressed(keyEvent);

    }
}

void InputManager::InjectKeyUp(unsigned int iVirtualKeyCode, unsigned int iScanCode)
{
    m_pressedKeys[iVirtualKeyCode] = false;

    if (m_pKeyListener != nullptr)
    {
        wchar_t c = GetKeyCodeAsWChar(iScanCode);

        KeyEvent keyEvent(iScanCode, c);
        m_pKeyListener->OnKeyReleased(keyEvent);
    }
}

void InputManager::InjectMousePosition(int iMouseX, int iMouseY)
{
    m_iMouseX = iMouseX;
    m_iMouseY = iMouseY;
}

void InputManager::GetMouseAbsoluteLocation(int& mouseX, int& mouseY)
{
    mouseX = m_iMouseX;
    mouseY = m_iMouseY;
}

// todo : essayer avec les m_iMouseX et m_iMouseY
void InputManager::GetMouseRelativePosition(float& fX, float& fY)
{
    POINT p;
    if (!GetCursorPos(&p))
    {
        DWORD xy = GetMessagePos();
        p.x = LOWORD(xy);
        p.y = HIWORD(xy);
    }

    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    GetWindowRect(SYSTEM->GetHwnd(), &rect);

    float x = p.x - rect.left - borderX;
    float y = p.y - rect.top - borderY;

    fX = ((float) x / (float) SYSTEM->GetWindowWidth()) - 0.5f;
    fY = ((float) y / (float) SYSTEM->GetWindowHeight()) - 0.5f;
}

bool InputManager::IsKeyDown(unsigned int key)
{
    return m_pressedKeys[key] == true;
}

bool InputManager::IsKeyUp(unsigned int key)
{
    return m_pressedKeys[key] == false;
}

void InputManager::InjectMouseButtonDown(MouseButton button)
{
    m_iMouseButton = button;
}

void InputManager::InjectMouseButtonUp()
{
    m_iMouseButton = MouseButtoNone;
}

bool InputManager::IsMouseButtonDown(unsigned int button)
{
    return m_iMouseButton == button;
}

std::string InputManager::GetKeyCodeAsString(char key)
{
    wchar_t w = GetKeyCodeAsWChar(key);

    std::wstring wStr;
    wStr.push_back( w );

    std::string str = Formater(wStr);

    return str;
}

wchar_t InputManager::GetKeyCodeAsWChar(char key)
{
    BYTE keyState[256];
    HKL layout = GetKeyboardLayout(0);

    if (GetKeyboardState(keyState) == 0)
    {
        return 0;
    }

    unsigned int vk = MapVirtualKeyEx(key, 3, layout);

    if (vk == 0)
    {
        return 0;
    }

    WCHAR buff[3] = {0};
    int ascii = ToUnicodeEx(vk, key, keyState, buff, 3, 0, layout);

    if (ascii == 1 && m_deadKey != '\0')
    {
        WCHAR wcBuff[3] = {buff[0], m_deadKey, '\0'};
        WCHAR out[3];

        if (FoldStringW(MAP_PRECOMPOSED, (LPWSTR)wcBuff, 3, (LPWSTR)out, 3))
        {
            return out[0];
        }
    }
    else if (ascii == -1)
    {    
        return buff[0];
    }
    else if (ascii == 2)
    {
        switch (buff[0])    
        {
            case 0x5E: // Circumflex accent: â
                m_deadKey = 0x302;
                break;
            case 0x60: // Grave accent: à
                m_deadKey = 0x300;
                break;
            case 0xA8: // Diaeresis: ü
                m_deadKey = 0x308;
                break;
            case 0xB4: // Acute accent: é
                m_deadKey = 0x301;
                break;
            case 0xB8: // Cedilla: ç
                m_deadKey = 0x327;
                break;
            default:
                m_deadKey = buff[0];
                break;
        }

    }    
    else if (ascii == 1)
    {        
        m_deadKey = '\0';
        return buff[0];
    }

    return 0;
}

void InputManager::CenterMouseCursor()
{
    int borderX = GetSystemMetrics(SM_CXSIZEFRAME);
    int borderY = GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME);

    RECT rect;
    HWND hwnd = SYSTEM->GetHwnd();

    GetWindowRect(hwnd, &rect);

    unsigned int iWindowWidth = SYSTEM->GetWindowWidth();
    unsigned int iWindowHeight = SYSTEM->GetWindowHeight();

    SetCursorPos(0.5f * iWindowWidth + rect.left + borderX, 0.5f * iWindowHeight + rect.top + borderY);
}

 

Voici la fonction WndProc utilisée pour obtenir les entrées afin de les passer à la classe InputManager :

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (!InputManager::IsCreated())
    {
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    switch (message)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_KEYDOWN:    
            {
                INPUT_MANAGER->InjectKeyDown(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierDown(InputManager::KeyModifier::ALT);
                }
            }
            break;

        case WM_KEYUP:
            {
                INPUT_MANAGER->InjectKeyUp(wParam, HIWORD(lParam));

                if (wParam == VK_SHIFT)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::SHIFT);
                }

                if (wParam == VK_CONTROL)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::CTRL);
                }

                if (wParam == VK_MENU)
                {
                    INPUT_MANAGER->InjectKeyModifierUp(InputManager::KeyModifier::ALT);
                }

                break;
            }
        case WM_MOUSEMOVE:
            {
                int iMouseX = (int) LOWORD(lParam);
                int iMouseY = (int) HIWORD(lParam);

                INPUT_MANAGER->InjectMousePosition(iMouseX, iMouseY);

                break;
            }
        case WM_LBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonLeft);

                break;
            }
        case WM_RBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonRight);

                break;
            }
        case WM_MBUTTONDOWN:
            {
                INPUT_MANAGER->InjectMouseButtonDown(InputManager::MouseButton::MouseButtonMiddle);

                break;
            }
        case WM_MBUTTONUP:
        case WM_LBUTTONUP:
        case WM_RBUTTONUP:
            {
                INPUT_MANAGER->InjectMouseButtonUp();

                break;
            }
        default:
            {            
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
    }
 
    return 0;
}

 

Résumé :

Nous avons présenter une manière de récupérer les entrées clavier de votre jeu ou application.

Voici l’archive du code complet pour cet RawInputManager.zip

Références :

Une caméra à la première personne (FPS)

Intro :

camera

Pour pouvoir observer nos entités dans une scène 3D, il faut pour cela mouvoir une caméra que l’on peut bouger aisément.

Prérequis :

– Savoir lire et écrire du C++

– Savoir utiliser les objets et fonctions mathématiques de la librairie D3DX

– Savoir ce qu’est une matrice

– Savoir ce que sont les transformations de repère

– Savoir utiliser la classe InputManager utilisée dans le code présenté

– Savoir utiliser les mathématiques des vecteurs (produit vectoriel et produit scalaire)

Explications :

Voici le code tout simplement de notre caméra à la première personne.

A noté l’utilisation de la classe InputManager avec les appels à INPUT_MANAGER dans le code.

Voici le code pour le fichier FPSCamera.h :

#ifndef FPS_CAMERA_H
#define FPS_CAMERA_H

#include <d3d10.h>
#include <d3dx10.h>

class FPSCamera
{
public:
    FPSCamera();
    virtual ~FPSCamera();

    D3DXMATRIX* GetViewMatrix();
    D3DXMATRIX* GetProjectionMatrix();

    D3DXVECTOR3 GetPosition();

    void LookAt(D3DXVECTOR3& target);

    void UpdateLens(float fFov, float fAspect, float fNearZ, float fFarZ);
    
    void SetSpeed(float fSpeed);

    void Update(float dt);

    void Walk(float d);

    void Strafe(float d);

private:
    void BuildView();

private:
    D3DXMATRIX m_view;
    D3DXMATRIX m_proj;

    D3DXVECTOR3 m_pos;

    D3DXVECTOR3 m_right;
    D3DXVECTOR3 m_up;
    D3DXVECTOR3 m_lookAt;

    float m_fSpeed;
};

 

Voici le code pour le fichier FPSCamera.cpp :

#include "FPSCamera.h"

FPSCamera::FPSCamera() :
m_fSpeed(30)
{
    D3DXMatrixIdentity(&m_view);
    D3DXMatrixIdentity(&m_proj);

    m_pos = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_lookAt = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

    UpdateLens((float)D3DX_PI * 0.25f, (float)D3D10_RENDERER->GetViewportWidth() / (float)D3D10_RENDERER->GetViewportHeight(), 0.1f, 100.0f);
}

FPSCamera::~FPSCamera()
{
}

D3DXMATRIX* FPSCamera::GetViewMatrix()
{
    return &m_view;
}

D3DXMATRIX* FPSCamera::GetProjectionMatrix()
{
    return &m_proj;
}

D3DXVECTOR3 FPSCamera::GetPosition()
{
    return m_pos;
}

void FPSCamera::LookAt(D3DXVECTOR3& target)
{
    D3DXVECTOR3 L = target - m_pos;
    D3DXVec3Normalize(&L, &L);

    D3DXVECTOR3 R;
    D3DXVec3Cross(&R, &m_up, &L);
    D3DXVec3Normalize(&R, &R);

    D3DXVECTOR3 U;
    D3DXVec3Cross(&U, &L, &R);
    D3DXVec3Normalize(&U, &U);

    m_pos = m_pos;
    m_right = R;
    m_up = U;
    m_lookAt = L;

    BuildView();
}

void FPSCamera::UpdateLens(float fFov, float fAspect, float fNearZ, float fFarZ)
{
    D3DXMatrixPerspectiveFovLH(&m_proj, fFov, fAspect, fNearZ, fFarZ);
}
    
void FPSCamera::SetSpeed(float fSpeed)
{
    m_fSpeed = fSpeed;
}

void FPSCamera::Walk(float d)
{
    m_pos += d * m_lookAt;
}

void FPSCamera::Strafe(float d)
{
    m_pos += d * m_right;
}

void FPSCamera::Update(float dt)
{
    if (INPUT_MANAGER->IsKeyDown('Z'))
    {
        Walk(dt * m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('S'))
    {
        Walk(dt * -m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('D'))
    {
        Strafe(dt * m_fSpeed);
    }

    if (INPUT_MANAGER->IsKeyDown('Q'))
    {
        Strafe(dt * -m_fSpeed);
    }

    float x = 0.0f;
    float y = 0.0f;

    INPUT_MANAGER->GetMouseRelativePosition(x, y);

    float pitch = y * dt * 1800;
    float yAngle = x * dt * 1800;
    
    /* Contrainte du pitch */

    D3DXMATRIX R;
    D3DXMatrixRotationAxis(&R, &m_right, pitch);

    D3DXVECTOR3 tmpVect;
    D3DXVec3TransformCoord(&tmpVect, &m_up, &R);

    D3DXVECTOR3 axisY(0.0f, -1.0f, 0.0f);

    if (D3DXVec3Dot(&tmpVect, &axisY) < 0.0f)
    {
        D3DXVec3TransformCoord(&m_up, &m_up, &R);
        D3DXVec3TransformCoord(&m_lookAt, &m_lookAt, &R);    
    }

    /* Rotate Y */

    D3DXMatrixRotationY(&R, yAngle);
    D3DXVec3TransformCoord(&m_right, &m_right, &R);
    D3DXVec3TransformCoord(&m_up, &m_up, &R);
    D3DXVec3TransformCoord(&m_lookAt, &m_lookAt, &R);

    BuildView();

    INPUT_MANAGER->CenterMouseCursor();
}

void FPSCamera::BuildView()
{
    D3DXVec3Normalize(&m_lookAt, &m_lookAt);

    D3DXVec3Cross(&m_up, &m_lookAt, &m_right);
    D3DXVec3Normalize(&m_up, &m_up);

    D3DXVec3Cross(&m_right, &m_up, &m_lookAt);
    D3DXVec3Normalize(&m_right, &m_right);

    float x = -D3DXVec3Dot(&m_pos, &m_right);
    float y = -D3DXVec3Dot(&m_pos, &m_up);
    float z = -D3DXVec3Dot(&m_pos, &m_lookAt);

    m_view(0,0) = m_right.x;
    m_view(1,0) = m_right.y;
    m_view(2,0) = m_right.z;
    m_view(3,0) = x;

    m_view(0,1) = m_up.x;
    m_view(1,1) = m_up.y;
    m_view(2,1) = m_up.z;
    m_view(3,1) = y;

    m_view(0,2) = m_lookAt.x;
    m_view(1,2) = m_lookAt.y;
    m_view(2,2) = m_lookAt.z;
    m_view(3,2) = z;

    m_view(0,3) = 0.0f;
    m_view(1,3) = 0.0f;
    m_view(2,3) = 0.0f;
    m_view(3,3) = 1.0f;
}

#endif

 

Comment se servir du code précédent ? 

Ce qui nous intéresse dans cette implémentation de caméra ce sont la matrice de vue (m_view) et la matrice de projection (m_proj).

Dans votre propre code, avant d’effectuer le rendu de vos modèles 3D vos devez spécifier ces matrices au shader correspondant :

m_pViewShaderVariable->SetMatrix((float*)CAMERA->GetViewMatrix());
m_pProjShaderVariable->SetMatrix((float*)CAMERA->GetProjectionMatrix();

 

Et voilà c’est tout simple !

 

Résumé :

Nous avons implémenté une caméra à la première personne (FPS) qui nous permet de naviguer facilement dans notre scène 3D.

Nous accédons aux matrices de vue et de projection avec les méthodes GetViewMatrix() et GetProjectionMatrix().

Références :

– Introduction to DirectX 9.0c – A shader approach

Un Performance Timer pour synchroniser vos entités

timer-image

Intro :

Dans un jeu, il peut être utile de calculer le temps entre chaque affichage pour synchroniser la mise à jour de certaines entités.

En effet la vitesse de calcul fait par votre processeur peut rendre la vitesse de ces entités trop rapide.

Par exemple la vitesse de déplacement d’un modèle peut être plus rapide sur la machine d’un PC avec un processeur plus rapide que le vôtre, parce que la boucle de rendu est appelée plus fréquemment que chez vous.

Un mécanisme de calcul du temps passé entre chaque rendu d’affichage permet de pallier ce problème.

Prérequis :

– Savoir lire du C++.

Explications :

Nous allons utiliser la fonction QueryPerformanceCounter qui donne le temps présent en « count ».

#include <windows.h>

__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);

/* Instructions */

__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);

On fait la soustraction : B – A, et on a le temps passé (en count) entre l’exécution de ces instructions !

D’autre part il nous faut le temps en seconde (et non en count !)

Dond avec la fonction QueryPerformanceFrequency on obtient le count par seconde.
On aura donc le temps escompté en seconde !


__int64 cntsPerSec = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&cntsPerSec);

 

Le nombre de secondes escompté en un count est calculé par :

// Secondes par count :
float secsPerCnt = 1.0f / (float)cntsPerSec;

// Temps en secondes :
valueInSecs = valueInCounts * secsPerCnt;


Temps passé entre chaque affichage :

On peut calculer le temps passé entre chaque rendu d’affichage. Notons t le temps passé ;
et t _{i} le temps passé depuis le ième affichage et t _{i-1} le temps passé depuis le précédent affichage.

\Delta t = t _{i} { - } t_{i-1}

 

On calcule ainsi le temps passé entre chaque affichage. On utilise cette valeur de temps afin de synchroniser la mise à jour de nos entités dans la boucle de rendu :


void MaBoucleDeRendu()
{
    /* Instructions */
    
    __int64 currTimeStamp = 0;
    QueryPerformanceCounter((LARGE_INTEGER*)&currTimeStamp);
    
    float fTimeSinceLastFrame = (currTimeStamp - prevTimeStamp) * cntsPerSec;

    UpdateScene(fTimeSinceLastFrame);
    DrawScene();
    
    prevTimeStamp = currTimeStamp;
    
    /* Instructions */
}

 

Voici un exemple de mise à jour d’une entité en utilisant la valeur fTimeSinceLastFrame (toujours changeante) :

 

Résumé :

Nous avons vu de quelle façon synchroniser la mise à jour de vos entités de façon à ce qu’elles varient dans le temps à la même vitesse sur toutes les machines.

Génération procédurale d’un labyrinthe 3D

laby

Intro :

La génération procédurale d’un contenu par un quelconque algorithme permet de le créer de manière intelligente !

Dans cet article nous allons voir comment générer à la volée un labyrinthe en 3D.

Prérequis :

– Savoir lire du C++.

– Savoir ce qu’est un vertex buffer et un index buffer.

– Savoir manipuler un tableau à deux dimensions.

Explications :

Nous allons utiliser une classe Array2D qui nous permettra de manipuler un tableau à deux dimensions.

Voilà le code pour ce tableau spécial et simple d’utilisation (Array2D.h) :

#ifndef ARRAY_2D_H
#define ARRAY_2D_H

// Classe pour gérer facilement un tableau à deux dimensions.
template<typename T>
class Array2D
{
public:
    // Constructeur.
    Array2D(int iWidth = 0, int iHeight = 0) :
    m_iWidth(iWidth),
    m_iHeight(iHeight),
    m_vArray(iWidth*iHeight)
    {
    }

    // Redimensionner.
    void resize(int iWidth, int iHeight)
    {
        m_iWidth = iWidth;
        m_iHeight = iHeight ;
        m_vArray.resize(iWidth*iHeight);
    }

    inline T& operator() (unsigned int x, unsigned int y)
    {
        assert(((y*m_iWidth) + x) < m_vArray.size());
        return m_vArray[ (y*m_iWidth) + x ];
    }

    inline T& operator[] (int pos)
    {
        return m_vArray[pos];
    }

    inline const T& operator() (unsigned int x, unsigned int y) const
    {
        assert(((y*m_iWidth) + x) < m_vArray.size());
        return m_vArray[ (y*m_iWidth) + x ];
    }

    inline const T& operator[] (int x) const
    {
        return m_vArray[pos];
    }   

    inline int getWidth() const { return m_iWidth; }
    inline int getHeight() const { return m_iHeight; }

private:
    int m_iWidth; // Largeur.
    int m_iHeight; // Hauteur.
    std::vector<T> m_vArray; // Le vecteur qui va servir de tableau à deux dimensions.
};

#endif

Voici le fichier d’en-tête MazeGenerator.h

#ifndef MAZE_GENERATOR_H
#define MAZE_GENERATOR_H

#include "Array2D.h"

class MazeGenerator
{
public:
    MazeGenerator();
    virtual ~MazeGenerator() {};

    void generate(const int iRowCellNumber, const int iColumnCellNumber);

    void computeNeighbourgsCeils();

    void createMazeNode(scene::ISceneManager* pSceneManager, NewtonWrapper::CollisionManager* pCollisionManager);

    int generateRandomCellNumber()
    {
        assert(m_iRowCellNumber && m_iColumnCellNumber);

        return rand() % m_iCellNumber;
    }

    scene::CDynamicMeshBuffer* createMeshBuffer();

private:
    Array2D<MazeCell*> m_aMaze;
    int m_iRowCellNumber;
    int m_iColumnCellNumber;
    int m_iCellNumber;
};

#endif

Références :

– http://www.mazeworks.com/mazegen/mazetut/

Caméra à la 3ème personne trigonométrique

caméra

Intro :

Pour se déplacer dans la scène 3D il faut mouvoir une caméra.

Dans cet article nous allons implémenter une caméra qui tourne autour d’un joueur (ou autour d’une position)

Prérequis :

– Comprendre un minimum de notions trigonométriques

Explications :

Déclarer ces trois variables dans un fichier quelconque :

float m_fCameraSpeed;
float m_fDistance;
D3DXVECTOR2 m_v2CameraAngle;

 

Voilà les fonctions d’initialisation et de mise à jour de la caméra à la 3ème personne :


void CameraManager::Initialize()
{
    m_v2CameraAngle = D3DXVECTOR2(0.0f, 0.0f);
}

void CameraManager::UpdateThirdPersonCamera()
{
    // C'est deux fonctions retournent simplement la quantité de mouvement
    // du pointeur de la souris
    // Voir l'article sur le InputManager
    float fX = InputManager::getSingletonPtr()->getMouseRelativePos().x;
    float fY = InputManager::getSingletonPtr()->getMouseRelativePos().y;

#define CAMERA_SPEED_ROTATE_AROUND 0.0037f
    // Normalement il faut utiliser le temps passé depuis le dernier affichage
    // (voir l'article sur la variable fTimeSinceLastFrame "Performance Timer")
    m_v2CameraAngle.x += fX * CAMERA_SPEED_ROTATE_AROUND;
    m_v2CameraAngle.y -= fY * CAMERA_SPEED_ROTATE_AROUND;
    // Clamp : une fonction qui permet de garder le résultat
    // passé entre un intervalle donné
    m_v2CameraAngle.y = Clamp(camAngle.y, 0.003f, Ogre::Math::PI - 0.003f);

    // Usage de la trigonométrie pour faire en sorte que la caméra
    // fasse une rotation autour d'un point
    float cosX = cos(m_v2CameraAngle.x);
    float cosY = cos(m_v2CameraAngle.y);
    float sinY = sin(m_v2CameraAngle.y);
    float sinX = sin(m_v2CameraAngle.x);

    float xf = cosX * sinY;
    float yf = cosY;
    float zf = sinX * sinY;

    D3DXVECTOR3 finalPos(xf, yf, zf);

    /* Position du joueur (où de l'objet auquel vous voulez tourner autour !*/
    D3DXVECTOR3 playerPos = GAME->getPlayer()->getSceneNode()->getPosition();

    // Votre objet caméra utilisé
    m_pCamera->setPosition(finalPos * m_fDistance + playerPos);
    m_pCamera->lookAt(GAME->getPlayer()->getSceneNode()->getPosition());

    /** Pour centrer le curseur de ma souris */
    centerCursorPos();
}

Résumé :

Nous avons présenté un moyen d’implémenter une caméra trigonométrique qui opère une révolution autour de la position du joueur !

Classe CommandLineManager pour récupérer les paramètres de votre exécutable

Terminal-icon-shell-linux-unix

Intro :

[utiliser la classe DataParameters]

On peut passer à exécutable des paramètres comme suit : mon_exécutable.exe -param1 arg1 arg2 -param2 arg3

Cette classe va vous permettre de les récupérer vos arguments plus facilement.

Préréquis :

– Savoir lire du C++

– Savoir écrire un programme rudimentaire  en C++

Explications :

Voici le fichier d’en-tête ParametersManager.h :

#ifndef PARAMETERS_MANAGER_H
#define PARAMETERS_MANAGER_H

/**
* \brief Une classe pour gérer les paramètres des arguments d'entrée de
* ligne de commande du programme.
*/
class Parameter
{
public:
    Parameter(std::string sName);
    virtual ~Parameter();

    // Met un argument du paramètre
    void pushArgument(const std::string& sArgument);
    // Récupère le nom du paramètre
    const std::string& getName() const;
    // Récupère un argument
    const char* getArgument(unsigned int iArgumentNumber) const;
    // Retourne le nombre d'argument
    int getArgumentCount() const;

private:
    std::string m_sName;
    std::vector<std::string> m_vArguments;
};

class CommandLineManager : public Singleton<CommandLineManager>
{
public:
    CommandLineManager(int argc, char* argv[]);
    ~CommandLineManager();
    
    // Est-ce que la ligne de commande a ce paramètre
    bool hasParameter(const std::string& sParameter);
    // Le nombre de paramètre
    int getParameterCount();
    // Récupère le paramètre
    Parameter* getParameter(const std::string& sParameter);

private:
    // Evalue la ligne de commande
    void parseCommandLineArguments(int iArgumentCount, char* paArguments[]);

private:
    std::map<std::string, Parameter*> m_vParameters;
};

#endif

Voici le fichier .cpp :

// Format : -PARAM ARG1 ARG2...
Parameter::Parameter(std::string sName) :
m_sName(sName)
{
}

Parameter::~Parameter()
{
}

// On ajoute les arg1, arg2 à la suite des un et des autres
void Parameter::pushArgument(std::string sArgument)
{
    m_vArguments.push_back(sArgument);
}

const char* Parameter::getArgument(unsigned int iArgumentNumber)  const
{
    if (iArgumentNumber < m_vArguments.size())
        return m_vArguments[iArgumentNumber].c_str();
    else
        return "Unknown";
}

int Parameter::getArgumentCount() const
{
    return m_vArguments.size();
}

/********************************** Déclaration de la classe CommandLineManager **********************************/

CommandLineManager::CommandLineManager(int argc, char* argv[])
{
    parseCommandLineArguments(argc, argv);
}

CommandLineManager::~CommandLineManager()
{
    // Libère les objets paramètre de la mémoire.
    std::map<std::string, Parameter*>::iterator it;
    for (it = m_vParameters.begin(); it != m_vParameters.end(); ++it)
    {
        Parameter* pParam = (*it).second;
        SAFE_DELETE(pParam);
    }
}

void CommandLineManager::parseCommandLineArguments(int iArgumentCount, char* paArguments[])
{
    Parameter* pCurrentParameter = nullptr;

    // Exemple de ligne de commande : game.exe -fullscreen -res 800 600 -opengl
    for (int i = 0; i < iArgumentCount; ++i)
    {
        std::string sCurrentArgumentName(paArguments[i]);    

        // Si on a un nouvel argument qui commence par un '-'.
        if (sCurrentArgumentName[0] == '-')
        {
            // On enlève l'indicateur de paramètre (premier caractère).
            sCurrentArgumentName.erase(0, 1);
            // On créé un nouveau objet paramètre avec son nom.
            pCurrentParameter = new Parameter(sCurrentArgumentName);
            // On l'ajoute dans la liste des arguments.
            m_vParameters[sCurrentArgumentName] = pCurrentParameter;
        }
        else if (pCurrentParameter)// C'est peut-être un argument, (paramètre qui ne commence pas par '-').
        {
            pCurrentParameter->pushArgument(sCurrentArgumentName);
        }
    }
}

bool CommandLineManager::hasParameter(const std::string& sParameter)
{
    return m_vParameters.count(sParameter) > 0;
}

int CommandLineManager::getParameterCount()
{
    return m_vParameters.size();
}

Parameter* CommandLineManager::getParameter(const std::string& sParameter)
{
    if (m_vParameters.count(sParameter) > 0)
    {
        return m_vParameters[sParameter];
    }
    else
    {
        return nullptr;
    }
}

 

Utilisation :

On utilise ce bout de code dans la fonction main (int argc, char** argv) :

CommandLineManager* pCommandLineManager = new CommandLineManager(argc, argv);

// Bloc d'analyse des arguments de ligne de commande, etc...
unsigned int iWindowWidth = SYSTEM_MINIMUM_WINDOW_WIDTH;
unsigned int iWindowHeight = SYSTEM_MINIMUM_WINDOW_HEIGHT;
bool bFullScreen = false;

// Si on a au moins un paramètre.
if (pCommandLineManager->getParameterCount() > 0)
{
    // Paramètre de résolution de la fenêtre
    if (pCommandLineManager->hasParameter("res"))
    {
        Parameter* pWindowResParameter = pCommandLineManager->getParameter("res");

        if (pWindowResParameter->getArgumentCount() > 1)
        {
            // Acquisition des paramètres de résolution de la fenêtre
            std::string sResolutionWidth = pWindowResParameter->getArgument(0);
            std::string sResolutionHeight = pWindowResParameter->getArgument(1);

            iWindowWidth = atoi(sResolutionWidth.c_str());
            iWindowHeight = atoi(sResolutionHeight.c_str());
        }
    }

    if (pCommandLineManager->hasParameter("fullscreen"))
        bFullScreen = true;
}

 

Résumé :

Nous avons présenté une méthode pour analyser vos arguments en ligne de commande.

Classe DataParameters pour faciliter l’échange d’information entre modules

todo : refaire le stream reset avec une méthode

Intro :

Parfois il est utile de regrouper des paramètres ensemble afin de les échanger entre modules.

Explications :

Voici le fichier DataParameters.h :

#ifndef DATA_PARAMETERS_H
#define DATA_PARAMETERS_H
 
#include <string>
#include <map>

class DataParameters
{
public:
    // Différents types de variables à stocker
    struct Param { int i; float f; std::string s; };
 
    DataParameters();
    virtual ~DataParameters();
     
    // Premier numéro d'index (c'est-à-dire le numéro 0)
    DataParameters(int param);
    DataParameters(float param);
    DataParameters(char* param);
    DataParameters(const std::string& sParam);
 
    // Par nom
    DataParameters(const std::string& sParamName, int param);
    DataParameters(const std::string& sParamName, float param);
    DataParameters(const std::string& sParamName, const char* param);
    DataParameters(const std::string& sParamName, const std::string& sParam);
 
    /******************/
 
    // Par nom
    void setParam(const std::string& sParamName, int param);
    void setParam(const std::string& sParamName, float param);
    void setParam(const std::string& sParamName, const char* param);
    void setParam(const std::string& sParamName, const std::string& sParam);
 
    // Par index
    void pushParam(int param);
    void pushParam(float param);
    void pushParam(const char* param);
    void pushParam(const std::string& param);
 
    Param* getParameter(const std::string& sParamName);
    int getParameterAsInt(const std::string& sParamName);
    float getParameterAsFloat(const std::string& sParamName);
    const char* getParameterAsString(const std::string& sParamName);
 
    // Par accès au premier argument
    Param* getFirstParameter();
    int getFirstParameterAsInt();
    float getFirstParameterAsFloat();
    const char* getFirstParameterAsString();

    unsigned int getParamNumber();
 
    // Opérateurs >> flux pour lire les données
    DataParameters& operator >>(bool& data);
    DataParameters& operator >>(int& data);
    DataParameters& operator >>(unsigned int& data);
    DataParameters& operator >>(float& data);
    DataParameters& operator >>(char* data);
    DataParameters& operator >>(std::string& data);

    // Opérateurs << flux pour écrire des données
    DataParameters& operator <<(bool data);
    DataParameters& operator <<(int data);
    DataParameters& operator <<(unsigned int data);
    DataParameters& operator <<(float data);
    DataParameters& operator <<(char* data);
    DataParameters& operator <<(std::string data);

    std::map<std::string, Param>& GetParameters();

    // Variable pour réinitialiser les flux sortants
    static char* StreamReset;

private:
    // Liste des paramètres
    std::map<std::string, Param> m_params;

    // Numéro de paramètre courant
    unsigned int m_iIndex;

    // Position de lecture du stream courant
    unsigned int m_iCurrentStreamPos;
};
 
#endif

Voici le fichier .cpp :

#include "DataParameters.h"
 
/********************************* Class DataParameters *********************************/

char* DataParameters::StreamReset  = "StreamReset";

DataParameters::DataParameters() :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
}
 
DataParameters::~DataParameters()
{
    m_params.clear();
}
 
DataParameters::DataParameters(int param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(float param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(char* param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
DataParameters::DataParameters(const std::string& param) :
m_iIndex(0),
m_iCurrentStreamPos(0)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
/******* Par nom *******/
 
DataParameters::DataParameters(const std::string& sParamName, int param)
{
    m_params[sParamName].i = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, float param)
{
    m_params[sParamName].f = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, const char* param)
{
    m_params[sParamName].s = param;
}
 
DataParameters::DataParameters(const std::string& sParamName, const std::string& sParam)
{
    m_params[sParamName].s = sParam;
}
 
/**********************************/
 
void DataParameters::setParam(const std::string& sParamName, int param)
{
    m_params[sParamName].i = param;
}
 
void DataParameters::setParam(const std::string& sParamName, float param)
{
    m_params[sParamName].f = param;
}
 
void DataParameters::setParam(const std::string& sParamName, const char* param)
{
    m_params[sParamName].s = param;
}
 
void DataParameters::setParam(const std::string& sParamName, const std::string& param)
{
    m_params[sParamName].s = param;
}
 
/*************** Getters ***************/
 
int DataParameters::getParameterAsInt(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].i;
    }
 
    return 0;
}
 
float DataParameters::getParameterAsFloat(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].f;
    }
 
    return 0.0f;
}
 
const char* DataParameters::getParameterAsString(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return m_params[sParamName].s.c_str();
    }
 
    return nullptr;
}
 
DataParameters::Param* DataParameters::getParameter(const std::string& sParamName)
{
    if (m_params.count(sParamName) > 0)
    {
        return &m_params[sParamName];
    }
 
    return nullptr;
}

std::map<std::string, DataParameters::Param>& DataParameters::GetParameters()
{
    return m_params;
}

/*************** Getters sans paramètres ***************/
 
int DataParameters::getFirstParameterAsInt()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].i;
    }
 
    return 0;
}
 
float DataParameters::getFirstParameterAsFloat()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].f;
    }
 
    return 0.0f;
}
 
const char* DataParameters::getFirstParameterAsString()
{
    if (m_params.count("0") > 0)
    {
        return m_params["0"].s.c_str();
    }
 
    return nullptr;
}
 
DataParameters::Param* DataParameters::getFirstParameter()
{
    if (m_params.count("0") > 0)
    {
        return &m_params["0"];
    }
 
    return nullptr;
}
 
unsigned int DataParameters::getParamNumber()
{
    return m_params.size();
}
 
/****************/
 
void DataParameters::pushParam(int param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = param;
    m_iIndex++;
}
 
void DataParameters::pushParam(float param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = param;
    m_iIndex++;
}
void DataParameters::pushParam(const char* param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}
 
void DataParameters::pushParam(const std::string& param)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = param;
    m_iIndex++;
}

//----------------------------------------
// Opérateurs >> pour lire les données
//----------------------------------------
DataParameters& DataParameters::operator >>(bool& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = (m_params[sParamName].i == 0) ? 0 : 1;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = false;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(int& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].i;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(unsigned int & data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].i;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(float& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].f;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = 0.0f;
    }

    return *this;
}

DataParameters& DataParameters::operator >>(char* data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = (char*)m_params[sParamName].s.c_str();

        m_iCurrentStreamPos++;
    }
    else
    {
        data = "";
    }

    return *this;
}

DataParameters& DataParameters::operator >>(std::string& data)
{
    std::string sParamName = std::to_string(m_iCurrentStreamPos);
 
    if (m_params.count(sParamName) > 0)
    {
        data = m_params[sParamName].s;

        m_iCurrentStreamPos++;
    }
    else
    {
        data = "";
    }

    return *this;
}

//----------------------------------------
// Opérateurs << pour écrire des données
//----------------------------------------
DataParameters& DataParameters::operator <<(bool data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(int data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(unsigned int data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].i = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(float data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].f = data;
    m_iIndex++;

    return *this;
}

DataParameters& DataParameters::operator <<(char* data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = data;
    m_iIndex++;

    if (data == "StreamReset")
    {
        m_iCurrentStreamPos = 0;
    }

    return *this;
}

DataParameters& DataParameters::operator <<(std::string data)
{
    std::string sParamName = std::to_string(m_iIndex);
 
    m_params[sParamName].s = data;
    m_iIndex++;

    return *this;
}

 

Résumé :

Nous avons présenté un objet rassemblant des données pouvant être de différents types.

Avec les flux << et >>  nous avons intégré une technique plus simple pour échanger des données.

Un système de Process et de ProcessManager

 

WoWScrnShot_082916_183045

Intro :

Dans un jeu vidéo on fait souvent appel à plusieurs sous-systèmes et pour les mettre à jour fréquemment.

On va utiliser deux classes permettant de gérer ces sous systèmes.

class Process : classe de base, on peut la dériver puis redéfinir la méthode VUpdate().
class ProcessManager : gestionnaire d’instances de Process.

Un Process peut être n’importe quoi : il faut juste tout simplement que ce soit un objet qui nécessite d’être mise à jour.

Par exemple on peut l’utiliser pour : le système de script, des collisions, de l’audio, de l’IA, du rendu de certaines entités.

Si on veut mettre le jeu en pause il  suffira de faire appel aux méthodes Pause() et UnPause().

Pour le fichier include Process.h


class Process
{
    friend class ProcessManager;

public:
    enum State
    {
        UNINITIALIZED = 0,
        REMOVED,
        RUNNING,
        PAUSED,
        SUCCEEDED,
        FAILED,
        ABORTED,
    };
    
private:
    State m_state; 

public:

    Process(void);
    virtual ~Process(void);
    
protected:
    virtual void VOnInit(void) { m_state = RUNNING; }
    virtual void VOnUpdate(unsigned long deltaMs) = 0;
    virtual void VOnSuccess(void) { }
    virtual void VOnFail(void) { }
    virtual void VOnAbort(void) { }

public:
    inline void Succeed(void);
    inline void Fail(void);
    
    inline void Pause(void);
    inline void UnPause(void);
    State GetState(void) const { return m_state; }
    bool IsAlive(void) const { return (m_state == RUNNING
                               || m_state == PAUSED); }
    bool IsDead(void) const { return (m_state == SUCCEEDED
                              || m_state == FAILED
                              || m_state == ABORTED); }
    bool IsRemoved(void) const { return (m_state == REMOVED); }
    bool IsPaused(void) const { return m_state == PAUSED; }

    inline void AttachChild(Process* pChild);
    Process* RemoveChild(void);
    Process* PeekChild(void) { return m_pChild; }

private:
    void SetState(State newState) { m_state = newState; }
};

Dans le fichier Process.cpp :


Process::Process(void)
{
    m_state = UNINITIALIZED;
}

Process::~Process(void)
{
    if (m_pChild)
    {
        m_pChild->VOnAbort();
    }
}

inline void Process::Succeed(void)
{
    assert(m_state == RUNNING || m_state == PAUSED);
    m_state = SUCCEEDED;
}

inline void Process::Fail(void)
{
    assert(m_state == RUNNING || m_state == PAUSED);
    m_state = FAILED;
}

inline void Process::AttachChild(StrongProcessPtr pChild)
{
    if (m_pChild)
        m_pChild->AttachChild(pChild);
    else
        m_pChild = pChild;
}

inline void Process::Pause(void)
{
    if (m_state == RUNNING)
        m_state = PAUSED;
    else
        std::cout <<
        "Attempting to pause a process that isn't running"
        << std::endl;
}

inline void Process::UnPause(void)
{
    if (m_state == PAUSED)
        m_state = RUNNING;
    else
        std::cout <<
        "Attempting to unpause a process that isn't paused"
        << std::endl;
}

Process* Process::RemoveChild(void)
{
    if (m_pChild)
    {
        m_pChild = NULL;
    }

    return NULL;
}

Maintenant pour le fichier include ProcessManager.h :


#include "Process.h"

class ProcessManager
{
    typedef std::list<Process*> ProcessList;

    ProcessList m_processList;

public:

    ~ProcessManager(void);

    unsigned int UpdateProcesses(unsigned long deltaMs);
    Process* AttachProcess(Process* pProcess);
    void AbortAllProcesses(bool immediate);

    unsigned int GetProcessCount(void) const {
                 return m_processList.size(); }

private:
    void ClearAllProcesses(void);
};

Pour le fichier ProcessManager.cpp :


ProcessManager::~ProcessManager(void)
{
    ClearAllProcesses();
}

unsigned int ProcessManager::UpdateProcesses(unsigned long deltaMs)
{
    unsigned short int successCount = 0;
    unsigned short int failCount = 0;

    ProcessList::iterator it = m_processList.begin();
    while (it != m_processList.end())
    {
        Process* pCurrProcess = (*it);

        ProcessList::iterator thisIt = it;
        ++it;

        if (pCurrProcess->GetState() == Process::UNINITIALIZED)
            pCurrProcess->VOnInit();

        if (pCurrProcess->GetState() == Process::RUNNING)
            pCurrProcess->VOnUpdate(deltaMs);

        if (pCurrProcess->IsDead())
        {
            switch (pCurrProcess->GetState())
            {
                case Process::SUCCEEDED :
                {
                    pCurrProcess->VOnSuccess();
                    Process* pChild = pCurrProcess->RemoveChild();
                    if (pChild)
                        AttachProcess(pChild);
                    else
                        ++successCount;
                    break;
                }

                case Process::FAILED :
                {
                    pCurrProcess->VOnFail();
                    ++failCount;
                    break;
                }

                case Process::ABORTED :
                {
                    pCurrProcess->VOnAbort();
                    ++failCount;
                    break;
                }
            }

            m_processList.erase(thisIt);
        }
    }

    return ((successCount << 16) | failCount);
}

Process* ProcessManager::AttachProcess(Process* pProcess)
{
    m_processList.push_front(pProcess);
    return pProcess;
}

void ProcessManager::ClearAllProcesses(void)
{
    m_processList.clear();
}

void ProcessManager::AbortAllProcesses(bool immediate)
{
    ProcessList::iterator it = m_processList.begin();
    while (it != m_processList.end())
    {
        ProcessList::iterator tempIt = it;
        ++it;

        StrongProcessPtr pProcess = *tempIt;
        if (pProcess->IsAlive())
        {
            pProcess->SetState(Process::ABORTED);
            if (immediate)
            {
                pProcess->VOnAbort();
                m_processList.erase(tempIt);
            }
        }
    }
}


Résumé :

Ces classes vous seront utiles pour implémenter vos propres systèmes / process.

Références :

– Game Coding Complete 4 (code LGPL)