Gestion optimisée du clavier avec l'API Win32

Résolu
Djazzyman Messages postés 10 Date d'inscription lundi 14 juillet 2008 Statut Membre Dernière intervention 16 mai 2013 - 31 juil. 2008 à 23:00
Djazzyman Messages postés 10 Date d'inscription lundi 14 juillet 2008 Statut Membre Dernière intervention 16 mai 2013 - 12 août 2008 à 11:15
Bonjour à tous !

Je developpe des petits programmes Windows depuis peu sous Dev C++ 5 (version 4.9.9.2 beta).
Jusqu'alors, je me contentais des simples WM_KEYDOWN et WM_KEYUP pour gérer le clavier, ce qui suffisait amplement à mes besoins.
Mais depuis peu, j'essaye de créer des mini-jeux 2D et il semblerait qu'il faille optimiser cette gestion du clavier.
Certes, il existe les fameux OpenGL et DirectX mais je préfèrerais utiliser l'API Win32, car je n'ai pas trop le temps de me pencher sur ces technologies.

Vous trouverez ci-dessous le code source complet de mon programme qui permet de déplacer un "pixel" dans une fenêtre Windows à l'aide des flèches du clavier.
Malheureusement, lorsqu'on appuie sur plusieurs touches en même temps (par exemple : flèche HAUT et flèche GAUCHE), le deplacement du "pixel" n'est pas FLUIDE.
Il y a un temps de réaction et les messages WM_KEYDOWN semblent être envoyés par Windows de façon bizarre...
Je ne comprends pas bien pourquoi mon "pixel" ne se déplace pas normalement et/ou s'immobilise !!!
Quelqu'un pourrait-il m'aider ? Ca serait vraiment sympa !

Voici le code source en C (il n'y a que 3 petites fonctions : l'incontournable WinMain, WindowProcedure pour la gestion des évènements et afficherPixel) :

#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "Windows App";

int WINAPI WinMain (HINSTANCE hThisInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR lpszArgument,
                    int nFunsterStil)

