05:05
Episode 1
Introduction Il est l'heure. L'heure d'apprendre à programmer votre premier jeux-vidéo. Rome ne s'est pas fait en un jour, mais le jeu Pong peut l'être! Apprenez rapidement à coder votre premier jeu-vidéo!
00:07
Episode 2
Définir des données, les différents types Comment créer, modifier et lire des données en JavaScript. Comment rendre une données variable ou constante. Commençons par le début, et initiez vous à la programmation!
00:10
Episode 3
Les booléens et les conditions Apprenez en plus sur les booléens, leur conjonctions et disjonctions. Et comprenez enfin comment les structures conditionnelles fonctionnent!
00:08
Episode 4
Création et utilisation de fonctions Créer vos premières fonctions! Ces blocs de codes réutilisables et paramétrables vous servirons à réduire votre code et ainsi augmenter votre productivité dans tous vos projets.
00:10
Episode 5
Qu'est-ce que la boucle de jeu? Comprendre et utiliser une boucle de jeu. Installation et utilisation basique du kit de démarrage GameDev.Ninja https://github.com/GameDev-Ninja/starter-kit

Codage du Pong - La base

author
Romain BILLOIR

Entrons désormais dans le vif du sujet! Dans cette vidéo nous mettrons en pratique toutes les connaissances acquises des vidéos précédentes pour créer les bases du jeu Pong.

Nous définirons des variables et des constantes, des conditions et booléens et les fonctions de la boucle de jeu du starter kit.

Installation du starter kit vierge

Tout d'abord, il nous faut à nouveau installer le starter kit, exactement de la même manière que la première partie du cours précédent:

  • Rendez vous sur la page https://github.com/GameDev-Ninja/starter-kit
  • Téléchargez l'archive ZIP en cliquant sur le boutn Code puis Download ZIP
  • Extrayez l'archive
  • Ouvrez le dossier avec votre éditeur de code, et le fichier index.html avec votre navigateur

Une fois le kit installé, nous supprimons dans le fichier script.js tous les éléments inutiles: la définition de l'objet logo et son chargement, ainsi que son dessin dans la fonction DrawGame. Le code obtenu doit être le suivant:


/**
 * Données initiales du jeu
 */

/**
 * Exécutée une seule fois, au chargement
 */
function LoadGame(canvas, context) {

}

/**
 * Exécutée perpétuellement pour mettre à jour les données
 */
function UpdateGame(deltaTime) {

}

/**
 * Exécutée perpétuellement pour dessiner la frame actuelle
 */
function DrawGame(context) {

}

C'est ainsi que vous démarrerez tous vos projets à partir du starter kit, la boucle de jeu est fonctionnelle, mais ne crée et n'affiche aucun élément.

Notre premier élément

Désormais commençons par créer notre premier élément: une raquette. Nous définissons au début du fichier sous le commentaire Données initiales du jeu une donnée playerLeftPad de type objet:

const playerLeftPad = {
    x: 0, // Position horizontale de la raquette à l'écran
    y: 0, // Position verticale de la raquette à l'écran
    width: 25, // Largeur de la raquette
    height: 100 // Hauteur de la raquette
}

Une fois notre élément créé, nous pouvons d'ores et déjà l'afficher à l'écran, dans la fonction DrawGame à l'aide de la fonction context.fillRect qui nécessite justement les 4 paramètres que nous avons définis dans notre objet: la position horizotale du rectangle, sa position verticale, sa largeur, et sa hauteur:

function DrawGame(context) {
    context.fillRect(playerLeftPad.x, playerLeftPad.y, playerLeftPad.width, playerLeftPad.height)
}

Vous pouvez rafraîchir votre écran de jeu dans le navigateur pour voir apparaître un rectangle blanc, sur le bord haut/gauche.
Il nous faut encore le positionner correctement: le décaler légèrement vers la droite pour créer une marge, et le centrer à l'écran.
La création des données comme nous l'avons fait précédemment nécessite d'être réalisée au début du fichier, afin que la donnée soit accessible à nos trois fonction de la boucle de jeu. En revanche pour définir la position verticale parfaitement au centre de l'écran, nous devrons le faire dans la fonction LoadGame.

