Dans ce chapitre nous allons enfin faire fonctionner le jeu !
Les références
On va avoir les nouveaux fichiers play.js et end.js, on va donc le charger dans index.html :
<!-- Load Crazy Bird --> <script src="js/game.js"></script> <script src="js/boot.js"></script> <script src="js/particles.js"></script> <script src="js/load.js"></script> <script src="js/settings.js"></script> <script src="js/utils.js"></script> <script src="js/menu.js"></script> <script src="js/collisions.js"></script> <script src="js/play.js"></script> <script src="js/end.js"></script>
D’autre part dans le fichier load.js on avait commenté les événements pour éviter de générer des erreurs, on va avoir besoin maintenant de tous les dé-commenter :
... C.sprites.sky.mousedown = C.play.onFlight; C.sprites.sky.tap = C.play.onFlight; ... C.sprites.land.mousedown = C.play.onFlight; C.sprites.land.tap = C.play.onFlight; ... C.sprites.playAgain.mousedown = C.end.onPlayAgain; C.sprites.playAgain.touchstart = C.end.onPlayAgain; ...
Le fichier play.js
Toute la logique du jeu se trouve dans ce fichier :
/* global C, SAT */ C.play = { on: function () { // Manage sky and land C.sprites.sky.tilePosition.x -= 1; C.sprites.land.tilePosition.x -= 2; // Manage planes collisions C.sprites.planes.forEach(function (e) { if (e.state) { if (C.collisions.birdPlane(e)) { // Check lifes if (C.lifes) { C.sounds.crashPlane.play(); C.particles.planeParticles.updateOwnerPos(e.x + 180, e.y + 110); C.particles.planeParticles.emit = true; e.state = false; e.position.set(C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222)); --C.lifes; C.score += 10; C.utils.refreshText(); } else { // Bird crash and end game C.sounds.crashBird.play(); C.particles.birdParticles.updateOwnerPos(C.sprites.bird.x + 68, C.sprites.bird.y + 60); C.particles.birdParticles.emit = true; C.sprites.bird.visible = false; if (C.score > localStorage.getItem('highScore')) { localStorage.setItem('highScore', C.score); } C.state = C.end.on; } } } }); // Manage balloons collisions C.sprites.balloons.forEach(function (e) { if (e.state === 1) { if (C.collisions.birdBalloonCircle(e)) { // Check life balloon if (e.life) { C.sounds.life.play(); ++C.lifes; } else { C.sounds.balloon.play(); ++C.balloonsDone; if (C.balloonsDone === C.maxBalloons) { C.balloonsDone = 0; ++C.level; } } ++C.score; e.state = 2; e.play(); C.utils.refreshText(); } else if (C.collisions.birdBalloonPolygon(e)) { e.rotation = -0.5; } } }); // Move bird var newY = C.sprites.bird.y - C.sprites.bird.vy; if (newY < C.pannelHeight + 1) { // Top collision C.sprites.bird.vy = .1; C.sprites.bird.y += C.sprites.bird.vy; } else if (newY > C.renderer.height - 120) { // Bottom collision C.sprites.bird.vy = 0; } else { // Casual flight C.sprites.bird.y = newY; C.sprites.bird.vy -= 0.07; } // Move planes C.sprites.planes.forEach(function (e) { if (e.state) { e.x -= C.speed + C.level * 2; if (e.x < -356) { e.state = false; e.x = C.renderer.width; e.y = C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222); } } }); // Move balloons C.sprites.balloons.forEach(function (e) { if (e.state) { e.x -= (C.speed + C.level * 2) / 2; if (e.rotation < 0) { e.rotation += .004; } if (e.state === 2) { e.y += 5; } if (e.x < -50 || e.y > C.stageHeight + 80) { e.state = false; e.rotation = 0; e.gotoAndStop(0); e.position.set(C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 80, C.renderer.height - 75)); } } }); // Check less than 2 planes in right section var n = 0; for (var i = 0; i < C.nbrPlanes; ++i) { if (C.sprites.planes[i].state && C.sprites.planes[i].x > C.renderer.width - 400) { ++n; } } // Manage planes if (n < 2) { C.sprites.planes.forEach(function (e) { if (!e.state) { if (C.utils.getRandomInt(0, 200 - C.level * 10) === 50) { // Check for free place while (!C.collisions.freeOfPlane(e)) { e.y += 1; // Check stage limit if (e.y > C.renderer.height - 222) { e.y = C.pannelHeight; } } e.state = true; } } }); } // Manage balloons C.sprites.balloons.forEach(function (e) { if (!e.state) { if (C.utils.getRandomInt(0, 200 - C.level * 10) === 50) { if (C.utils.getRandomInt(0, 10) === 5) { e.tint = 0xff0000; e.life = true; } else { e.tint = 0xffff00; e.life = false; } e.state = 1; } } }); }, onFlight: function () { C.sprites.bird.vy += 2.4; C.sprites.bird.gotoAndPlay(0); } };
On va un peu analyser tout ça…
Déplacement du fond
La partie la plus simple est le déplacement du fond (pour donner l’impression que l’oiseau se déplace) :
C.sprites.sky.tilePosition.x -= 1; C.sprites.land.tilePosition.x -= 2;
Le ciel se déplace deux fois moins vite que les collines pour créer l’effet de perspective
Déplacement de l’oiseau
L’oiseau peut juste monter et descendre. Il monte par action du joueur et il descend par action de la gravité :
// Move bird var newY = C.sprites.bird.y - C.sprites.bird.vy; if (newY < C.pannelHeight + 1) { // Top collision C.sprites.bird.vy = .1; C.sprites.bird.y += C.sprites.bird.vy; } else if (newY > C.renderer.height - 120) { // Bottom collision C.sprites.bird.vy = 0; } else { // Casual flight C.sprites.bird.y = newY; C.sprites.bird.vy -= 0.07; }
On gère les collisions avec les bord supérieur et inférieur.
Pour le vol de l’oiseau on gère un clic (ou toucher) sur le fond :
onFlight: function () { C.sprites.bird.vy += 2.4; C.sprites.bird.gotoAndPlay(0); }
Déplacement des avions
Les avions se déplacent de plus en plus vite avec les niveaux :
C.sprites.planes.forEach(function (e) { if (e.state) { e.x -= C.speed + C.level * 2; if (e.x < -356) { e.state = false; e.x = C.renderer.width; e.y = C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222); } } });
D’autre part on détecte la sortie du canvas, on les repositionne alors de façon aléatoire à droite du canvas en prévision de leur prochain départ.
Les avions on deux états (state
) :
-
0 : en attente de départ,
-
1 : en vol.
Déplacement des ballons
Le principe est le même que pour les avions :
C.sprites.balloons.forEach(function (e) { if (e.state) { e.x -= (C.speed + C.level * 2) / 2; if (e.rotation < 0) { e.rotation += .004; } if (e.state === 2) { e.y += 5; } if (e.x < -50 || e.y > C.stageHeight + 80) { e.state = false; e.rotation = 0; e.gotoAndStop(0); e.position.set(C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 80, C.renderer.height - 75)); } } });
Mais les ballons peuvent avoir 3 états (state
) :
-
0 : en attente de départ,
-
1 : en vol,
-
2 : en chute.
Gestion des avions
Il faut gérer le départ des avions :
// Check less than 2 planes in right section var n = 0; for (var i = 0; i < C.nbrPlanes; ++i) { if (C.sprites.planes[i].state && C.sprites.planes[i].x > C.renderer.width - 400) { ++n; } } // Manage planes if (n < 2) { C.sprites.planes.forEach(function (e) { if (!e.state) { if (C.utils.getRandomInt(0, 200 - C.level * 10) === 50) { // Check for free place while (!C.collisions.freeOfPlane(e)) { e.y += 1; // Check stage limit if (e.y > C.renderer.height - 222) { e.y = C.pannelHeight; } } e.state = true; } } }); }
Avant de faire partir un avion on vérifie qu’il n’y en a pas déjà deux dans la partie droite du canvas pour éviter de coincer l’oiseau. Si ce n’est pas la cas alors on lâche les avions de façon aléatoire en augmentant la fréquence avec les niveaux. D’autre part on évite d’empiler les avions avec une détection de collision avec un avion déjà en vol.
Gestion des ballons
La gestion des ballons est plus simple parce qu’on ne s’inquiète pas de les empiler :
// Manage balloons C.sprites.balloons.forEach(function (e) { if (!e.state) { if (C.utils.getRandomInt(0, 200 - C.level * 10) === 50) { if (C.utils.getRandomInt(0, 10) === 5) { e.tint = 0xff0000; e.life = true; } else { e.tint = 0xffff00; e.life = false; } e.state = 1; } } });
Par contre de façon aléatoire on les teinte en rouge.
Collision avec les avions
Lorsque l’oiseau entre en collision avec un avion on a deux cas :
-
il a des vies : l’avion explose (on gagne 10 points),
-
il n’a pas de vie : il explose et le jeu est fini.
// Manage planes collisions C.sprites.planes.forEach(function (e) { if (e.state) { if (C.collisions.birdPlane(e)) { // Check lifes if (C.lifes) { C.sounds.crashPlane.play(); C.particles.planeParticles.updateOwnerPos(e.x + 180, e.y + 110); C.particles.planeParticles.emit = true; e.state = false; e.position.set(C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222)); --C.lifes; C.score += 10; C.utils.refreshText(); } else { // Bird crash and end game C.sounds.crashBird.play(); C.particles.birdParticles.updateOwnerPos(C.sprites.bird.x + 68, C.sprites.bird.y + 60); C.particles.birdParticles.emit = true; C.sprites.bird.visible = false; if (C.score > localStorage.getItem('highScore')) { localStorage.setItem('highScore', C.score); } C.state = C.end.on; } } } });
Si le jeu se termine on vérifie si le score obtenue est meilleur que celui mémorisé et, si c’est le cas, on remplace cette valeur par la nouvelle.
Collision avec les ballons
On a aussi deux cas :
-
ballon jaune : on ajoute un point, le ballon explose et commence sa chute,
-
ballon rouge : on gagne une vie, le ballon explose et commence sa chute.
// Manage balloons collisions C.sprites.balloons.forEach(function (e) { if (e.state === 1) { if (C.collisions.birdBalloonCircle(e)) { // Check life balloon if (e.life) { C.sounds.life.play(); ++C.lifes; } else { C.sounds.balloon.play(); ++C.balloonsDone; if (C.balloonsDone === C.maxBalloons) { C.balloonsDone = 0; ++C.level; } } ++C.score; e.state = 2; e.play(); C.utils.refreshText(); } else if (C.collisions.birdBalloonPolygon(e)) { e.rotation = -0.5; } } });
D’autre part on vérifie si on a crevé le nombre de ballons nécessaire (maxBalloons
) pour changer de niveau.
Le fichier end.js
On a dans ce fichier la gestion de la fin du jeu avec la possibilité de rejouer :
/* global C, SAT */ C.end = { // Properties rotationStep: .01, on: function () { if (C.sprites.gameOver.y > C.renderer.height / 3) { C.sprites.gameOver.y -= 3; C.sprites.gameOver.rotation += C.end.rotationStep; if (C.sprites.gameOver.rotation > .15) { C.end.rotationStep = -.01; } else if (C.sprites.gameOver.rotation < -.15) { C.end.rotationStep = .01; } } else { C.sprites.gameOver.rotation = 0; if (C.sprites.playAgain.y > C.renderer.height * 2 / 3) { C.sprites.playAgain.y -= 4; } } }, onPlayAgain: function() { // Reset variables C.state = C.play.on; C.sprites.gameOver.y = C.renderer.height + 70; C.sprites.playAgain.y = C.renderer.height + 70; C.level = 1; // Set planes C.sprites.planes.forEach(function (e) { e.state = false; e.position.set(C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222)); }); // Set balloons C.sprites.balloons.forEach(function (e) { e.state = false; e.position.set(C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 75, C.renderer.height - 75)); }); C.balloonsDone = 0; // Set bird C.sprites.bird.visible = true; C.sprites.bird.position.set(C.renderer.width / 4, C.renderer.height / 2); // Refresh text C.utils.refreshText(); } };
La fin du jeu intervient lorsque l’oiseau explose suite à une collision avec un avion alors qu’il n’a plus de vie en réserve. On a alors un message de fin de jeu et une proposition de rejouer :
Le « GAME OVER » arrive du bas de l’écran et oscille, il est suivi par le « PLAY AGAIN » qui monte normalement :
rotationStep: .01, on: function () { if (C.sprites.gameOver.y > C.renderer.height / 3) { C.sprites.gameOver.y -= 3; C.sprites.gameOver.rotation += C.end.rotationStep; if (C.sprites.gameOver.rotation > .15) { C.end.rotationStep = -.01; } else if (C.sprites.gameOver.rotation < -.15) { C.end.rotationStep = .01; } } else { C.sprites.gameOver.rotation = 0; if (C.sprites.playAgain.y > C.renderer.height * 2 / 3) { C.sprites.playAgain.y -= 4; } } },
Si on clique (ou touche) sur « PLAY AGAIN » on fait un reset complet pour relancer le jeu :
onPlayAgain: function() { // Reset variables C.state = C.play.on; C.sprites.gameOver.y = C.renderer.height + 70; C.sprites.playAgain.y = C.renderer.height + 70; C.level = 1; // Set planes C.sprites.planes.forEach(function (e) { e.state = false; e.position.set(C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222)); }); // Set balloons C.sprites.balloons.forEach(function (e) { e.state = false; e.position.set(C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 75, C.renderer.height - 75)); }); C.balloonsDone = 0; // Set bird C.sprites.bird.visible = true; C.sprites.bird.position.set(C.renderer.width / 4, C.renderer.height / 2); // Refresh text C.utils.refreshText(); }
Et c’est reparti pour un tour !
Minification
Mantenant que le jeu est terminé il est judicieux de rassembler et minifier tous les fichiers JavaScript. Il existe des services en ligne pour le faire, par exemple ici. On copie et on colle le code et on clique sur « Compress Javascript » pour compresser le tout :
On obtient alors un seul fichier JavaScript comprenant l’ensemble de code. On a donc beaucoup moins de fichiers à charger :
Et le fichier index.html est très allégé :
<!DOCTYPE HTML> <html> <title>Crazy Bird</title> <meta charset="UTF-8"> <style> body { margin: 0; overflow: hidden; background-color: black; } </style> <body> <script src="js/crazybird.js"></script> </body> </html>
C’est la version du jeu qui se trouve dans le dossier www du dépôt.
En résumé
Dans ce chapitre on a :
-
mis en place la logique du jeu,
-
mis en place la gestion de la fin du jeu,
-
créé une version alléger du jeu pour le mettre en ligne.
Le jeu est à présent terminé et j’espère que ce cours vous donnera envie d’en créer un !