Doty-Lauf

Doty Run ist ein Endlos-Runner-Spiel mit dynamischem Gameplay, prozeduraler Generierung und innovativen Mechaniken. Unter der Leitung des abenteuerlustigen Doty navigieren die Spieler durch eine eisige Landschaft voller Münzen, Hindernisse und scharfer Kurven – und versuchen dabei, einen Highscore zu erzielen. Doty Run unterstreicht die Leistungsfähigkeit von Niantic Studio bei der Entwicklung komplexer, interaktiver Webspiele.

Gestalten Sie es ganz nach Ihren Wünschen mit den Beispielprojekten. 

player controller

Spieler-Controller checkmark bullet

Lernen Sie, wie man einen Player-Controller implementiert, der auf Eingabeaktionen und Berührungsgesten reagiert.

Beispielprojekt ansehen
Gyroscope Controller

Gyroskop-Steuerung checkmark bullet

Beherrsche die gyroskopische Neigungssteuerung für ein intuitives und fesselndes Gameplay.

Beispielprojekt ansehen

Behind the Build: Doty Run

Written by Camilo Medina

December 4, 2024


Introduction

Doty Run ist ein spannendes Endlos-Laufspiel, in dem die Spieler Doty, einen unerschrockenen Abenteurer, über einen eisigen Pfad führen, um die wertvollen Doty-Münzen zu sammeln. Das Ziel? Überlebe so lange wie möglich, komme so weit wie möglich und sammle so viele Münzen wie möglich, um deinen Highscore zu schlagen.


Steuer Doty, indem du dein Smartphone nach links oder rechts neigst, um seitwärts zu bewegen, und wische nach oben, unten, links oder rechts, um zu springen, zu rollen oder dich zu drehen. Für PC-Spieler wird die Bewegung mit den Pfeiltasten gesteuert. Navigiere durch den Eisweg, weiche Hindernissen aus und folge der Spur, um das Abenteuer fortzusetzen. Jeder Lauf speichert Ihre beste Punktzahl – jedes Abenteuer ist eine Chance, diese zu übertreffen!

Project Structure

3D-Szenen-

  • GameManager: Enthält das Game-Manager- Skript, das für den Kernstatus des Spiels und die Punktzahl verantwortlich ist.
  • GameUI: Enthält die Entitäten, die für die UI-
    verantwortlich sind
      .
    • Start: Enthält das Skript "ui-start
  • "
    • .
    • Gameplay: Enthält das Skript "ui-gameplay
  • "
    • .
    • GameOver: Enthält das Skript "ui-game-over
  • "
    • .
  • Environment: 3D-Modelle der Spielumgebung
  • .
  • Fog: Enthält das Skript "fog-controller " zur Verwaltung von Nebeleffekten in der Szene
  • .
  • Sfx: Enthält die Audiokomponente für Hintergrundmusik und das sfx-manager-Skript für Soundeffekte
  • PathRoot: Dies ist der Stammordner der während der Laufzeit generierten Pfadelemente und enthält das path-movement-Skript
  • PathPool: Enthält die Skripte path-manager und object-spawner zum dynamischen Erstellen und Spawnen von Pfadobjekten aus verfügbaren untergeordneten Elementen während des Spiels
    • Base: Basis-Kachel-3D-Modell, in dem sich der Spieler zwischen drei Positionen bewegen kann: links, Mitte und rechts. Enthält ein Netz und einen Physik-Kollisions
    • Teilweise: Teilweises 3D-Kachelmodell, bei dem der Spieler nur an eine Position (links, Mitte oder rechts) wechseln kann.

Vermögenswerte

3D-Modelle

  • Umgebung
    • env-1.glb: Ein 3D-Modell der Hauptaufgabe
  • Objekte
    • coin_1.glb: Ein 3D-Modell einer Münze, die als Sammelobjekt verwendet wird.
    • doty_coin_gold_v2.glb: Alternative Version des Münzenmodells.
    • ice_large.glb: Ein großes 3D-Modell eines Eis-Hindernisses.
    • ice_small.glb: Ein 3D-Modell eines kleinen Eis-Hindernisses.
  • Pfad
    • arc.glb: Ein 3D-Modell einer Bogenkonstruktion.
    • base_v1.glb: Ein 3D-Modell der Basisfliese.
    • base_v2.glb: Alternatives 3D-Modell der Basistegelfläche.
    • corner.glb: Ein 3D-Modell der Eckkachel.
    • partial.glb: Ein 3D-Modell der Basis-Teilfliese.

