Fil d'Ariane
Mis à jour le jeudi 31 août 2017
  • 20 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Utilisez les fonctions existantes pour créer de nouvelles armes

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

Maintenant que vous avez vu comment mettre en place des dégâts, il est temps d'aller plus loin ! Un FPS ne se compose pas d'une arme unique : il va nous falloir élargir notre champ de possibilités ! Pour ça, nous allons rendre notre code modulaire au maximum. 

Vous allez donc devoir : 

  • Créer un fichier de contrôle des données des armes

  • Dynamiser l'ajout des armes

  • Dynamiser les fonctions associées aux armes

Le défi ? Vous allez créer trois armes parfaitement distinctes par leur fonctionnement : une arme de corps-à-corps, un fusil et notre lance-roquettes actuel. Ce chapitre risque d'être dense, alors accrochez-vous à votre clavier, parce qu'on y va ! ^^

Armory.js, un fichier de contrôle

Notre objectif est de rendre la modification des paramètres simple et l'ajout d'arme facile. Nous allons donc concevoir un fichier qui aura tous les paramètres et qui servira de maître étalon pour créer les armes. C'est maintenant que vous allez avoir le plaisir de créer un fichier qu'on va appeler Armory.js. :soleil:

Créez le fichier JavaScript et liez-le à index.html. Maintenant que c'est fait, il ne nous reste plus qu'à écrire dedans ! Nous allons créer un objet JSON qui déterminera tous les paramètres des armes. Je vous propose ici une mise en forme, à vous de choisir ce que vous souhaitez faire.

Armory = function(game, player) {
    this.weapons=[
        {
            'name':'Crook',
            'model' : {
                // 'meshUrl': '',
                'meshName': 'Crook'
            },
            'type':'closeCombat',
            'setup':{
                // Distance de frappe de l'arme de CaC
                'range': 2,
                'damage' : 20,
                'cadency' : 500,
                'colorMesh' : new BABYLON.Color3((59/255), (195/255), (203/255))
            }
        },
        {
            'name':'Timmy',
            'model' : {
                // 'meshUrl': '',
                'meshName': 'Timmy'
            },
            'type':'ranged',
            'setup':{
                'damage' : 2,
                'cadency' : 50,
                'ammos' : {
                    'type' : 'bullet',
                    'baseAmmos' : 200,
                    'maximum' : 400,
                    'refuel' : 50
                },
                'colorMesh' : new BABYLON.Color3((27/255), (235/255), (37/255))
            }
        },
        {
            'name':'Ezekiel',
            'model' : {
                // 'meshUrl': '',
                'meshName': 'Ezekiel'
            },
            'type':'ranged',
            'setup':{
                'damage' : 30,
                'cadency' : 800,
                'ammos' : {
                    'type' : 'rocket',
                    'baseAmmos' : 15,
                    'refuel' : 10,
                    'maximum' : 40,
                    // 'meshAmmosUrl' : '',
                    'meshAmmosName' : 'Rockets',
                    // Rapidité de déplacement de la roquette
                    'rocketSpeed' : 10,
                    // Taille de la roquette
                    'rocketSize' : 1,
                    // Rayon de l'explosion
                    'explosionRadius' : 40
                },
                // Couleur du mesh par défault
                'colorMesh' : new BABYLON.Color3((209/255), (7/255), (26/255))
            }
        },
        {
            'name':'Armageddon',
            'model' : {
                // 'meshUrl': '',
                'meshName': 'Armageddon'
            },
            'type':'ranged',
            'setup':{
                'damage' : 1000,
                'cadency' : 2000,
                'ammos' : {
                    'type' : 'laser',
                    'spread' : 1,
                    'baseAmmos' : 5,
                    'maximum' : 15,
                    'refuel' : 5
                },
                'colorMesh' : new BABYLON.Color3((133/255), (39/255), (139/255))
            }
        }
    ];
    return 1
};

Le fichier est relativement imposant, mais il permet de bien comprendre tous les paramètres. Chaque objet a plusieurs valeurs détaillant le fonctionnement de l'objet, rendant le tout le plus modulaire possible.

