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 !

Déplacez la caméra sur la scène

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

Dans l'exercice précédent, je vous ai demandé de réaliser une scène avec une lumière et quelques objets pour que vous preniez en main BabylonJS. Maintenant que l'exercice est terminé, nous allons tous partir sur la même base de travail.

Une arène toute prête !

Voici l'arène dont nous allons nous servir. Elle fait une bonne taille et elle possède des objets qui seront intéressants tout au long de cette partie mais très peu au total.

Voici le code que vous devez récupérer pour Arena.js :

Arena = function(game) {
    // Appel des variables nécéssaires
    this.game = game;
    var scene = game.scene;

    // Création de notre lumière principale
    var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 10, 0), scene);
    var light2 = new BABYLON.HemisphericLight("light2", new BABYLON.Vector3(0, -1, 0), scene);
    light2.intensity = 0.8;

    // Material pour le sol
    var materialGround = new BABYLON.StandardMaterial("wallTexture", scene);
    materialGround.diffuseTexture = new BABYLON.Texture("assets/images/tile.jpg", scene);
    materialGround.diffuseTexture.uScale = 8.0;
    materialGround.diffuseTexture.vScale = 8.0;

    // Material pour les objets
    var materialWall = new BABYLON.StandardMaterial("groundTexture", scene);
    materialWall.diffuseTexture = new BABYLON.Texture("assets/images/tile.jpg", scene);

    var boxArena = BABYLON.Mesh.CreateBox("box1", 100, scene, false, BABYLON.Mesh.BACKSIDE);
    boxArena.material = materialGround;
    boxArena.position.y = 50 * 0.3;
    boxArena.scaling.y = 0.3;
    boxArena.scaling.z = 0.8;
    boxArena.scaling.x = 3.5;

    var columns = [];
    var numberColumn = 6;
    var sizeArena = 100*boxArena.scaling.x -50;
    var ratio = ((100/numberColumn)/100) * sizeArena;
    for (var i = 0; i <= 1; i++) {
        if(numberColumn>0){
            columns[i] = [];
            let mainCylinder = BABYLON.Mesh.CreateCylinder("cyl0-"+i, 30, 5, 5, 20, 4, scene);
            mainCylinder.position = new BABYLON.Vector3(-sizeArena/2,30/2,-20 + (40 * i));
            mainCylinder.material = materialWall;
            columns[i].push(mainCylinder);

            if(numberColumn>1){
                for (let y = 1; y <= numberColumn - 1; y++) {
                    let newCylinder = columns[i][0].clone("cyl"+y+"-"+i);
                    newCylinder.position = new BABYLON.Vector3(-(sizeArena/2) + (ratio*y),30/2,columns[i][0].position.z);
                    columns[i].push(newCylinder);
                }
            }
        }
    }
};

En plus du code de Arena, il vous faut modifier la caméra dans Player.js pour que la fonction  _initCamera ressemble à ceci :

_initCamera : function(scene, canvas) {
    // On crée la caméra
    this.camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(-20, 5, 0), scene);
    
    // On demande à la caméra de regarder au point zéro de la scène
    this.camera.setTarget(BABYLON.Vector3.Zero());

    // On affecte le mouvement de la caméra au canvas
    this.camera.attachControl(canvas, true);
}

Maintenant que tout le monde possède les mêmes fichiers Arena.js et Player.js, nous pouvons nous y mettre ! :)

Un pas devant l'autre

Actuellement, notre caméra se déplace sans aucune contrainte. Elle navigue sans tenir compte de la gravité ni des objets qui l'entourent. Nous allons débuter cette nouvelle partie en gérant la caméra pour lui donner un semblant de gravité et en activant les collisions. 

Ce que nous allons faire cependant, c'est gérer les déplacements ! Vous allez voir que FreeCamera de BabylonJS nous donne de très nombreux paramètres et qu'ils sont très utiles pour de très nombreux cas, mais que certaines de nos contraintes ne sont pas gérées ici.

Quelles contraintes ?  

Eh bien nous allons voir tout ça en développant le déplacement de notre personnage. Allez, zou ! ^^

Transformer les inputs en déplacement