Spieler

  • doty_opti_web_anim.glb: Ein hochoptimiertes 3D-Modell des Spielercharakters mit Animationen, die für die Web-Performance optimiert sind.

Soundeffekte (SFX)

  • coin.mp3: Soundeffekt, der abgespielt wird, wenn eine Münze gesammelt wird.
  • Fast_Lane_Fun.mp3: Hintergrundmusik für rasante Spielsequenzen.

UI

  • Game Over
    • game_over_background.jpg: Hintergrundbild, das auf dem Game-Over-Bildschirm angezeigt wird.
    • restart_button.png: Schaltflächenbild zum Neustarten des Spiels.
  • Gameplay
    • coin_icon.png: Münzsymbol für die Benutzeroberfläche.
  • Start
    • start_background.jpg: Hintergrundbild, das auf dem Startbildschirm angezeigt wird.
    • start_button.png: Schaltflächenbild zum Starten des Spiels.

Skripte

Dies ist eine kurze Übersicht über die Skripte in diesem Projekt

.

app.js: Diese .js-Datei dient als Haupteinstiegspunkt für die Webanwendung und kann zur Implementierung der Kernfunktionalität verwendet werden. Hier werden globale und modulare CSS-Stile (utilities.css, start.css, gameplay.css und game-over.css) importiert, um das visuelle Erscheinungsbild und das Layout der Spieloberfläche zu definieren. Zusätzlich wird die von der Benutzeroberfläche zu verwendende Schriftart eingefügt, um ein einheitliches Design zu gewährleisten.

game-manager.ts: Verwaltet die Kernzustände des Spiels (Start, Gameplay, Gameover), berechnet die Punktestände der Spieler und verwaltet das Speichern und Abrufen von
den Highscores.

utilities.js: Implementiert Kernmechanismen, die Zugriff auf die THREE -Bibliothek erfordern, wie z. B. die Funktion zum Anfordern des Zugriffs auf den Gyrosensor. Diese Dienstprogramme werden getrennt von den Studio-Skripten aufbewahrt, um die Organisation zu gewährleisten und Konflikte mit der ECS-Bibliothek zu vermeiden.

Camera

camera-follow-player.ts: Implementiert ein einfaches Kamerasystem aus der Perspektive einer dritten Person. Im Schema ist ein Verweis auf den Player erforderlich. Die Kamera verfolgt die Position und Drehung des Spielers und folgt dabei der Drehung und Position entlang der Y-Achse.

Diese Mechanik wird erreicht, indem eine leere Entität erstellt wird, die dem Spieler folgt, und dann die Kamera zu einem Kind dieser Entität gemacht wird. Die Kamera wird an ihrer aktuellen Position positioniert, wodurch ein Versatz relativ zum Spieler entsteht. Dieser Ansatz macht die Berechnung von Transformationsmatrizen zur dynamischen Anpassung der Kameraposition relativ zum Spieler überflüssig.

Das Skript kann erweitert werden, um dem Spieler auch entlang der X-Achse zu folgen. Aus spieltechnischen Gründen ist diese Funktion nicht aktiviert, kann jedoch durch Entfernen des Kommentars im entsprechenden Abschnitt des Codes aktiviert werden.

CSS

game-over.css: Verarbeitet die UI-Stile für den Game-Over-Bildschirm. Dies umfasst das Aussehen des Game-Over-Hintergrunds, die Punktetabelle und den Neustart-Button.

gameplay.css: Definiert die UI-Stile für den Spielstatus. Es umfasst statische und dynamische Anzeigen, die von links nach rechts angezeigt werden: Münzanzahl, Entfernung und Punkte.

start.css: Verwaltet die UI-Stile für den Startbildschirm. Es definiert das Aussehen des Start-Hintergrunds und der Start-Schaltfläche.

utilities.css: Stellt Utility-Klassen für UI-Elemente bereit, z. B. Klassen zum Ausblenden von Elementen und zum Hinzufügen von Animationen.

