Ce chapitre est la quatrième partie de notre tutoriel de programmation de notre jeu de type Tower Defense avec Three.JS.
Si vous souhaitez commencer par le début, voici un lien vers la première partie :
Si vous êtes à jour, commençons !
Ce chapitre est également disponible au format vidéo !
Introduction et objectif
Dans la partie précédente, nous avions implémenté un système de curseur contrôlé par la souris ou l’écran tactile. Dans cette nouvelle partie, nous utiliserons ce curseur pour cibler la position de création des tours de notre jeu !
Créer et supprimer des tours sur notre carte de jeu
Le modèle 3D de notre tour
Commençons par créer une variable globale tower_mesh
dans notre fichier index.html
:
var tower_mesh = undefined; // ThreeJS Mesh - TOWER
Puis, dans notre fonction init
, créons un modèle 3D pour notre tour. Vous pouvez charger un modèle 3D d’un fichier externe, ou utiliser les primitives Three.JS comme dans cet exemple :
// TOWER MESH const material = new THREE.MeshLambertMaterial({ color : 0xc0392b}); const tower_geometry = new THREE.BoxGeometry( 1, 3, 1 ); tower_mesh = new THREE.Mesh( tower_geometry, material );
Si vous choisissez l’option de charger un modèle 3D externe, vous pouvez utiliser ce lien pour vous aider :
Chaque nouvelle tour créée sera un clone du Mesh
de notre variable tower_mesh
.
La gestion des tours
Créons un fichier towermanager.js
, puis créons-y TowerManager
, une classe exportable :
export class TowerManager { constructor() { //code } }
Dans le constructeur de notre nouvelle classe, créons trois variables :
// ---- Tower List ---- this.towerArray = new Array(); // ---- Temporary variables ---- this.newTowerMeshToCreate = undefined; this.selectedTower = undefined;
La variable towerArray
constitue notre liste, dans laquelle les tours sont stockées.
Les deux autres variables sont des conteneurs temporaires que nous utiliserons lors d’événements de création d’une tour ou de sélection d’une tour existante.
Puis, créons une simple classe Tower
:
class Tower { constructor() { this.mesh = undefined; } }
Cette classe est pour le moment simplement utilisée pour stocker une propriété mesh
de type Mesh
Three.JS.
Notre liste towerArray
stockera des instances de cette classe Tower
.
Lorsque c’est chose faite, continuons de créer notre classe TowerManager
. Créons les méthodes addTower
et deleteTower
:
addTower(newtowermesh) { var newtower = new Tower(); newtower.mesh = newtowermesh; this.towerArray.push(newtower); } deleteTower(TowerObj) { const index = this.towerArray.indexOf(TowerObj); if (index > -1) { this.towerArray.splice(index, 1); } }
La méthode addTower
nécessite un paramètre de type Mesh
Three.JS. A partir de ce dernier, elle créera et ajoutera un objet Tower
dans la liste towerArray
.
La méthode deleteTower
accepte un paramètre de type Tower
, et le supprimera de la liste towerArray
.
Pour finir, créons une méthode getTowerAtPosition
, qui retourne l’objet Tower
à la positions donnée :
getTowerAtPosition(x, z) { for(var i = 0 ; i < this.towerArray.length ; i++ ) { if(this.towerArray[i].mesh.position.x == x && this.towerArray[i].mesh.position.z == z ) { return this.towerArray[i]; } } return null; }
Notre module est désormais terminé ! importons le dans index.html
, et créons-y une variable globale de type TowerManager
:
import {TowerManager } from './towermanager.js'; [...] var towerMngr = new TowerManager();
L’interface graphique de notre jeu
Avant de commencer l’ajout et la suppression de tours, créons une interface graphique pour notre jeu.
Nous avons besoin de deux fenêtres pop-up, une pour chaque action. Voici un aperçu des interfaces que nous allons créer :
Commençons par nous occuper de notre pop-up de création dans index.html
:
<!-- CREATE MENU --> <div id="createTowerDiv" class="popupdiv"> <h2 style="text-align : center;">Create Tower ?</h2> <div style="display:flex; align-items: center; justify-content: center;"> <button class="buttonYesNo buttonyes" id="buttonyes">Yes</button> <div style="width : 5%"></div> <button class="buttonYesNo buttonno" id="buttonno" >No</button> </div> </div>
Puis, créons la pop-up de sélection. Cette interface prévoit deux balises span
pour y afficher la position de la tour sélectionnée :
<!-- TOWER INFO MENU --> <div id="TowerInfoDiv" class="popupdiv"> <h2 style="text-align : center;">Selected Tower Info</h2> <p>Position : <span id="posXinfo">NULL</span> / <span id="posZinfo" >NULL</span></p> </br> <div style="display:flex; align-items: center; justify-content: center;"> <button class="buttonYesNo buttonno" id="buttondelete" >Delete Tower</button> <div style="width : 5%"></div> <button class="buttonYesNo buttonyes" id="buttonclose">Close</button> </div> </div>
Ensuite, occupons nous du CSS associé à ces balises :
.buttonYesNo { width : 40%; height : 48px; font-size : 1.5em; border-radius : 6px; border : none; } .buttonyes { background-color : #16a085; color : white; } .buttonno { background-color : #c0392b; color : white; } .popupdiv { display: none; opacity : 0.7; position : absolute; left : 5%; bottom : 5%; box-sizing: border-box; padding : 25px; width: 90%; color : white; font-family: roboto-font, sans-serif; background-color : black; border-radius : 6px; }
Nos deux pop-up sont invisibles par défaut, grâce à la règle CSS display: none
.
Ensuite, lorsque nos interfaces sont prêtes, créons un nouveau fichier gui.js
. Dans ce module, produisons quatre méthodes exportables :
createTowerGui_open
– Ouvrir l’interface de création.createTowerGui_close
– Fermer l’interface de création.infoTowerGui_open
– Ouvrir l’interface puis remplir les champs d’informations de la tour séléctionée.infoTowerGui_close
– Fermer l’interface d’informations de la tour sélectionnée.
export function createTowerGui_open() { document.getElementById("createTowerDiv").style.display = "block"; } export function createTowerGui_close() { document.getElementById("createTowerDiv").style.display = "none"; } export function infoTowerGui_open(tower_posx, tower_posz) { document.getElementById("posXinfo").innerHTML = tower_posx; document.getElementById("posZinfo").innerHTML = tower_posz; document.getElementById("TowerInfoDiv").style.display = "block"; } export function infoTowerGui_close() { document.getElementById("TowerInfoDiv").style.display = "none"; document.getElementById("posXinfo").innerHTML = "NULL"; document.getElementById("posZinfo").innerHTML = "NULL"; }
Lorsque c’est chose faite, importons notre nouveau module dans index.html
:
import {createTowerGui_open, createTowerGui_close , infoTowerGui_open, infoTowerGui_close} from './gui.js'
Ainsi, nos interfaces sont désormais prêtes. Continuons notre projet !
Réagir aux événements Raycaster
Dans le chapitre précédent, nous avions mis en place un curseur basé sur la classe Raycaster
de Three.JS. Nous allons utiliser ce curseur pour effectuer des actions sur notre carte de jeu :
- Si la case ciblée est vide, nous proposons de créer une tour.
- Si la case est déjà occupée par une tour, nous affichons les informations de cette tour.
Commençons avec la fonction onMouseUp
. Dans cette fonction, définissons sur undefined
les deux variables temporaires de towerMngr
( notre instance de TowerManager
) :
function onMouseUp(event) { cursor_cube.material.emissive.g = 0; towerMngr.newTowerMeshToCreate = undefined; towerMngr.selectedTower = undefined; }
Il est maintenant nécessaire de créer une variable globale cursorValid
.
Puis, dans onMouseDown
, lorsque un élément éligible au ciblage du Raycaster
est sélectionné, nous définissons la valeur de cursorValid
sur true
. Sinon, nous définissons sa valeur sur false
:
var cursorValid = false; [...] function onMouseDown(event) { [...] if(intersects.length > 0) { [...] cursorValid = true; } else { [...] cursorValid = false; } }
Désormais, nous allons utiliser cette variable dans onMouseUp
. Si cette dernière est égale à true
, nous vérifions si une tour est déjà créée sur la position de notre curseur :
if( cursorValid) { var checkTower = towerMngr.getTowerAtPosition(cursor_cube.position.x, cursor_cube.position.z); }
Puis, dans notre structure if
, utilisons le résultat de la vérification stocké dans la variable checkTower
. Deux cas sont possibles :
- La valeur de
checkTower
estnull
– Aucune tour n’est actuellement créée sur la case, nous ouvrons l’interface de création. - La valeur de
checkTower
est différente denull
– Une tour est déjà créée sur cette case, nous ouvrons l’interface d’informations de la tour.
Créer une tour
Ainsi, dans le premier cas, nous créons un clone de tower_mesh
.
Puis, nous lui attribuons la position courante du curseur et nous stockons ce nouvel objet dans la variable temporaire newTowerMeshToCreate
de towerMngr
.
Pour finir, nous ouvrons et fermons les interfaces graphiques appropriées.
if(checkTower == null) { var newtower = tower_mesh.clone(); newtower.position.set( cursor_cube.position.x, 1 , cursor_cube.position.z); towerMngr.newTowerMeshToCreate = newtower; infoTowerGui_close(); createTowerGui_open(); }
Désormais, lorsque nous sélectionnons une case vide, notre interface de création s’ouvrira. Il faut maintenant attribuer des actions aux boutons Yes et No de notre interface.
Créons un événement JavaScript, dans notre fonction init
, pour réagir à un clic sur le bouton Yes. Puis, dans la fonction liée à cet évènement, nous utiliserons la variable temporaire newTowerMeshToCreate
de towerMngr
.
document.getElementById("buttonyes").addEventListener('click', function() { event.stopPropagation(); var tmpTower = towerMngr.newTowerMeshToCreate; scene.add(tmpTower); towerMngr.addTower(tmpTower); towerMngr.newTowerMeshToCreate = undefined; createTowerGui_close(); });
Dans cette fonction, nous l’ajoutons à la scène et dans la liste towerArray
de towerMngr
, grâce à notre méthode addTower
.
Puis, une fois le traitement terminé, nous redéfinissons la valeur de newTowerMeshToCreate
sur undefined
, et nous fermons l’interface.
Nous sommes désormais capables de placer une tour en appuyant sur le bouton Yes ! Pour finir, créons un événement pour le bouton No :
document.getElementById("buttonno").addEventListener('click', function() { event.stopPropagation(); towerMngr.newTowerMeshToCreate = undefined; createTowerGui_close(); });
Cette fonction simple réinitialise la variable temporaire newTowerMeshToCreate
sur undefined
et ferme l’interface.
La fonctionnalité de création de tours dans notre carte est désormais terminée !
Supprimer une tour
Retournons dans la fonction onMouseUp
.
Si la variable checkTower
est différente de null
, nous définissons son contenu comme valeur de la variable temporaire selectedTower
. Puis, nous ouvrons et fermons les interfaces appropriées :
if(checkTower == null) { [...] } else { towerMngr.selectedTower = checkTower; createTowerGui_close(); infoTowerGui_open(checkTower.mesh.position.x, checkTower.mesh.position.z); }
Désormais, lorsque nous sélectionnons une case occupée par une tour, notre interface d’information s’ouvrira.
Comme dans la partie précédente, il faut désormais attribuer des actions aux boutons Delete Tower et Close de notre interface.
Dans notre fonction init
, créons un évènement JavaScript pour réagir à un clic sur le bouton Delete Tower.
Premièrement, notre fonction événement supprime la tour de la scène et de la liste towerArray
de towerMngr
. Puis, nous fermons l’interface et redéfinissons la variable temporaire selectedTower
sur undefined
.
document.getElementById("buttondelete").addEventListener('click', function() { event.stopPropagation(); towerMngr.deleteTower(towerMngr.selectedTower); scene.remove(towerMngr.selectedTower.mesh); infoTowerGui_close(); towerMngr.selectedTower = undefined; });
Ainsi, nous sommes désormais capables de supprimer une tour existante. Pour finir, créons un évènement pour le second bouton (Close) :
document.getElementById("buttonclose").addEventListener('click', function() { event.stopPropagation(); infoTowerGui_close(); towerMngr.selectedTower = undefined; });
La fonctionnalité de suppression de tours est désormais terminée !
Finalisation des évènements
Il manque une dernière étape pour clôturer ce chapitre. Dans le chapitre précédent, nous avions créé des événements de type document.addEventListener
pour les événements pointerdown
et pointerup
.
Mais, il est aujourd’hui nécessaire de transformer ces lignes de code. Changeons document.addEventListener
par renderer.domElement.addEventListener
– Uniquement pour les évenements pointerdown
et pointerup
!
renderer.domElement.addEventListener('pointerdown', onMouseDown, false); renderer.domElement.addEventListener('pointerup', onMouseUp, false);
Pourquoi ce changement ?
Lorsque nous cliquons sur les boutons de nos interfaces graphiques, les actions liées sont correctement déclenchées. Cependant, les événements pointerdown
et pointerup
de l’élément document
sont également activés, car un clic est détecté sur la page !
Changer la cible de ces deux événements est donc nécessaire.
Résultat final
Téléchargez le code final : Github.
Félicitations, vous avez réussi à terminer ce nouveau chapitre. Désormais, nous sommes capables de créer et supprimer des tours dans notre carte de jeu !
Pour finir ce chapitre, voici un aperçu de notre réalisation :
Dans le prochain chapitre, nous intégrerons des ennemis dans notre carte de jeu !
[…] Créer un jeu de Tower Defense avec Three.JS – Partie 4 : Création et suppression de tours Étiquettesdefensegamedevthree.jsthreejstower […]