Wie man ein Smartphone-gesteuertes 3D-Webspiel erstellt

Avatar of Charlie Walter
Charlie Walter am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der folgende Beitrag ist ein Gastartikel von Charlie Walter. Charlie arbeitet viel mit Three.js (3D im Browser mit WebGL) und Spielkonzepten. Wenn Sie sich für diese Themen interessieren, lesen Sie weiter!

In diesem Tutorial erkläre ich einen Ansatz, wie man sein Smartphone mit einem 3D-Webspiel verbindet. Wir werden ein Auto bauen, das du durch Neigen deines Handys (unter Verwendung des Beschleunigungsmessers des Handys) steuern kannst. Wir werden die JavaScript-Bibliothek three.js für die Arbeit mit WebGL sowie WebSockets über die socket.io-Bibliothek und einige andere Webtechnologien verwenden.

Jetzt ausprobieren

Hier ist die Live-Demo, mit der du jetzt spielen kannst. Beachte, dass sie am besten über WLAN funktioniert.

Einrichtung

Du musst Node installieren, falls du es noch nicht getan hast. Wir werden Express zur Einrichtung unseres Servers und socket.io für die WebSocket-Kommunikation verwenden.

Erstelle ein Verzeichnis für dieses Projekt und lege diese package.json-Datei im Stammverzeichnis ab.

{
  "name": "smartphone-controller-game",
  "version": "0.1.0",
  "devDependencies": {
    "express": "*"
  }
}

Öffne nun dein Projektverzeichnis im Terminal und installiere deine Projektabhängigkeiten mit diesem Befehl.

npm install

Dies sucht in der Datei package.json und verwendet das Objekt devDependencies, um die korrekten Abhängigkeiten und Versionen dieser Abhängigkeiten zu installieren. NPM verwendet die Semvar-Versionierungsnotation, und „*“ bedeutet „aktuellste“.

Um socket.io nutzen zu können, müssen wir einen Server einrichten. Dies kann mit Express geschehen. Zuerst servieren wir eine Index-Datei. Erstelle eine Datei, die unseren Servercode enthält. Nenne sie server.js.

var express = require('express'),
    http    = require('http'),
    app     = express(),
    server  = http.createServer(app),
    port    = 8080;

server.listen(port);

app

  // Set up index
  .get('/', function(req, res) {

    res.sendFile(__dirname + '/index.html');

  });

// Log that the servers running
console.log("Server running on port: " + port);

Dies richtet einen Server ein, der auf Port :8080 läuft. Wenn die Stammressource („/“) angefordert wird, wird die Datei `index.html` in der Antwort gesendet.

Erstelle die Datei index.html.

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Smartphone Controller Game</title>
</head>

<body>
  Hello World!
</body>

</html>

Führe nun dies im Terminal aus:

node server.js

Es sollte folgendes anzeigen:

Server running on port: 8080

Öffne die URL localhost:8080 in einem Browser, und deine Index-Datei sollte gerendert werden!

Sockets zum Laufen bringen

Nachdem wir unser Projekt eingerichtet haben, bringen wir unseren Client und Server über Sockets zum Kommunizieren. Zuerst müssen wir socket.io installieren.

npm install socket.io --save

Füge in index.html socket.io in den <head> ein.

<script src="/socket.io/socket.io.js"></script>

und nach dem öffnenden <body>-Tag füge hinzu:

<script>
var io = io.connect();

io.on('connect', function() {
  alert("Connected!");
});
</script>

Dies verbindet sich mit socket.io und gibt eine Meldung aus, die uns wissen lässt, dass es funktioniert.

Füge dies in server.js hinzu:

var io = require('socket.io').listen(server);

io.sockets.on('connection', function (socket) {
  console.log("Client connected!")
});

Dies richtet socket.io unter Verwendung des Servers ein und protokolliert, wenn neue Clients eine Verbindung herstellen.

Da sich der Servercode geändert hat, müssen wir den Server neu starten. Drücke „Strg + C“ im Terminal, um den aktuellen Prozess abzubrechen. Dies muss jedes Mal erfolgen, wenn server.js aktualisiert wird.

Wir sollten nun die Meldung sehen, die uns über eine erfolgreiche Socket.io-Verbindung informiert, und das Terminal sollte fast sofort „Client connected!“ protokollieren.

Das Telefon verbinden