{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof(WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default color as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx(&wincl))
        return 0;

    /* The class is registered, let's create the program */
    hwnd = CreateWindowEx(
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           "Test clavier",       /* Title Text */
           WS_SYSMENU|WS_CAPTION|WS_MINIMIZEBOX|WS_VISIBLE,
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           406,                 /* The programs width */
           425,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow(hwnd, nFunsterStil);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while(GetMessage(&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}

// VARIABLE GLOBALE :
// La position courante du pixel dans la fenetre, initialement au centre
POINT position = {200, 200};
// Declaration de la fonction d'affichage du pixel a la position courante
void afficherPixel(HDC hdc);

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        // Gestion simultanee des touches du clavier (BUG WINDOWS ???) :
        // Deplacer la position courante du pixel puis afficher le contenu de la fenetre
        case WM_KEYDOWN:
            {
            short bitMaskKeyPressed = 32768;
            // La touche HAUT est enfoncee
            if(GetKeyState(VK_UP) & bitMaskKeyPressed)
                position.y -= 1;
            // La touche BAS est enfoncee
            if(GetKeyState(VK_DOWN) & bitMaskKeyPressed)
                position.y += 1;
            // La touche DROITE est enfoncee
            if(GetKeyState(VK_RIGHT) & bitMaskKeyPressed)
                position.x += 1;
            // La touche GAUCHE est enfoncee
            if(GetKeyState(VK_LEFT) & bitMaskKeyPressed)
                position.x -= 1;
            }
            /* NE FONCTIONNE PAS DU TOUT (Gestion simultannnee des touches du clavier)
            switch(wParam)
            {
                // Touche HAUT
                case VK_UP :
                    position.y -= 1;
                    break;
                // Touche BAS
                case VK_DOWN :
                    position.y += 1;
                    break;
                // Touche DROITE
                case VK_RIGHT :
                    position.x += 1;
                    break;
                // Touche GAUCHE
                case VK_LEFT :
                    position.x -= 1;
                    break;
            }
            */
            // Afficher le contenu de la fenetre
            HDC hdc=GetDC(hwnd);
            afficherPixel(hdc);
            ReleaseDC(hwnd, hdc);
            return 0;
        // Quitter le programme
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        // Afficher le contenu de la fenetre quand c'est necessaire
        case WM_PAINT:
            {
                HDC hdc;
                PAINTSTRUCT ps;
   
                hdc=BeginPaint(hwnd, &ps);
                afficherPixel(hdc);
                EndPaint(hwnd, &ps);
            }
            return 0;
        // Laisser Windows gerer par defaut les autres evenements
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

// Fonction d'affichage du pixel a la position courante
void afficherPixel(HDC hdc)
{
    HDC hdcMem = CreateCompatibleDC(NULL);
    HBITMAP hBmpMem = CreateCompatibleBitmap(hdc, 400, 400);
    HBITMAP hOldBmp = SelectObject(hdcMem, hBmpMem);
    RECT windowRect = {0, 0, 400, 400};
    RECT pixelRect = {position.x - 5, position.y - 5, position.x + 10, position.y + 10};

    // Effacer le contenu de la fenetre (en rouge)
    FillRect(hdcMem, &windowRect, CreateSolidBrush(255));
    // Dessiner le pixel a la position courante (un carre de 10x10 en noir)
    FillRect(hdcMem, &pixelRect, CreateSolidBrush(0));
   
    BitBlt(hdc, 0, 0, 400, 400, hdcMem, 0, 0, SRCCOPY);
    SelectObject(hdcMem, hOldBmp);
    DeleteObject(hBmpMem);
    DeleteObject(hOldBmp);
    DeleteDC(hdcMem);
}

7 réponses

cs_Adeon Messages postés 293 Date d'inscription jeudi 21 août 2003 Statut Membre Dernière intervention 10 avril 2015 2
3 août 2008 à 17:55
Moi perso j'utilise GetAsyncKeyState()
Comme son nom l'indique cette fonction dit de façon asynchrone si la touche est appuyée ou non. pas besoin d'attendre le message WM_KEYDOWN

je ne connais pas toute les subtilités de cette fonction mais si elle retourne un nombre négatif, la touche est appuyée exemple pour savoir si la touche VK_LEFT est appuyée :

if ( GetAsyncKeyState(VK_LEFT) < 0 )  { la touche VK_LEFT est enfoncée }

voila et je met la liste des touche que je veux etudier dans l'Update de mon programme.

a++
______________________
Adeon, programmeur de jeux sur directX 9 (http://theolith.com) a votre service !
3
cs_Adeon Messages postés 293 Date d'inscription jeudi 21 août 2003 Statut Membre Dernière intervention 10 avril 2015 2
5 août 2008 à 19:00
- Je vois que tu utilise GetMessage() pour envoyer tes message, sache qu'il existe aussi PeekMessage() je ne sais pas si ca va jouer beaucoup mais tu peux toujours essayer avec PeekMessage
"Unlike GetMessage, the PeekMessage function does not wait for a message to be posted before returning."(MSDN)

- ensuite je vois une grosse erreure :
on ne deplace jamais un objet comme tu le fait :
pour toi a chaque update l'objet se deplace eventuelement de 1pxl vers la gauche ou/et 1px vers la droite ou/et 1 pxl en haut.....NON !.
Pour deplacer un objet on decide une vitesse : par exemple je decide que le point se deplace a 100 pxl / seconde

et apres moi j'orais fait un truc ds ce genre dans l'update :
Float composanteDistanceHorizontalUnitaire = 0;
Float composanteDistanceVerticaleUnitaire = 0;

//si on va en haut sans aller en bas
if  (  GetAsyncKeyState(VK_UP) < 0 && GetAsyncKeyState(VK_DOWN) >= 0 )
{ composanteDistanceVerticaleUnitaire = -1; }

//si on va en bas sans aller en haut

else if  (  GetAsyncKeyState(VK_DOWN) < 0 && GetAsyncKeyState(VK_UP) >= 0 )

{ composanteDistanceVerticaleUnitaire = +1; }

//si on va a gauche sans aller a droite

if  (  GetAsyncKeyState(VK_LEFT) < 0 && GetAsyncKeyState(VK_RIGHT) >= 0 )

{ composanteDistanceHorizontaleUnitaire = -1;   }

//si on va a droite sans aller a gauche


if  (  GetAsyncKeyState(VK_RIGHT) < 0 && GetAsyncKeyState(VK_LEFT) >= 0 )


{ composanteDistanceHorizontaleUnitaire = +1;   }

//si les 2 composante ne sont pas nulle on est rigoureux et on les multiplie toute les 2 par cos(pi/4) sinon le point va accelerer quand il prendra la diagonale

if ( absf(composanteDistanceHorizontalUnitaire) + absf(composanteDistanceVerticaleUnitaire) == 2 )
{
composanteDistanceHorizontalUnitaire *= cos(pi/4);
composanteDistanceVerticaleUnitaire *= cos(pi/4);
}

//a la fin on ajoute la distance a notre point sur les 2 composantes
position.x += VitessePoint * (timeGetTime() - TimeOfLastUpdate) * composanteDistanceHorizontalUnitaire;
position.y += VitessePoint * (timeGetTime() - TimeOfLastUpdate) * composanteDistanceVerticaleUnitaire;
//TimeOfLastUpdate etant comme son nom l'indique un temps que je te conseil de d'actualiser a la fin de chaque update

bon voila jpense pas m'etre trompé

essaye ca et tiens moi au courant...

______________________
Adeon, programmeur de jeux sur directX 9 (http://theolith.com) a votre service !
3
cs_Adeon Messages postés 293 Date d'inscription jeudi 21 août 2003 Statut Membre Dernière intervention 10 avril 2015 2
7 août 2008 à 19:31
Pour avoir le temps entre 2 upate voila comment je fais :

DWORD TimeOfLastUpdate = 0;//je declare cette variable "a l'exterieur de mes fonctions" (je crois que ca s'appelle en global mais je manque encore un peu du vocabulaire adequat en c++ ^^ )

Update() //voila la fonction qui est appelée lors d'un update
{

DWORD TimeOfCurrentUpdate = timeGetTime();//fonction que te donne en milliseconde le temps depuis lequel ton PC est allumé (il est codé sur 32bit donc pour arriver à 0xffffffff il met plusieur mois donc t'a le temps ;-) )

//pour eviter que ton point face un enorme bon lors de la premiere update
if  ( TimeOfLastUpdate  == 0 ) { TimeOfLastUpdate  = TimeOfCurrentUpdate ; }

printf("le temps ecoulé depuis la derniere update est de : %d",TimeOfCurrentUpdate - TimeOfLastUpdate );

//bon ba ici tu peu faire l'update de ton prog....

TimeOfLastUpdate = TimeOfCurrentUpdate;//a la fin tu actualise le temps du dernier update
}

pour répondre a ta question sur "la structure MSG et son paramètre time" en effet on peut mettre en place un timer, avec je ne sais plus quelle fonction et qui enverra le message WM_TIMER a ta fenetre toute les x milliseconde.

"De plus, je recherche à "caler" la vitesse de mon déplacement de point sur la fréquence du microprocesseur, mais je ne sais pas trop comment faire."
Je pense deja que si justement en laissant ton ".x += 1;" a chaque update sans imposer une vitesse comme je te l'avait dit, + ton processeur a une grande frequance, + la fonction update va etre appelé de fois / seconde, et + la vitesse de ton point va augmenter. Si je te demandait d'imposer une vitesse, c'est justement pour eviter que ton point est une vitesse + grande avec une machine + puissante ^^. C'est je pense a peut pres proportionnel.
Bon apres si tu veux une proportionalité parfaite entre la frequence et la vitesse, je te conseillerai tout simplement de te renseigner comment avoir la frequence de son proc. il doit y avoir une fonction toute conne qui existe.


Bon ba voila ! bonne continuation


P.S.1 : je pense sortir enfin la version beta jouable d'ici qq semaine ^^ (apres 3 ans de boulot (et d'apprentissage sur la prog 3D ) dessus )

P.S.2 : lol oui tkt moi aussi j'ai fait le programme du point ^^ et moi aussi j'ai ragé sur le c++ ^^


______________________
Adeon, programmeur de jeux sur directX 9 (http://gorygems.com/2.html) a votre service !
3
Djazzyman Messages postés 10 Date d'inscription lundi 14 juillet 2008 Statut Membre Dernière intervention 16 mai 2013
4 août 2008 à 22:44
Merci pour ta participation, Adeon-theolith. Que la force soit avec nous !

Il semblerait en effet que la fonction GetAsyncKeyState soit plus adaptée que GetKeyState, puisqu'elle récupère directement l'état d'une touche clavier, au niveau hardware.
Mais le problème n'est pas résolu pour autant...

J'ai suivi tes conseils, j'ai placé les tests clavier dans l'update avec des GetAsyncKeyState, je suis même passé sous l'environnement de développement Code::Blocks 8.02 entre temps.
Mais le problème persiste (voir le code source mis à jour plus bas).
Je m'explique : il suffit de faire le test suivant, dans l'ordre, pour s'en rendre compte :
1) Tu maintiens la touche FLECHE HAUT (VK_UP) enfoncée, le pixel se déplace vers le haut : jusqu'ici tout va bien
2) Tu maintiens la touche VK_UP enfoncée et tu appuies simultanément sur FLECHE GAUCHE (VK_LEFF) : il y a un temps de réaction avant que le pixel se déplace en haut à gauche, en diagonale (POURQUOI ???)
3) Tu maintiens les touches VK_UP et VK_LEFT enfoncées et tu appuies simultanément sur FLECHE DROITE (VK_RIGHT) : il y a un temps de réaction avant que le pixel se déplace en haut, verticalement (POURQUOI ???)
4) Enfin, tu maintiens la touche VK_UP enfoncée et tu relâches simultanément VK_LEFT et VK_RIGHT : le pixel s'immobilise (POURQUOI ??? NDEDIEU !!!)