Vous voyez qu'on a actuellement 3 types que nous allons instancier :closeCombatbulletrocket et laser. Cela nous permet de couvrir une grande partie de ce qui est possible d'avoir dans les jeux.

Si vous passez rapidement les paramètres en revue, vous pouvez voir, communs à tous :

  •  damage qui détermine les dégâts infligés par l'arme ;

  •  cadency qui représente le temps avant de pouvoir re-tirer ;

  •  baseAmmosrefuel et maximum qui vont nous être utiles pour déterminer les munitions de l'arme.

Il y a d'autres paramètres détaillés dans le code, je vous laisse les lire pour comprendre comment nous les utiliserons. ^^

Maintenant que  Armory est créé, il va nous falloir l'appeler dans nos fonctions ! Nous allons le définir dans Game.js, juste avant Player et Arena, pour qu'il soit accessible depuis n'importe où. 

// Ajout de l'armurerie
var armory = new Armory(this);
_this.armory = armory;

Avec cette variable qui appelle les paramètres que nous avons définis plus tôt, nous allons pouvoir l'appeler dans  Weapons ! Retournez dans le fichier et appelez à nouveau  Armory, juste après avoir défini Player :

// Import de l'armurerie depuis Game
this.Armory = Player.game.armory;

Tout est enfin prêt pour que nous puissions utiliser ce nouveau fichier fraîchement créé ! Passons à une section qui va être complexe : le re-factoring des fonctions de  Weapons !

Dyamisation de Weapons.js

Maintenant que nous avons un fichier de paramétrage prêt, nous allons commencer par retravailler les fonctions de création d'arme pour intégrer tous les paramètres que nous avons paramétrés dans Armory.

  • Il va falloir commencer par réadapter  Weapons en créant un inventaire et en rendant la fonction newWeapon dynamique.

  • Il va falloir différencier chaque type d'arme dans la fonction launchFire.

  • Il faut dire au système d'utiliser une fonction différente pour chaque type de dégât infligé. Nous avons déjà createRocket, mais nous allons aussi créer shootBullet et hitHand pour le corps-à-corps et createLaser pour créer une arme inspirée du rail gun. :) 

  • Il faudra permettre au joueur de changer d'arme avec la molette de la souris.

  • Il nous faudra enfin réadapter toutes les fonctions annexes pour les adapter à notre nouveau système de gestion des armes.

Sacré liste ! Allez, on se lance tout de suite ! 

newWeapon et la variable inventory

Pour commencer, il va falloir créer une variable qui stockera toutes nos armes et nous permettra de nous balader dans notre inventaire. Allez en haut du fichier  Weapons.js et ajoutez un array vide nommé inventory, juste en dessous de bottomPosition et topPositionY.

Maintenant, il va falloir faire en sorte que les armes que nous allons créer récupèrent les paramètres de  Armory. Pour cela, nous allons changer la façon de créer l'arme que le joueur a dans la main.

// Ajout de l'inventaire
this.inventory = [];

// Appel de newWeapon pour créer l'arme qui a pour nom Ezekiel
var ezekiel = this.newWeapon('Ezekiel')
this.inventory[0] = ezekiel;

Comme vous le voyez dans ce code, nous allons créer les armes selon le nom que nous leur avons donné dans Armory.js. Vous allez voir que  newWeapon change peu dans sa structure mais beaucoup dans la forme.

newWeapon : function(typeWeapon) {
    var newWeapon;
    for (var i = 0; i < this.Armory.weapons.length; i++) {
        if(this.Armory.weapons[i].name === typeWeapon){

            newWeapon = BABYLON.Mesh.CreateBox('rocketLauncher', 0.5, this.Player.game.scene);

            // Nous faisons en sorte d'avoir une arme d'apparence plus longue que large
            newWeapon.scaling = new BABYLON.Vector3(1,0.7,2);

            // On l'associe à la caméra pour qu'il bouge de la même facon
            newWeapon.parent = this.Player.camera;

            // On positionne le mesh APRES l'avoir attaché à la caméra
            newWeapon.position = this.bottomPosition.clone();

            newWeapon.isPickable = false;

            // Ajoutons un material de l'arme pour le rendre plus visible
            var materialWeapon = new BABYLON.StandardMaterial('rocketLauncherMat', this.Player.game.scene);
            materialWeapon.diffuseColor=this.Armory.weapons[i].setup.colorMesh;

            newWeapon.material = materialWeapon;
            
            newWeapon.typeWeapon = i;

            newWeapon.isActive = false;
            break;
        }else if(i === this.Armory.weapons.length -1){
            console.log('UNKNOWN WEAPON');
        }
    };

    return newWeapon
},

