// 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);
}