Avec notre code actuel, la caméra se déplace grâce aux flèches directionnelles et on doit maintenir le clic pour tourner la vue. Pour transformer ce mouvement, nous allons développer nous-mêmes nos events pour gérer tout cela. Tout d'abord, nous allons désactiver les mouvements de base. En route pour  Player.js dans la fonction  _initCamera  ! On supprime simplement l'attache à la caméra et l'angularSensibility.  _initCamera ressemble désormais à cela :

_initCamera : function(scene, canvas) {
    // On crée la caméra
    this.camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(-20, 5, 0), scene);
    
    // Axe de mouvement X et Z
    this.camera.axisMovement = [false,false,false,false];
    
    // Si le joueur est en vie ou non
    this.isAlive = true;
    
    // On demande à la caméra de regarder au point zéro de la scène
    this.camera.setTarget(BABYLON.Vector3.Zero());
},

Dans ce code, vous indiquez que chaque touche pour le mouvement des déplacements (Up, Down, Left et Right) est égale à false. On le met dans une chaîne de caractère car BabylonJS permet d'insérer ici plusieurs touches différentes. Nous ajoutons aussi un tableau de quatre valeurs qui nous donneront les deux axes de déplacement (X et Z) pour correspondre au mouvement horizontal et en profondeur. isAlive, quant à lui, va nous être utile pour vérifier pas mal de petites choses tout au long du jeu. ;)

Maintenant qu'on a désactivé cette fonction, à nous de faire notre propre cuisine dans la classe de base Player.

Player = function(game, canvas) {
    // Le jeu, chargé dans l'objet Player
    this.game = game;

    // Initialisation de la caméra
    this._initCamera(this.game.scene, canvas);
};

Maintenant que nous avons notre axe, vous allez ajouter deux events qui vont capter les touches pressées par l'utilisateur. 

Pourquoi deux events ? On pourrait tout simplement capter quand la touche est pressée, non ?

Vous avez raison, c'est vrai qu'on pourrait utiliser l’événement  keypress, mais nous avons besoin de récupérer le moment où la touche est pressée et celui où elle est relâchée. Nous allons voir pourquoi très bientôt ! ;)

Player = function(game, canvas) {
    // _this est l'accès à la caméraà l'interieur de Player
    var _this = this;

    // Le jeu, chargé dans l'objet Player
    this.game = game;

    // Axe de mouvement X et Z
    this.axisMovement = [false,false,false,false];

    window.addEventListener("keyup", function(evt) {
        
        switch(evt.keyCode){
            case 90:
            _this.camera.axisMovement[0] = false;
            break;
            case 83:
            _this.camera.axisMovement[1] = false;
            break;
            case 81:
            _this.camera.axisMovement[2] = false;
            break;
            case 68:
            _this.camera.axisMovement[3] = false;
            break;
        }
    }, false);
    
    // Quand les touches sont relachés
    window.addEventListener("keydown", function(evt) {
        switch(evt.keyCode){
            case 90:
            _this.camera.axisMovement[0] = true;
            break;
            case 83:
            _this.camera.axisMovement[1] = true;
            break;
            case 81:
            _this.camera.axisMovement[2] = true;
            break;
            case 68:
            _this.camera.axisMovement[3] = true;
            break;
        }
    }, false);
    
    // Initialisation de la caméra
    this._initCamera(this.game.scene, canvas); 
};

Beaucoup de nouveau code ici, mais aussi pas mal de choses qui se répètent ! On va y aller point par point pour chaque nouveauté.

_this permet d'accéder à notre caméra depuis la fonction  Player. Ainsi, nous pouvons modifier ses paramètres.

addEventListener nous donne accès à la pression des touches de l'utilisateur.

Vous pouvez voir ainsi que axisMovement possède quatre variables, toutes à  false au début. Ces quatre variables représentent les 4 axes de déplacement (avant, arrière, gauche et droite).

Avec tout cela, votre code est maintenant capable d'intercepter les touches, qu'on utilise pour déterminer le mouvement de notre personnage ! À vous maintenant de transformer cela pour bouger notre Player ! Mais avant cela, il vous faut intercepter le mouvement de la souris pour lui permettre de gérer le regard du joueur.

Intercepter le mouvement de la souris