Vous connaissez déjà les mécaniques de cette fonction. Il est cependant important de s'attarder quelque peu sur la boucle  for. Elle va chercher à travers toutes les armes présentes dans  Armory l'arme qui possède le même id que l'arme actuellement en main. Si elle n'en trouve pas, un message s'affichera dans la console JavaScript. Comme vous le voyez, pour l'instant il va chercher uniquement la couleur qu'il doit associer à l'arme depuis  Armory, mais nous appellerons des variables bien plus utiles dans la suite du cours. ;) 

J'ai lancé l'application mais aucune arme ne s'affiche à l'écran. :(

C'est normal, ne vous inquiétez pas : le mesh se trouve en position basse, que nous avons affecté avec bottomPosition. Pas de panique donc si vous ne trouvez pas de carré de couleur à côté de votre arme ! ^^

Avant de passer à la fonction launchFire, nous devons nous intéresser à quelques variables présentes dans l'objet  Weapons :

// Ajout de l'inventaire
this.inventory = [];

// Créons notre lance-roquettes
var ezekiel = this.newWeapon('Ezekiel')
this.inventory[0] = ezekiel;

// Notre arme actuelle est Ezekiel, qui se trouve en deuxième position
// dans le tableau des armes dans Armory
this.actualWeapon = this.inventory.length -1;

// On dit que notre arme en main est l'arme active
this.inventory[this.actualWeapon].isActive = true;

// On dit que la cadence est celle de l'arme actuelle (grâce à typeWeapon)
this.fireRate = this.Armory.weapons[this.inventory[this.actualWeapon].typeWeapon].setup.cadency;

this._deltaFireRate = this.fireRate;

this.canFire = true;

this.launchBullets = false;

Plusieurs variables gèrent les armes quand le joueur arrive en jeu. Toutes ces variables doivent être dynamiques, sans quoi les armes auront toutes la même cadence de tir (et avouez qu'un lance-roquettes avec une cadence de tir d'un minigun, ce n'est pas forcément très équilibré :p).

launchFire, pierre angulaire du tir avec Weapons

Cette fonction est la clé de voûte de notre système. C'est elle qui va choisir dynamiquement le type de tir que l'arme effectuera, selon ce que Armory lui dictera.

  • Premièrement, nous allons déterminer cinq paramètres que nous allons utiliser pour chacune des armes. Tout d’abord, idWeapon nous servira à récupérer l'id de l'arme que nous avons en main (cet id représente la position de l'arme dans  Armory).

  • Ensuite, renderWidth et renderHeight vont, comme pour createRocket, nous permettre de calculer le centre de l'écran.

  • Et enfin, direction va créer un rayon depuis la position du joueur jusqu'à l'endroit pointé par le centre de la fenêtre du navigateur. Ainsi, nous sommes sûrs de tirer parfaitement droit. 

Voici, sans plus attendre, ces fameuses variables.

// Id de l'arme en main
var idWeapon = this.inventory[this.actualWeapon].typeWeapon;

// Détermine la taille de l'écran
var renderWidth = this.Player.game.engine.getRenderWidth(true);
var renderHeight = this.Player.game.engine.getRenderHeight(true);

// Cast un rayon au centre de l'écran
var direction = this.Player.game.scene.pick(renderWidth/2,renderHeight/2,function (item) {
    if (item.name == "playerBox" || item.name == "weapon" || item.id == "headMainPlayer")
        return false;
    else
        return true;
});

Maintenant que les variables qui nous seront utiles ont été définies, nous allons devoir déterminer le type de tir que le joueur effectue. Une simple condition if fera l'affaire.

var idWeapon = this.inventory[this.actualWeapon].typeWeapon;
var weaponAmmos = this.inventory[this.actualWeapon].ammos;
var renderWidth = this.Player.game.engine.getRenderWidth(true);
var renderHeight = this.Player.game.engine.getRenderHeight(true);
var direction = this.Player.game.scene.pick(renderWidth/2,renderHeight/2,function (item) {
    if (item.name == "playerBox" || item.name == "weapon" || item.id == "hitBoxPlayer")
        return false;
    else
        return true;
});
// Si l'arme est une arme de distance
if(this.Armory.weapons[idWeapon].type === 'ranged'){
    if(this.Armory.weapons[idWeapon].setup.ammos.type === 'rocket'){
        // Nous devons tirer une roquette
    }else if(this.Armory.weapons[idWeapon].setup.ammos.type === 'bullet'){
        // Nous devons tirer des balles simples
    }else{
       // Nous devons tirer au laser
    }
}else{
    // Si ce n'est pas une arme à distance, il faut attaquer au corps-à-corps
}
this.canFire = false; 

On vérifie tout d'abord quel est le type de tir effectué. Si c'est une attaque à distance, on vérifie le type de munitions utilisées. Si ce n'est pas une roquette ou une balle, on considère que c'est un tir de laser. Enfin, si ce n'est pas une attaque à distance, il faudra donc attaquer au corps-à-corps avec la fonction correspondante. ^^ On peut d'ores et déjà remettre ce que nous avions fait pour createRocket(), mais nous allons aussi nous intéresser à dynamiser les valeurs présentes.

Chacune de ces trois fonctions de tir a une dynamique différente : createBullet inflige directement des dégâts à l'ennemi, hitHand possède une portée d'utilisation et createLaser crée un rayon lumineux à partir de deux points. 

var idWeapon = this.inventory[this.actualWeapon].typeWeapon;
var weaponAmmos = this.inventory[this.actualWeapon].ammos;
var renderWidth = this.Player.game.engine.getRenderWidth(true);
var renderHeight = this.Player.game.engine.getRenderHeight(true);
var direction = this.Player.game.scene.pick(renderWidth/2,renderHeight/2,function (item) {
    if (item.name == "playerBox" || item.name == "weapon" || item.id == "hitBoxPlayer")
        return false;
    else
        return true;
});
// Si l'arme est une arme de distance
if(this.Armory.weapons[idWeapon].type === 'ranged'){
    if(this.Armory.weapons[idWeapon].setup.ammos.type === 'rocket'){
        direction = direction.pickedPoint.subtractInPlace(this.Player.camera.playerBox.position);
        direction = direction.normalize();

        this.createRocket(this.Player.camera.playerBox,direction)
    }else if(this.Armory.weapons[idWeapon].setup.ammos.type === 'bullet'){
        
    }else{
        
    }
}else{

}
this.canFire = false; 

createRocket

Nous n'allons pas nous attarder sur cette fonction, nous allons simplement modifier certaines lignes. Commencez par définir idWeapon tout en haut de la fonction ainsi que setupRocket qui va récupérer ce qui est dans  Armory.js.

// Permet de connaitre l'id de l'arme dans Armory.js
var idWeapon = this.inventory[this.actualWeapon].typeWeapon;

// Les paramètres de l'arme
var setupRocket = this.Armory.weapons[idWeapon].setup.ammos;

Nous devons aussi changer  direction. Maintenant que nous récupérons ce paramètre, nous n'avons plus à le calculer :

newRocket.direction = direction;

De nouveau, nous allons préparer la roquette à recevoir des données que nous utiliserons plus tard. Nous définissons quand même une couleur récupérée depuis l'armurerie. Juste avant d'envoyer les roquettes à _rockets, écrivez donc ceci :

newRocket.material.diffuseColor = this.Armory.weapons[idWeapon].setup.colorMesh;
newRocket.paramsRocket = this.Armory.weapons[idWeapon].setup;

Les roquettes sont de nouveau opérationnelles ! Nous allons devoir cependant continuer un peu pour pouvoir nous amuser. ^^ 

shootBullet

Le code de cette fonction de tir est le plus basique des trois. Nous allons récupérer l'endroit où se trouve le centre de l'écran du joueur et vérifier si le mesh rencontré est un joueur. Envoyons tout d'abord les données depuis  launchFire :

// Si l'arme est une arme de distance
if(this.Armory.weapons[idWeapon].type === 'ranged'){
    if(this.Armory.weapons[idWeapon].setup.ammos.type === 'rocket'){
        direction = direction.pickedPoint.subtractInPlace(this.Player.camera.playerBox.position);
        direction = direction.normalize();

        this.createRocket(this.Player.camera.playerBox,direction)
    }else if(this.Armory.weapons[idWeapon].setup.ammos.type === 'bullet'){
        this.shootBullet(direction)
    }else{
        
    }
}else{

}
this.canFire = false; 

C'est tout ? La direction nous suffit ?

Eh oui ! Puisque que nous tirons toujours droit et que nous n'intégrons pas de recul à l'arme, le joueur tirera toujours tout droit. Passons à la fonction shootBullet en elle-même.

shootBullet : function(meshFound) {
    // Permet de connaitre l'id de l'arme dans Armory.js
	var idWeapon = this.inventory[this.actualWeapon].typeWeapon;
		
    var setupWeapon = this.Armory.weapons[idWeapon].setup;
    
    if(meshFound.hit && meshFound.pickedMesh.isPlayer){
        // On a touché un joueur
    }else{
        // L'arme ne touche pas de joueur
        console.log('Not Hit Bullet')
    }
},

Et c'est tout ! Nous vérifions simplement si le rayon que nous avons envoyé est entré en contact avec quelque chose et si ce quelque chose était un joueur. Si c'est le cas, on inflige des dégâts à ce joueur. Comme nous ne gérons pas encore le multijoueur, nous n'allons rien mettre ici pour l'instant.

hitHand

Vous allez voir, il est facile de créer le coup avec l'arme de corps-à-corps. C'est exactement comme shootBullet, mais avec une portée ! On appelle donc ainsi depuislaunchFire :

// Si l'arme est une arme de distance
if(this.Armory.weapons[idWeapon].type === 'ranged'){
    if(this.Armory.weapons[idWeapon].setup.ammos.type === 'rocket'){
        direction = direction.pickedPoint.subtractInPlace(this.Player.camera.playerBox.position);
        direction = direction.normalize();

        this.createRocket(this.Player.camera.playerBox,direction)
    }else if(this.Armory.weapons[idWeapon].setup.ammos.type === 'bullet'){
        this.shootBullet(direction)
    }else{
        
    }
}else{
    // Appel de l'attaque au corps a corps
    this.hitHand(direction)
}
this.canFire = false; 

 Quand à hitHand, vous reconnaîtrez shootBullet. ;) 

hitHand : function(meshFound) {
    // Permet de connaitre l'id de l'arme dans Armory.js
	var idWeapon = this.inventory[this.actualWeapon].typeWeapon;
	
    var setupWeapon = this.Armory.weapons[idWeapon].setup;

    if(meshFound.hit 
    && meshFound.distance < setupWeapon.range*5 
    && meshFound.pickedMesh.isPlayer){
        // On a touché un joueur
    }else{
        // L'arme frappe dans le vide
        console.log('Not Hit CaC')
    }
},

Nous pouvons désormais passer à createLaser(). :) 

createLaser

Cette fonction va être un peu particulière. Dans launchFire, nous allons appeler la fonction de la même façon que shootBullet. Nous allons devoir retracer un nouveau rayon bien particulier en évitant certains objets que nous ne souhaitons pas capter.s

// Si l'arme est une arme de distance
if(this.Armory.weapons[idWeapon].type === 'ranged'){
    if(this.Armory.weapons[idWeapon].setup.ammos.type === 'rocket'){
        direction = direction.pickedPoint.subtractInPlace(this.Player.camera.playerBox.position);
        direction = direction.normalize();

        this.createRocket(this.Player.camera.playerBox,direction)
    }else if(this.Armory.weapons[idWeapon].setup.ammos.type === 'bullet'){
        this.shootBullet(direction)
    }else{
        this.createLaser(direction)
    }
}else{
    this.hitHand(direction)
}
this.canFire = false; 

 Jusque-là, c'était facile pour la création de laser, mais c'est maintenant que ça va se corser ! Nous allons devoir morceler le code ce cette fonction pour bien comprendre son fonctionnement.

createLaser : function(meshFound) {
    // Permet de connaitre l'id de l'arme dans Armory.js
	var idWeapon = this.inventory[this.actualWeapon].typeWeapon;
	
    var setupLaser = this.Armory.weapons[idWeapon].setup.ammos;

    var positionValue = this.inventory[this.actualWeapon].absolutePosition.clone();
}

Comme pour les autres fonctions, on définit différentes valeurs pour accéder plus simplement à l'objet plus tard, celles-ci permettant d'accéder plus simplement aux paramètres du laser et de la scène. 

// Permet de connaitre l'id de l'arme dans Armory.js
var idWeapon = this.inventory[this.actualWeapon].typeWeapon;

var setupLaser = this.Armory.weapons[idWeapon].setup.ammos;

var positionValue = this.inventory[this.actualWeapon].absolutePosition.clone();

if(meshFound.hit){

    var laserPosition = positionValue;
    // On crée une ligne tracée entre le pickedPoint et le canon de l'arme
    let line = BABYLON.Mesh.CreateLines("lines", [
                laserPosition,
                meshFound.pickedPoint
    ], this.Player.game.scene);
    // On donne une couleur aléatoire
    var colorLine = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
    line.color = colorLine;
    
    // On élargit le trait pour le rendre visible
    line.enableEdgesRendering();
    line.isPickable = false;
    line.edgesWidth = 40.0;
    line.edgesColor = new BABYLON.Color4(colorLine.r, colorLine.g, colorLine.b, 1);
    if(meshFound.pickedMesh.isPlayer){
        // On inflige des dégâts au joueur
    }
    this.Player.game._lasers.push(line);
}

On voit plusieurs choses importantes dans ces lignes.

  •  CreateLines nous permet de créer des lignes selon un chemin de point donné. Nous donnons donc deux points, celui du canon de l'arme et celui renvoyé par le rayon créé avec directionPoint.

  •  enableEdgesRendering permet de tracer un trait sur une ligne ou un mesh déjà existant. Nous rendons donc le trait plus gros grâce à cela, et nous lui donnons une couleur au hasard.

  • On push le laser dans un tableau, de la même façon que vous l'avez fait pour les roquettes. Cela permet d'effacer le trait que nous avons créé au bout d'un certain laps de temps.

Pour en finir avec createLaser, vous allez créer la fonction qui va rendre les traits de laser à l'écran. Direction  Game.js !

_lasers dans Game.js

De la même façon qu'avec _explosionRadius ou _rockets, vous allez créer une fonction dans le prototype de  Game qui s'occupera de modifier les lasers stockés dans _lasers.

renderLaser : function(){
    if(this._lasers.length > 0){
        for (var i = 0; i < this._lasers.length; i++) {
            this._lasers[i].edgesWidth -= 0.5;
            if(this._lasers[i].edgesWidth<=0){
                this._lasers[i].dispose();
                this._lasers.splice(i, 1);
            }
        }
    }
},

Ici, nous diminuons l'épaisseur du trait, jusqu'à ce que cette épaisseur soit à 0. À ce moment là, on détruit le trait. Il ne nous reste plus qu'à appeler la fonction dans la boucle de rendu, et c'est prêt ! :) 

