Dans le développement Web moderne, les frontières entre les applications classiques et les applications Web s'estompent chaque jour. Aujourd'hui, nous pouvons créer non seulement des sites Web interactifs, mais également des jeux à part entière directement dans le navigateur. L'un des outils qui rendent cela possible est la bibliothèque - un outil puissant pour créer des graphiques 3D basés sur à l'aide de la technologie . React Three Fiber Three.js React À propos de la pile React Three Fibre est un wrapper sur qui utilise la structure et les principes de pour créer des graphiques 3D sur le Web. Cette pile permet aux développeurs de combiner la puissance de avec la commodité et la flexibilité de , rendant le processus de création d'une application plus intuitif et organisé. React Three Fiber Three.js React Three.js React Au cœur de se trouve l'idée selon laquelle tout ce que vous créez dans une scène est un composant . Cela permet aux développeurs d'appliquer des modèles et des méthodologies familiers. React Three Fiber React L'un des principaux avantages de est sa facilité d'intégration avec l'écosystème . Tous les autres outils peuvent toujours être facilement intégrés lors de l'utilisation de cette bibliothèque. React Three Fiber React React Pertinence de Web-GameDev a connu des évolutions majeures ces dernières années, passant de simples jeux 2D à des projets 3D complexes comparables à des applications bureautiques. Cette croissance en popularité et en capacités fait du Web-GameDev un domaine incontournable. Web-GameDev Flash L’un des principaux avantages du jeu en ligne est son accessibilité. Les joueurs n'ont pas besoin de télécharger et d'installer de logiciel supplémentaire : il suffit de cliquer sur le lien dans leur navigateur. Cela simplifie la distribution et la promotion des jeux, les rendant accessibles à un large public dans le monde entier. Enfin, le développement de jeux Web peut être un excellent moyen pour les développeurs de s’essayer au gamedev en utilisant des technologies familières. Grâce aux outils et bibliothèques disponibles, même sans expérience en graphisme 3D, il est possible de créer des projets intéressants et de qualité ! Performances du jeu dans les navigateurs modernes Les navigateurs modernes ont parcouru un long chemin, passant d'outils de navigation Web assez simples à des plates-formes puissantes permettant d'exécuter des applications et des jeux complexes. Les principaux navigateurs tels que , , et sont constamment optimisés et développés pour garantir des performances élevées, ce qui en fait une plate-forme idéale pour développer des applications complexes. Chrome Firefox Edge autres L'un des outils clés qui ont alimenté le développement des jeux sur navigateur est . Cette norme permettait aux développeurs d'utiliser l'accélération graphique matérielle, ce qui améliorait considérablement les performances des jeux 3D. Avec d'autres webAPI, ouvre de nouvelles possibilités pour créer des applications Web impressionnantes directement dans le navigateur. WebGL WebGL Néanmoins, lors du développement de jeux pour navigateur, il est crucial de considérer différents aspects de performances : l'optimisation des ressources, la gestion de la mémoire et l'adaptation aux différents appareils sont autant de points clés qui peuvent affecter la réussite d'un projet. À vos marques! Cependant, les mots et la théorie sont une chose, mais l’expérience pratique en est une autre. Pour vraiment comprendre et apprécier tout le potentiel du développement de jeux Web, le meilleur moyen est de se plonger dans le processus de développement. Par conséquent, comme exemple de développement réussi d’un jeu Web, nous créerons notre propre jeu. Ce processus nous permettra d'apprendre les aspects clés du développement, de faire face à des problèmes réels et d'y trouver des solutions, et de voir à quel point une plate-forme de développement de jeux Web peut être puissante et flexible. Dans une série d'articles, nous verrons comment créer un jeu de tir à la première personne en utilisant les fonctionnalités de cette bibliothèque, et plongerons dans le monde passionnant du développement de jeux Web ! Démo finale https://codesandbox.io/p/github/JI0PATA/fps-game?embedable=true Dépôt sur GitHub Maintenant, commençons ! Configuration du projet et installation des packages Tout d'abord, nous aurons besoin d'un modèle de projet . Commençons donc par l'installer. React npm create vite@latest sélectionnez la bibliothèque ; React sélectionnez . JavaScript Installez des packages npm supplémentaires. npm install three @react-three/fiber @react-three/drei @react three/rapier zustand @tweenjs/tween.js ensuite tout ce qui est inutile de notre projet. Supprimez Code de section Personnalisation de l'affichage du canevas Dans le fichier , ajoutez un élément div qui sera affiché sur la page en tant que portée. Insérez un composant et définissez le champ de vision de la caméra. À l’intérieur du composant , placez le composant . main.jsx Canvas Canvas App Ajoutons des styles à pour étendre les éléments de l'interface utilisateur sur toute la hauteur de l'écran et afficher la portée sous la forme d'un cercle au centre de l'écran. index.css Dans le composant , nous ajoutons un composant , qui sera affiché en arrière-plan dans notre scène de jeu sous la forme d'un ciel. App Sky Code de section Surface du sol Créons un composant et plaçons-le dans le composant . Ground App Dans , créez un élément de surface plane. Sur l'axe Y, déplacez-le vers le bas pour que ce plan soit dans le champ de vision de la caméra. Et retournez également le plan sur l’axe X pour le rendre horizontal. Ground Même si nous avons spécifié le gris comme couleur du matériau, l'avion apparaît complètement noir. Code de section Éclairage de base Par défaut, il n'y a pas d'éclairage dans la scène, ajoutons donc une source de lumière , qui éclaire l'objet de tous les côtés et n'a pas de faisceau dirigé. En tant que paramètre, définissez l'intensité de la lueur. ambientLight Code de section Texture pour la surface du sol Pour que la surface du sol ne paraisse pas homogène, nous ajouterons de la texture. Créez un motif de la surface du sol sous la forme de cellules qui se répètent tout au long de la surface. Dans le dossier , ajoutez une image PNG avec une texture. des ressources Pour charger une texture sur la scène, utilisons le hook du package . Et comme paramètre pour le hook nous passerons l'image de texture importée dans le fichier. Définissez la répétition de l'image dans les axes horizontaux. useTexture @react-trois/drei Code de section Mouvement de caméra À l'aide du composant du package , fixez le curseur sur l'écran afin qu'il ne bouge pas lorsque vous déplacez la souris, mais change la position de la caméra sur la scène. PointerLockControls @react-trois/drei Faisons une petite modification pour le composant . Ground Code de section Ajout de physique Pour plus de clarté, ajoutons un simple cube à la scène. <mesh position={[0, 3, -5]}> <boxGeometry /> </mesh> Pour l'instant, il est simplement suspendu dans l'espace. Utilisez le composant du package pour ajouter de la « physique » à la scène. En tant que paramètre, configurez le champ de gravité, où nous définissons les forces gravitationnelles le long des axes. Physique @react-trois/rapier <Physics gravity={[0, -20, 0]}> <Ground /> <mesh position={[0, 3, -5]}> <boxGeometry /> </mesh> </Physics> Cependant, notre cube est à l’intérieur du composant physique, mais rien ne lui arrive. Pour que le cube se comporte comme un véritable objet physique, nous devons l'envelopper dans le composant du package . RigidBody @react-trois/rapier Après cela, nous verrons immédiatement qu'à chaque rechargement de la page, le cube tombe sous l'influence de la gravité. Mais maintenant, il y a une autre tâche : il est nécessaire de faire du sol un objet avec lequel le cube peut interagir et au-delà duquel il ne tombera pas. Code de section Le sol comme objet physique Revenons au composant et ajoutons un composant comme enveloppe sur la surface du sol. Ground RigidBody Désormais, en tombant, le cube reste au sol comme un véritable objet physique. Code de section Soumettre un personnage aux lois de la physique Créons un composant qui contrôlera le personnage sur la scène. Player Le personnage est le même objet physique que le cube ajouté, il doit donc interagir avec la surface du sol ainsi qu'avec le cube sur la scène. C'est pourquoi nous ajoutons le composant . Et créons le personnage sous la forme d'une capsule. RigidBody Placez le composant à l'intérieur du composant Physics. Player Maintenant, notre personnage est apparu sur scène. Code de section Déplacer un personnage - créer un crochet Le personnage sera contrôlé à l'aide des touches et sautera à l'aide de la . WASD barre d'espace Avec notre propre crochet de réaction, nous implémentons la logique de déplacement du personnage. Créons un fichier et ajoutons-y une nouvelle fonction . hooks.js usePersonControls Définissons un objet au format {"keycode": "action à effectuer"}. Ensuite, ajoutez des gestionnaires d'événements pour appuyer et relâcher les touches du clavier. Lorsque les gestionnaires seront déclenchés, nous déterminerons les actions en cours d’exécution et mettrons à jour leur état actif. Comme résultat final, le hook renverra un objet au format {"action in progress": "status"}. Code de section Déplacer un personnage - implémenter un hook Après avoir implémenté le hook , il doit être utilisé lors du contrôle du personnage. Dans le composant , nous ajouterons le suivi de l'état de mouvement et mettrons à jour le vecteur de direction de mouvement du personnage. usePersonControls Player Nous définirons également des variables qui stockeront les états des directions de mouvement. Pour mettre à jour la position du personnage, fourni par le package . Ce hook fonctionne de la même manière que et exécute le corps de la fonction environ 60 fois par seconde. utilisons le cadre @react-two/fiber requestAnimationFrame Explication du code : Créez un lien pour l'objet joueur. Ce lien permettra une interaction directe avec l'objet joueur sur la scène. 1. const playerRef = useRef(); Lorsqu'un hook est utilisé, un objet avec des valeurs booléennes indiquant quels boutons de commande sont actuellement enfoncés par le joueur est renvoyé. 2. const { avancer, reculer, gauche, droite, sauter } = usePersonControls(); Le hook est appelé sur chaque image de l'animation. A l'intérieur de ce crochet, la position et la vitesse linéaire du joueur sont mises à jour. 3. useFrame((state) => { ... }); Vérifie la présence d'un objet joueur. S'il n'y a pas d'objet joueur, la fonction arrêtera l'exécution pour éviter les erreurs. 4. if (!playerRef.current) return; Obtenez la vitesse linéaire actuelle du joueur. 5. vitesse const = playerRef.current.linvel(); Définissez le vecteur de mouvement avant/arrière en fonction des boutons enfoncés. 6. frontVector.set(0, 0, arrière - avant) ; Définissez le vecteur de mouvement gauche/droite. 7. sideVector.set(gauche - droite, 0, 0); Calculez le vecteur final du mouvement du joueur en soustrayant les vecteurs de mouvement, en normalisant le résultat (de sorte que la longueur du vecteur soit de 1) et en multipliant par la constante de vitesse de mouvement. 8. direction.subVectors(frontVector, sideVector).normalize().multiplyScalar(MOVE_SPEED); "Réveille" l'objet joueur pour s'assurer qu'il réagit aux changements. Si vous n'utilisez pas cette méthode, après un certain temps, l'objet "se mettra en veille" et ne réagira plus aux changements de position. 9. playerRef.current.wakeUp(); Définissez la nouvelle vitesse linéaire du joueur en fonction de la direction de mouvement calculée et conservez la vitesse verticale actuelle (afin de ne pas affecter les sauts ou les chutes). 10. playerRef.current.setLinvel({ x : direction.x, y : vitesse.y, z : direction.z }); En conséquence, en appuyant sur les touches , le personnage a commencé à se déplacer dans la scène. Il peut également interagir avec le cube, car ce sont tous deux des objets physiques. WASD Code de section Déplacer un personnage - sauter Afin d'implémenter le saut, utilisons les fonctionnalités des packages et . Dans cet exemple, vérifions que le personnage est au sol et que la touche saut a été enfoncée. Dans ce cas, nous définissons la direction et la force d'accélération du personnage sur l'axe Y. @dimforge/rapier3d-compat @react-trois/rapier Pour nous ajouterons une rotation de masse et de bloc sur tous les axes, afin qu'il ne tombe pas dans des directions différentes lors d'une collision avec d'autres objets de la scène. Player, Explication du code : Accéder à la scène du moteur physique . Il contient tous les objets physiques et gère leur interaction. const monde = rapier.world; Rapier C'est ici qu'a lieu le "raycasting" (raycasting). Un rayon est créé qui commence à la position actuelle du joueur et pointe vers le bas de l'axe y. Ce rayon est « projeté » dans la scène pour déterminer s'il croise un objet de la scène. const ray = world.castRay(new RAPIER.Ray(playerRef.current.translation(), { x : 0, y : -1, z : 0 })); La condition est vérifiée si le joueur est au sol : const grounded = ray && ray.collider && Math.abs(ray.toi) <= 1,5; - si le a été créé ; rayon rayon - si le rayon est entré en collision avec un objet sur la scène ; ray.collider - le "temps d'exposition" du rayon. Si cette valeur est inférieure ou égale à la valeur donnée, cela peut indiquer que le joueur est suffisamment proche de la surface pour être considéré « au sol ». Math.abs(ray.toi) Vous devez également modifier le composant pour que l'algorithme de lancer de rayons permettant de déterminer l'état « d'atterrissage » fonctionne correctement, en ajoutant un objet physique qui interagira avec d'autres objets de la scène. Sol Montons la caméra un peu plus haut pour avoir une meilleure vue de la scène. Code de section Premier commit Deuxième commit Déplacer la caméra derrière le personnage Pour déplacer la caméra, nous obtiendrons la position actuelle du joueur et changerons la position de la caméra à chaque fois que l'image sera actualisée. Et pour que le personnage se déplace exactement le long de la trajectoire vers laquelle la caméra est dirigée, nous devons ajouter . applyEuler Explication du code : La méthode applique la rotation à un vecteur en fonction des angles d'Euler spécifiés. Dans ce cas, la rotation de la caméra est appliquée au vecteur . Ceci est utilisé pour faire correspondre le mouvement par rapport à l'orientation de la caméra, de sorte que le joueur se déplace dans la direction de rotation de la caméra. applyEuler direction Ajustons légèrement la taille du et rendons-le plus grand par rapport au cube, augmentant ainsi la taille de et corrigeant la logique de "saut". Player CapsuleCollider Code de section Premier commit Deuxième commit Génération de cubes Pour que la scène ne semble pas complètement vide, ajoutons la génération de cubes. Dans le fichier json, listez les coordonnées de chacun des cubes puis affichez-les sur la scène. Pour ce faire, créez un fichier , dans lequel nous listerons un tableau de coordonnées. cubes.json [ [0, 0, -7], [2, 0, -7], [4, 0, -7], [6, 0, -7], [8, 0, -7], [10, 0, -7] ] Dans le fichier , créez un composant , qui générera des cubes en boucle. Et le composant sera un objet directement généré. Cube.jsx Cubes Cube import {RigidBody} from "@react-three/rapier"; import cubes from "./cubes.json"; export const Cubes = () => { return cubes.map((coords, index) => <Cube key={index} position={coords} />); } const Cube = (props) => { return ( <RigidBody {...props}> <mesh castShadow receiveShadow> <meshStandardMaterial color="white" /> <boxGeometry /> </mesh> </RigidBody> ); } Ajoutons le composant créé au composant en supprimant le cube unique précédent. Cubes App Code de section Importer le modèle dans le projet Ajoutons maintenant un modèle 3D à la scène. Ajoutons un modèle d'arme pour le personnage. Commençons par rechercher un modèle 3D. Par exemple, prenons . celui-ci Téléchargez le modèle au format GLTF et décompressez l'archive à la racine du projet. Afin d'obtenir le format dont nous avons besoin pour importer le modèle dans la scène, nous devrons installer le package complémentaire . gltf-pipeline npm i -D gltf-pipeline À l'aide du package , reconvertissez le modèle du au , car dans ce format toutes les données du modèle sont placées dans un seul fichier. En tant que répertoire de sortie pour le fichier généré, nous spécifions le dossier . gltf-pipeline format GLTF format GLB public gltf-pipeline -i weapon/scene.gltf -o public/weapon.glb Ensuite, nous devons générer un composant React qui contiendra le balisage de ce modèle pour l'ajouter à la scène. Utilisons la des développeurs . ressource officielle @react-trois/fiber Pour accéder au convertisseur, vous devrez charger le fichier converti. arme.glb Par glisser-déposer ou par recherche dans l'Explorateur, recherchez ce fichier et téléchargez-le. Dans le convertisseur, nous verrons le composant de réaction généré, dont nous transférerons le code à notre projet dans un nouveau fichier , en changeant le nom du composant pour le même nom que le fichier. WeaponModel.jsx Code de section Affichage du modèle d'arme sur scène Importons maintenant le modèle créé dans la scène. Dans le fichier , ajoutez le composant . App.jsx WeaponModel Code de section Ajout d'ombres À ce stade de notre scène, aucun des objets ne projette d’ombre. Pour activer sur la scène, vous devez ajouter l'attribut au composant . les ombres shadows Canvas Ensuite, nous devons ajouter une nouvelle source de lumière. Bien que nous ayons déjà sur la scène, il ne peut pas créer d'ombres pour les objets, car il ne dispose pas de faisceau lumineux directionnel. Ajoutons donc une nouvelle source de lumière appelée et configurons-la. L'attribut permettant d'activer le mode ombre " " est . C'est l'ajout de ce paramètre qui indique que cet objet peut projeter une ombre sur d'autres objets. ambientLight directionnelleLight cast castShadow Après cela, ajoutons un autre attribut au composant , ce qui signifie que le composant de la scène peut recevoir et afficher des ombres sur lui-même. containShadow Ground Des attributs similaires doivent être ajoutés aux autres objets de la scène : cubes et joueur. Pour les cubes, nous ajouterons et , car ils peuvent à la fois projeter et recevoir des ombres, et pour le joueur, nous ajouterons uniquement . castShadow containShadow castShadow Ajoutons pour . castShadow Player Ajoutez et pour . castShadow containShadow Cube Code de section Ajout d'ombres - correction de l'écrêtage des ombres Si vous regardez attentivement maintenant, vous constaterez que la surface sur laquelle l’ombre est projetée est assez petite. Et en dépassant cette zone, l’ombre est simplement coupée. La raison en est que, par défaut, la caméra ne capture qu'une petite zone des ombres affichées de . Nous pouvons pour le composant en ajoutant des attributs supplémentaires pour étendre cette zone de visibilité. Après avoir ajouté ces attributs, l'ombre deviendra légèrement floue. Pour améliorer la qualité, nous ajouterons l'attribut . directionnelLight directionnelLight shadow-camera-(top, bottom, left, right) shadow-mapSize Code de section Lier des armes à un personnage Ajoutons maintenant l'affichage des armes à la première personne. Créez un nouveau composant , qui contiendra la logique de comportement de l'arme et le modèle 3D lui-même. d'arme import {WeaponModel} from "./WeaponModel.jsx"; export const Weapon = (props) => { return ( <group {...props}> <WeaponModel /> </group> ); } Plaçons ce composant au même niveau que le du personnage et dans le hook nous définirons la position et l'angle de rotation en fonction de la position des valeurs de la caméra. RigidBody useFrame Code de section Animation d'un balancement d'arme en marchant Pour rendre la démarche du personnage plus naturelle, nous ajouterons un léger mouvement de l'arme lors du déplacement. Pour créer l'animation, nous utiliserons la bibliothèque installée. tween.js Le composant sera enveloppé dans une balise de groupe afin que vous puissiez y ajouter une référence via le hook . Weapon useRef Ajoutons un pour enregistrer l'animation. useState Créons une fonction pour initialiser l'animation. Explication du code : Création d'une animation d'un objet "balançant" de sa position actuelle vers une nouvelle position. const twSwayingAnimation = new TWEEN.Tween(currentPosition) ... Création d'une animation de l'objet revenant à sa position de départ une fois la première animation terminée. const twSwayingBackAnimation = new TWEEN.Tween(currentPosition) ... Connecter deux animations afin que lorsque la première animation se termine, la deuxième animation démarre automatiquement. twSwayingAnimation.chain(twSwayingBackAnimation); Dans , nous appelons la fonction d'initialisation de l'animation. useEffect Il faut maintenant déterminer le moment pendant lequel le mouvement se produit. Cela peut être fait en déterminant le vecteur actuel de la direction du personnage. Si un mouvement de personnage se produit, nous actualiserons l'animation et la réexécuterons une fois terminé. Explication du code : Ici, l'état de mouvement de l'objet est vérifié. Si le vecteur direction a une longueur supérieure à 0, cela signifie que l'objet a une direction de mouvement. const isMoving = direction.length() > 0; Cet état est exécuté si l'objet se déplace et que l'animation "swinging" est terminée. if (isMoving && isSwayingAnimationFinished) { ... } Dans le composant , ajoutons un où nous mettrons à jour l'animation interpolée. App useFrame met à jour toutes les animations actives dans la bibliothèque . Cette méthode est appelée sur chaque image d'animation pour garantir le bon déroulement de toutes les animations. TWEEN.update() TWEEN.js Code de rubrique : Premier commit Deuxième commit Animation de recul Nous devons définir le moment où un coup de feu est tiré, c'est-à-dire le moment où le bouton de la souris est enfoncé. Ajoutons pour stocker cet état, pour stocker une référence à l'objet arme et deux gestionnaires d'événements pour appuyer et relâcher le bouton de la souris. useState useRef Implémentons une animation de recul lorsque vous cliquez sur le bouton de la souris. Nous utiliserons la bibliothèque à cet effet. tween.js Définissons des constantes pour la force de recul et la durée de l'animation. Comme pour l'animation de mouvement de l'arme, nous ajoutons deux états useState pour l'animation de recul et de retour à la position d'origine et un état avec l'état de fin de l'animation. Créons des fonctions pour obtenir un vecteur aléatoire d'animation de recul - et . generateRecoilOffset generateNewPositionOfRecoil Créez une fonction pour initialiser l'animation de recul. Nous ajouterons également , dans lequel nous spécifierons l'état "shot" comme dépendance, afin qu'à chaque plan l'animation soit à nouveau initialisée et de nouvelles coordonnées de fin soient générées. useEffect Et dans , ajoutons une vérification pour "maintenir" la touche de la souris pour le tir, afin que l'animation de tir ne s'arrête pas tant que la touche n'est pas relâchée. useFrame Code de section Animation pendant l'inactivité Réaliser l'animation « d'inactivité » pour le personnage, afin qu'il n'y ait pas de sensation de jeu « suspendu ». Pour ce faire, ajoutons de nouveaux états via . useState Corrigeons l'initialisation de l'animation "wiggle" pour utiliser les valeurs de l'état. L'idée est que différents états : marcher ou s'arrêter, utiliseront des valeurs différentes pour l'animation et à chaque fois l'animation sera initialisée en premier. Conclusion Dans cette partie, nous avons implémenté la génération de scènes et le mouvement des personnages. Nous avons également ajouté un modèle d'arme, une animation de recul lors du tir et au ralenti. Dans la prochaine partie, nous continuerons à affiner notre jeu en ajoutant de nouvelles fonctionnalités. Également publié . ici