// sauvage. mélanger HTML et JS ce n'est pas bon window.addEventListener('load', init); // ou window.onload = init; // On peut faire de plein de manière différentes, // avec jquery etc. Mais jQuery est de plus en plus // obsolète et ne marche pas avec d'autres // frameworks comme angularJS, ReactJS, Polymer // Pour ce cours on fera du pur JS, qui est très bien. let canvas, ctx, width, height, b1, e1; let tableauxDesBalles = []; // un tableau vide en JS // Pour afficher les ftps, un div let fpsContainer; function init() { console.log("la page est chargée"); // Pour le nombre d'images / s fpsContainer = document.createElement('div'); document.body.appendChild(fpsContainer); // BONNE PRATIQUEZ : canvas, ctx en variables globales // let en ES6 est comme var en javascript 5 mais respecte la portée // des variables de Java, portée = entre {...} alors que var // pour une variable locale a une portée = la fonction entière canvas = document.querySelector("#myCanvas"); width = canvas.width; height = canvas.height; // pratique de les avoir globaux // querySelector = comme le $ de jQuery, à l'intérieur un // selecteur CSS // Ici le querySelector a bien renvoyé le canvas // car le DOM est prêt, la page chargée, le canvas // existe. // si on fait canvas = document.querySelector("#myCanvas"); en // avant, -> erreur. ctx = canvas.getContext('2d'); // autre possibilité 'webgl' pour la 3D //drawMonstre(0, 0); //drawMonstre(120, 30); //drawMonstre(240, 130); // etc. monstre.draw(); // x, y, rayon, couleur, vx, vy //b1 = new Balle(100, 100, 50, "pink", 10, 8); creerDesBalles(25); //e1 = new Ennemi(20, 20, 50, 50, "red", 4, 2); // On est dans le programme principal, on va écouter les // touches avant de démarrer l'animation creerLesEcouteurs(); // on demande au browser UNE frame d'animation // qui sera dessinée par mainloop() requestAnimationFrame(mainLoop); } function creerDesBalles(nbBalles) { for(let i = 0; i < nbBalles; i++) { let x = Math.random() * width; // Math.random() renvoie un nombre entre 0 et 1 let y = Math.random() * height; let rayon = 2 + Math.random() * 10; // rayon entre 2 et 12 let R = Math.round(255 *Math.random()); // valeur entre 0 et 255 let G = Math.round(255 *Math.random()); let B = Math.round(255 *Math.random()); let couleur = "rgb(" + R + "," + G + "," + B +")"; let vx = 1+Math.random() *5; // entre 1 et 5 let vy = 1+Math.random() *5; //console.log(couleur) let b = new Balle(x, y, rayon, couleur, vx, vy); // on la rajoute au tableau des balles tableauxDesBalles.push(b); } } function creerLesEcouteurs() { // Touches, sur window window.addEventListener('keydown', toucheEnfoncee); window.addEventListener('keyup', toucheRelachee); // Ecouteurs de souris, on peut mettre sur le canvas canvas.addEventListener('mouseup', boutonSourisRelache); canvas.addEventListener('mousedown', boutonSourisEnfonce); canvas.addEventListener('mousemove', sourisDeplacee); } function boutonSourisEnfonce(evt) { //console.log("bouton enfoncé"); // Ca depend du sens !!! monstre.v = monstre.v *5; } function boutonSourisRelache(evt) { //console.log("bouton relache"); monstre.v = Math.sign(monstre.v); } function sourisDeplacee(evt) { // La ligne suivante tient compte des propriétés // du canvas (bordure, pos, marges etc.) let rect = canvas.getBoundingClientRect(); let mx = evt.clientX - rect.left; let my = evt.clientY - rect.top; //console.log("mouse move x = " + mx + " y = " + my); monstre.x = mx; monstre.y = my; } function toucheEnfoncee(evt) { //console.log("touche enfoncee key = " + evt.key); switch(evt.key) { case 'ArrowRight' : //console.log("fleche à droite"); monstre.v = 1; // CA VA LE FAIRE ALLER A DROITE // CAR ON VA TESTER CA 60 fois // PAR SECONDE DANS LA BOUCLE // D'ANIMATION break; case 'ArrowLeft' : //console.log("fleche à gauche"); monstre.v = -1; break; } } function toucheRelachee(evt) { //console.log("touche relachee"); monstre.vx = 0; } // On a dessiné le monstre avec une fonction, // mais on peut aussi en faire un objet // Comme ce monstre est unique (on ne va pas) // en instancier 40 !!!, c'est l'occasion de voir // la première syntaxe pour faire dez objets en JS // Cette syntaxe est la même en ES5 qu'en ES6 // ES5 = depuis 2009, marche partout // ES6 = la dernière version de JavaScript aussi // appelée ES2015, et avec ses extensions ES2016 // ou ES2016+ // Syntaxe 1 : objet "singleton" let monstre = { // propriétés x:100, y:100, width:100, height:100, v: 0, vx:0, // vitesse en x vy: 0, // vitesse en y couleurYeux: 'blue', couleurNez : 'red', couleurCorps: 'brown', // methodes move: function(x, y) { this.x = x; this.y = y; }, // ici une virgule ! draw: function() { ctx.save(); ctx.translate(this.x, this.y); // Le corps du monstre ctx.fillStyle = this.couleurCorps; ctx.fillRect(0, 0, this.width, this.height); // Les yeux ctx.fillStyle = this.couleurYeux; ctx.fillRect(20, 15, 10, 10); ctx.fillRect(68, 15, 10, 10); // Le nez ctx.fillStyle = this.couleurNez; ctx.fillRect(45, 30, 10, 30); ctx.restore(); } // ici pas de virgule !!! } // ICI UNE AUTRE MANIERE DE FAIRE DES OBJETS // CLASSES ES6, attention : ne marchera pas dans // de vieux browsers comme IE // Opera, FF, Edge, Chrome, sans doute safari // ca doit marcher. // On va faire une classe "balle" class ObjetGraphique { constructor(x, y, couleur, vx, vy) { this.x = x; this.y = y; this.couleur = couleur; this.vx = vx; this.vy = vy; } move() { this.x += this.vx; this.y += this.vy; } draw() { // ici on peut dessiner par ex un petit cercle // au point x, y // Pour dessiner un cercle, faire comme ceci // j'explique après... ctx.save(); // bonne pratique ctx.translate(this.x, this.y); // On dessine en 0,0 ctx.beginPath(); ctx.arc(0, 0, 2, 0, 2*Math.PI); ctx.fillStyle = this.couleur; ctx.fill(); ctx.restore(); } } class Balle extends ObjetGraphique { constructor(x, y, rayon, couleur, vx, vy) { // appel du constructeur hérité super(x, y, couleur, vx, vy); this.rayon = rayon; } draw() { // Pour dessiner un cercle, faire comme ceci // j'explique après... ctx.save(); // bonne pratique ctx.translate(this.x, this.y); // On dessine en 0,0 ctx.beginPath(); ctx.arc(0, 0, this.rayon, 0, 2*Math.PI); ctx.fillStyle = this.couleur; ctx.fill(); ctx.restore(); // Appel de la méthode héritée super.draw(); } } class Ennemi extends ObjetGraphique { constructor(x, y, width, height, couleur, vx, vy) { // appel du constructeur hérité super(x, y, couleur, vx, vy); this.width = width; this.height = height; } draw() { // Pour dessiner un cercle, faire comme ceci // j'explique après... ctx.save(); // bonne pratique ctx.translate(this.x, this.y); // On dessine en 0,0 ctx.fillRect(0, 0, 2*this.width, this.height); ctx.restore(); // Appel de la méthode héritée super.draw(); } } // vars for counting frames/s, used by the measureFPS function let frameCount = 0; let lastTime; let fps; let measureFPS = function(newTime){ // test for the very first invocation if(lastTime === undefined) { lastTime = newTime; return; } // calculate the delta between last & current frame var diffTime = newTime - lastTime; if (diffTime >= 1000) { fps = frameCount; frameCount = 0; lastTime = newTime; } // and display it in an element we appended to the // document in the start() function fpsContainer.innerHTML = 'FPS: ' + fps; frameCount++; }; // ICI BOUCLE D'animation à 60 images/s function mainLoop(time) { // temps écoulé depuis que la page a été chargée et que // cette webapp a démarré //console.log("time = " + time); // compute FPS, called each frame, uses the high resolution time parameter // given by the browser that implements the requestAnimationFrame API measureFPS(time); // 1 - on efface le contenu du canvas ctx.clearRect(0, 0, width, height); // 2 - on dessine le joueur, les ennemis, le score // etc. monstre.draw(); //b1.draw(); // la balle ! //e1.draw(); // un ennemi ! dessinerEtDeplacerLesBalles(); // 3 - on déplace les objets // 60 fois par seconde on fait cela // Si monstre.vx vait 0 -> immobile // si j'appuie sur une touche, ca passe à 1 // je déplace 60 fois par seconde vers la droite monstre.x += monstre.v; //b1.move(); //e1.move(); // monstre.x et monstre.y = le coin en haut à gauche // du rectangle testeCollisionMonstreAvecMurs(); //testeCollisionBalleAvecMurs(); testerCollisionJoueurAvecBalles(); // 4 - on redemande une nouvelle frame d'animation // on demande au browser UNE frame d'animation // qui sera dessinée par mainloop() requestAnimationFrame(mainLoop); } // Fonctions utilitaires pour la detection de collision function circleCollide(x1, y1, r1, x2, y2, r2) { let dx = x1 - x2; let dy = y1 - y2; return ((dx * dx + dy * dy) < (r1 + r2)*(r1+r2)); } // Collisions between aligned rectangles function rectsOverlap(x1, y1, w1, h1, x2, y2, w2, h2) { if ((x1 > (x2 + w2)) || ((x1 + w1) < x2)) return false; // No horizontal axis projection overlap if ((y1 > (y2 + h2)) || ((y1 + h1) < y2)) return false; // No vertical axis projection overlap return true; // If previous tests failed, then both axis projections // overlap and the rectangles intersect } // Collisions between rectangle and circle function circRectsOverlap(x0, y0, w0, h0, cx, cy, r) { let testX=cx; let testY=cy; if (testX < x0) testX=x0; if (testX > (x0+w0)) testX=(x0+w0); if (testY < y0) testY=y0; if (testY > (y0+h0)) testY=(y0+h0); return (((cx-testX)*(cx-testX)+(cy-testY)*(cy-testY))< r*r); } // Ci-dessous : pas des objets, une version // "fonctions" function testerCollisionJoueurAvecBalles() { tableauxDesBalles.forEach(function(b, index, tab) { if(circRectsOverlap(monstre.x, monstre.y, monstre.width, monstre.height, b.x, b.y, b.rayon)) { console.log("collision"); tableauxDesBalles.splice(index, 1); } }); } function dessinerEtDeplacerLesBalles() { /* // autre syntaxe for(let i = 0; i < tableauxDesBalles.length;i++) { var b = tableauxDesBalles[i]; b.draw(); b.move(); testeCollisionBalleAvecMurs(b); } */ // ici avec un itérateur tableauxDesBalles.forEach(function(b, index, tab) { // b est une balle dans la collection b.draw(); b.move(); testeCollisionBalleAvecMurs(b); }); } function testeCollisionBalleAvecMurs(b) { if(((b.x + b.rayon) > width) || ((b.x - b.rayon) < 0)) { // on a touché un bord vertical // on inverse la vitesse en x b.vx = -b.vx; } if(((b.y + b.rayon) > height) || ((b.y - b.rayon) < 0)) { // on a touché un bord vertical // on inverse la vitesse en x b.vy = -b.vy; } } function testeCollisionMonstreAvecMurs() { if((monstre.x+monstre.width) > canvas.width) { // BONNE PRATIQUE : toujours se remettre à la position // de contact. Si jamais on est allé "trop loin" // on va rester en position de collision et rester // "collé au mur" monstre.x = canvas.width - monstre.width; monstre.v = -monstre.v; } else if(monstre.x < 0) { // idem monstre.x = 0; monstre.v = -monstre.v; } // A FAIRE : COLLISIONS AVEC MURS HAUT ET BAS ! } function drawMonstre(x, y) { // Ici on va dessiner un monstre ! // BONNE PRATIQUE No 1: sauvegarder tout le contexte // au debut de chaque fonction qui change une propriété // Ca ne coûte rien, tout est fait dans le GPU, // dans des registres spécialisés ctx.save(); // BONNE PRATIQUE No 2: on dessine en 0, 0, mais // on translate tout le repère qui est normalement // situé en haut à gauche du canvas (x vers la droite // y vers le bas) ctx.translate(x, y); // Le corps du monstre ctx.fillStyle = "brown"; ctx.fillRect(0, 0, 100, 100); // Les yeux ctx.fillStyle = "yellow"; ctx.fillRect(20, 15, 10, 10); ctx.fillRect(68, 15, 10, 10); // Le nez ctx.fillStyle = "red"; ctx.fillRect(45, 30, 10, 30); // etc.... // BONNE PRATIQUE 1: restaurer le contexte à la fin ctx.restore(); } function testGraphics() { ctx.fillStyle = 'blue'; ctx.fillRect(10, 10, 100, 100); // contour jaune ctx.lineWidth = 10; ctx.strokeStyle = 'yellow'; ctx.strokeRect(10, 10, 100, 100); // stroke au lieu de fill = en fil de fer ! ctx.strokeStyle = 'red'; ctx.lineWidth=5; ctx.strokeRect(200, 200, 50, 50); // texte let fontSize = 40; ctx.font = fontSize + 'px Courier'; ctx.testAlign='center'; ctx.fillText("Hello!", 150, 40); ctx.strokeStyle = "pink;" ctx.lineWidth = 2; ctx.strokeText("Hello!", 150, 40); }