Est-ce que cela vient du complilo (Dev C++ et Code::Blocks utilisent gcc et Mingw), est-ce un bug Windows ?

KESSEPASSTIL ??? AU SECOURS !!!

Voici le code source mis à jour (la fonction WinMain n'a pas changé, voir plus haut dans le forum) :
ATTENTION : ce n'est pas de la programmation en mode CONSOLE.


// VARIABLE GLOBALE :
// La position courante du pixel dans la fenetre, initialement au centre
POINT position = {200, 200};
// Declaration de la fonction d'affichage du pixel a la position courante (UPDATE)
void afficherPixel(HDC hdc);

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        // Quitter le programme
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        // UPDATE dans les autres cas + Laisser Windows gerer par defaut les autres evenements
        default:
            {
            HDC hdc=GetDC(hwnd);
            afficherPixel(hdc);
            ReleaseDC(hwnd, hdc);
            }
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

// Fonction d'affichage du pixel a la position courante (UPDATE)
void afficherPixel(HDC hdc)
{
    // Recuperer les infos du clavier et modifier la position courante du pixel :
    // La touche HAUT est enfoncee
    if(GetAsyncKeyState(VK_UP) < 0)
        position.y -= 1;
    // La touche BAS est enfoncee
    if(GetAsyncKeyState(VK_DOWN) < 0)
        position.y += 1;
    // La touche DROITE est enfoncee
    if(GetAsyncKeyState(VK_RIGHT) < 0)
        position.x += 1;
    // La touche GAUCHE est enfoncee
    if(GetAsyncKeyState(VK_LEFT) < 0)
        position.x -= 1;

    // Afficher le contenu de la fenetre :
    HDC hdcMem = CreateCompatibleDC(NULL);
    HBITMAP hBmpMem = CreateCompatibleBitmap(hdc, 400, 400);
    HBITMAP hOldBmp = SelectObject(hdcMem, hBmpMem);
    RECT windowRect = {0, 0, 400, 400};
    RECT pixelRect = {position.x - 5, position.y - 5, position.x + 10, position.y + 10};

    // Effacer le contenu de la fenetre (en rouge)
    FillRect(hdcMem, &windowRect, CreateSolidBrush(255));
    // Dessiner le pixel a la position courante (un carre de 10x10 en noir)
    FillRect(hdcMem, &pixelRect, CreateSolidBrush(0));

    BitBlt(hdc, 0, 0, 400, 400, hdcMem, 0, 0, SRCCOPY);
    SelectObject(hdcMem, hOldBmp);
    DeleteObject(hBmpMem);
    DeleteObject(hOldBmp);
    DeleteDC(hdcMem);
}
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
cs_Adeon Messages postés 293 Date d'inscription jeudi 21 août 2003 Statut Membre Dernière intervention 10 avril 2015 2
5 août 2008 à 19:33
Pour ton temps de retard je crois que ta fonction AfficherPixel ( que je te conseillerai d'appeler UpdatePixel ) ne devrais pas etre en defaut, il y a un message qui gere l'Update. je ne m'en souviens plus... regarde un peu qq source

Je t'avoue que ces message windows m'on bien pris la tete pdt un moment. c'est pas facil de comprendre toute la subtilité du truc. Moi maintenant dans mes programme de jeux en temps reel je fait un gros copier/coller de fonctions qui gere tout ca impecablement j'ai plus qu'a ecrire dans les fonctions pour update, draw, initialiseProg, endProg. Un vrai bonheur ^^ Mais pour ca j'ai du me tapper la comprehension du SDK de directx9... Jte conseil vivement de rester la bibliotheque de dessin windows pour debuter c'est mieux

bonne chance
______________________
Adeon, programmeur de jeux sur directX 9 (http://theolith.com) a votre service !
0
Djazzyman Messages postés 10 Date d'inscription lundi 14 juillet 2008 Statut Membre Dernière intervention 16 mai 2013
7 août 2008 à 17:43
Un grand MERCI Adéon !

Tes conseils m'ont été précieux.
Tu avais raison, le problème venait de ma message loop GetMessage (...), placée dans WinMain.
Il est en grande partie résolu avec l'utilisation de PeekMessage : maintenant mon pixel se déplace super vite et de façon fluide.
Ma boucle d'événements (dans WinMain) ressemble désormais à ça (le reste n'est pas encore au top) :

while(TRUE)
{
    if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if(msg.message == WM_QUIT)
        {
            break;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    update(); // Tests clavier GetAsyncKeyState (je verrai plus tard pour la souris)
    draw(hwnd); // Affichage du pixel à la position courante
}

Concernant tes différentes remarques, qui sont justes, je voulais seulement préciser que les codes sources proposés dans cette discussion n'avaient pour but que d'exprimer le plus clairement possible un problème de gestion du clavier, sans rentrer dans le détail de l'optimisation.

Je vais donc me permettre à mon tour de faire une remarque, concernant ce que tu dis :
"Pour ton temps de retard (- déplacement non fluide -) je crois que ta fonction AfficherPixel ( que je te conseillerai d'appeler UpdatePixel ) ne devrais pas etre en defaut, il y a un message qui gere l'Update."

NON, il n'y a pas de message particulier qui gère un update de donnnées sous l'API Win 32 : tous les messages postés permettent de le faire. C'est pourquoi j'avais initialement déclenché mon update (position du pixel) sur le message WM_KEYDOWN (cf code source du tout premier message de cette discussion).
Apparemment, la gestion des messages ne se fait pas de la même manière sous l'API Win 32 et sous DirectX.
Et puis si j'ai envie d'appeler ma fonction "afficherPixel", je l'appelerai "afficherPixel" ! Ca me parraissait plus clair pour tout le monde. Nan mais des fois

Ceci étant dit, ton algorithme de déplacement du point me paraît très bien, cela me rappelle mes débuts en programmation, alors que j'essayais de créer des jeux en C à l'époque du DOS de Windows 3.1.
Une petite piqure de rappel en trigonométrie et en physique (vitesse = distance / temps, accélération) ne fait jamais de mal !

Cependant, je rencontre une difficulté à récupérer ce fameux facteur temps entre 2 update, sous l'API Win 32.
J'ai essayé de travailler avec la structure MSG et son paramètre time (qui correspond à quoi d'ailleurs ? Des milisecondes ?), mais pour l'instant, cela ne fonctionne pas.
De plus, je recherche à "caler" la vitesse de mon déplacement de point sur la fréquence du microprocesseur, mais je ne sais pas trop comment faire.

Peut-être as-tu une solution à proposer ?
En attendant, je vais continuer à chercher et je te remercie encore pour tes conseils éclairés.

A+

P.S.1 : j'aime bien l'histoire de la pierre précieuse, c'est sympa theolith
P.S.2 : je m'excuse d'avoir douté de Dev C++, Code::Blocks, gcc, Mingw et Windows : en fait, mon code n'était pas au point
0
Djazzyman Messages postés 10 Date d'inscription lundi 14 juillet 2008 Statut Membre Dernière intervention 16 mai 2013
12 août 2008 à 11:15
Désolé d'avoir tardé à te répondre, Adeon : j'étais parti en week-end

Oui, j'ai fini par résoudre le problème avec timeGetTime() : il fallait seulement que je lie la lib winmm à mon programme.

Au regard de ma message loop while(TRUE) { if PeekMessage(..., je pense que la solution que tu proposes pour l'update est meilleure que l'utilisation d'un timer.
Le timer pourraît plutôt me servir pour jouer des animations.

Mon programme de gestion optimisée du clavier est désormais au top, grâce à tes conseils.
Il ne me reste plus qu'à trouver une fonction qui me permette de peaufiner le facteur vitesse en fonction de la configuration matérielle (microprocesseur et carte graphique).
J'ai en effet fait des tests sur des ordinateurs différents et je n'obtiens pas la même fluidité de déplacement avec le même facteur vitesse.

Si tu as des questions sur l'API Win32, n'hésite pas à me contacter - ne serait-ce que par message privé, je serais ravi de pouvoir te filer un coup de main si possible !
Et tiens-moi au courant de la sortie de ta version beta de theolith ;-)

A+ et encore merci pour tout
0
Rejoignez-nous