// On appelle nos deux fonctions de calcul pour les roquettes
_this.renderRockets();
_this.renderExplosionRadius();

// On calcule la diminution de la taille du laser
_this.renderLaser();

Avec tout cela, nous pouvons enfin tirer avec notre arme ! Mais il nous reste encore quelques petites étapes avant d'avoir réellement terminé et passer au chapitre suivant. Il va nous falloir animer et interpréter le changement d'arme.

Changer d'arme avec la molette de la souris

Allez dans  Player : nous allons devoir détecter quand le joueur va actionner la molette de la souris. En plus de cela, nous devrons détecter le sens de celle-ci. Nous allons aussi faire en sorte de détecter quand deux tours de molette sont trop proches, afin de ne pas les compter. Beaucoup de détections à coder !

// Changement des armes
this.previousWheeling = 0;

canvas.addEventListener("mousewheel", function(evt) {
    // Si la différence entre les deux tours de souris sont minimes
    if(Math.round(evt.timeStamp - _this.previousWheeling)>10){
        if(evt.deltaY<0){
            // Si on scroll vers le haut, on va chercher l'arme suivante
            _this.camera.weapons.nextWeapon(1);
        }else{
            // Si on scroll vers le bas, on va chercher l'arme précédente
            _this.camera.weapons.nextWeapon(-1);
        }
        //On affecte a previousWheeling la valeur actuelle
        _this.previousWheeling = evt.timeStamp;
    }
    
}, false);