Eingaben

gyro-controller.ts: Diese Komponente implementiert Eingabemechanismen unter Verwendung des Gyroskops des Telefons, um die Drehung um die Y-Achse (Neigung nach links/rechts) basierend auf einer konfigurierbaren Schemaeigenschaft, angleLimit, zu erkennen. Der Zugriff auf das Gyroskop einiger Geräte erfordert die ausdrückliche Zustimmung des Benutzers, die durch eine direkte Interaktion des Benutzers, z. B. durch Drücken einer Taste, eingeholt werden muss. Die Komponente kann leicht modifiziert werden, um andere Arten der Drehung zu erkennen, wie z. B. die Drehung um die Z-Achse (Kompassrichtung) oder die X-Achse (Neigung nach vorne/hinten).

input-manager.ts: Diese Komponente verwaltet die Implementierung des Studio-Eingabesystems. Derzeit bietet Studio nur die Möglichkeit, mit input.getAction() zu überprüfen, ob eine Eingabe aktiv ist, ohne Informationen darüber zu liefern, wann die Eingabe ausgelöst wird oder endet. Dieses Skript implementiert ein eigenes System, das Ereignisse sowohl für den Fall "on-input-action-triggered" als auch für den Fall "on-input-action-ended" auslöst.

touch-input-controller.ts: Diese Komponente verwaltet Touch-Interaktionen auf dem Bildschirm und löst das Ereignis "on-input-action-triggered" aus, wenn ein Finger über den Bildschirm nach oben, unten, links oder rechts wischt.

Path Builder

fog-controller.ts: Diese Komponente konfiguriert einen Nebeleffekt für die Studio-Szene und wendet ihn an, indem sie über world.three.scene auf die three.js-Szene zugreift. Das Schema ermöglicht die Anpassung der Farbe des Nebels (mithilfe von RGB-Werten) sowie seiner Nah- und Fernentfernungen.

object-spawner.ts: Diese Komponente positioniert Objekte auf Kacheln in einem neuen Abschnitt des Pfades basierend auf einer prozedural generierten Konfiguration. Die Konfiguration legt fest, welche Objekte – wie Münzen, Hindernisse oder Power-Ups – auf den einzelnen Kacheln platziert werden.

path-manager.ts: Diese Komponente verwaltet eine Kernmechanik des Spiels: die dynamische Generierung neuer Wegabschnitte. Es platziert Kacheln basierend auf einer prozedural generierten Konfiguration und bestimmt dabei die Länge, Richtung und teilweise Positionierung der Kacheln.

path-movement.ts: Diese Komponente bewegt die aktuellen "
"-Kacheln im Pfadabschnitt in Vorwärtsrichtung und erzeugt so die Illusion einer Spielerbewegung durch die "
"-Spielszene.

Player

player-animator.ts: Steuert die Animationen des Spielers, indem es auf Ereignisse im Spiel wie Springen, Rollen, Fallen und Kollisionen reagiert. Es wechselt zwischen Animationen wie " Idle", " Run", "Jump" und " Fall " (basierend auf dem " ") je nach den Aktionen des Spielers oder den Ereignissen im Spiel. Die Komponente übernimmt auch das Timing für bestimmte Animationen, wie das Zurückschalten auf "Run" nach einem Roll oder das Anhalten von Animationen bei Spielende. Es überprüft auch auf einfache und unkomplizierte Weise, ob es zu Kollisionen mit dem Bogenhindernis kommt: indem es überprüft, ob die aktuelle Animation "Roll" ist. Dieser Ansatz stellt sicher, dass der Spieler die Abwärts-Taste zum richtigen Zeitpunkt gedrückt hat, ohne den Code zu kompliziert zu machen.

player-collision.ts: Verarbeitet alle Kollisionen zwischen Spielern.

Implementation

Bei der Entwicklung eines Endlos-Rennspiels muss ein besonderes Detail beachtet werden: Der Spielercharakter bewegt sich nicht, sondern der Weg muss sich auf den Spieler zu bewegen, wodurch die Illusion entsteht, dass sich der Spieler immer vorwärts bewegt.

image1-3

