Tutoriel de jeu v4.0f

Aujourd'hui, ou peut-être demain, vous construirez un système de jeu.

Vous devez lire ce document et faire attention afin de comprendre la façon dont le système de jeu est construit. Pendant que vous vous y mettez, n'hésitez pas à rechercher quelques mots-clés ou concepts sur le web. Plus vous comprendrez les éléments, plus il vous sera facile de créer votre proper jeu après avoir terminé ce tutoriel.

D'abord quelques concepts

Balise HTML5 <canvas>

Une balise qui vous permet de dessiner des pixels à l'aide de JavaScript. Le canvas est comme une image (une grille de pixels), comme une toile si on veut, sur laquelle vous pouvez peindre avec des commandes JS. Il est 2D et plat, il n'a pas de couches. Vous donnez l’illusion d’empiler des objets en redessinant et en réorganisant les pixels. Et on modifie l’affichage dans le canvas pour créer des animations.

Sprite

Un objet graphique réutilisable utilisé dans un programme qui peut être déplacé et manipulé. Tout ce qui est visuel dans un jeu ou un autre programme peut être appelé un sprite. Dans ce jeu, nous utiliserons des sprites pour peindre nos objets de jeu sur le canvas : joueur, goodies, etc.

Objet

Un concept de programmation pour l'organisation logique des variables et des fonctions dans un programme. Ce programme utilise des objets de manière très superficielle, mais c'est une bonne idée de savoir ce qu'est un objet en programmation. N'oubliez pas que les variables sont des conteneurs de valeurs, les fonctions sont des conteneurs de commandes et de variables. Les objets sont des conteneurs de variables et de fonctions. (C'est une carricature, c’est légèrement plus complexe que cela.)

Entrée utilisateur : clavier

Pour permettre à l'utilisateur de contrôler le programme avec le clavier, vous devez tester les touches individuelles. Chaque touche a un identifiant unique. Lorsqu'une touche est enfoncée, un événement est déclenché. Vous devez valider quelle touche est enfoncée pour déterminer l'action. Dans ce tutoriel, nous utilisons principalement les touches fléchées.

Entrée utilisateur : toucher

Le jeu utilise également l'événement tactile pour le contrôle sur mobile. Cela fonctionne en identifiant l’objet cible qui déclenche l’événement tactile. Cet objet devient un bouton de contrôle, si vous voulez. Dans ce tutoriel, il y a quatre de ces ‘boutons’ de chaque côté du canvas. Ceux-ci peuvent être utilisés pour jouer au jeu sur mobile.

Boucle de jeu

C'est ce qui fait bouger le jeu. C'est une fonction lancée à un certain intervalle. On utilise une telle boucle pour modifier les paramètres de l'affichage et pour repeindre le canvas très rapidement. Dans notre cas, nous utiliserons une commande JS appelée requestAnimationFrame() pour faire ça.

Test de collision

Il s’agit généralement d’un test (une fonction avec plusieures conditions) permettant de voir si deux sprites entrent en contact. En d’autres termes, vérifiez s’ils partagent une zone de l’espace de jeu. Ce tutoriel utilise une fonction de test de collision de base. Cette fonction peut être réutilisée si jamais vous en avez besoin pour un autre projet.

NOTES IMPORTANTES

Téléchargez le package de fichiers pour ce tutoriel ici. Le package contient les fichiers suivants. Les noms de fichier sont en anglais.

Je fournis le fichier de base HTML (index.html) ainsi que les images (dans le dossier images/). Jetez un œil au code HTML et CSS pour vous assurer de comprendre comment il est construit et comment les éléments sont utilisés. Par exemple, les boutons tactiles sont invisibles, mais ils sont là ; leurs propriétés sont claires dans le CSS.

Tout le code ce trouvant de le tutoriel est en JavaScript. Il est écrit principalement en anglais. Vous devez écrire ce code dans un fichier appelé game.js à l'intérieur du dossier js/. Ce fichier est vide dans le package, utilisez-le.

L'ordre et l’organisation du code sont importants. J'indique dans les instructions où insérer les différentes pièces, alors faites attention.

Techniquement, vous n’avez pas besoin d’inclure les lignes de commentaires dans votre code de jeu. Mais c’est bien sûr une bonne idée de le faire. Cela rendra votre programme plus lisible et plus facile à mettre à jour dans un jeu final une fois le tutoriel terminé.

Vous pouvez tester l'interface tactile mobile à l'aide des outils de développement du fureteur Chrome (ou un autre). Ou vous pouvez aussi utiliser votre téléphone. Mais je trouve plus facile de tester sur ordinateur pendant que je développe.

Vous trouverez un jeu terminé ici. Le but du jeu est de récupérer les goodies. Votre joueur (cercle vert) doit se déplacer pour collecter les trois goodies (carrés rouges) pour que vous puissiez gagner. Bien sûr que c'est basique et ridicule. Mais construire cela vous apprendra certains éléments de la programmation de jeux. Vous pourrez créer presque n’importe quel jeu ou application complexe à partir de ce système très basique.

Le tutoriel est séparé en 5 étapes que j'appelle États. Chaque État construit des parties du système. À la fin de chaque état, vous pouvez tester votre jeu. De nombreuses parties de code doivent être réécrites à mesure que vous passez d’un État à l’autre. Faites attention à mes instructions, elles vous indiquent où écrire chaque morceau de code et quoi remplacer ou modifier.

J'ai inclus chaque état de développement comme fichiers séparés dans le package. Reportez-vous à ces fichiers dans le dossier js/ si vous n'êtes pas sûr de ce à quoi le programme devrait ressembler pendant que vous travaillez.

Maintenant, construisons un jeu !

ÉTAT A — DESSINER LES ÉLÉMENTS

Dans le fichier JS (game.js), la première chose que nous faisons est de créer le canvas et de le charger dans le DOM. Le canvas aura les mêmes dimensions que la fenêtre du fureteur. Écrivez le code suivant en haut de votre fichier game.js, qui doit être vide avant de commencer.

Pour ce premier état, vous écrirez chaque segment de code dans l’ordre dans lequel ils sont présentés.

// Créer le canevas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
document.querySelector("#gameBox").appendChild(canvas);

Ensuite, nous allons charger les sprites. Il nous en faut quatre : arrière-plan, joueur, goody et estampe gagnant.

Parce que nous traitons des images, nous avons besoin d’un système qui ne placera l’image qu’une fois chargée en mémoire. Nous avons donc besoin d’une variable pour chaque image qui n’est vraie que lorsque l’image est prête. Ce sont bgReady, winReady, etc. Cela garantit que nous ne dessinons pas une image cassée ou un demi-sprite.

//Charger les sprites
// Image d'arrière-plan
var bgReady = false;
var bgImage = new Image();
bgImage.src = "images/background.png";
bgImage.onload = function () {
    bgReady = true; 
};

// Estampe gagnant
var winReady = false;
var winImage = new Image(); 
winImage.src = "images/win.png"; 
winImage.onload = function () {
    winReady = true; 
};

// Image du joueur
var playerReady = false;
var playerImage = new Image(); 
playerImage.src = "images/player.png"; 
playerImage.onload = function () {
    playerReady = true; 
};

// Image des goodies
var goodyReady = false;
var goodyImage = new Image(); 
goodyImage.src = "images/goody.png"; 
goodyImage.onload = function () {
    goodyReady = true; 
};

Nous aurons maintenant besoin de quelques fonctions.

Tout d’abord, une fonction de boucle qui sera appelée en permanence pour maintenir le jeu en marche : nous l’appelons main(). Il s'agit d'une fonction importante que nous construirons progressivement tout au long du tutoriel. Elle contiendra tout ce qui doit se passer pendant que le jeu se joue. Mais à ce stade, tout ce qu'elle fera, c'est appeler la fonction render() pour dessiner les éléments. (Nous définissons cette fonction render() à l’étape suivante.)

Notez que la fonction appelle aussi la commande pour activer la boucle requestAnimationFrame();.

// La boucle de jeu principale 
var main = function () {
    render();
    window.requestAnimationFrame(main); 
};

Nous avons besoin de la fonction render() appelée par main(). Ceci est utilisé pour dessiner les éléments.

Pour l'instant, nous n'avons que le joueur et un goody, et nous les plaçons en position statique sur la toile. Nous voulons juste être sûrs que nos photos se chargent correctement. Cette fonction dessinera également une étiquette de texte que nous utiliserons pour tester et suivre nos éléments ultérieurement.

Notez que nous vérifions que les images sont chargées (en utilisant bgReady, playerReady, etc.) avant de les dessiner sur le canevas. Cela évite les erreurs et les problèmes.

// Dessinez le tout
var render = function () {
    if (bgReady) {
        ctx.fillStyle = ctx.createPattern(bgImage, 'repeat');
        ctx.fillRect(0,0,canvas.width,canvas.height);
    }
    if (playerReady) {
        ctx.drawImage(playerImage, 100, 100);
    }
    if (goodyReady) {
        ctx.drawImage(goodyImage, 200, 200);
    }

    //Label
    ctx.fillStyle = "rgb(250, 250, 250)";
    ctx.fillText("Game on!", 32, 32);
};

La dernière chose dont nous avons besoin avant de tester : activer la boucle. Cette commande doit être écrite à la fin du fichier. Elle est exécutée une fois que tout le reste est chargé en mémoire et elle démarre le jeu.

Vous remarquerez que c'est la même commande que celle que nous avons écrite dans main(). Mais c'est la première fois que nous l'appelons pour commencer le jeu. Ensuite, elle sera appelée environ 60 fois par seconde. Cela signifie que notre jeu roule idéalement à 60 images par seconde (FPS).

//Démarrer le jeu
window.requestAnimationFrame(main);

Il est maintenant temps de tester l’état A !

Ouvrez le fichier index.html dans un navigateur pour voir si cela fonctionne. Vous devriez voir le joueur vert et un goody rouge.

ÉTAT B — DESSINER LA MISE EN PAGE PAR DÉFAUT

Nous allons créer des objets programmatiquement contrôlables pour nos sprites. Il s’agit simplement d’un moyen simple de gérer la position des sprites et les collisions pour plus tard. Vous contrôlez un espace réservé abstrait ou invisible – comme un fantôme du sprite – et vous dessinez le sprite aux coordonnées de l'emplacement du fantôme.

Ces objets sont simplement logiques, ils n'apparaissent jamais dans le jeu, ils sont utilisés uniquement pour les calculs.

Pour commencer, nous allons créer un joueur et trois goodies. Notez que nous n'avons besoin que d'une seule image pour générer autant de goodies que nous le souhaitons. C'est le pouvoir des sprites ! Pas besoin de recharger l'image.

Localisation du code : Écrivez ce qui suit juste au-dessus de la fonction main(), sous le code pour charger les images.

// Créer des objets de jeu globaux 
var player = {
    speed : 5, // mouvement en pixels par tick 
    width: 32,
    height: 32
};

var goodies = [ // ceci est un tableau (array)
    { width: 32, height: 32 }, // un goody
    { width: 32, height: 32 }, // deux goodies
    { width: 32, height: 32 }  // trois goodies
];

Au lieu de commencer le jeu avec la boucle principale tout de suite, nous ajouterons une fonction init() qui préparera l’espace de jeu avant le début pour placer les objets au bon endroit. Nous plaçons tout d’abord le joueur au milieu et les goodies au hasard dans le canvas.

Notez les références à x et y. Ce sont les coordonnées d’objets sur un plan cartésien. Le x est horizontal, et le y est vertical. Vous devriez déjà être au courant de cela — Maths du secondaire.

Localisation du code : Vous pouvez écrire cette fonction à la suite des objets de l'étape précédente, avant la fonction main().

//Définir l'état initial
var init = function () {
    //Mettre le joueur au centre
    player.x = (canvas.width - player.width) / 2; 
    player.y = (canvas.height - player.height) / 2;

    //Placez des goodies à des endroits aléatoires 
    for (var i in goodies) {
        goodies[i].x = (Math.random() * (canvas.width - goodies[i].width));
        goodies[i].y = (Math.random() * (canvas.height - goodies[i].height));
    }
};

Maintenant que nous avons déterminé l'état initial de notre espace de jeu, nous devons mettre à jour la fonction render() pour peindre les images de sprites aux bons endroits. Cela permettra également de produire l’illusion du mouvement, plus tard puisque render() est appelé à chaque boucle.

Localisation du code : Remplacer les blocs playerReady et goodyReady dans votre fonction render() :

if (playerReady) {
    ctx.drawImage(playerImage, player.x, player.y);
}
if (goodyReady) {
    for (var i in goodies) {
        ctx.drawImage(goodyImage, goodies[i].x, goodies[i].y);
    }
}

Une dernière chose : nous devons appeler le init() juste avant de démarrer la boucle. Comme ça le statut inital de l’espace de jeu est an place avant de démarrer le jeu.

Localisation du code : En bas du fichier, juste au-dessus de la dernière ligne, ajoutez :

init();

Test de l’état B !

Vous devriez voir le joueur au centre et trois goodies distribués aléatoirement sur le canvas. Si vous actualisez, les goodies apparaîtront à différents endroits.

Notez que ce code est simple et peut être un peu bogué. Il est possible que vous ne voyiez qu'un ou deux goodies de temps en temps. Cela se produit lorsqu'un goody est placé au hasard par init() en collision avec le joueur déclenchant immédiatement son retrait. Ne t'inquiète pas pour ça.

ÉTAT C — AJOUTER UNE INTERACTION

Pour rendre le jeu jouable, nous devons gérer les événements du clavier et du toucher afin de permettre à l’utilisateur (humain) de déplacer le joueur (sprite). Pour ce faire, nous devons d’abord créer des variables pour controler la vitesse du joueur; vitesse veut dire movement. Cela déterminera aussi dans quelle direction on va : haut/bas ou axe Y vs gauche/droite ou axe X.

Localisation du code : Les trois blocs de code suivants doivent être insérés, dans l'ordre, juste en dessous des objets globaux créés dans l'état B (player et goodies).

// Variables de vitesse
var vX = 0;
var vY = 0;

Maintenant, nous pouvons écrire le code qui gère le clavier.

Nous créons un auditeur d'événement (event listener) qui se déclenchera lorsqu’une touche du clavier est enfoncée. Les chiffres keyCode font référence à chaque touche : 38 est la flèche vers le haut, 40 est la flèche vers le bas, etc.

Lorsque l'utilisateur appuie sur une touche fléchée, le joueur commence à se déplacer dans cette direction à la vitesse définie dans l'objet player (5 pixels).

Notez que, dans ce tutoriel, le joueur commence à bouger dès que vous appuyez sur la touche, et il continue de bouger tout seul dans cette direction jusqu’à ce qu’il frappe le bord du canvas. (Nous verrons ça plus tard.) Le movement ne s'arrête pas lorsque vous relâchez la touche. Il s’agit de rendre l’interface mobile plus facile à gérer. Par contre, il est possible d’arrêter le joueur avec la barre d'espace (keyCode 32).

Localisation du code : Mettre ce bloc de code à la suite du précedent.

// Gérer les commandes du clavier
addEventListener(« keydown », fonction (e) {
    //Touches
    if (e.keyCode == 38) { // HAUT
        vX = 0;
        vY = -player.speed;
    }
    if (e.keyCode == 40) { // BAS
        vX = 0;
        vY = player.speed;
    }
    if (e.keyCode == 37) { // GAUCHE
        vX = -player.speed;
        vY = 0;
    }
    if (e.keyCode == 39) { // DROITE
        vX = player.speed;
        vY = 0;
    }
    if (e.keyCode == 32) { // ARRÊT barre d’espace
        vX = 0;
        vY = 0;
    }
}, false);

Ensuite, nous devons ajouter les commandes pour les événements tactiles pour permettre de jouer sur mobile. Dans ce cas, nous nous référerons à l'attribut ID des boutons dans le code HTML pour trouver la cible tactile. (Voir index.html pour référence.)

Le target fait référence à l'élément qui a été touché par l'utilisateur pour déclencher l’événement touchstart.

Localisation du code : Encore mettre ce bloc à la suite du précedent.

// Gérer les commandes tactiles
addEventListener("touchstart", function (e) {
    if (e.target.id == "uArrow") { // HAUT
        vX = 0;
        vY = -player.speed;
    }
    else if (e.target.id == "dArrow") { // BAS
        vX = 0;
        vY = player.speed;
    }
    else if (e.target.id == "lArrow") { // GAUCHE
        vX = -player.speed;
        vY = 0;
    }
    else if (e.target.id == "rArrow") { //DROIT
        vX = player.speed;
        vY = 0;
    }
    else { // ARRÊT S’arrête si vous touchez ailleurs
        vX = 0;
        vY = 0;
    }
});

Nous allons maintenant mettre à jour la fonction de boucle principale afin qu'elle gère le mouvement de l'objet player. Nous devons changer la direction du mouvement du joueur en fonction de la touche ou du bouton enfoncé.

Vous remarquerez que nous veillons également à ce que le joueur ne sorte pas du canvas en le faisant rebondir dans la direction opposée lorsqu'il atteint un bord.

Localisation du code : Ajoutez ce code avant la commande render(); à l'intérieur de votre fonction main().

//déplacer le joueur
if (player.x > 0 && player.x < canvas.width - player.width) {
    player.x += vX;
}
else {
    player.x -= vX;
    vX = -vX; //rebond horizontal
}

if (player.y > 0 && player.y < canvas.height - player.height) {
    player.y += vY
}
else {
    player.y -= vY;
    vY = -vY; //rebond vertical
}

Pour nous assurer que tout va bien, nous imprimerons les coordonnées du joueur dans l'étiquette de texte que nous avons créée au début. Cette approche nous permet de valider les valeurs et de confirmer que tout est en ordre.

Localisation du code : Remplacer la ligne qui contient fillText à l'intérieur de la fonction render() par ceci :

ctx.fillText(player.x+" "+player.y, 32, 32);

Test de l’état C!

Vous devriez pouvoir déplacer le joueur à l'aide des touches fléchées du clavier ou en touchant les boutons situés sur les bords de l'écran sur un mobile ou dans l'outil de développement de votre navigateur.

Les coordonnées de l'objet joueur devraient apparaître dans l'étiquette en haut à gauche du canvas.

ÉTAT D — COLLECTER LES GOODIES

Afin de pouvoir collecter les goodies, nous devons effectuer un test de collision. Construisons donc une fonction qui teste si deux objets se touchent. Plus techniquement si elles partagent le même espace dans le canvas.

J'ai écrit cette fonction dans un style compact. Vous n’avez pas besoin de tout comprendre au début. Elle prend deux objets qui ont des coordonnées et des dimensions, et renvoie vrai si les objects entrent en collision. Vous pouvez utiliser cette même fonction pour deux objets quelconques dans tout autre projet similaire.

Localisation du code : Vous pouvez écrire cette fonction après le render() – suivant le bloc complet, donc après le }; – juste devant l’appel de init();.