L'event est simple et permet d'éviter de capter les doublons et les roulements trop minimes. Maintenant que Player appelle  weapons, on y retourne ! Il va falloir coder la fonction nextWeapon dans le prototype de Weapons ! :) 

Cette fonction est quelque peu complexe : il va falloir déterminer selon l'arme actuelle quelle est l'arme suivante dans  Armory. L’intérêt est que les armes soient ordonnés dans nextWeapon alors que l'inventaire sera désordonné. Pour cela, vous allez vous servir du modulo et d'une boucle  for sans condition de fin.

Pour commencer, on définit la fonction et on lui donne trois variables :

nextWeapon : function(way) {
    // On définit armoryWeapons pour accéder plus facilement à Armory
    var armoryWeapons = this.Armory.weapons;
    
    // On dit que l'arme suivante est logiquement l'arme plus le sens donné
    var nextWeapon = this.inventory[this.actualWeapon].typeWeapon + way;
    
    //on définit actuellement l'arme possible utilisable à 0 pour l'instant
    var nextPossibleWeapon = null;
}

Avec ces trois variables, vous allez désormais pouvoir déterminer quelle est l'arme suivante utilisable dans notre inventaire. On va commencer par voir comment déterminer l'arme qui vient après la nôtre dans notre inventaire.