Warum?

Da das Spiel nicht weiß, wie weit der Spieler sich bewegen kann, kann eine zu große Entfernung vom globalen Ursprung aufgrund der Genauigkeitsbeschränkungen bei Fließkommazahlen zu Fehlern in den mathematischen Berechnungen führen. Darüber hinaus vereinfacht das Festhalten des Spielers an der Position (0,0,0) die Entwicklung bestimmter Spielmechaniken, wie beispielsweise die Kollisionserkennung und die Kamerabewegung.


Was macht also was?


  • Spieler: Bewegt sich nur entlang der X- und Y-Achse und führt Aktionen wie Springen, Rollen, seitliches Bewegen und Drehen um die Y-Achse beim Abbiegen aus.

  • Pfad: Bewegt sich entlang der Z-Achse auf den Spieler zu und erzeugt so die Illusion einer Vorwärtsbewegung.

UI-

Jedes Spiel benötigt eine Benutzeroberfläche, und Studio bietet ein integriertes UI-System. Da dieses Spiel jedoch für die Ausführung in einem Webbrowser konzipiert ist, kann die Benutzeroberfläche von Studio mithilfe von CSS verbessert werden, was stilvollere Designs und eine größere Anpassungsfähigkeit ermöglicht.

Hier ist ein einfacher Arbeitsablauf für die Implementierung einer Benutzeroberfläche mit TypeScript und CSS in Studio:

  1. Erstellen Sie eine ECS-Komponente: z. B. my-component.ts
  2. Fügen Sie die Komponente zu einer Szenenentität hinzu: Fügen Sie die benutzerdefinierte Komponente zu einer Entität in Ihrer Szene hinzu.
  3. Instanziieren Sie das UI-Element: Erstellen Sie ein neues div-Element und weisen Sie ihm eine eindeutige ID zu. Beispiel: const myUiElement = document.createElement('div') myUiElement.id = 'my-ui-element'
  4. UI-Element an das Dokument anhängen: Fügen Sie das Element zum Dokumentkörper hinzu. z. B.
image2-2

 

Da das Spiel Zugriff auf den Gyrosensor benötigt, um den Spieler zu steuern, löst das Drücken der Starttaste die Funktion requestGyroscopePermission aus dem Skript utilities.js aus. Dadurch wird der Browser aufgefordert, vor Spielbeginn die erforderlichen Berechtigungen vom Benutzer anzufordern.

Player-Controller-

Das Skript player-controller.ts ist dafür ausgelegt, Eingaben von drei separaten Skripten zu verarbeiten: gyro-controller.ts, input-manager.ts und touch-input-controller.ts. Jedes Skript liefert spezifische Eingabedaten, die der Player-Controller zum Bewegen oder Animieren des Player-Charakters verwendet.

image5

Das Skript " gyro-controller.ts " sendet das Ereignis " on-device-orientation-changed " einschließlich des deviceOrientationState als Daten. Dieser Status gibt die Ausrichtung des Geräts an (links, mittig oder rechts). Die Datei "player-controller.ts" wartet auf dieses Ereignis und ruft die Funktion "movePlayerSideways(side)" auf, um die Position des Spielers entsprechend anzupassen.

Sowohl "input-manager.ts" als auch " touch-input-controller.ts " senden das Ereignis " on-input-action-triggered " mit "actionName" als Daten. Dies stellt die ausgelöste Aktion dar (nach oben, links, rechts oder unten). Die Datei "player-controller.ts" wartet auf dieses Ereignis und ruft "rotateAndMovePlayer(actionName)" auf, das sowohl die Drehung als auch die Bewegung basierend auf der angegebenen Aktion ausführt.

Für jede vom Spieler ausgeführte Aktion sendet das Skript "player-controller.ts " das entsprechende Animationsereignis, um sicherzustellen, dass die richtige Animation ausgelöst wird.

Weggenerator

Wie bereits erwähnt, bewegt sich der Spielercharakter nicht; stattdessen bewegt sich der Pfad auf den Spieler zu und erzeugt so die Illusion einer Vorwärtsbewegung. Um dies zu erreichen, implementiert das Spiel einen Pfad, der aus Abschnitten besteht, wobei jeder Abschnitt aus Kacheln zusammengesetzt ist. Es gibt verschiedene Arten von Fliesen.