Assurez-vous d'écrire ce bloc avec précision, il est assez compact et chaque élément est important.

//Fonction générique pour vérifier les collisions 
var checkCollision = function (obj1,obj2) {
    if (obj1.x < (obj2.x + obj2.width) && 
        (obj1.x + obj1.width) > obj2.x && 
        obj1.y < (obj2.y + obj2.height) && 
        (obj1.y + obj1.height) > obj2.y
        ) {
            return true;
    }
};

Supprimons donc chaque goody lorsque le joueur l'a touché. Dans la fonction main(), nous allons ajouter une nouvelle boucle for qui vérifie les collisions entre le joueur player et chacun des 3 goodies.

Lorsqu'une collision s'avère vraie, nous supprimons ce goody du tableau (array). (La commande splice supprime un élément de la liste.)

Localisation du code : Cela doit être à l'intérieur de la fonction main(), devant l’appel de render();.

for (var i in goodies) {
    if (checkCollision(player,goodies[i])) {
        goodies.splice(i,1);
    }
}

Encore une fois, à des fins de test, nous ajouterons un compteur à l'étiquette de texte en haut du canvas. Alors, mettez à jour à nouveau la ligne fillText avec ceci :

ctx.fillText(player.x+" "+player.y+ " "+goodies.length, 32, 32);

