Course Doty
Doty Run est un jeu de course sans fin avec un gameplay dynamique, une génération procédurale et des mécanismes innovants. Guidés par l'aventurier Doty, les joueurs parcourent un sentier glacé jonché de pièces, d'obstacles et de virages serrés, tout en essayant d'obtenir le meilleur score possible. Doty Run met en avant la capacité de Niantic Studio à créer des jeux web complexes et interactifs.
Personnalisez-le grâce aux exemples de projets

Player Controller
Learn how to implement a player controller that responds to input actions and touch gestures.
View sample project
Gyroscope Controller
Master gyroscope-based tilt controls for intuitive and immersive gameplay.
View sample projectBehind the Build: Doty Run

Written by Camilo Medina
December 4, 2024
Introduction
Doty Run est un jeu de course sans fin passionnant dans lequel les joueurs guident Doty, un aventurier intrépide qui parcourt un chemin glacé pour collecter les précieuses pièces Doty. L'objectif ? Survivez aussi longtemps que possible, allez aussi loin que possible et collectez autant de pièces que possible pour battre votre meilleur score.
Contrôlez Doty en inclinant votre téléphone vers la gauche ou vers la droite pour vous déplacer latéralement, et balayez vers le haut, le bas, la gauche ou la droite pour sauter, rouler ou tourner. Pour les joueurs sur PC, les déplacements se contrôlent à l'aide des touches fléchées. Naviguez sur la piste glacée, évitez les obstacles et suivez le sentier pour poursuivre l'aventure. Chaque course enregistre votre meilleur score : chaque aventure est une chance
de le battre ! Jusqu'où pouvez-vous aller ?
Project Structure
- GameManager: contient le script
du gestionnaire de jeu
responsable de l'état du jeu et du score. - GameUI: contient les entités responsables de l'
de l'interface utilisateur- .
- Start: contient le script
ui-start
.
- Gameplay: contient le script
ui-gameplay
.
- GameOver: contient le script
ui-game-over
.
- Start: contient le script
- Environment: modèles 3D de l'environnement du jeu .
- Fog: contient le script
fog-controller
pour gérer les effets de brouillard dans la scène .
- Sfx: Contient le composant audio pour la musique de fond et le
script sfx-manager
pour les effets sonores - PathRoot : Il s'agit du parent racine des éléments de chemin générés pendant l'exécution, qui contient le
script path-movement
- PathPool : Contient les
scripts
path-manager et
object-spawner
pour créer et générer dynamiquement des objets de chemin à partir des éléments enfants disponibles pendant le jeu- Base : Modèle 3D de tuile de base où le joueur peut se déplacer entre trois positions : gauche, centre et droite. Contient un maillage et un collisionneur physique
- Partiel: modèle 3D partiel où le joueur ne peut se déplacer que vers une seule position (gauche, centre ou droite). Contient un maillage et un collisionneur physique.
- Corner: modèle 3D de carreau d'angle où le chemin change de direction. Contient un maillage et un collisionneur physique.
- Arc: modèle 3D d'obstacle en forme d'arc. Contient un maillage et un collisionneur physique
- SmallObstacle: petit obstacle en 3D, activable sous certaines conditions. Contient un maillage et un collisionneur physique
- LargeObstacle: Modèle 3D d'obstacle de grande taille, activable sous certaines conditions. Contient un maillage et un collisionneur physique.
- Coin: modèle 3D de pièce de monnaie. Contient un maillage et un collisionneur physique configuré pour les événements uniquement
- Player: contient un collisionneur physique et tous les scripts liés au joueur :
player-controller
,player-collision
,input-manager
,gyro-controller
,player-animator
ettouch-input-controller
- Doty: modèle 3D de Doty, contient un maillage et des animations pour toutes les actions du joueur.
- Caméra: contient la caméra du jeu
- Lumière ambiante: contient la lumière ambiante du jeu
- Lumière directionnelle: contient la lumière directionnelle du jeu
de scène 3D
Actifs
Modèles 3D
- Environnement
- env-1.glb: Modèle 3D de l'environnement principal
- Objets
- coin_1.glb: modèle 3D d'une pièce de monnaie, utilisée comme objet à collectionner.
- doty_coin_gold_v2.glb: autre version du modèle de pièce.
- ice_large.glb: modèle 3D d'un grand obstacle de glace.
- ice_small.glb: modèle 3D d'un petit obstacle de glace.
- Chemin
- arc.glb: modèle 3D d'une structure en arc.
- base_v1.glb: modèle 3D de la tuile de base.
- base_v2.glb: autre modèle 3D de la tuile de base.
- corner.glb: modèle 3D de la tuile d'angle.
- partial.glb: Modèle 3D de la tuile partielle de base.
Joueur
- doty_opti_web_anim.glb: modèle 3D hautement optimisé du personnage du joueur, avec des animations conçues pour les performances web.
Effets sonores (SFX)
- coin.mp3: effet sonore joué lorsqu'une pièce est collectée.
- Fast_Lane_Fun.mp3: musique de fond pour les séquences de jeu rapides.
Interface utilisateur
- Fin de partie
- game_over_background.jpg: image d'arrière-plan affichée sur l'écran Game Over.
- restart_button.png: image du bouton utilisé pour redémarrer le jeu.
- Gameplay
- coin_icon.png: icône de pièce pour l'interface utilisateur.
- Démarrer
- start_background.jpg: image d'arrière-plan affichée sur l'écran de démarrage.
- start_button.png: image du bouton utilisé pour démarrer le jeu.
des scripts
Voici un aperçu rapide des scripts utilisés dans ce projet
.app.js
: ce fichier .js sert de point d'entrée principal pour l'application web et peut être utilisé pour implémenter les fonctionnalités de base. Ici, il importe des styles CSS globaux et modulaires (utilities.css
, start.css
, gameplay.css
et game-over.css
) afin de définir l'apparence visuelle et la disposition de l'interface utilisateur du jeu. De plus, il injecte la police à utiliser par l'interface utilisateur afin d'assurer la cohérence du style.
game-manager.ts
: gère les états de base du jeu (démarrage
, gameplay
, fin de partie
), calcule les scores des joueurs et gère la sauvegarde et la récupération de l'
s sur les meilleurs scores.
utilities.js
: implémente les mécanismes de base qui nécessitent l'accès à la bibliothèque THREE
, comme la fonction permettant de demander l'accès au gyroscope. Ces utilitaires sont séparés des scripts Studio afin de maintenir l'organisation et d'éviter les conflits avec la bibliothèque ECS.
Camera
camera-follow-player.ts :
Implémente un système de caméra simple à la troisième personne. Dans le schéma, il faut une référence au lecteur. La caméra suit la position et la rotation du joueur, en suivant la rotation et la position le long de l'axe Y.
Ce mécanisme est obtenu en créant une entité vide qui suit le joueur, puis en faisant de la caméra un enfant de cette entité. La caméra est positionnée à sa position actuelle, créant ainsi un décalage par rapport au joueur. Cette approche évite d'avoir à calculer des matrices de transformation pour ajuster dynamiquement la position de la caméra par rapport au joueur.
Le script peut être étendu pour suivre également le joueur le long de l'axe X. Pour des raisons liées au gameplay, cette fonctionnalité n'est pas activée, mais elle peut être activée en décommentant la section correspondante du code.
CSS
game-over.css
: gère les styles de l'interface utilisateur pour l'écran de fin de partie. Cela comprend l'apparence de l'arrière-plan « Game Over », le tableau des scores et le bouton « Redémarrer ».
gameplay.css
: définit les styles de l'interface utilisateur pour l'état du gameplay. Il comprend des indicateurs statiques et dynamiques affichés de gauche à droite : quantité de pièces, distance et points.
start.css
: gère les styles de l'interface utilisateur pour l'écran de démarrage. Il définit l'apparence de l'arrière-plan et du bouton de démarrage.
utilities.css
: fournit des classes utilitaires pour les éléments de l'interface utilisateur, telles que des classes permettant de masquer des éléments et d'ajouter des animations.
Entrées
gyro-controller.ts
: Ce composant implémente des mécanismes de saisie à l'aide du gyroscope du téléphone pour détecter la rotation autour de l'axe Y (inclinaison gauche-droite) en fonction d'une propriété de schéma configurable, angleLimit. L'accès au gyroscope sur certains appareils nécessite l'autorisation explicite de l'utilisateur, qui doit être demandée par une interaction directe de l'utilisateur, par exemple en appuyant sur un bouton. Le composant peut être facilement modifié pour détecter d'autres types de rotation, tels que la rotation autour de l'axe Z (direction de la boussole) ou de l'axe X (inclinaison avant-arrière).
input-manager.ts
: Ce composant gère la mise en œuvre du système d'entrée Studio. Actuellement, Studio permet uniquement de vérifier si une entrée est active à l'aide de la méthode input.getAction()
, sans fournir d'informations sur le moment où l'entrée est déclenchée ou prend fin. Ce script implémente son propre système, distribuant les événements pour les deux cas : on-input-action-triggered et
on-input-action-ended
.
touch-input-controller.ts
: Ce composant gère les interactions tactiles sur l'écran, déclenchant l'événement on-input-action-triggered
lorsqu'un doigt glisse vers le haut, le bas, la gauche ou la droite sur l'écran.
Path Builder
fog-controller.ts
: ce composant configure et applique un effet de brouillard à la scène Studio en accédant à la scène three.js via world.three.scene
. Le schéma permet de personnaliser la couleur du brouillard (à l'aide de valeurs RVB), ainsi que ses distances proches et lointaines.
object-spawner.ts
: Ce composant positionne les objets sur les tuiles dans une nouvelle section du chemin en fonction d'une configuration générée de manière procédurale. La configuration détermine quels objets (pièces, obstacles, bonus, etc.) sont placés sur chaque tuile.
path-manager.ts
: ce composant gère un mécanisme central du jeu : la génération dynamique de nouvelles sections de chemin. Il place les tuiles en fonction d'une configuration générée de manière procédurale, déterminant la longueur, la direction et le positionnement partiel des tuiles.
path-movement.ts
: Ce composant déplace vers l'avant les tuiles d'
s actuelles dans la section du chemin, créant ainsi l'illusion du mouvement du joueur dans la scène du jeu
.
Player
player-animator.ts
: Contrôle les animations du joueur en réagissant aux événements du jeu tels que les sauts, les roulades, les chutes et les collisions. Il passe d'une animation à l'autre, telles que « Inactif
», « Courir
», « Sauter
» et « Chute
» ( ), en fonction des actions du joueur ou des événements du jeu. Le composant gère également la synchronisation d'animations spécifiques, telles que le retour à l'exécution après
un roulement ou la mise en pause des animations lorsque le jeu est terminé. Il vérifie également les collisions avec l'obstacle arc de manière simple et directe : en vérifiant si l'animation actuelle est Roll
. Cette approche garantit que le joueur a appuyé sur le bouton bas au bon moment sans compliquer inutilement le code.
player-collision.ts
: gère toutes les collisions entre joueurs. Le jeu implémente deux types principaux de collisions : un pour les tuiles et un autre pour les objets. Il envoie des événements correspondant à chaque collision afin de déclencher des actions dans d'autres scripts. Étant donné que Studio ne dispose actuellement d'aucun système de balisage ou de nommage des entités, le filtrage des collisions est effectué à l'aide du nom objectThree
, qui peut être obtenu ou défini via world.three.entityToObject.get(entity)
. Sachez que cette approche peut entraîner un comportement étrange lors du test du jeu dans l'aperçu en direct.
player-controller.ts
: gère les mouvements du joueur, notamment les sauts, les roulades et les rotations, en fonction des événements d'entrée provenant de gyro-controller.ts
, input-manager.ts
et touch-input-controller.ts
. Il déclenche les animations correspondantes via player-animator.ts
et vérifie les conditions de fin de partie
, telles que la chute ou la collision avec des obstacles, en déclenchant l'événement on-player-lost-game
si nécessaire. De plus, il s'adapte à la méthode de saisie : sur les ordinateurs de bureau ou les appareils sans gyroscope, il permet les mouvements latéraux via le clavier ou des gestes de balayage respectivement.
SFX
sfx-manager.ts
: contrôle la lecture de la musique de fond, déclenchant le son lorsque l'événement de démarrage
lance le jeu.
UI
ui-game-over.ts
: Crée des divs individuelles pour contenir chaque élément de l'écran game overUI, y compris l'arrière-plan, le bouton de redémarrage et le panneau affichant le score du jeu. Il gère également l'événement déclenché lorsque le bouton de redémarrage est enfoncé.
ui-gameplay.ts
: Crée des divs individuelles pour contenir chaque élément de l'écran d'interface utilisateur du gameplay, représentant les trois statistiques du jeu : pièces, distance et points. Il suit la distance parcourue par le joueur et le score du jeu pendant la partie.
ui-start.ts
: Crée des divs individuelles pour contenir chaque élément de l'écran d'interface utilisateur de démarrage du jeu, y compris l'arrière-plan et le bouton de démarrage. Il gère également l'événement déclenché lorsque le bouton de démarrage est enfoncé.
Implementation
Lors du développement d'un jeu de course sans fin, il y a un détail particulier à garder à l'esprit : le personnage du joueur ne bouge pas ; c'est le chemin qui doit se déplacer vers le joueur, créant ainsi l'illusion que le joueur avance en permanence.

Pourquoi ?
Comme le jeu ne sait pas jusqu'où le joueur peut aller, s'éloigner trop de l'origine globale peut entraîner des erreurs dans les calculs mathématiques en raison des limites de précision des nombres à virgule flottante. De plus, le fait de maintenir le joueur immobile à la position (0,0,0)
simplifie le développement de certains mécanismes de gameplay, tels que la détection des collisions et le mouvement de la caméra.
Alors, qui fait quoi ?
Joueur: se déplace uniquement selon les axes X et Y, effectuant des actions telles que sauter, rouler, se déplacer latéralement et pivoter autour de l'axe Y lorsqu'il tourne.- Chemin: se déplace le long de l'axe Z vers le joueur, créant l'illusion d'un mouvement d'
vers l'avant.
de l'interface utilisateur
Chaque jeu nécessite une interface utilisateur, et Studio fournit un système d'interface utilisateur intégré. Cependant, comme ce jeu est conçu pour fonctionner dans un navigateur Web, l'interface utilisateur de Studio peut être améliorée à l'aide de CSS, ce qui permet des designs plus élégants et une plus grande personnalisation.Voici un workflow simple pour implémenter une interface utilisateur avec TypeScript et CSS dans Studio :
- Créez un composant ECS: par exemple,
my-component.ts
- Ajoutez le composant à une entité de scène: associez le composant personnalisé à une entité de votre scène.
- Instanciez l'élément d'interface utilisateur: créez un nouvel élément
div
et attribuez-lui un identifiant unique. Exemple :const myUiElement = document.createElement('div') myUiElement.id = 'my-ui-element'
- Ajouter l'élément d'interface utilisateur au document: Ajoutez l'élément au corps du document. Par exemple,
document.body.append(myUiElement)
- Créer un fichier CSS: Par exemple,
my-css-style.css
- Définir les styles pour le nouvel élément d'interface utilisateur: Utilisez l'ID unique pour cibler l'élément dans votre fichier CSS. Par exemple,
#my-ui-element {/_ Ajoutez les styles ici _/}
- Importez le fichier CSS dans l'application: Importez le fichier CSS nouvellement créé dans votre fichier app.js. Par exemple,
import './my-css-style.css'
Astuce : le fichier app.js
doit être créé manuellement ; il n'est pas inclus par défaut dans un projet Studio.
Et voilà ! Grâce à ce workflow, il est possible d'implémenter n'importe quel élément d'interface utilisateur.
Ce jeu comporte trois écrans d'interface utilisateur différents, chacun implémenté en suivant les étapes ci-dessus :
- Écran de démarrage: affiche une image d'arrière-plan, le titre du jeu et un bouton de démarrage pour lancer le jeu.
- Écran de jeu: fournit des mises à jour en temps réel, telles que les scores et le statut des joueurs.
- Écran Game Over: affiche le score final et comprend un bouton de redémarrage pour rejouer la partie.

Étant donné que le jeu nécessite l'accès au gyroscope pour contrôler le joueur, appuyer sur le bouton Démarrer déclenche la fonction requestGyroscopePermission
à partir du script utilities.js
. Cela incite le navigateur à demander les autorisations nécessaires à l'utilisateur avant le début du jeu.
du contrôleur du joueur
Le scriptplayer-controller.ts
est conçu pour gérer les entrées provenant de trois scripts distincts : gyro-controller.ts
, input-manager.ts
et touch-input-controller.ts
. Chaque script fournit des données d'entrée spécifiques que le contrôleur du joueur utilise pour déplacer ou animer le personnage du joueur.
Le script gyro-controller.ts
déclenche l'événement on-device-orientation-changed
, en incluant deviceOrientationState
comme données. Cet état représente l'orientation de l'appareil (gauche, centre ou droite). Le fichier player-controller.ts
écoute cet événement et appelle la fonction movePlayerSideways(side)
, ajustant la position du joueur en conséquence.
Les fichiers input-manager.ts
et touch-input-controller.ts
déclenchent tous deux l'événement on-input-action-triggered
, avec actionName
comme données. Cela représente l'action déclenchée (haut, gauche, droite ou bas). Le fichier player-controller.ts
écoute cet événement et appelle rotateAndMovePlayer(actionName)
, qui gère à la fois la rotation et le déplacement en fonction de l'action spécifiée.
Pour chaque action exécutée par le joueur, le script player-controller.ts
envoie l'événement d'animation correspondant afin de garantir que l'animation appropriée est déclenchée.
Créateur de chemin
Comme mentionné précédemment, le personnage du joueur ne bouge pas ; c'est le chemin qui se déplace vers le joueur, créant ainsi l'illusion d'un mouvement vers l'avant. Pour ce faire, le jeu met en place un parcours composé de sections, chacune d'entre elles étant constituée de tuiles. Il existe différents types de carreaux.

- Tuile de base: Tuile standard complète. Sur une tuile de base, le joueur peut se déplacer latéralement entre trois positions : gauche, centre et droite.
- Tuile partielle: une tuile fine, couvrant seulement un tiers de la largeur de la tuile de base. Ici, le joueur ne peut se déplacer que dans une seule direction latérale.
- Tuile d'angle: Indique un changement de direction du chemin et marque le début d'une nouvelle section. Chaque carreau d'angle marque le début d'une nouvelle section du chemin. Dans cette tuile, le joueur est autorisé à tourner pour continuer dans la direction du chemin.
Lorsque le jeu commence et lors du placement du coin, path-manager.ts
calcule la configuration d'une nouvelle section à l'aide de la fonction computeNewSectionPathConfig(includeCornerTile, includePartialTile)
.

// TODO: convert image to code
Cette fonction génère un tableau de chaînes représentant la séquence de tuiles dans la section.
Par exemple :
['tile-base', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-corner']
Chaque chaîne décrit le type de tuile à générer dans la section. En passant la valeur false aux paramètres includeCorner
ou includeVariants
, le jeu peut exclure les tuiles d'angle ou partielles selon les besoins.
Le fichier path-manager.ts
écoute l'événement on-tile-changed
envoyé par le script player-collision.ts
. Cet événement se déclenche chaque fois que le joueur entre en collision avec une nouvelle tuile. La fonction de rappel handleTileChange(event)
gère cet événement en supprimant la tuile précédente et en ajoutant une nouvelle afin de maintenir le chemin de manière continue.
Lorsqu'une nouvelle tuile est ajoutée à la section, elle est attachée en tant qu'enfant de l'entité PathRoot
. Le script path-movement.ts
déplace toutes les tuiles enfants vers l'avant dans la direction du joueur, créant ainsi l'illusion que le joueur se déplace toujours vers l'avant.

// TODO: convert image to code
Le placement des objets, tels que les pièces, les obstacles ou les bonus, suit un processus similaire à celui du placement des tuiles. Le script object-spawner.ts
utilise la fonction computeObjectConfigInSectionTiles(addCoins, addObstacles, addPowerUps)
pour générer des configurations pour les objets dans la nouvelle section.
Cette fonction est déclenchée par l'événement on-new-path-section-computed
envoyé par path-manager.ts
.
Par exemple { 0: [], 1: [], 2: ['obstacle'], 3: ['coin', 'obstacle'], 4: ['powerUp'], 5: [], 6: ['corner'] }
Chaque clé correspond à un index de tuile, et la valeur est un tableau d'objets à placer sur cette tuile. Ce système est conçu pour être flexible et extensible, permettant l'ajout de nouveaux types d'objets.
Afin d'améliorer l'esthétique du gameplay, un effet de brouillard a été implémenté dans Studio, donnant au joueur l'illusion que le chemin est sans fin. Cela empêche le joueur de remarquer comment les tuiles sont ajoutées une par une.
s
sur le gameplay
Pendant le jeu, le nombre actuel de pièces collectées s'affiche à gauche de l'écran, tandis que le nombre de points actuels s'affiche à droite. Une fois que le joueur atteint tous les 250 mètres, une mise à jour du statut affiche la distance actuelle.
Lorsque le joueur perd, soit en tombant, soit en entrant en collision avec un obstacle, le script game-manager.js
calcule le nouveau score. Si le nouveau score est supérieur au précédent, le nouveau score le plus élevé est enregistré et affiché avec un message unique ; sinon, seul le score est affiché. La sauvegarde et la récupération des données s'effectuent à l'aide de ces deux fonctions.

// TODO: convert image to code