Jetzt verbinden wir ein Browserfenster auf einem Handy (dem Controller für das Auto) mit einem Desktop-Browser (dem Spiel). So wird das funktionieren:

  • Der Spiel-Client teilt dem Server mit, dass er sich als Spiel verbinden möchte.
  • Der Server speichert dann diesen Spiel-Socket und teilt dem Spiel-Client mit, dass er verbunden ist.
  • Der Spiel-Client erstellt dann eine URL, die seine Socket-ID als URL-Parameter verwendet.
  • Das Handy (oder ein anderer Tab/Fenster) ruft dann diesen Link auf und teilt dem Server mit, dass es sich als Controller für den Spiel-Socket mit der ID in seiner URL verbinden möchte.
  • Der Server speichert dann diesen Controller-Socket zusammen mit der ID des Spiel-Sockets, mit dem er sich verbindet.
  • Der Server weist dann dem entsprechenden Spiel-Socket-Objekt die ID dieses Controller-Sockets zu.
  • Der Server teilt dann diesem Spiel-Socket mit, dass er einen Controller verbunden hat, und teilt dem Controller-Socket mit, dass er verbunden ist.
  • Der entsprechende Spiel-Socket und Controller-Socket werden dann mit alert() benachrichtigt.

Lass uns den Spiel-Client den Server darüber informieren lassen, dass er sich als Spiel-Client verbindet. Ersetze die alert() mit:

io.emit('game_connect');

Dies sendet ein von uns benanntes Ereignis namens game_connect. Der Server kann dann auf dieses Ereignis hören, den Socket speichern und eine Nachricht an den Client zurücksenden, um ihn darüber zu informieren, dass er verbunden ist. Füge also dies als neue globale Variable hinzu:

var game_sockets = {};

Füge dann dies anstelle des console.log() hinzu:

socket.on('game_connect', function(){

  console.log("Game connected");

  game_sockets[socket.id] = {
    socket: socket,
    controller_id: undefined
  };

  socket.emit("game_connected");
});

Die controller_id wird mit der Socket-ID des Controllers gefüllt, der mit diesem Spiel verbunden ist, sobald er sich verbindet.

Starte nun den Server neu und aktualisiere den Client. Das Terminal sollte nun die Spielerverbindungen protokollieren.

Game connected

Da der Server nun ein Ereignis namens game_connected speziell an diesen Socket sendet (der game_connect gesendet hat), kann der Client darauf hören und die URL erstellen:

var game_connected = function() {
  var url = "http://x.x.x.x:8080?id=" + io.id;
  document.body.innerHTML += url;
  io.removeListener('game_connected', game_connected);
};

io.on('game_connected', game_connected);

Ersetze x.x.x.x durch deine tatsächliche IP-Adresse. Um diese zu erhalten, kannst du `ifconfig` über dein Mac/Linux-Terminal oder `ipconfig` in der Windows-Eingabeaufforderung verwenden. Dies ist die IPv4-Adresse.

Wenn du den Server neu startest und zum Client gehst, sollte eine URL für deine IP-Adresse auf Port :8080 mit einem ID-Parameter am Ende vorhanden sein.

Großartig! Wenn diese URL in einen anderen Tab kopiert (oder manuell in ein Handy eingegeben) wird, passiert nichts weiter, außer dass eine weitere URL erstellt wird. Das ist nicht das, was wir wollen. Wir möchten, dass der Client, wenn er auf diese URL (mit dem ID-Parameter) zugreift, erkennt, dass er diesen Parameter hat, und dem Server mitteilt, sich stattdessen als Controller zu verbinden.