Test de l’état D!

Les goodies rouges devraient disparaître lorsque vous les touchez avec le joueur. Vérifiez également le compteur dans l’étiquette de texte.

ÉTAT E — GAGNER !

La dernière chose dont nous avons besoin est de montrer quand l’utilisateur gagne ou attrape tous les goodies. Pour cela, nous devrons vérifier combien de goodies restent dans le tableau, et lorsque nous atteignons 0, nous affichons le cadre gagnant.

Nous construirons une fonction checkWin() qui est appelée depuis la boucle de jeu et renvoie faux (false) tant qu'il y a des goodies dans le tableau.

Localisation du code : Écrivez ceci après le test de collision, avant l’appel init();.

//Vérifiez si nous avons gagné
var checkWin = function () {
    if (goodies.length > 0) { 
        return false;
    } else { 
        return true;
    }
};

Nous devons maintenant ajouter un appel à cette fonction dans notre boucle principale main(). Au final, la boucle principale comporte deux parties :

  1. que faire lorsque vous gagnez : afficher le cadre gagnant
  2. que faire tant qu’on n’a pas gagné : jouer au jeu

Jusqu'à présent, la fonction main() ne s'est comportée que comme si nous n'avions pas encore gagné, donc tout le code qui est là à ce stade sera dans la deuxième partie. La première partie est le nouveau code qui s'exécute lorsque nous gagnons. Pour vous simplifier la tâche, j'ai réécrit toute la fonction ci-dessous.

