Vous n'êtes pas connecté. Connexion
|
|
Accueil > Intranet Michel Buffa > Cours HTML5/NodeJS/Angular etc. Master 2 NTDP 2015-2016 > Moteur de jeu en HTML5/JS, master Miage NTDP 2015-2016
Moteur de jeu en HTML5/JS, master Miage NTDP 2015-2016De $1Table des matières
Introduction : la boucle d'animationRemarque : La suite de ce document est une version simplifiée de ce chapitre de cours en anglais que je suis en train d'écrire pour un cours sur les jeux HTML5 : HTML5 Games introduction.pdf, si vous êtes à l'aise en anglais, je vous conseille de le récupérer, il est plus détaillé que la version française qui suit... Tout jeu, logiciel animé utilise ce qu'on appelle communément une "boucle d'animation" ou une "game loop" dans le jargon des créateurs de jeu vidéo. Cette boucle est le composant principal. Elle sépare la logique du "jeu" et sa couche graphique des interactions de l'utilisateur via le clavier, souris ou manette. Les applications classiques, comme un traitement de texte, une base de données, une calculatrice, ne font rien quand l'utilisateur ne fait rien. Ce sont des applications qui n'agissent que lorsque l'utilisateur interagit avec l'application, en tapant sur le clavier par exemple. Quand l'utilisateur ne fait rien, l'application en fait qu'attendre qu'il fasse quelque chose ! Dans le cas d'un jeu c'est différent. Il attend toujours les interactions de l'utilisateur mais au lieu de ne rien faire d'autre que attendre, le jeu va déplacer, dessiner, animer des objets graphiques à une fréquence élevée, tester des collisions, jouer de la musique, etc. Pour des raisons historiques, on a fixé le seuil de fluidité à 60 images / secondes. Il y a plusieurs manière de coder une "game loop" en JavaScript. Utilisation de la fonction setInterval(...)La fonction setInterval permet d'appeler une autre fonction à intervalle régulier (en ms). Voici un exemple en ligne: http://jsbin.com/yuhule/3/edit Code source : var addStarToTheBody = function(){ document.body.innerHTML += "*"; }; //this will add one star to the document each 200ms (1/5s) setInterval(addStarToTheBody, 200); Beaucoup mieux: utilisation de RequestAnimationFrameHTML5 a introduit à la demande des développeurs de jeux et d'applications musicales une API permettant de faire une "game loop" à 60 images par seconde, avec de nombreuses optimisations :
Exemple typique : http://jsbin.com/gixepe/2/edit Code source : window.onload = function init() { // called after the page is entirely loaded requestAnimationFrame(mainloop); }; function mainloop(time) { document.body.innerHTML += "*"; // call back itself every 60th of second requestAnimationFrame(mainloop); } Notez que la dernière ligne de la boucle d'animation est simplement une demande au navigateur de re-exécuter cette même boucle d'animation (requestAnimationFrame = "je demande une nouvelle frame d'animation). Le paramètre est la fonction qui implémente la boucle d'animation. C'est une "sorte" d'appel récursif si ce n'est que cet appel est "asyncrhone" : en attendant le rappel, le navigateur n'est pas bloqué, d'autres insrtructions peuvent s'exécuter etc. Notez qu'on peut avoir plusieurs game loops simultanément dans un même programme. Elles sont toutes "en tâche de fond", non blocantes et entre chaque exécution le programme peut réagir aux interactions clavier, souris, etc. Implémentation d'un petit "moteur de jeu"Voici le petit squelette d'un jeu: var GF = function(){ var mainLoop = function(time){ //main function, called each frame requestAnimationFrame(mainLoop); }; var start = function(){ requestAnimationFrame(mainLoop); }; //our GameFramework returns a public API visible from outside its scope return { start: start }; }; Et voici comment avec ce squelette on créera un jeu :
var game = new GF(); // Launch the game, start the animation loop etc. game.start(); Rajoutons donc quelque chose dans la game loop :
var mainLoop = function(time){ //main function, called each frame document.body.innerHTML = Math.random(); // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; Et voici l'exemple complet testable : http://jsbin.com/kafehi/3/edit Compter le nombre d'images par secondeVoici une fonction "measureFPS" qui permet de compter le nombre d'images par seconde :
// vars for counting frames/s, used by the measureFPS function var frameCount = 0; var lastTime; var fpsContainer; var fps; var measureFPS = function(newTime){ // test for the very first invocation if(lastTime === undefined) { lastTime = newTime; return; } //calculate the difference 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++; }; On appellera cette fonction depuis la boucle principale :
var mainLoop = function(time){ //main function, called each frame measureFPS(time); // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; Dans la fonction start() du moteur de jeu, nous créeons un simple élément <div> html pour afficher le nombre d'images par seconde : var start = function(){ // adds a div for displaying the fps value fpsContainer = document.createElement('div'); document.body.appendChild(fpsContainer); requestAnimationFrame(mainLoop); }; Exemple testable en ligne : http://jsbin.com/kafehi/5/edit Et oh ???? Où sont les graphismes ???? L'élément CANVAS de HTML5Ah ah... pour faire du dessin il faut utiliser l'élément <canvas> de HTML5 et son API JavaScript.
Voici un exemple qui dessine un petit monstre dans la boucle d'animation, à l'aide de fonctions de dessin de rectangles : http://jsbin.com/pagipi/2/edit Code HTML : <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> </head> <body> <canvas id="myCanvas" width="200" height="200"></canvas> </body> </html> Code CSS très simple, pour voir la bordure du canvas : canvas { border: 1px solid black; } Bonnes pratiques pour utiliser le canvas :
Code JavaScript de l'exemple : // useful to have them as global variables var canvas, ctx, w, h; window.onload = function init() { // called AFTER the page has been loaded canvas = document.querySelector("#myCanvas"); // often useful w = canvas.width; h = canvas.height; // important, we will draw with this object ctx = canvas.getContext('2d'); // ready to go ! drawMyMonster(); }; function drawMyMonster() { // draw a big monster ! // head ctx.strokeRect(10, 10, 100, 100); // eyes ctx.fillRect(30, 30, 10, 10); ctx.fillRect(75, 30, 10, 10); // nose ctx.strokeRect(55, 50, 10, 40); // mouth ctx.strokeRect(45, 94, 30, 10); // teeth ctx.fillRect(48, 94, 10, 10); ctx.fillRect(62, 94, 10, 10); } Introduction aux transformations géométriques 2DL'API du canvas contient des fonctions propres à la gestion des repères / aux transformations géométriques très pratiques. Par ailleurs, on peut empiler/sauvegarder et dépiler/retaurer des contextes graphiques, ce qui va permettra de travailler avec plusieurs repères. Par exemple, le code ci-dessous est équivalent au code de l'exemple précédent, si ce n'est que le monstre est maintenant dessiné en (0, 0) mais on translate le repère avant de le dessiner : function drawMyMonster(x, y) { // draw a big monster ! // head // save the context ctx.save(); // translate the coordinate system, draw relative to it ctx.translate(x, y); // (0, 0) is the top left corner of the monster. ctx.strokeRect(0, 0, 100, 100); // eyes ctx.fillRect(20, 20, 10, 10); ctx.fillRect(65, 20, 10, 10); // nose ctx.strokeRect(45, 40, 10, 40); // mouth ctx.strokeRect(35, 84, 30, 10); // teeth ctx.fillRect(38, 84, 10, 10); ctx.fillRect(52, 84, 10, 10); // restore the context ctx.restore(); } L'appel de cette fonction est un peu différent maintenant : // Try to change the parameter values to move // the monster drawMyMonster(10, 10); Vous pouvez tester cette version ici : http://jsbin.com/pagipi/3/edit, essayez donc de changer les valeurs des paramètres ! Animer le monstre, l'inclure dans notre moteur d'animationNous allons modifier légèrement le moteur d'animation développé au début de ce tutorial :
Version en ligne à tester : http://jsbin.com/kafehi/7/edit Code HTML : <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> </head> <body> <canvas id="myCanvas" width="200" height="200"></canvas> </body> </html> Code JavaScript complet : // Inits window.onload = function init() { var game = new GF(); game.start(); }; // GAME FRAMEWORK STARTS HERE var GF = function(){ // Vars relative to the canvas var canvas, ctx, w, h; // vars for counting frames/s, used by the measureFPS function var frameCount = 0; var lastTime; var fpsContainer; var fps; var measureFPS = function(newTime){ // test for the very first invocation if(lastTime === undefined) { lastTime = newTime; return; } //calculate the difference 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++; }; // clears the canvas content function clearCanvas() { ctx.clearRect(0, 0, w, h); } // Functions for drawing the monster and maybe other objects function drawMyMonster(x, y) { // draw a big monster ! // head // save the context ctx.save(); // translate the coordinate system, draw relative to it ctx.translate(x, y); // (0, 0) is the top left corner of the monster. ctx.strokeRect(0, 0, 100, 100); // eyes ctx.fillRect(20, 20, 10, 10); ctx.fillRect(65, 20, 10, 10); // nose ctx.strokeRect(45, 40, 10, 40); // mouth ctx.strokeRect(35, 84, 30, 10); // teeth ctx.fillRect(38, 84, 10, 10); ctx.fillRect(52, 84, 10, 10); // restore the context ctx.restore(); } var mainLoop = function(time){ //main function, called each frame measureFPS(time); // Clear the canvas clearCanvas(); // draw the monster drawMyMonster(10+Math.random()*10, 10+Math.random()*10); // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; var start = function(){ // adds a div for displaying the fps value fpsContainer = document.createElement('div'); document.body.appendChild(fpsContainer); // Canvas, context etc. canvas = document.querySelector("#myCanvas"); // often useful w = canvas.width; h = canvas.height; // important, we will draw with this object ctx = canvas.getContext('2d'); // start the animation requestAnimationFrame(mainLoop); }; //our GameFramework returns a public API visible from outside its scope return { start: start }; }; Remarquez que 99% de la fonction init() de l'exemple précédent a été déplacé dans la fonction start() du moteur d'animation. Notez aussi que pour voir le monstre bouger on a ajouté des valeurs aléatoires à sa position, ainsi il "tremble". Interactions avec l'utilisateurGestion du clavierEn JavaScript on définit des "écouteurs" que l'on associe à certains éléments HTML (par exemple le canvas) et qui vont écouter certains événements (appui sur une touche, déplacement ou click souris), de manière "non blocante" (on dit : "asynchrone"). Comme pendant ce temps, la boucle d'animation tourne, on va simplement changer des "états" que l'on stocke dans un objet JavaScript et ces états seront consultés 60 fois par seconde depuis la boucle d'animation. On veut aussi pouvoir détecter l'appui de touches simultanés et la souris (déplacements, click), en même temps, dans la boucle. On va donc utiliser un objet qui contiendra la liste des touches enfoncées, une valeur pour dire si un bouton de la souris est enfoncé ou non, la position du curseur de la souris, etc... Détection de l'appui sur une touche : window.addEventListener('keydown', function(event) { if (event.keyCode === 37) { //left arrow was pressed } }, false); Ici on a défini un écouteur sur l'objet "window" (la fenêtre entière, la page web complète), pour l'événement "keydown", et la fonction qui traitera cet événement est le dernier paramètre. Elle prend en paramètre l'événement détecté. C'est un objet JavaScript qui a dans ses propriétés des informations relatives à l'événement : ici le code de la touche pressée. Liste des codes : On peut tester interactivement les codes ici : http://www.asquare.net/javascript/tests/KeyCode.html Ajout de la détection de touches dans notre moteur d'animationExemple en ligne : http://jsbin.com/kafehi/9/edit Code source : // Inits window.onload = function init() { var game = new GF(); game.start(); }; // GAME FRAMEWORK STARTS HERE var GF = function(){ // Vars relative to the canvas var canvas, ctx, w, h; // vars for counting frames/s, used by the measureFPS function var frameCount = 0; var lastTime; var fpsContainer; var fps; // vars for handling inputs var inputStates = {}; var measureFPS = function(newTime){ // test for the very first invocation if(lastTime === undefined) { lastTime = newTime; return; } //calculate the difference 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++; }; // clears the canvas content function clearCanvas() { ctx.clearRect(0, 0, w, h); } // Functions for drawing the monster and maybe other objects function drawMyMonster(x, y) { // draw a big monster ! // head // save the context ctx.save(); // translate the coordinate system, draw relative to it ctx.translate(x, y); // (0, 0) is the top left corner of the monster. ctx.strokeRect(0, 0, 100, 100); // eyes ctx.fillRect(20, 20, 10, 10); ctx.fillRect(65, 20, 10, 10); // nose ctx.strokeRect(45, 40, 10, 40); // mouth ctx.strokeRect(35, 84, 30, 10); // teeth ctx.fillRect(38, 84, 10, 10); ctx.fillRect(52, 84, 10, 10); // restore the context ctx.restore(); } var mainLoop = function(time){ //main function, called each frame measureFPS(time); // Clear the canvas clearCanvas(); // draw the monster drawMyMonster(10+Math.random()*10, 10+Math.random()*10); // check inputStates if (inputStates.left) { ctx.fillText("left", 150, 20); } if (inputStates.up) { ctx.fillText("up", 150, 50); } if (inputStates.right) { ctx.fillText("right", 150, 80); } if (inputStates.down) { ctx.fillText("down", 150, 120); } if (inputStates.space) { ctx.fillText("space bar", 140, 150); } // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; var start = function(){ // adds a div for displaying the fps value fpsContainer = document.createElement('div'); document.body.appendChild(fpsContainer); // Canvas, context etc. canvas = document.querySelector("#myCanvas"); // often useful w = canvas.width; h = canvas.height; // important, we will draw with this object ctx = canvas.getContext('2d'); // default police for text ctx.font="20px Arial"; //add the listener to the main, window object, and update the states window.addEventListener('keydown', function(event){ if (event.keyCode === 37) { inputStates.left = true; } else if (event.keyCode === 38) { inputStates.up = true; } else if (event.keyCode === 39) { inputStates.right = true; } else if (event.keyCode === 40) { inputStates.down = true; } else if (event.keyCode === 32) { inputStates.space = true; } }, false); //if the key will be released, change the states object window.addEventListener('keyup', function(event){ if (event.keyCode === 37) { inputStates.left = false; } else if (event.keyCode === 38) { inputStates.up = false; } else if (event.keyCode === 39) { inputStates.right = false; } else if (event.keyCode === 40) { inputStates.down = false; } else if (event.keyCode === 32) { inputStates.space = false; } }, false); // start the animation requestAnimationFrame(mainLoop); }; //our GameFramework returns a public API visible from outside its scope return { start: start }; }; Detection des événements sourisPour la souris on va procéder de même si ce n'est que l'on va s'intéresser à trois types d'évènements : déplacement, appui et relâchement d'un bouton. La détection de la position de la souris nécessite un petit traitement particulier car en HTML les éléments peuvent avoir une bordure et des marges intérieures et extérieures. La position du coin en haut à gauche (0, 0) doit être ajustée en fonction de ces marges. C'est ce qui est fait dans la fonction qui renvoit la position du curseur un peu plus bas dans cette section. Exemple en ligne : http://jsbin.com/bizudu/4/edit Code source : var canvas, ctx; var inputStates = {}; window.onload = function init() { canvas = document.getElementById('myCanvas'); ctx = canvas.getContext('2d'); canvas.addEventListener('mousemove', function (evt) { inputStates.mousePos = getMousePos(canvas, evt); var message = 'Mouse position: ' + inputStates.mousePos.x + ',' + inputStates.mousePos.y; writeMessage(canvas, message); }, false); canvas.addEventListener('mousedown', function (evt) { inputStates.mousedown = true; inputStates.mouseButton = evt.button; var message = "Mouse button " + evt.button + " down at position: " + inputStates.mousePos.x + ',' + inputStates.mousePos.y; writeMessage(canvas, message); }, false); canvas.addEventListener('mouseup', function (evt) { inputStates.mousedown = false; var message = "Mouse up at position: " + inputStates.mousePos.x + ',' + inputStates.mousePos.y; writeMessage(canvas, message); }, false); }; function writeMessage(canvas, message) { var ctx = canvas.getContext('2d'); ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.font = '18pt Calibri'; ctx.fillStyle = 'black'; ctx.fillText(message, 10, 25); ctx.restore(); } function getMousePos(canvas, evt) { // necessary to take into account CSS boudaries var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; } Ajout de l'écouteur souris dans le moteur d'animationExemple en ligne : http://jsbin.com/kafehi/12/edit Code source : // Inits window.onload = function init() { var game = new GF(); game.start(); }; // GAME FRAMEWORK STARTS HERE var GF = function(){ // Vars relative to the canvas var canvas, ctx, w, h; // vars for counting frames/s, used by the measureFPS function var frameCount = 0; var lastTime; var fpsContainer; var fps; // vars for handling inputs var inputStates = {}; var measureFPS = function(newTime){ // test for the very first invocation if(lastTime === undefined) { lastTime = newTime; return; } //calculate the difference 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++; }; // clears the canvas content function clearCanvas() { ctx.clearRect(0, 0, w, h); } // Functions for drawing the monster and maybe other objects function drawMyMonster(x, y) { // draw a big monster ! // head // save the context ctx.save(); // translate the coordinate system, draw relative to it ctx.translate(x, y); // (0, 0) is the top left corner of the monster. ctx.strokeRect(0, 0, 100, 100); // eyes ctx.fillRect(20, 20, 10, 10); ctx.fillRect(65, 20, 10, 10); // nose ctx.strokeRect(45, 40, 10, 40); // mouth ctx.strokeRect(35, 84, 30, 10); // teeth ctx.fillRect(38, 84, 10, 10); ctx.fillRect(52, 84, 10, 10); // restore the context ctx.restore(); } var mainLoop = function(time){ //main function, called each frame measureFPS(time); // Clear the canvas clearCanvas(); // draw the monster drawMyMonster(10+Math.random()*10, 10+Math.random()*10); // check inputStates if (inputStates.left) { ctx.fillText("left", 150, 20); } if (inputStates.up) { ctx.fillText("up", 150, 40); } if (inputStates.right) { ctx.fillText("right", 150, 60); } if (inputStates.down) { ctx.fillText("down", 150, 80); } if (inputStates.space) { ctx.fillText("space bar", 140, 100); } if (inputStates.mousePos) { ctx.fillText("x = " + inputStates.mousePos.x + " y = " + inputStates.mousePos.y, 5, 150); } if (inputStates.mousedown) { ctx.fillText("mousedown b" + inputStates.mouseButton, 5, 180); } // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; function getMousePos(evt) { // necessary to take into account CSS boudaries var rect = canvas.getBoundingClientRect(); return { x: evt.clientX - rect.left, y: evt.clientY - rect.top }; } var start = function(){ // adds a div for displaying the fps value fpsContainer = document.createElement('div'); document.body.appendChild(fpsContainer); // Canvas, context etc. canvas = document.querySelector("#myCanvas"); // often useful w = canvas.width; h = canvas.height; // important, we will draw with this object ctx = canvas.getContext('2d'); // default police for text ctx.font="20px Arial"; //add the listener to the main, window object, and update the states window.addEventListener('keydown', function(event){ if (event.keyCode === 37) { inputStates.left = true; } else if (event.keyCode === 38) { inputStates.up = true; } else if (event.keyCode === 39) { inputStates.right = true; } else if (event.keyCode === 40) { inputStates.down = true; } else if (event.keyCode === 32) { inputStates.space = true; } }, false); //if the key will be released, change the states object window.addEventListener('keyup', function(event){ if (event.keyCode === 37) { inputStates.left = false; } else if (event.keyCode === 38) { inputStates.up = false; } else if (event.keyCode === 39) { inputStates.right = false; } else if (event.keyCode === 40) { inputStates.down = false; } else if (event.keyCode === 32) { inputStates.space = false; } }, false); // Mouse event listeners canvas.addEventListener('mousemove', function (evt) { inputStates.mousePos = getMousePos(evt); }, false); canvas.addEventListener('mousedown', function (evt) { inputStates.mousedown = true; inputStates.mouseButton = evt.button; }, false); canvas.addEventListener('mouseup', function (evt) { inputStates.mousedown = false; }, false); // start the animation requestAnimationFrame(mainLoop); }; //our GameFramework returns a public API visible from outside its scope return { start: start }; }; Remarquez qu'on a ajouté tous les écouteurs dans la fonction start() qui est exécutée au tout début de la création du moteur. Déplacement du joueur à l'aide des flèches du clavier, changement de vitesse avec la sourisExemple en ligne : http://jsbin.com/bemebi/2/edit On a simplement créé un objet correspondant au monstre/joueur, contenant des propriétés telles que sa position et sa vitesse : // The monster ! var monster = { x:10, y:10, speed:1 }; Ces propriétés seront utilisées depuis l'intérieur de la game loop, à la fois pour savoir où dessiner le monstre mais aussi, en fonction de l'état du clavier et de la souris, on modifiera ces valeurs pour déplacer le monstre. Voici la nouvelle boucle d'animation, qui est typique d'un jeu vidéo : var mainLoop = function(time){ //main function, called each frame measureFPS(time); // Clear the canvas clearCanvas(); // draw the monster drawMyMonster(monster.x, monster.y); // Check inputs and move the monster updateMonsterPosition(); // call the animation loop every 1/60th of second requestAnimationFrame(mainLoop); }; Et voici le code source de la partie qui va gérer les déplacements en fonction de l'état du clavier et de la souris. Cette fonction est appelée 60 fois par seconde : function updateMonsterPosition() { monster.speedX = monster.speedY = 0; // check inputStates if (inputStates.left) { ctx.fillText("left", 150, 20); monster.speedX = -monster.speed; } if (inputStates.up) { ctx.fillText("up", 150, 40); monster.speedY = -monster.speed; } if (inputStates.right) { ctx.fillText("right", 150, 60); monster.speedX = monster.speed; } if (inputStates.down) { ctx.fillText("down", 150, 80); monster.speedY = monster.speed; } if (inputStates.space) { ctx.fillText("space bar", 140, 100); } if (inputStates.mousePos) { ctx.fillText("x = " + inputStates.mousePos.x + " y = " + inputStates.mousePos.y, 5, 150); } if (inputStates.mousedown) { ctx.fillText("mousedown b" + inputStates.mouseButton, 5, 180); monster.speed = 5; } else { // mouse up monster.speed = 1; } monster.x += monster.speedX; monster.y += monster.speedY; } Donc 60 fois par seconde, si vous regardez les dernières ligne de ce code, on va ajouter à la position (x, y) un incrément (la vitesse en x, et en y du monstre). Su aucune touche n'est enfoncée, ces incréments valent zéro, le monstre ne bouge pas. Si la flèche vers le bas est enfoncée, alors la vitesse en Y vaut monster.speed, c'est-à-dire 1 (valeur dans la création du monstre), si on appuie aussi sur la flèche à droite, la vitesse en X vaut aussi 1, le monstre se déplace en diagonale. Si on relâche une touche, la vitesse correspondante revaut zéro. Si un bouton de la souris est enfoncé, la vitesse monster.speed vaut 5 : le monstre se déplacera plus vite, si on relache le bouton, la vitesse revient à 1, etc... JavaScript objet, animer plusieurs objets, détection de collisions
Exemples vu en cours (en plus des exemples du PDF ci-dessus) :
Autre version avec les états (game over, jeu en cours, etc) et c'est la version qu'on a éclaté en fichiers et qui est présente dans le zip ci-dessous (ouvrir index.html pour lancer) : http://jsbin.com/fopomo/15/edit Présentation powerpoint données à des élèves de terminale, contient les exemples de collisions entre balles (voir aussi le chapitre dans le PDF consacré aux collisions)
Mots clés:
|
Powered by MindTouch Deki Open Source Edition v.8.08 |