En premier, on va détecter le mouvement de la souris et lui permettre d'agir sur l'axe de la caméra. Pour cela, ajoutez un eventListener pour le mousemove juste en dessous des eventListener précédant. Cette fonction va être appelée à chaque fois que la souris va bouger.

window.addEventListener("mousemove", function(evt) {
    // if(_this.rotEngaged === true){
        _this.camera.rotation.y+=evt.movementX * 0.001 * (_this.angularSensibility / 250);
        var nextRotationX = _this.camera.rotation.x + (evt.movementY * 0.001 * (_this.angularSensibility / 250));
        if( nextRotationX < degToRad(90) && nextRotationX > degToRad(-90)){
            _this.camera.rotation.x+=evt.movementY * 0.001 * (_this.angularSensibility / 250);
        }
    // }
}, false);

Beaucoup de nouveau concepts…

Mais ça veut dire quoi tout ça ? :o

C'est un sacré pavé de code tout ça ! Pas de panique ! On va prendre tout ça en détail, ligne par ligne. 

if(_this.rotEngaged === true)

Cette ligne, qui est commenté pour l'instant, va vous permettre de savoir quand est-ce que la souris a bien été capturée dans la scène et donc qu'on peut tourner la caméra selon le mouvement de la souris.

_this.camera.rotation.y+=evt.movementX * 0.001 * (_this.angularSensibility / 250);

C'est la première ligne qui va changer la position de la caméra ! Elle agit sur l'axe horizontal de la caméra. 

Vous voyez ici qu'on ajoute à l'axe de rotation  evt.movementX multiplié par une valeur très petite et encore multiplié par une valeur nommée  angularSensibility.

  • evt.movementX est une valeur qui vaut -1, 0 ou 1. Comme pour les touches qu'on presse, on indique le sens de mouvement.

  • On multiplie par une valeur très petite, car la valeur de rotation de la caméra est exprimé en radians. si on ne diminue pas la valeur ajoutée à chaque mouvement de la souris, celle-ci s’emballe.

  •  _this.angularSensibility est une valeur qu'il nous faut définir plus haut dans Player.js. Elle va servir à régler la sensibilité de rotation (on choisit la même sensibilité que celle qui se pratique dans les jeux vidéo). 

var nextRotationX = _this.camera.rotation.x + (evt.movementY * 0.001 * (_this.angularSensibility / 250));

Ici, on détermine quel sera le mouvement de la caméra, sur l'axe vertical cette fois-ci. On ne le modifie pas directement pour vérifier si cette rotation est possible. Si le joueur regarde à ses pieds ou au contraire en l'air, il ne sera pas possible d'aller plus loin que la verticale. C'est l’intérêt de la ligne suivante :

if( nextRotationX < degToRad(90) && nextRotationX > degToRad(-90)){
    _this.camera.rotation.x+=evt.movementY * 0.001 * (_this.angularSensibility / 250);
}

 Avec cette ligne, on affecte enfin le mouvement en X si cela est possible. On voit cependant l'ajout de la fonction  degToRad. Comme je vous l'ai dit précédemment, les valeurs de rotation de la caméra sont exprimés en radians ; il vous faut donc les convertir en degrés pour les rendre utilisables.

Je vous conseille de mettre les deux fonctions ci-après tout en dessous de votre code dans  Game.js, hors de la classe  Game. Elles vous seront très utiles ! 

// ------------------------- TRANSFO DE DEGRES/RADIANS 
function degToRad(deg)
{
   return (Math.PI*deg)/180
}
// ----------------------------------------------------

// -------------------------- TRANSFO DE DEGRES/RADIANS 
function radToDeg(rad)
{
   // return (Math.PI*deg)/180
   return (rad*180)/Math.PI
}
// ----------------------------------------------------

Avec ce code, on va se simplifier la vie puissance 1000 ! ^^