image6
  1. Basisfliese: Die standardmäßige, vollständige Fliese. Auf einem Basisfeld kann der Spieler seitlich zwischen drei Positionen wechseln: links, Mitte und rechts.
  2. Teilfeld: Ein dünnes Feld, das nur ein Drittel der Breite des Basisfeldes bedeckt. Hier kann sich der Spieler nur seitwärts bewegen.
  3. Eckstein: Zeigt eine Änderung der Wegrichtung an und markiert den Beginn eines neuen Abschnitts. Jede Eckfliese geht in einen neuen Wegabschnitt über. In diesem Feld kann der Spieler wenden, um in der Richtung des Pfades weiterzufahren.


Wenn das Spiel beginnt und die Ecke platziert wird, berechnet path-manager.ts die Konfiguration eines neuen Abschnitts mithilfe der Funktion computeNewSectionPathConfig(includeCornerTile, includePartialTile).

image9
         
// TODO: convert image to code

      

Diese Funktion erzeugt ein Array von Strings, die die Reihenfolge der Kacheln im Abschnitt darstellen.

Beispiel:

['tile-base', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-partial', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-base', 'tile-corner']

Jede Zeichenfolge beschreibt den Typ der Kachel, die in diesem Abschnitt erzeugt werden soll. Durch Übergeben von "false" an die Parameter " includeCorner " oder " includeVariants " kann das Spiel Eck- oder Teilkacheln nach Bedarf ausschließen. "

" Die Datei "path-manager.ts" wartet auf das Ereignis "on-tile-changed", das vom Skript " player-collision.ts " ausgelöst wird. Dieses Ereignis wird immer dann ausgelöst, wenn der Spieler mit einem neuen Kachel kollidiert. Die Callback-Funktion handleTileChange(event) verwaltet dieses Ereignis, indem sie die vorherige Kachel entfernt und eine neue hinzufügt, um den Pfad kontinuierlich aufrechtzuerhalten.

Wenn eine neue Kachel zum Abschnitt hinzugefügt wird, wird sie als untergeordnetes Element der PathRoot-Entität angehängt.

image4-3
         
// TODO: convert image to code

      

Die Platzierung von Objekten wie Münzen, Hindernissen oder Power-Ups erfolgt ähnlich wie die Platzierung von Kacheln. Das Skript "object-spawner.ts" verwendet die Funktion "computeObjectConfigInSectionTiles(addCoins, addObstacles, addPowerUps) ", um Konfigurationen für Objekte im neuen Abschnitt zu generieren. "

"

Diese Funktion wird durch das Ereignis "on-new-path-section-computed

"

ausgelöst, das von " path-manager.ts" gesendet wird.

Beispiel : { 0: [], 1: [], 2: ['obstacle'], 3: ['coin', 'obstacle'], 4: ['powerUp'], 5: [], 6: ['corner'] }

Jeder Schlüssel entspricht einem Kachelindex, und der Wert ist ein Array von Objekten, die auf dieser Kachel platziert werden sollen. Dieses System ist flexibel und erweiterbar, sodass neue Objekttypen hinzugefügt werden können.

Um die Ästhetik des Spiels zu verbessern, wurde in Studio ein Nebeleffekt implementiert, der dem Spieler die Illusion vermittelt, dass der Weg endlos ist.

Spiel-

Während des Spiels wird die aktuell gesammelte Anzahl an Münzen auf der linken Seite des Bildschirms angezeigt, während auf der rechten Seite die aktuellen Punkte angezeigt werden. Sobald der Spieler alle 250 Meter erreicht hat, zeigt eine Statusaktualisierung die aktuelle Entfernung an.

image3-1

Wenn der Spieler verliert, entweder durch Stürzen oder durch Kollision mit einem Hindernis, berechnet das Skript "game-manager.js" die neue Punktzahl. Wenn die neue Punktzahl höher ist als die vorherige, wird die neue Highscore-Punktzahl mit einer eindeutigen Meldung gespeichert und angezeigt. Andernfalls wird lediglich die Punktzahl angezeigt.

image7
         
// TODO: convert image to code