La fonction LoadGame reçois deux paramètres, celui qui nous intéresse est le premier: canvas qui est un élément Canvas affiché dans le navigateur utilisé pour dessiner notre écran de jeu. Cet élément a les propriétés width et height qui nous permettrons de positionner correctement nos raquettes.

Dans un soucis de simplicité, pour pouvoir être réutilisé dans les autres fonctions, nous créons sous notre donnée playerLeftPad, une seconde donnée screen dans laquelle nous stockerons la largeur et la hauteur de notre écran de jeu:

const screen = {
    width: 0,
    height: 0
}

Enfin dans la fonction LoadGame nous récupérons la largeur et la hauteur de l'écran de jeu, et calculons par rapport à celui-ci:

function LoadGame(canvas, context) {
    // On récupères et stock
    // la largeur et la hauteur de l'écran de jeu
    screen.width = canvas.width
    screen.height = canvas.height

    // On décale la raquette de 10 pixels vers la droite
    playerLeftPad.x = 10
    // On la positionne à la moitié de la hauteur de l'écran de jeu
    playerLeftPad.y = screen.height / 2
}

En rafraichissant notre écran de jeu, vous pouvez constater que notre raquettes est mieux positionnée, mais pas parfaitement au centre. Cela est dû au fait que les coordoonées de positionnement qui sont fournies à la fonction fillRect sont utilisées pour dessiner le rectangle depuis son coin haut/gauche. Nous devons donc modifier le calcul de la position verticale de la raquette pour lui soustraire la moitié de sa hauteur:

    playerLeftPad.y = screen.height / 2 - playerLeftPad.height / 2

Désormais notre première raquette est parfaitement positionnée. Vous pouvez le visualiser dans la démo suivante.

Seconde raquette et balle

Maintenant que nous avons vu comment créer un élément et le positionner à l'écran selon les dimensions de l'écran de jeu, créons les deux autres éléments fondamentaux du Pong: la seconde raquette ainsi que la balle.

Nous ajoutons tout d'abord deux nouvelles données en haut de notre fichier, près de nos données déjà existantes playerLeftPad et screen:

// Raquette de droite
const playerRightPad = {
    x: 0,
    y: 0,
    width: 25,
    height: 100
}

// Balle
const ball = {
    x: 0,
    y: 0,
    width: 16,
    height: 16
}

Nous dessinons ces deux nouveaux éléments à l'écran à l'aide de la même fonction fillRect utilisée précédemment, dans la fonction DrawGame

function DrawGame(context) {
    // Dessine la raquette de gauche
    context.fillRect(playerLeftPad.x, playerLeftPad.y, playerLeftPad.width, playerLeftPad.height)

    // Dessine la raquette de droite
    context.fillRect(playerRightPad.x, playerRightPad.y, playerRightPad.width, playerRightPad.height)

    // Dessine la balle
    context.fillRect(ball.x, ball.y, ball.width, ball.height)
}

Et enfin, à nouveau dans la fonction LoadGame, nous calculons les positions initiales de ces éléments, en fonction de la taille de notre écran de jeu.

function LoadGame(canvas, context) {
    // Récupération de la taille de l'écran
    screen.width = canvas.width
    screen.height = canvas.height

    // Positionnement de la raquette de gauche
    playerLeftPad.x = 10
    playerLeftPad.y = screen.height / 2 - playerLeftPad.height / 2

    // Positionnement de la raquette de droite
    playerRightPad.x = screen.width - 10 - playerRightPad.width
    playerRightPad.y = screen.height / 2 - playerRightPad.height / 2

    // Positionnement de la balle
    ball.x = screen.width / 2 - ball.width / 2
    ball.y = screen.height / 2 - ball.height / 2
}