Maintenant que la rotation est set, il nous faut déterminer quand la souris est intégrée dans le  canvas (c'est-à-dire, quand le joueur a cliqué dans la scène).

Pour cela, nous allons ajouter des lignes qui nous permettent de cacher la souris. Allez dans la partie  prototype de Player et ajoutez une nouvelle fonction au-dessus de _initCamera. Comme pour le précédent ajout de code, nous allons tout faire d'un coup puis voir en détail juste après.

_initPointerLock : function() {
    var _this = this;
    
    // Requete pour la capture du pointeur
    var canvas = this.game.scene.getEngine().getRenderingCanvas();
    canvas.addEventListener("click", function(evt) {
        canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
        if (canvas.requestPointerLock) {
            canvas.requestPointerLock();
        }
    }, false);

    // Evenement pour changer le paramètre de rotation
    var pointerlockchange = function (event) {
        _this.controlEnabled = (document.mozPointerLockElement === canvas || document.webkitPointerLockElement === canvas || document.msPointerLockElement === canvas || document.pointerLockElement === canvas);
        if (!_this.controlEnabled) {
            _this.rotEngaged = false;
        } else {
            _this.rotEngaged = true;
        }
    };
    
    // Event pour changer l'état du pointeur, sous tout les types de navigateur
    document.addEventListener("pointerlockchange", pointerlockchange, false);
    document.addEventListener("mspointerlockchange", pointerlockchange, false);
    document.addEventListener("mozpointerlockchange", pointerlockchange, false);
    document.addEventListener("webkitpointerlockchange", pointerlockchange, false);
},
  •  En premier lieu, nous récupérons le canvas de la scène pour cacher la souris et rendre son mouvement infini. Avec le clic, nous appelons la requestPointerLock. Celle-ci va demander au navigateur si la souris peut être cachée à l'utilisateur. 

  • Si le navigateur (et l'utilisateur) accepte, le pointeur est changé et l'event  pointerlockchange est appelé par les addEventListener en bas de la fonction.

  • Cette fonction change l'état de  _this.rotEngaged qui détermine si la souris peut bouger dans la scène.

Après tout cela, il ne nous reste plus qu'à décommenter _this.rotEngaged présent dans l'addEventListener de mousemove et ajouter à  Player deux lignes tout à la fin du code :

// Le joueur doit cliquer dans la scène pour que controlEnabled soit changé
this.controlEnabled = false;

// On lance l'event _initPointerLock pour checker le clic dans la scène
this._initPointerLock(); 

Comme je vous l'explique en commentaire, on initialise controlEnabled qui est appelé plus tard dans  _initPointerLock. On appelle enfin une première fois  _initPointerLock pour instancier l'event.

Magie ! Le navigateur nous cache la souris et bouge la caméra quand on a cliqué dans la scène !

Bougeons ce Player !

Maintenant que vous avez récupéré les mouvements du joueur, nous allons faire un peu de math pour déterminer l'axe de déplacement !

Quand on appuie sur la touche pour avancer, on avance dans le sens dans lequel notre personnage pointe. Il va donc falloir déterminer où regarde le personnage, puis déplacer le joueur à chaque frame. Pour cela, vous allez retourner brièvement dans  Game.js  pour appeler la mise à jour de la position du joueur à toutes les frames . 

Game = function(canvasId) {
    // Canvas et engine défini ici
    var canvas = document.getElementById(canvasId);
    var engine = new BABYLON.Engine(canvas, true);
    var _this = this;
    _this.actualTime = Date.now();
    // On initie la scène avec une fonction associée à l'objet Game
    this.scene = this._initScene(engine);
    
    var _player = new Player(_this, canvas);

    var _arena = new Arena(_this);

    // Permet au jeu de tourner
    engine.runRenderLoop(function () {
        // Récuperer le ratio par les fps
        _this.fps = Math.round(1000/engine.getDeltaTime());

        // Checker le mouvement du joueur en lui envoyant le ratio de déplacement
        _player._checkMove((_this.fps)/60);

        _this.scene.render();
    });

    // Ajuste la vue 3D si la fenetre est agrandie ou diminuée
    window.addEventListener("resize", function () {
        if (engine) {
            engine.resize();
        }
    },false);

};

_player._checkMove va vérifier comment déplacer le personnage à chaque frame, avec le ratio de un ratio basé sur les FPS de la machine.

Maintenant que la fonction est appelée, il est temps de la créer dans  Player.js. Vous pouvez la créer à la suite de  _initPointerLock et  _initCamera. Voici les lignes concernées.

_checkMove : function(ratioFps) {
    let relativeSpeed = this.speed / ratioFps;
    if(this.camera.axisMovement[0]){
        this.camera.position = new BABYLON.Vector3(this.camera.position.x + (Math.sin(this.camera.rotation.y) * relativeSpeed),
            this.camera.position.y,
            this.camera.position.z + (Math.cos(this.camera.rotation.y) * relativeSpeed));
    }
    if(this.camera.axisMovement[1]){
        this.camera.position = new BABYLON.Vector3(this.camera.position.x + (Math.sin(this.camera.rotation.y) * -relativeSpeed),
            this.camera.position.y,
            this.camera.position.z + (Math.cos(this.camera.rotation.y) * -relativeSpeed));
    }
    if(this.camera.axisMovement[2]){
        this.camera.position = new BABYLON.Vector3(this.camera.position.x + Math.sin(this.camera.rotation.y + degToRad(-90)) * relativeSpeed,
            this.camera.position.y,
            this.camera.position.z + Math.cos(this.camera.rotation.y + degToRad(-90)) * relativeSpeed);
    }
    if(this.camera.axisMovement[3]){
        this.camera.position = new BABYLON.Vector3(this.camera.position.x + Math.sin(this.camera.rotation.y + degToRad(-90)) * - relativeSpeed,
            this.camera.position.y,
            this.camera.position.z + Math.cos(this.camera.rotation.y + degToRad(-90)) * - relativeSpeed);
    }
}

Voilà notre dernier pack de lignes un peu velu pour le déplacement de la caméra ! Vous voulez tester tout de suite ? Pas de souci ! Le joueur peut désormais se déplacer dans la scène !

Mais comment ça marche tout ça ? o_O

Heureusement pour nous, chaque  if est très similaire au précédent. Chacun a pour fonction de déplacer le joueur chaque frame selon la position actuelle du joueur et la rotation, et ce sur les trois axes X, Y et Z.

Allez, découpons tout ça pour comprendre.

RelativeSpeed

Cette variable va varier selon les performances, comme expliqué précédemment.

 AxisMovement

axisMovement, variable définie dans l'objet  Player, va déterminer quelles sont les touches pressées par le joueur. La première valeur détermine s'il avance, la deuxième s'il recule, la troisième s'il va à gauche et la quatrième s'il va à droite.

Nous mettons quatre  if ici car toutes les touches peuvent être pressées en même temps.

Le calcul sur les trois axes

Ici, il va falloir ressortir les vieux cahiers de mathématiques pour bien comprendre comment on déplace le joueur selon son axe de rotation. Nous allons décortiquer la formule petit à petit. ;)

Tout d’abord, nous devons transformer l'axe de rotation du joueur en un vecteur de mouvement utilisable.

À partir de cet angle en radians, nous allons déterminer où est l'avant relatif de la caméra. Nous voulons que le joueur avance dans le sens où il regarde, car c'est une convention dans tous les FPS modernes. Pour cela, une règle simple de math nous dit que le sinus de  camera.position.y exprimé en radians nous donne la valeur de déplacement sur X et le cosinus du même angle la valeur de déplacement sur Z.

OK, je vois comment donner un vecteur de déplacement. Mais on l'intègre comment dans la formule ?

Eh bien c'est là que les variables  this.speed et  camera.position entrent en jeu ! Le vecteur déterminé sur X et Z nous donnent une valeur entre 0 et 1 et nous allons multiplier cette valeur par  this.speed et y ajouter la position actuelle.

 Maintenant que nous avons cela, il est très simple de l'affecter aux trois axes restant.

  • Pour aller en arrière (axisMovement[1]), il suffit de donner une valeur de vitesse négative.

  • Pour aller à gauche (axisMovement[2]), il faut déplacer le vecteur de déplacement de -90 degrés.

  • Pour aller à droite (axisMovement[3]), il faut aussi déplacer le vecteur de -90 degrés et donner une valeur de vitesse négative.

Et voilà ! Avec tout cela, notre caméra se déplace de la même façon que dans les jeux ! Vous verrez plus tard dans le développement pourquoi nous avons fait ainsi pour le mouvement. Tout cela est prévu pour la connexion avec les autres joueurs ! 

  

Ce chapitre est probablement le plus dense en quantité d'informations. Prenez le temps de revenir sur tout ce que je vous ai dit ici, c'est très important. Dès que vous avez bien tout vu, nous ‌allons passer à la suite! :)

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