Wickle also alles innerhalb von io.on('connect', function() { in das else von diesem ein:

if (window.location.href.indexOf('?id=') > 0) {

  alert("Hey, you're a controller trying to connect to: " + window.location.href.split('?id=')[1]);

} else {

  // In here

}

Lade den Client und wenn du zu einer erstellten URL navigierst, wird dir eine Meldung angezeigt, dass du versuchst, dich als Controller und nicht als Spiel zu verbinden. Hier werden wir ihn mit dem Server verbinden und mit dem entsprechenden Spiel-Socket verknüpfen.

Ersetze die alert() mit:

io.emit('controller_connect', window.location.href.split('?id=')[1]);

Dies sendet ein Ereignis namens controller_connect und sendet die ID, die wir in der URL haben, an den Server. Nun kann der Server auf dieses Ereignis hören, den Controller-Socket speichern und ihn mit dem entsprechenden Spiel verbinden. Zuerst benötigen wir eine globale Variable, um die Controller-Sockets zu speichern:

var controller_sockets = {};

Füge dies innerhalb von io.sockets.on('connection', function (socket) { } hinzu:

socket.on('controller_connect', function(game_socket_id){

  if (game_sockets[game_socket_id] && !game_sockets[game_socket_id].controller_id) {

    console.log("Controller connected");

    controller_sockets[socket.id] = {
      socket: socket,
      game_id: game_socket_id
    };

    game_sockets[game_socket_id].controller_id = socket.id;

    game_sockets[game_socket_id].socket.emit("controller_connected", true);

    socket.emit("controller_connected", true);

  } else {

    console.log("Controller attempted to connect but failed");

    socket.emit("controller_connected", false);
  }

});

Dies prüft, ob ein Spiel mit dieser ID existiert und bestätigt, dass es noch keinen Controller damit verbunden hat. Der Server sendet ein Ereignis auf dem Controller-Socket namens controller_connected und erhält je nach Erfolg ein Boolesches Ergebnis. Der Erfolg wird ebenfalls console.log() ausgegeben. Wenn die Prüfung erfolgreich ist, wird der neue Controller-Socket zusammen mit der ID des Spiels, mit dem er sich verbindet, gespeichert. Die Socket-ID des Controllers wird auch auf dem entsprechenden bestehenden Spiel-Socket-Element gesetzt.

Nun sollte das Terminal anzeigen, wenn ein Controller mit dem Spiel verbunden wird. Wenn wir versuchen, einen zweiten Controller zu verbinden, wird dies fehlschlagen (aufgrund des zweiten Teils der Validierung).

Server running on port: 8080
Game connected
Controller connected
Controller attempted to connect but failed

Auch wenn wir die URL bearbeiten und versuchen, uns mit einem zufälligen ID-Spiel zu verbinden http://x.x.x.x:8080/?id=RANDOMID, wird dies fehlschlagen, da kein Spiel mit dieser ID existiert (erster Teil der Validierung). Dies geschieht auch, wenn wir das Spiel nicht starten.

Der Controller-Client kann nun auf dieses 'controller_connected'-Ereignis hören und je nach Erfolg eine Meldung ausgeben.

io.on('controller_connected', function(connected) {

  if (connected) {

    alert("Connected!");

  } else {

    alert("Not connected!");
  }

});

Trennen

Nun funktioniert die Prüfung, ob ein Spiel existiert, auch wenn der Spiel-Tab geschlossen wird, bevor der Controller verbunden wird, da wir keine Socket-Disconnection-Ereignisse implementiert haben. Machen wir das also, indem wir dies zum Servercode hinzufügen:

socket.on('disconnect', function () {

  // Game
  if (game_sockets[socket.id]) {

    console.log("Game disconnected");

    if (controller_sockets[game_sockets[socket.id].controller_id]) {
 
      controller_sockets[game_sockets[socket.id].controller_id].socket.emit("controller_connected", false);
      controller_sockets[game_sockets[socket.id].controller_id].game_id = undefined;
    }

    delete game_sockets[socket.id];
  }

  // Controller
  if (controller_sockets[socket.id]) {

    console.log("Controller disconnected");

    if (game_sockets[controller_sockets[socket.id].game_id]) {

      game_sockets[controller_sockets[socket.id].game_id].socket.emit("controller_connected", false);
      game_sockets[controller_sockets[socket.id].game_id].controller_id = undefined;
    }

    delete controller_sockets[socket.id];
  }
});

Dies prüft, ob die ID des getrennten Sockets in der Spiel- oder Controller-Sammlung vorhanden ist. Es verwendet dann die Eigenschaft der verbundenen ID („game_id“, wenn der Socket ein Controller ist, „controller_id“, wenn der Socket ein Spiel ist), um den entsprechenden Socket über die Trennung zu informieren und ihn aus der entsprechenden Socket-Referenz zu entfernen. Der trennende Socket wird dann gelöscht. Das bedeutet, dass Controller keine Verbindung zu Spielen herstellen können, die heruntergefahren wurden.

Wenn nun ein Tab, der als Controller mit einem Spiel verbunden ist, geschlossen wird, sollte dies im Terminal erscheinen:

Controller disconnected

Hinzufügen eines QR-Codes

Wenn du die Controller-URL manuell in dein Handy eingegeben hast, wirst du erfreut sein zu erfahren, dass es Zeit ist, einen QR-Code-Generator einzubauen. Wir werden diesen QR-Code-Generator verwenden.

Füge dies in den <head> ein.

<script src="//davidshimjs.github.com/qrcodejs/qrcode.min.js"></script>

Füge nun im else, wo wir prüfen, ob die URL einen Parameter enthält (wo wir game_connect senden), dies hinzu:

var qr = document.createElement('div');

qr.id = "qr";

document.body.appendChild(qr);

Dies erstellt ein Element mit der ID „qr“ und hängt es an den Body an.
Ersetze nun, wo wir gerade die URL in den Body schreiben, document.body.innerHTML += url; durch:

var qr_code = new QRCode("qr");
qr_code.makeCode(url);

Dies erstellt einen QR-Code (mit der Bibliothek) im Inneren unter Verwendung unseres neu erstellten Divs mit der ID qr aus der bereitgestellten URL.

Nun aktualisiere! Cool, oder?

Der QR-Code ist immer noch vorhanden, auch nachdem der Controller verbunden wurde. Also, lass uns das beheben, indem wir dies im else hinzufügen (wo wir den Spielcode machen):

io.on('controller_connected', function(connected){

  if (connected) {

    qr.style.display = "none";

  }else{

    qr.style.display = "block";

  }

});

Dies ändert die CSS des QR-Code-Elements, wenn das Ereignis controller_connected empfangen wird. Aktualisiere nun! Der QR-Code sollte nun je nach Konnektivität des Controllers ein- und ausgeblendet werden. Versuche, den Controller zu trennen.

Hinweis: Dein Handy muss sich in derselben Internetverbindung wie dein Computer befinden. Wenn du auf deinem Handy eine 504-Fehlermeldung siehst, versuche, deine Firewall-Einstellungen anzupassen.

Das Auto und den Boden bauen

Gute Nachrichten. Der schwierige Teil ist erledigt! Jetzt wollen wir Spaß mit 3D haben.

Zuerst muss der Server statische Dateien bereitstellen können, da wir ein Automodell laden werden. Füge dies in server.js irgendwo im „globalen“ Geltungsbereich hinzu:

app.use("/public", express.static(__dirname + '/public'));

Erstelle einen public-Ordner im Stammverzeichnis und lege die Datei car.js (hier herunterladen) hinein.

Füge in index.html die 3D-Bibliothek in den Kopf ein:

<script src="//threejs.org/build/three.min.js"></script>

Jetzt richten wir die drei Szenen ein. Nach der Deklaration der Funktion game_connected fügen wir eine Menge Konfigurationen hinzu:

var renderer = new THREE.WebGLRenderer({
  antialias: true
}),
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 10000),

// Lights
ambient_light 		= new THREE.AmbientLight(0x222222),
directional_light = new THREE.DirectionalLight(0xffffff, 1),

// Used to load JSON models
loader = new THREE.JSONLoader(),

// Floor mesh
floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(300,300), new THREE.MeshLambertMaterial({color: 0x22FF11})),

// Render loop
render = function(){

  // Render using scene and camera
  renderer.render(scene, camera);

  if (car)
    car.rotation.y += 0.01;

  // Call self
  requestAnimationFrame(render);
},
car;

// Enable shadows
renderer.shadowMapEnabled = true;

// Moves the camera "backward" (z) and "up" (y)
camera.position.z = -300;
camera.position.y = 100;

// Points the camera at the center of the floor
camera.lookAt(floor.position);

// Moves the directional light
directional_light.position.y = 150; // "up" / "down"
directional_light.position.x = -100; // "left" / "right"
directional_light.position.z = 60; // "forward" / "backward"

// Make the light able to cast shadows
directional_light.castShadow = true;

// Rotates the floor 90 degrees, so that it is horizontal
floor.rotation.x = -90 * (Math.PI / 180)

// Make the floor able to recieve shadows
floor.receiveShadow = true;

// Add camera, lights and floor to the scene
scene.add(camera);
scene.add(ambient_light);
scene.add(directional_light);
scene.add(floor);

// Load the car model
loader.load(
  'public/car.js',

  function ( geometry, materials ) {

    // Create the mesh from loaded geometry and materials
    var material = new THREE.MeshFaceMaterial( materials );
    car = new THREE.Mesh( geometry, material );

    // Can cast shadows
    car.castShadow = true;

    // Add to the scene
    scene.add( car );
  }
)

// Set size of renderer using window dimensions
renderer.setSize(window.innerWidth, window.innerHeight);

// Append to DOM
document.body.appendChild(renderer.domElement);

// This sets off the render loop
render();

Dies richtet die 3D-Szene mit einer THREE-Ebene als Boden, zwei Lichtern (einem Umgebungslicht und einem gerichteten Licht, um Schatten zu werfen) und einem geladenen Automodell ein, das sich bei jedem requestAnimationFrame dreht.

Im Detail deklariert dies die benötigten THREE-Komponenten, positioniert die Kamera etwas „oben“ und „hinten“ und dreht die Kamera mit der Methode .lookAt, die einen Vector3 akzeptiert. Das gerichtete Licht wird dann positioniert und angewiesen, Schatten zu werfen. Dies bewirkt, dass das Licht mit Meshes interagiert, deren Eigenschaft castShadow oder receiveShadow auf true gesetzt ist.

In diesem Fall wollen wir, dass das gerichtete Licht und das Auto Schatten werfen und der Boden Schatten empfängt.

Der Boden wird um -90 Grad gedreht, um ihn „horizontal“ zur Kamera zu machen, und so eingestellt, dass er Schatten empfängt.

Die Kamera, die Lichter und der Boden werden zur Szene hinzugefügt. Der Renderer wird auf die Abmessungen des Fensters eingestellt und in das DOM eingefügt.

Das Automodell wird dann mit dem JSONLoader, der bei THREE mitgeliefert wird, aus dem öffentlichen Verzeichnis angefordert. Die Callback-Funktion (die ausgelöst wird, sobald die Modelldatei geladen ist) gibt die Geometrie und die Materialien des Modells zurück, die dann zur Erstellung eines Meshes verwendet werden. Es wird so eingestellt, dass es Schatten wirft und zur Szene hinzugefügt wird.

Schließlich wird die render()-Schleife ausgeführt, die die Szene mit der Kamera rendert (unter Verwendung der render-Methode des Renderers), das Auto dreht, wenn es geladen wurde (damit wir wissen, dass die render-Schleife korrekt funktioniert), und sich selbst bei requestAnimationFrame aufruft.

body {
  margin: 0;
}
#QR_code {
  position: absolute;
  top: 0;
  padding: 20px;
  background: white;
}