Pour connaître l'arme suivante, nous allons utiliser le modulo. 

Cette manière de faire permet d'explorer un tableau depuis une position précise, sans jamais en sortir. Nous allons faire autant de tours de boucle qu'il y a d'éléments dans  Armory et vérifier avec le modulo (depuis  nextWeapon) quelle est l'arme suivante que nous allons pouvoir utiliser. 

var armoryWeapons = this.Armory.weapons;
var nextWeapon = this.inventory[this.actualWeapon].typeWeapon + way;
var nextPossibleWeapon = null;
// Si le sens est positif
if(way>0){
    // La boucle commence depuis nextWeapon
    for (var i = nextWeapon; i < nextWeapon + this.Armory.weapons.length; i++) {
        // L'arme qu'on va tester sera un modulo de i et de la longueur de Weapon
        var numberWeapon = i % this.Armory.weapons.length;
        // On compare ce nombre aux armes qu'on a dans l'inventaire
        for (var y = 0; y < this.inventory.length; y++) {
            if(this.inventory[y].typeWeapon === numberWeapon){
                // Si on trouve quelque chose, c'est donc une arme qui vient arès la nôtre
                nextPossibleWeapon = y;
                break;
            }
        }
        // Si on a trouvé une arme correspondante, on n'a plus besoin de la boucle for
        if(nextPossibleWeapon != null){
            break;
        }   
    }
}