Localisation du code : Remplacez complètement l'ensemble de votre fonction main() par ceci :

// La boucle de jeu principale
var main = function () {
    if (checkWin()) {
        //GAGNANT Afficher le cadre
        if (winReady) {
            ctx.drawImage(winImage, (canvas.width - winImage.width)/2, 
                (canvas.height - winImage.height)/2);
        }
    }
    else {
        //Pas encore gagné, jouer le jeu
        //déplacer le joueur
        if (player.x > 0 && player.x < canvas.width - player.width) {
            player.x += vX;
        }
        else {
            player.x -= vX;
            vX = -vX; //bounce
        }
        if (player.y > 0 && player.y < canvas.height - player.height) {
            player.y += vY
        }
        else {
            player.y -= vY;
            vY = -vY; //bounce
        }
        //vérifier les collisions
        for (var i in goodies) {
            if (checkCollision(player,goodies[i])) {
                goodies.splice(i,1);
            }
        }

        render();
        window.requestAnimationFrame(main);
    }
};

Enfin, nous rendrons l’étiquette de texte plus utile à l’utilisateur plutôt qu’au programmeur.

Changer la ligne fillText avec :

ctx.fillText("Goodies restants : "+goodies.length, 32, 32);

Test de l’état E!

Le jeu est terminé, vous devriez voir le cadre de victoire lorsque vous attrapez tous les goodies. Encore une fois, c'est un peu buggé, le cadre de victoire ne couvre pas complètement la toile, donc parfois vous voyez le joueur, parfois non lorsque vous gagnez. Ne vous inquiétez pas, vous corrigerez ces petits détails au fur et à mesure que vous terminerez votre jeu.