Dies entfernt den unerwünschten Standardrand des Canvas und positioniert den QR-Code entsprechend über dem Canvas-Element.

Hier ist, was wir sehen sollten:

Das Auto steuern

Nun, da die 3D-Szene bereit ist und die Controller-Verbindung funktioniert, ist es an der Zeit, die beiden zu verbinden. Zuerst erstellen wir die Ereignisse für die Controller-Instanz. In ‘controller_connected’, nach der Meldung, füge dies hinzu:

var controller_state = {
  accelerate: false,
  steer: 0
},
emit_updates = function(){
  io.emit('controller_state_change', controller_state);
}
touchstart = function(e){
  e.preventDefault();

  controller_state.accelerate = true;
  emit_updates();
},
touchend = function(e){
  e.preventDefault();

  controller_state.accelerate = false;
  emit_updates();
},
devicemotion = function(e){
  controller_state.steer = e.accelerationIncludingGravity.y / 100;

  emit_updates();
}

document.body.addEventListener('touchstart', touchstart, false); // iOS & Android
document.body.addEventListener('MSPointerDown', touchstart, false); // Windows Phone
document.body.addEventListener('touchend', touchend, false); // iOS & Android
document.body.addEventListener('MSPointerUp', touchend, false); // Windows Phone
window.addEventListener('devicemotion', devicemotion, false);