Parmis les choses importantes à bien comprendre dans ce code:

  • la soustraction de la moitié de la largeur et hauteur de la balle dans son positionnement, nécessaire pour la centrer parfaitement à l'écran, la fonction fillRect dessinant pour rappel les rectangle selon leur bord haut/gauche.
  • la soustraction de la largeur de la raquette de droite dans son positionnement, pour exactement la même raison que le positionnement de la balle.

Désormais comme vous pouvez le visualiser dans la démo ci-dessous, nous avons tous nos éléments de notre jeu Pong.

Gestion des événements claviers

Maintenant que nous avons tous nos éléments, il est temps de prendre le contrôles de nos deux raquettes, nous utiliserons les touche Flèche Haute et Flèche Basse pour contrôler la raquette de droite, et Z et S pour contrôler la raquette de gauche.

Le starter kit fournis une fonction isKeyDown, cette fonction permet de vérifier si une touche est enfoncée ou non à un instant T.
Elle prends en paramètre le nom de la touche dont nous voulons connaître l'état, et retourne un booléen: true si la touche demandée est enfoncée, false si ce n'est pas le cas.
Dans un navigateur, chaque touche du clavier est identifié par un nom, basé sur les standards américain QWERTY, vous pouvez vous servir de cet utilitaire pour retrouver le nom de la touche à passer en paramètre, il s'agît du e.code de la touche sur la page de l'utilitaire.
Dans notre cas nous aurons dont besoin de vérifier l'état de ces quatres touches:

  • ArrowUp pour Flèche haute
  • ArrowDown pour Flèche basse
  • KeyW pour Z
  • KeyS pour S

Pour déplacer une raquette, il nous suffis simplement d'en modifier les coordonnées, dans la fonction UpdateGame:

function UpdateGame(deltaTime) {

    // Si la touche Flèche haute est enfoncée
    if (isKeyDown('ArrowUp')) {
        // On retire 2 pixels au positionnement en hauteur
        // De la raquette de droite
        playerRightPad.y = playerRightPad.y - 2
    }

    // Si la touche Flèche basse est enfoncée
    if (isKeyDown('ArrowDown')) {
        // On ajoute 2 pixels au positionnement en hauteur
        // De la raquette de droite
        playerRightPad.y = playerRightPad.y + 2
    }

    // Si la touche Z est enfoncée
    if (isKeyDown('KeyW')) {
        // On retire 2 pixels au positionnement en hauteur
        // De la raquette de gauche
        playerLeftPad.y = playerLeftPad.y - 2
    }

    // Si la touche S est enfoncée
    if (isKeyDown('KeyS')) {
        // On ajoute 2 pixels au positionnement en hauteur
        // De la raquette de gauche
        playerLeftPad.y = playerLeftPad.y + 2
    }
}

La fonction UpdateGame étant appelée perpétuellement avant chaque appel de la fonction DrawGame, toute modification des positions des raquettes est directement reportée à l'écran, comme vous pouvez le constater dans la démo suivante.

Vélocité, mouvement automatique de la balle

La balle elle n'est contrôlée par aucun des deux joueurs, nous utiliserons donc un système de vélocité pour la déplacer automatiquement.
Le système de vélocité consiste à définir une donnée de "vitesse" à un élément du jeu et à mettre à jour perpétuellement la position de l'élément par rapport à cette vitesse. Si la vitesse est de 0, alors l'élément ne bouges pas, sinon il se déplace.

Dans un environnement en deux dimensions, nous aurons besoin de deux vélocités pour notre balle: une vélocité horizontale, et une vélocité verticale. Ajoutons donc ces deux propriétés à notre objet ball, toutes deux initialisées à 0:

const ball = {
    x: 0,
    y: 0,
    // Vélocité horizontale
    vx: 0,
    // Vélocité verticale
    vy: 0,
    width: 16,
    height: 16
}