Maintenant qu'on a l'arme suivante, il va falloir s'occuper de l'arme précédente. On va faire un système un peu similaire sur le fond, mais pas sur la forme. On va partir de nextWeapon, comme avant, et aller à rebours. Nous n'allons pas donner de condition de fin à notre boucle, il faudra donc être sur qu'on sortira de la boucle dans tout les cas. Quand on atteindra une valeur négative avec i (qui va diminuer à chaque tour de boucle), on repartira de la longueur de armoryWeapons. :)

var armoryWeapons = this.Armory.weapons;
var nextWeapon = this.inventory[this.actualWeapon].typeWeapon + way;
var nextPossibleWeapon = null;
if(way>0){
    for (var i = nextWeapon; i < nextWeapon + this.Armory.weapons.length; i++) {
        var numberWeapon = i % this.Armory.weapons.length;
        for (var y = 0; y < this.inventory.length; y++) {
            if(this.inventory[y].typeWeapon === numberWeapon){
                nextPossibleWeapon = y;
                break;
            }
        }
        if(nextPossibleWeapon != null){
            break;
        }   
    }
}else{
    for (var i = nextWeapon; ; i--) {
        if(i<0){
            i = this.Armory.weapons.length;
        }
        var numberWeapon = i;
        for (var y = 0; y < this.inventory.length; y++) {
            if(this.inventory[y].typeWeapon === numberWeapon){
                nextPossibleWeapon = y;
                break;
            }
        }
        if(nextPossibleWeapon != null){
            break;
        }   
    }
}