Dies erstellt ein controller_state-Objekt und hängt Ereignisse an den Dokument-Body und das Fenster an. Die Eigenschaft accelerate wechselt zwischen true und false bei touchstart und touchend (die entsprechenden Ereignisse für Windows Phone sind MSPointerDown und MSPointerUp). Die Eigenschaft steer speichert den Neigungswert des Telefons bei devicemotion.

In jeder dieser Funktionen wird ein benutzerdefiniertes Ereignis (controller_state_change) ausgelöst, das den aktuellen Zustand des Controllers enthält.

Nachdem der Controller-Client seinen Zustand bei Änderungen sendet, muss der Server diese Informationen an das entsprechende Spiel weiterleiten. Füge dies in `server.js` hinzu, wo ein Controller erfolgreich verbunden wurde, also nach:

game_sockets[game_socket_id].socket.emit("controller_connected", true);

Füge dies hinzu:

// Forward the changes onto the relative game socket
socket.on('controller_state_change', function(data) {

  if (game_sockets[game_socket_id]) {

    // Notify relevant game socket of controller state change
    game_sockets[game_socket_id].socket.emit("controller_state_change", data)
  }

});

Nachdem der Server die Controller-Daten an den entsprechenden Spiel-Socket weiterleitet, ist es an der Zeit, den Spiel-Client dazu zu bringen, darauf zu hören und die Daten zu verwenden. Zuerst benötigen wir eine controller_state-Variable im Geltungsbereich der Spielinstanz, damit sie in der Renderfunktion zugänglich ist (dies wird als unsere gameloop dienen). Füge dies nach der Deklaration eines car-Platzhalters hinzu:

var speed = 0,
controller_state = {};

Füge dies nach dem Listener controller_connected im Spiel-Geltungsbereich hinzu:

// When the server sends a changed controller state update it in the game
io.on('controller_state_change', function(state) {

  controller_state = state;

});

Dies ist der Listener dafür, wenn der Server einen neuen Controller-Zustand sendet; er aktualisiert den Controller-Zustand des Spiels, wenn der Server einen neuen sendet.

Das Spiel ändert nun den Controller-Zustand, wenn der verbundene Controller den Bildschirm berührt und das Handy neigt, aber wir verwenden diese Daten noch nicht. Ersetze:

if (car)
  car.rotation.y += 0.01;

mit

if (car) {

  // Rotate car
  if (controller_state.steer) {

    // Gives a number ranging from 0 to 1
    var percentage_speed = (speed / 2);

    // Rotate the car using the steer value
    // Multiplying it by the percentage speed makes the car not turn
    // unless accelerating and turns quicker as the speed increases.
    car.rotateY(controller_state.steer * percentage_speed);
  }

  // If controller is accelerating
  if (controller_state.accelerate) {

    // Add to speed until it is 2
    if (speed < 2) {
      speed += 0.05;
    } else {
      speed = 2;
    }

  // If controller is not accelerating
  } else {

    // Subtract from speed until 0
    if (0 < speed) {
      speed -= 0.05;
    } else {
      speed = 0;
    }
  }

  // Move car "forward" at speed
  car.translateZ(speed);

  // Collisions
  if (car.position.x > 150) {
    car.position.x = 150;
  }
  if (car.position.x < -150) {
    car.position.x = -150;
  }
  if (car.position.z > 150) {
    car.position.z = 150;
  }
  if (car.position.z < -150) {
    car.position.z = -150;
  }
}

Dies dreht das Auto unter Verwendung der (falls vorhanden) steer-Eigenschaft des Controller-Zustands. Es wird mit einem Geschwindigkeits多 (aktuelle Geschwindigkeit/maximale Geschwindigkeit) multipliziert, damit sich das Auto nicht dreht, wenn es stillsteht, und das Auto dreht sich allmählich schneller, wenn die Geschwindigkeit des Autos zunimmt.
Das Auto bewegt sich vorwärts unter Verwendung von speed, die sich je nach accelerate-Eigenschaft des Controller-Zustands allmählich erhöht oder verringert.

Der letzte Teil ist für Kollisionen, um zu verhindern, dass das Auto „vom Boden abhebt“. Für dieses Demo ist es fest codiert, sodass die x-Position des Autos zwischen -150 und 150 bleibt, dasselbe gilt für z.

Der letzte Schritt ist nun, den Controller-Zustand zurückzusetzen, wenn der Controller getrennt wurde, nachdem wir den QR-Code wieder angezeigt haben.

QR_code_element.style.display = "block";

füge dies hinzu:

controller_state = {};

Nun sollte das Auto anhalten und seine Lenkung zurücksetzen, wenn der Controller getrennt wurde.

Zusammenfassend

Herzlichen Glückwunsch, dass du bis hierhin durchgehalten hast! Wenn sich die Lenkung umgekehrt anfühlt, versuche, dein Handy um 180 Grad zu drehen. Dein Lenkrad ist auf dem Kopf!

Wenn du etwas Cooles auf Basis dessen erstellst, lass es mich unbedingt wissen. Kontaktiere mich auf Twitter unter @cjonasw.