Vous avez écrit une jeu vidéo canvas !

L’étape suivante consiste à concevoir votre proper jeu autour de ça…

Pour récapituler, le code contient trois parties générales que vous devez connaître :

  1. Variables de jeu et contrôles d'interface
  2. Fonctions principales : init(), main(), render()
  3. Autres fonctions secondaires

Les trois fonctions principales sont les suivantes :

init() • Définit le décor, détermine la position initiale des sprites et définit les paramètres de départ du jeu. Cette fonction n'est appelée qu'une seule fois, au tout début.

main() • Il s'agit de la boucle de jeu, elle effectue des calculs pour le mouvement des sprites et gère la progression du jeu : combien de goodies il nous reste, où sont les choses, etc. Cette fonction est appelée plusieurs fois par seconde par la commande requestAnimationFrame().

render() • Dessine ou peint la toile suivant les paramètres en cours de jeu. Tout ce que l'utilisateur voit en jouant au jeu est dessiné par cette fonction selon les valeurs déterminées par main(). Cette fonction est également appelée par main().

Bien qu'il existe de nombreux éléments déjà inclus dans ce tutoriel, et j'en montrerai d'autres, vous souhaiterez probablement ajouter ou modifier des éléments pour personnaliser votre jeu en fonction des fonctionnalités souhaitées. Vous voulez peut-être trouver comment gérer les méchants ou des obstacles que votre joueur doit éviter, ou vous voulez peut-être que les goodies bougent, qu’ils tombent du haut du canvas, ou vous voulez peut-être même faire apparaître des goodies ou des méchants, peut-être au hasard, etc. Tant que vous connaissez la logique du système, vous pouvez presque tout faire avec ce jeu.

À toi de jouer !