Ensuite dans la fonction UpdateGame ajoutons le déplacement par sa vélocité à notre balle. Ces instructions doivent être exécutées perpétuellement, aucune condition n'est donc requise:

    // Déplace la balle sur l'axe horizontale
    // selon sa vélocité horizontale actuelle
    ball.x = ball.x + ball.vx

    // Idem sur l'axe verticale
    ball.y = ball.y + ball.vy

Pour l'instant, les vélocités de la balle sont à 0, la balle est donc immobile. Dans la fonction LoadGame vous pouvez essayer de modifier ces vélocités pour comprendre cette mécanique.
Ainsi si vous mettez à la balle une vélocité horizontale positive, elle se déplacera vers la droite, si elle est négative, elle se déplacera vers la gauche. De même pour la vélocité verticale: si elle est positive, la balle se déplacera vers le bas, et si elle est négative, vers le haut. Si les deux vélocités sont différentes de 0, alors la balle prendra une direction oblique.

Dans notre cas, nous ferons prendre à la balle une direction en diagonale bas droite en indiquant une vélocité horizontale et verticale égales à 2:

    ball.vx = 2
    ball.vy = 2

Désormais si vous rafraichissez votre navigateur, votre balle entre en mouvement. Mais un problème se pose: notre balle sort de l'écran!
Ce comportement est tout à fait normal puisque nous n'avons jusqu'à présent. Gardez à l'esprit durant tout développement de jeu, que le jeu ne fera que ce que vous lui demandez de faire, dans notre cas, nous avons indiqué à la balle de se déplacer perpétuellement sur une diagonale bas/droite.

Pour empêcher la balle de sortir de notre écran, il nous faut donc coder un inversement de la vélocité verticale de la balle lorsque celle-ci sort de l'écran.
Dans notre fonction UpdateGame, nous intégrons donc cette condition:

    // Si la position verticale de la balle
    // Est supérieure à la hauteur de l'écran
    if (ball.y >= screen.height) {
        // On inverse la vélocité verticale
        ball.vy = -ball.vy
    }

Notez ici que nous assignons directement ici une nouvelle valeur à la donnée vy, qui n'est autre que son contraire mathématique:

  • Si la valeur de vy est 2, alors elle sera désormais de -2
  • Si la valeur de vy est -2, alors elle sera désormais de --2, c'est à dire 2

Notre condition est imparfaite, puisque vous constatez que notre balle sort de l'écran avant d'effectuer son rebond. Une fois de plus, il s'agît simplement du principe selon lequel fillRect dessine la balle depuis sa coordonnées y correspondant à son bord haut.
De même si vous modifiez dans LoadGame la vélocité verticale initiale de la balle en négatif pour la faire aller vers le haut, vous constaterez qu'il nous manque la détection du rebond sur le bord haut de l'écran. Modifions donc notre condition pour y ajouter la collision haute et la hauteur de la balle sur la collision basse:

    // Si la position verticale de la balle + sa hauteur
    // Est supéreieur à la hauteur de l'écran
    // OU si la position verticale de la balle
    // est inférieure à 0 (bord haut de l'écran)
    if (ball.y + ball.height >= screen.height || ball.y <= 0) {
        ball.vy = -ball.vy
    }

Désormais notre balle rebondis sur les bord haut et bas de l'écran de jeu, la preuve en démo.

Collision AABB entre la balle et les raquettes

Dans le cas la collision entre la balle et les raquettes, la détection de la collision est légèrement différentes, puisque nous ne devont pas détecter la collision sur un seule axe, mais sur les 4 côtés de la balle.

Vous retrouverez le concept de la collision AABB que nous utiliserons ici au travers de la fonction aabb fournie dans le starter-kit.

Nous utilisons les cookies pour personnaliser le contenu et analyser notre trafic. Veuillez décider quel type de cookies vous êtes prêt à accepter.