Nous allons voir dans ce chapitre le chargement des ressources et la création des sprites.
Les références
On va avoir les nouveaux fichiers load.js, utils.js et settings.js, on va donc les 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>
On va aussi avoir besoin de variables globales pour les sprites dans game.js où on va se retrouver avec toutes ces propriétés :
// Properties state: undefined, stage: undefined, renderer: undefined, pannelHeight: 40, sounds : { balloon: undefined, crash: undefined, crashPlane: undefined, life: undefined }, sprites: { loading : undefined, sky: undefined, land: undefined, title: undefined, play: undefined, help: undefined, bird: undefined, pannel: undefined, planes: [], balloons: [], gameOver: undefined, playAgain: undefined }, helpPannel: undefined, helpMessage: undefined, highScoreText: undefined, messageText: undefined, nbrPlanes: 3, nbrBalloons: 2, elapsed: Date.now(),
Le fichier utils.js
C’est un fichier de routines utilitaires :
/* global C */ C.utils = { // Random int helper getRandomInt: function (min, max) { return Math.floor(Math.random() * (max - min)) + min; }, // Text refresh refreshText: function () { C.messageText.text = "Level: " + C.level + " Balloons: " + C.balloonsDone + " Lifes: " + C.lifes + " Score: " + C.score; } };
On a deux fonctions :
-
getRandomInt : donne un entier aléatoire entre deux valeurs,
-
refreshText : on utilisera cette fonction pour rafraîchir le texte du panneau d’information.
Le fichier load.js
Voici le code complet de ce fichier :
/* global PIXI, C */ C.load = { // Initialisations init: function () { // Music and sounds new Howl({ urls: ['assets/background.mp3'], // I Can See DNA in the Sky by Skill_Borrower (c) 2015 Licensed under a Creative Commons Attribution Noncommercial (3.0) license. http://ccmixter.org/files/Skill_Borrower/49504 autoplay: true, loop: true, volume: 0.3 }); C.sounds.balloon = new Howl({ urls: ['assets/balloon.mp3'], volume: 0.6 }); C.sounds.crashBird = new Howl({ urls: ['assets/crash.mp3'], volume: 0.6 }); C.sounds.crashPlane = new Howl({ urls: ['assets/crashplane.mp3'], volume: 0.6 }); C.sounds.life = new Howl({ urls: ['assets/life.mp3'], volume: 0.8 }); // Init local storage if (localStorage.getItem('highScore') === null) { localStorage.setItem('highScore', 0); } // Loader var loader = PIXI.loader .add('sky', 'assets/sky.jpg') .add('land', 'assets/land.png') .add('assets/sprites.json') .once('complete', function () { C.load.setup(loader); C.settings.init(); }) .load(); }, setup: function (loader) { // Set particles C.particles.setParticles(); // Sky C.sprites.sky = new PIXI.extras.TilingSprite(loader.resources.sky.texture, C.renderer.width, C.renderer.height); //C.sprites.sky.mousedown = C.play.onFlight; //C.sprites.sky.tap = C.play.onFlight; this.setSprite(C.sprites.sky, 0, 0, 0, 0, false, true, false); // Land C.sprites.land = new PIXI.extras.TilingSprite(loader.resources.land.texture, C.renderer.width, C.renderer.height); //C.sprites.land.mousedown = C.play.onFlight; //C.sprites.land.tap = C.play.onFlight; this.setSprite(C.sprites.land, 0, 0, 0, 0, false, true, false); // Title C.sprites.title = new PIXI.Sprite(PIXI.Texture.fromFrame('title.png')); this.setSprite(C.sprites.title, C.renderer.width / 2, C.renderer.height / 3, 0.5, 0, false, false, false); // Play C.sprites.play = new PIXI.Sprite(PIXI.Texture.fromFrame('play.png')); //C.sprites.play.mousedown = C.menu.onPlay; //C.sprites.play.touchstart = C.menu.onPlay; this.setSprite(C.sprites.play, C.renderer.width / 2, C.renderer.height * 2 / 3, 0.5, 0, false, true, true); // Planes var planeTexture = PIXI.Texture.fromFrame('plane.png'); for (var i = 0; i < C.nbrPlanes; ++i) { var id = new PIXI.Sprite(planeTexture); id.state = false; C.sprites.planes.push(id); this.setSprite(id, C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222), 0, 0, true, false, false); } // Balloons frames = []; frames.push(PIXI.Texture.fromFrame('balloon1.png')); frames.push(PIXI.Texture.fromFrame('balloon2.png')); frames.push(PIXI.Texture.fromFrame('balloon3.png')); frames.push(PIXI.Texture.fromFrame('balloon4.png')); for (var i = 0; i < C.nbrBalloons; ++i) { var id = new PIXI.extras.MovieClip(frames); id.state = false; id.animationSpeed = 0.25; id.loop = false; C.sprites.balloons.push(id); this.setSprite(id, C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 80, C.renderer.height - 75), 0.5, 0, true, false, false); } // Animated bird var frames = []; frames.push(PIXI.Texture.fromFrame('bird2.png')); frames.push(PIXI.Texture.fromFrame('bird1.png')); C.sprites.bird = new PIXI.extras.MovieClip(frames); C.sprites.bird.position.set(C.renderer.width / 4, C.renderer.height / 2); C.sprites.bird.animationSpeed = 0.15; C.sprites.bird.vy = .1; C.sprites.bird.loop = false; C.sprites.bird.gotoAndStop(2); C.sprites.bird.play(); C.sprites.bird.visible = false; C.stage.addChild(C.sprites.bird); // Help C.sprites.help = new PIXI.Sprite(PIXI.Texture.fromFrame('help.png')); //C.sprites.help.mousedown = C.menu.onHelp; //C.sprites.help.touchstart = C.menu.onHelp; this.setSprite(C.sprites.help, C.renderer.width / 6, C.renderer.height * 3 / 4, 0.5, -0.6, false, true, true); C.helpPannel = new PIXI.Graphics(); C.helpPannel.beginFill(0x334477, 1); C.helpPannel.drawRoundedRect(C.renderer.width / 10, C.renderer.height / 10, C.renderer.width - C.renderer.width / 5, C.renderer.height - C.renderer.height / 3, 30); C.helpPannel.visible = false; C.helpPannel.interactive = true; C.helpPannel.buttonMode = true; //C.helpPannel.mousedown = C.menu.onHideHelp; //C.helpPannel.touchstart = C.menu.onHideHelp; C.stage.addChild(C.helpPannel); // Help text C.helpMessage = new PIXI.Text( "- Click on \"Play\" to start.\n- Click on screen to make your bird flight.\n- You must burst the ballons and avoid the planes.\n- You go up of level when you have bursted 8 yellow balloons.\n- You gain a life if you burst a red balloon.\n- When you crash in a plane you lose a life if you have one or you die and the game is finished.", { font: '44px Snippet', fill: 'lightgrey', stroke: '#ffffff', lineHeight: 56, strokeThickness: 3, wordWrap: true, wordWrapWidth: C.renderer.width - C.renderer.height / 2.4 } ); C.helpMessage.x = C.renderer.width / 8; C.helpMessage.y = C.renderer.height / 8; C.helpMessage.visible = false; C.stage.addChild(C.helpMessage); // Hight score text C.highScoreText = new PIXI.Text( "High score: " + localStorage.getItem('highScore'), { font: 'bold 42px Snippet', fill: 'lightgrey', stroke: '#FFFFFF', strokeThickness: 4 } ); C.highScoreText.x = 20; C.highScoreText.y = 20; C.highScoreText.visible = false; C.stage.addChild(C.highScoreText); // Pannel C.sprites.pannel = new PIXI.Graphics(); C.sprites.pannel.lineStyle(C.pannelHeight, 0x334477, 1); C.sprites.pannel.moveTo(0, 0); C.sprites.pannel.y = C.pannelHeight / 2; C.sprites.pannel.lineTo(C.renderer.width, 0); C.sprites.pannel.visible = false; C.stage.addChild(C.sprites.pannel); // Message text C.messageText = new PIXI.Text( "Level: " + C.level + " Balloons: " + C.balloonsDone + " Lifes: " + C.lifes + " Score: " + C.score, { font: '32px Snippet', fill: 'lightgrey', stroke: '#FFFFFF', strokeThickness: 4 } ); C.messageText.x = 10; C.messageText.visible = false; C.stage.addChild(C.messageText); // Game Over C.sprites.gameOver = new PIXI.Sprite(PIXI.Texture.fromFrame('gameover.png')); this.setSprite(C.sprites.gameOver, C.renderer.width / 2, C.renderer.height + 70, 0.5, 0, true, false, false); // Play Again C.sprites.playAgain = new PIXI.Sprite(PIXI.Texture.fromFrame('playagain.png')); //C.sprites.playAgain.mousedown = C.end.onPlayAgain; //C.sprites.playAgain.touchstart = C.end.onPlayAgain; this.setSprite(C.sprites.playAgain, C.renderer.width / 2, C.renderer.height + 70, 0.5, 0, true, true, true); }, setSprite: function (sprite, x, y, anchor, rotation, visible, interactive, buttonMode) { sprite.x = x; sprite.y = y; sprite.anchor.set(anchor); sprite.rotation = rotation; sprite.visible = visible; sprite.interactive = interactive; sprite.buttonMode = buttonMode; C.stage.addChild(sprite); } };
J’ai juste commenté les gestionnaires d’événements pour éviter de déclencher des erreurs puisque on n’a pas encore le code correspondant.
La fonction init
C’est la fonction qui est appelée en premier.
Les sons
On commence par charger tous les sons :
// Music and sounds new Howl({ urls: ['assets/background.mp3'], // I Can See DNA in the Sky by Skill_Borrower (c) 2015 Licensed under a Creative Commons Attribution Noncommercial (3.0) license. http://ccmixter.org/files/Skill_Borrower/49504 autoplay: true, loop: true, volume: 0.3 }); C.sounds.balloon = new Howl({ urls: ['assets/balloon.mp3'], volume: 0.6 }); C.sounds.crashBird = new Howl({ urls: ['assets/crash.mp3'], volume: 0.6 }); C.sounds.crashPlane = new Howl({ urls: ['assets/crashplane.mp3'], volume: 0.6 }); C.sounds.life = new Howl({ urls: ['assets/life.mp3'], volume: 0.8 });
Ils seront ainsi disponibles et on pourra les faire jouer dès qu’on en aura besoin. On lance immédiatement la musique de fond lorsqu’elle sera chargée (autoplay: true
).
Stockage de données locales
HTML5 permet de mémoriser des information en local, on va s’en servir pour conserver le meilleur score, on initialise la donnée highScore
:
// Init local storage if (localStorage.getItem('highScore') === null) { localStorage.setItem('highScore', 0); }
Le chargement des textures
On charge les textures qu’on a placées dans le dossier assets :
// Loader var loader = PIXI.loader .add('sky', 'assets/sky.jpg') .add('land', 'assets/land.png') .add('assets/sprites.json') .once('complete', function () { C.load.setup(loader); C.settings.init(); }) .load();
Lorsque c’est chargé on voit qu’on appelle la fonction setup.
La fonction setup
Dans cette fonction on va initialiser les particules et tous les sprites.
Les particules
Pour les particules c’est tout simple, on appelle la fonction qu’on a créée au chapitre précédent :
// Set particles C.particles.setParticles();
Le ciel
Le ciel (sky) est créé ici :
// Sky C.sprites.sky = new PIXI.extras.TilingSprite(loader.resources.sky.texture, C.renderer.width, C.renderer.height); //C.sprites.sky.mousedown = C.play.onFlight; //C.sprites.sky.tap = C.play.onFlight; this.setSprite(C.sprites.sky, 0, 0, 0, 0, false, true, false);
On le fait logiquement occuper tout l’espace. Remarquez l’utilisation de la fonction setSprite
qui permet d’alléger le codage.
L’ordre de création des sprites est important parce qu’ils se superposent sur le canvas, c’est pour cette raison qu’on commence par le ciel qui doit être en arrière de tous les autres. Pour le moment on ne le rend pas visible.
Les collines
Pour les collines (land) c’est le même code que pour le ciel :
// Land C.sprites.land = new PIXI.extras.TilingSprite(loader.resources.land.texture, C.renderer.width, C.renderer.height); //C.sprites.land.mousedown = C.play.onFlight; //C.sprites.land.tap = C.play.onFlight; this.setSprite(C.sprites.land, 0, 0, 0, 0, false, true, false);
Le titre du jeu
Le titre du jeu (title) est ensuite créé :
// Title C.sprites.title = new PIXI.Sprite(PIXI.Texture.fromFrame('title.png')); this.setSprite(C.sprites.title, C.renderer.width / 2, C.renderer.height / 3, 0.5, 0, false, false, false);
Remarquez qu’on le positionne horizontalement au centre et au tiers supérieur, le fait de mettre le point de référence au milieu (0.5) permet facilement le centrage.
Le bouton de jeu (play)
On a le même genre de code que pour le titre :
// Play C.sprites.play = new PIXI.Sprite(PIXI.Texture.fromFrame('play.png')); //C.sprites.play.mousedown = C.menu.onPlay; //C.sprites.play.touchstart = C.menu.onPlay; this.setSprite(C.sprites.play, C.renderer.width / 2, C.renderer.height * 2 / 3, 0.5, 0, false, true, true);
La différence c’est qu’on le rend interactif.
Les avions
Le nombre d’avions est donné par la propriété nbrPlanes
du jeu :
nbrPlanes: 3,
Dans le setup on fait une boucle pour créer les avions :
// Planes var planeTexture = PIXI.Texture.fromFrame('plane.png'); for (var i = 0; i < C.nbrPlanes; ++i) { var id = new PIXI.Sprite(planeTexture); id.state = false; C.sprites.planes.push(id); this.setSprite(id, C.renderer.width, C.utils.getRandomInt(C.pannelHeight, C.renderer.height - 222), 0, 0, true, false, false); }
On laisse les avions visibles mais on les positionne à droite en dehors du canvas, prêt à surgir :
Les ballons (balloons)
Le nombre de ballons est donné par la propriété nbrBalloons
du jeu :
nbrBalloons: 2,
On utilise une boucle pour les créer :
// Balloons frames = []; frames.push(PIXI.Texture.fromFrame('balloon1.png')); frames.push(PIXI.Texture.fromFrame('balloon2.png')); frames.push(PIXI.Texture.fromFrame('balloon3.png')); frames.push(PIXI.Texture.fromFrame('balloon4.png')); for (var i = 0; i < C.nbrBalloons; ++i) { var id = new PIXI.extras.MovieClip(frames); id.state = false; id.animationSpeed = 0.25; id.loop = false; C.sprites.balloons.push(id); this.setSprite(id, C.renderer.width + 50, C.utils.getRandomInt(C.pannelHeight + 80, C.renderer.height - 75), 0.5, 0, true, false, false); }
Comme les ballons peuvent être animés (quand on les crève et qu’ils se dégonflent) on crée des MovieClips. Comme pour les avions on les place à droite du canvas :
L’oiseau (bird)
L’oiseau peut lui aussi être animé donc on crée un MovieClip :
// Animated bird var frames = []; frames.push(PIXI.Texture.fromFrame('bird2.png')); frames.push(PIXI.Texture.fromFrame('bird1.png')); C.sprites.bird = new PIXI.extras.MovieClip(frames); C.sprites.bird.position.set(C.renderer.width / 4, C.renderer.height / 2); C.sprites.bird.animationSpeed = 0.15; C.sprites.bird.vy = .1; C.sprites.bird.loop = false; C.sprites.bird.gotoAndStop(2); C.sprites.bird.play(); C.sprites.bird.visible = false; C.stage.addChild(C.sprites.bird);
On le positionne dans le quart gauche à mi hauteur et on le rend invisible. La méthode gotoAndStop
permet de faire apparaître la texture 2 et d’arrêter l’animation.
L’aide (help)
On propose un écran d’aide avec un bouton, un panneau et un texte :
// Help C.sprites.help = new PIXI.Sprite(PIXI.Texture.fromFrame('help.png')); //C.sprites.help.mousedown = C.menu.onHelp; //C.sprites.help.touchstart = C.menu.onHelp; this.setSprite(C.sprites.help, C.renderer.width / 6, C.renderer.height * 3 / 4, 0.5, -0.6, false, true, true); C.helpPannel = new PIXI.Graphics(); C.helpPannel.beginFill(0x334477, 1); C.helpPannel.drawRoundedRect(C.renderer.width / 10, C.renderer.height / 10, C.renderer.width - C.renderer.width / 5, C.renderer.height - C.renderer.height / 3, 30); C.helpPannel.visible = false; C.helpPannel.interactive = true; C.helpPannel.buttonMode = true; //C.helpPannel.mousedown = C.menu.onHideHelp; //C.helpPannel.touchstart = C.menu.onHideHelp; C.stage.addChild(C.helpPannel); // Help text C.helpMessage = new PIXI.Text( "- Click on \"Play\" to start.\n- Click on screen to make your bird flight.\n- You must burst the ballons and avoid the planes.\n- You go up of level when you have bursted 8 yellow balloons.\n- You gain a life if you burst a red balloon.\n- When you crash in a plane you lose a life if you have one or you die and the game is finished.", { font: '44px Snippet', fill: 'lightgrey', stroke: '#ffffff', lineHeight: 56, strokeThickness: 3, wordWrap: true, wordWrapWidth: C.renderer.width - C.renderer.height / 2.4 } ); C.helpMessage.x = C.renderer.width / 8; C.helpMessage.y = C.renderer.height / 8; C.helpMessage.visible = false; C.stage.addChild(C.helpMessage);
Le bouton aura cet aspect :
Le panneau aura cet aspect :
Le meilleur score
Pour l’affichage du meilleur score on crée un texte :
// Hight score text C.highScoreText = new PIXI.Text( "High score: " + localStorage.getItem('highScore'), { font: 'bold 42px Snippet', fill: 'lightgrey', stroke: '#FFFFFF', strokeThickness: 4 } ); C.highScoreText.x = 20; C.highScoreText.y = 20; C.highScoreText.visible = false; C.stage.addChild(C.highScoreText);
On utilise la police Google qu’on a chargée avec cet aspect :
Le panneau des messages pendant le jeu (pannel)
Un panneau informe le joueur pendant le jeu, il est créé ici :
// Pannel C.sprites.pannel = new PIXI.Graphics(); C.sprites.pannel.lineStyle(C.pannelHeight, 0x334477, 1); C.sprites.pannel.moveTo(0, 0); C.sprites.pannel.y = C.pannelHeight / 2; C.sprites.pannel.lineTo(C.renderer.width, 0); C.sprites.pannel.visible = false; C.stage.addChild(C.sprites.pannel); // Message text C.messageText = new PIXI.Text( "Level: " + C.level + " Balloons: " + C.balloonsDone + " Lifes: " + C.lifes + " Score: " + C.score, { font: '32px Snippet', fill: 'lightgrey', stroke: '#FFFFFF', strokeThickness: 4 } ); C.messageText.x = 10; C.messageText.visible = false; C.stage.addChild(C.messageText);
On crée une simple ligne épaisse pour avoir une zone bleue foncée et on crée un texte :
Fin du jeu (‘Game Over » et « Play Again »)
Pour la fin du jeu on a deux sprites :
// Game Over C.sprites.gameOver = new PIXI.Sprite(PIXI.Texture.fromFrame('gameover.png')); this.setSprite(C.sprites.gameOver, C.renderer.width / 2, C.renderer.height + 70, 0.5, 0, true, false, false); // Play Again C.sprites.playAgain = new PIXI.Sprite(PIXI.Texture.fromFrame('playagain.png')); //C.sprites.playAgain.mousedown = C.end.onPlayAgain; //C.sprites.playAgain.touchstart = C.end.onPlayAgain; this.setSprite(C.sprites.playAgain, C.renderer.width / 2, C.renderer.height + 70, 0.5, 0, true, true, true);
On les positionne sous le canvas pour les faire monter lentement en fin de jeu.
Maintenant nos ressources sont devenues disponibles !
Les fichier settings.js
Voici le code complet de ce fichier :
/* global PIXI, C, Element */ C.settings = { init: function () { if (window.innerWidth < window.innerHeight) { C.sprites.loading.text = 'Set landscape please...'; C.state = C.settings.landscape; } else { C.state = undefined; C.settings.fullScreen(); } }, // Check landscape landscape: function () { if (window.innerWidth > window.innerHeight) { C.state = undefined; C.settings.fullScreen(); } }, // Set full screen fullScreen: function () { C.sprites.loading.text = 'Touch me for full screen'; C.sprites.loading.interactive = true; C.sprites.loading.buttonMode = true; C.sprites.loading.mousedown = this.setFullScreen; C.sprites.loading.touchstart = this.setFullScreen; }, setFullScreen: function () { if (document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); } else if (document.documentElement.mozRequestFullScreen) { document.documentElement.mozRequestFullScreen(); } else if (document.documentElement.webkitRequestFullscreen) { document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); } C.stage.removeChild(C.sprites.loading); C.sprites.title.visible = true; C.sprites.play.visible = true; C.sprites.help.visible = true; C.highScoreText.visible = true; C.sprites.sky.visible = true; C.sprites.land.visible = true; C.state = C.menu.show; } };
On a vu ci-dessus dans le loader qu’on appelle la fonction init du settings après le setup :
.once('complete', function () { C.load.setup(loader); C.settings.init(); })
Ici on va faire deux chose :
-
vérifier qu’on est en mode paysage et si ce n’est pas le cas inviter le joueur à y passer,
-
demander à passer en mode plein écran.
Mode paysage
On commence donc par vérifier ce mode paysage :
init: function () { if (window.innerWidth < window.innerHeight) { C.sprites.loading.text = 'Set landscape please...'; C.state = C.settings.landscape; } else { C.state = undefined; C.settings.fullScreen(); } },
Si l’affichage est plus haut que large on affiche le message :
On change l’état (state) :
C.state = C.settings.landscape;
On active donc régulièrement cette fonction :
// Check landscape landscape: function () { if (window.innerWidth > window.innerHeight) { C.state = undefined; C.settings.fullScreen(); } },
Dès qu’on est en paysage on appelle la fonction fullScreen
. On y passe directement si on est déjà en mode paysage.
Mode plein écran
Voilà la fonction pour le plein écran :
// Set full screen fullScreen: function () { C.sprites.loading.text = 'Touch me for full screen'; C.sprites.loading.interactive = true; C.sprites.loading.buttonMode = true; C.sprites.loading.mousedown = this.setFullScreen; C.sprites.loading.touchstart = this.setFullScreen; },
On change le message :
Si on a une action alors on active la fonction setFullScreen
:
setFullScreen: function () { if (document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); } else if (document.documentElement.mozRequestFullScreen) { document.documentElement.mozRequestFullScreen(); } else if (document.documentElement.webkitRequestFullscreen) { document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); } C.stage.removeChild(C.sprites.loading); C.sprites.title.visible = true; C.sprites.play.visible = true; C.sprites.help.visible = true; C.highScoreText.visible = true; C.sprites.sky.visible = true; C.sprites.land.visible = true; C.state = C.menu.show; }
On passe le navigateur en plein écran, on supprime le sprite qui nous a servi pour les message et on rend visible les sprites pour le menu :
Pour le moment ce menu ne fonctionne pas, c’est ce que nous verrons dans un prochain chapitre.
Vous devriez maintenant avoir ces dossiers et fichiers :
En résumé
Dans ce chapitre on a :
-
chargé toute les ressources (sons et textures).
-
créé tous les sprites.
-
réglé le problème du mode paysage.
-
passé en mode plein écran.
-
affiché le menu.