Maintenant que nextPossibleWeapon est défini, vous allez changer tous les paramètres des armes pour que tout soit dynamique. Évidemment, on vérifie que l'arme trouvée n'est pas celle qu'on a déjà en main. Ajoutez les lignes suivantes en dessous de ce que nous venons de faire.

if(this.actualWeapon != nextPossibleWeapon){
    // On dit à notre arme actuelle qu'elle n'est plus active
    this.inventory[this.actualWeapon].isActive = false;
    // On change l'arme actuelle avec celle qu'on a trouvé
    this.actualWeapon = nextPossibleWeapon;
    // On dit à notre arme choisie qu'elle est l'arme active
    this.inventory[this.actualWeapon].isActive = true;

    // On actualise la cadence de l'arme
    this.fireRate = this.Armory.weapons[this.inventory[this.actualWeapon].typeWeapon].setup.cadency;
    this._deltaFireRate = this.fireRate;
}

Nos armes sont créées, mises dans un inventaire, avec des types de tir différents... tout est défini, enfin presque ! Il faut maintenant dire aux armes de s'animer pour monter ou descendre si elles sont actives ou non et effacer proprement  inventory à la mort du  Player. ^^ 

Animer le changement d'arme 

Pour animer le changement d'arme, dirigez-vous dans  Game.js et créez une nouvelle fonction : renderWeapons ! :) Rappelez-vous, nous avons déjà défini une variable nommée isActive qui va nous permettre de déterminer quelle est l'arme actuellement prise en main.

Premièrement, vous allez définir la fonction dans le prototype de  Game :

renderWeapons : function(){
    if(this._PlayerData && this._PlayerData.camera.weapons.inventory){
        // On regarde toutes les armes dans inventory
        var inventoryWeapons = this._PlayerData.camera.weapons.inventory;
        
        for (var i = 0; i < inventoryWeapons.length; i++) {
            // Si l'arme est active et n'est pas à la position haute (topPositionY)
            if(inventoryWeapons[i].isActive && inventoryWeapons[i].position.y < this._PlayerData.camera.weapons.topPositionY){
                inventoryWeapons[i].position.y += 0.1;
            }else if(!inventoryWeapons[i].isActive && inventoryWeapons[i].position.y != this._PlayerData.camera.weapons.bottomPosition.y){
                // Sinon, si l'arme est inactive et pas encore à la position basse
                inventoryWeapons[i].position.y -= 0.1;
            }
        }
    }
},

Ainsi, à chaque tour de boucle on diminue ou augmente la position de l'arme pour qu'elle atteigne la valeur indiquée dans  Weapons. ^^

Maintenant on appelle  renderWeapons dans runRenderLoop :

// On calcule les animations des armes
_this.renderWeapons();

Et voilà, l'arme montera ou descendra selon si elle a isActive à true ou à false.

Supprimer proprement inventory dans playerDead

Pour nettoyer proprement  inventory, retournez dans playerDead(). Jusqu'à présent, on supprimait l'objet rocketLauncher. Il vous suffit de remplacer ça par une boucle qui explore inventory. Remplacez donc le dispose par :

var inventoryWeapons = this.camera.weapons.inventory;
for (var i = 0; i < inventoryWeapons.length; i++) {
    inventoryWeapons[i].dispose();
}
inventoryWeapons = [];

Et voilà ! On a désormais des armes dynamiques ! Si vous ajoutez une arme dans  Armory, vous pourrez l'appeler facilement !

Nous allons faire une petite pause sur le code pour détailler un sujet important que nous ne verrons pas dans notre FPS mais qu'il faut connaître : l'import d'objet 3D dans Babylon ! À tout de suite ! :)‌‌

Exemple de certificat de réussite
Exemple de certificat de réussite