So erstellen Sie ein Echtzeit-Multiplayer-Virtual-Reality-Spiel (Teil 2)
Veröffentlicht: 2022-03-10In dieser Tutorial-Serie erstellen wir ein webbasiertes Multiplayer-Virtual-Reality-Spiel, bei dem die Spieler zusammenarbeiten müssen, um ein Rätsel zu lösen. Im ersten Teil dieser Serie haben wir die im Spiel enthaltenen Kugeln entworfen. In diesem Teil der Serie werden wir Spielmechaniken hinzufügen und Kommunikationsprotokolle zwischen Spielerpaaren einrichten.
Die Spielbeschreibung hier ist ein Auszug aus dem ersten Teil der Serie: Jedes Spielerpaar erhält einen Kugelring. Das Ziel ist es, alle Kugeln „einzuschalten“, wobei eine Kugel „eingeschaltet“ ist, wenn sie erhöht und hell ist. Eine Kugel ist „aus“, wenn sie niedriger und schwach ist. Bestimmte „dominante“ Orbs wirken sich jedoch auf ihre Nachbarn aus: Wenn sie den Zustand wechseln, wechseln auch ihre Nachbarn den Zustand. Spieler 2 kann Kugeln mit gerader Nummer kontrollieren, und Spieler 1 kann Kugeln mit ungerader Nummer kontrollieren. Dies zwingt beide Spieler, zusammenzuarbeiten, um das Rätsel zu lösen.
Die 8 Schritte in diesem Tutorial sind in 3 Abschnitte unterteilt:
- Füllen der Benutzeroberfläche (Schritte 1 und 2)
- Spielmechanik hinzufügen (Schritte 3 bis 5)
- Kommunikation einrichten (Schritte 6 bis 8)
Dieser Teil endet mit einer voll funktionsfähigen Online-Demo, die jeder spielen kann. Sie werden A-Frame VR und mehrere A-Frame-Erweiterungen verwenden.
Den fertigen Quellcode finden Sie hier.
1. Fügen Sie visuelle Indikatoren hinzu
Zu Beginn fügen wir visuelle Indikatoren für die ID einer Kugel hinzu. Fügen Sie ein neues a-text
VR-Element als erstes untergeordnetes Element von #container-orb0
auf L36 ein.
<a-entity ...> <a-text class="orb-id" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="3 3 3" position="0 -2 -0.25" material="side:double"></a-text> ... <a-entity position...> ... </a-entity> </a-entity>
Die „Abhängigkeiten“ eines Orbs sind die Orbs, die er umschaltet, wenn er umgeschaltet wird: Sagen wir zum Beispiel, Orb 1 hat als Abhängigkeiten Orbs 2 und 3. Das bedeutet, dass, wenn Orb 1 umgeschaltet wird, auch Orbs 2 und 3 umgeschaltet werden. Wir werden visuelle Indikatoren für Abhängigkeiten wie folgt direkt nach .animation-position
.
<a-animation class="animation-position" ... /> <a-text class="dep-right" opacity="0.25" rotation="0 -90 0" value="4" color="#FFF" scale="10 10 10" position="0 0 1" material="side:double" ></a-text> <a-text class="dep-left" opacity="0.25"rotation="0 -90 0" value="1" color="#FFF" scale="10 10 10" position="0 0 -3" material="side:double" ></a-text>
Vergewissern Sie sich, dass Ihr Code mit unserem Quellcode für Schritt 1 übereinstimmt. Ihre Kugel sollte jetzt mit Folgendem übereinstimmen:
Damit sind die zusätzlichen visuellen Indikatoren abgeschlossen, die wir benötigen. Als Nächstes fügen wir Kugeln dynamisch zur VR-Szene hinzu, indem wir diese Vorlagenkugel verwenden.
2. Kugeln dynamisch hinzufügen
In diesem Schritt fügen wir Orbs gemäß einer JSON-ähnlichen Spezifikation einer Ebene hinzu. Dies ermöglicht es uns, neue Ebenen einfach zu spezifizieren und zu generieren. Wir werden die Kugel aus dem letzten Schritt in Teil 1 als Vorlage verwenden.
Importieren Sie zunächst jQuery, da dies die DOM-Änderungen und damit Änderungen an der VR-Szene vereinfacht. Fügen Sie direkt nach dem A-Frame-Import Folgendes zu L8 hinzu:
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
Geben Sie eine Ebene mithilfe eines Arrays an. Das Array enthält Objektliterale, die die „Abhängigkeiten“ jeder Kugel codieren. Fügen Sie innerhalb des <head>
-Tags die folgende Ebenenkonfiguration hinzu:
<script> var orbs = [ {left: 1, right: 4}, {}, {on: true}, {}, {on: true} ]; </script>
Im Moment kann jede Kugel nur eine Abhängigkeit „rechts“ und eine „links“ davon haben. Fügen Sie unmittelbar nach dem Deklarieren von orbs
oben einen Handler hinzu, der beim Laden der Seite ausgeführt wird. Dieser Handler wird (1) die Vorlagenkugel duplizieren und (2) die Vorlagenkugel entfernen, indem er die bereitgestellte Ebenenkonfiguration verwendet:
$(document).ready(function() { function populateTemplate(orb, template, i, total) {} function remove(selector) {} for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; var template = $('#template').clone(); template = populateTemplate(orb, template, i, orbs.length); $('#carousel').append(template); } remove('#template'); } function clickOrb(i) {}
Füllen Sie als Nächstes die Funktion „ remove
“ aus, die einfach ein Element aus der VR-Szene entfernt, wenn ein Selektor vorhanden ist. Glücklicherweise beobachtet A-Frame Änderungen am DOM, und daher reicht es aus, das Element aus dem DOM zu entfernen, um es aus der VR-Szene zu entfernen. Füllen Sie die Funktion zum remove
wie folgt aus.
function remove(selector) { var el = document.querySelector(selector); el.parentNode.removeChild(el); }
Füllen Sie die clickOrb
Funktion, die einfach die Klickaktion auf einer Kugel auslöst.
function clickOrb(i) { document.querySelector("#container-orb" + i).click(); }
Beginnen Sie als Nächstes mit dem Schreiben der Funktion populateTemplate
. Beginnen Sie in dieser Funktion mit dem Abrufen der .container
. Dieser Behälter für die Kugel enthält zusätzlich die visuellen Indikatoren, die wir im vorherigen Schritt hinzugefügt haben. Darüber hinaus müssen wir das onclick
-Verhalten der Kugel basierend auf ihren Abhängigkeiten ändern. Wenn eine Linksabhängigkeit besteht, ändern Sie sowohl den visuellen Indikator als auch das onclick
Verhalten, um dies widerzuspiegeln; dasselbe gilt für eine Rechtsabhängigkeit:
function populateTemplate(orb, template, i, total) { var container = template.find('.container'); var onclick = 'document.querySelector("#light-orb' + i + '").emit("switch");'; if (orb.left || orb.right) { if (orb.left) { onclick += 'clickOrb(' + orb.left + ');'; container.find('.dep-left').attr('value', orb.left); } if (orb.right) { onclick += 'clickOrb(' + orb.right + ');'; container.find('.dep-right').attr('value', orb.right); } } else { container.find('.dep-left').remove(); container.find('.dep-right').remove(); } }
Legen Sie weiterhin in der Funktion populateTemplate
die Orb-ID in allen Elementen des Orbs und seines Containers korrekt fest.
container.find('.orb-id').attr('value', i); container.attr('id', 'container-orb' + i); template.find('.orb').attr('id', 'orb' + i); template.find('.light-orb').attr('id', 'light-orb' + i); template.find('.clickable').attr('data-id', i);
Legen Sie noch in der populateTemplate
-Funktion das onclick
Verhalten fest, legen Sie den Zufallsstartwert so fest, dass jede Kugel visuell unterschiedlich ist, und legen Sie schließlich die Rotationsposition der Kugel basierend auf ihrer ID fest.
container.attr('onclick', onclick); container.find('lp-sphere').attr('seed', i); template.attr('rotation', '0 ' + (360 / total * i) + ' 0');
Geben Sie am Ende der Funktion die template
mit allen obigen Konfigurationen zurück.
return template;
Aktivieren Sie im Document Load Handler und nach dem Entfernen der Vorlage mit remove('#template')
die Orbs, die ursprünglich so konfiguriert waren, dass sie aktiviert waren.
$(document).ready(function() { ... setTimeout(function() { for (var i=0; i < orbs.length; i++) { var orb = orbs[i]; if (orb.on) { document.querySelector("#container-orb" + i).click(); } } }, 1000); });
Damit sind die Javascript-Modifikationen abgeschlossen. Als Nächstes ändern wir die Standardeinstellungen der Vorlage in die einer „Aus“-Kugel. Ändern Sie die Position und Skalierung für #container-orb0
wie folgt:
position="8 0.5 0" scale="0.5 0.5 0.5"
Ändern Sie dann die Intensität für #light-orb0
auf 0.
intensity="0"
Überprüfen Sie, ob Ihr Quellcode mit unserem Quellcode für Schritt 2 übereinstimmt.
Ihre VR-Szene sollte jetzt 5 Kugeln aufweisen, die dynamisch bevölkert sind. Eine der Kugeln sollte außerdem visuelle Indikatoren für Abhängigkeiten haben, wie unten:
Damit ist der erste Abschnitt zum dynamischen Hinzufügen von Kugeln abgeschlossen. Im nächsten Abschnitt werden wir drei Schritte damit verbringen, Spielmechaniken hinzuzufügen. Insbesondere kann der Spieler je nach Spieler-ID nur bestimmte Kugeln umschalten.
3. Terminalstatus hinzufügen
In diesem Schritt fügen wir einen Endzustand hinzu. Wenn alle Kugeln erfolgreich eingeschaltet wurden, sieht der Spieler eine „Sieg“-Seite. Dazu müssen Sie den Zustand aller Kugeln verfolgen. Jedes Mal, wenn eine Kugel ein- oder ausgeschaltet wird, müssen wir unseren internen Status aktualisieren. Angenommen, eine toggleOrb
aktualisiert den Status für uns. Rufen Sie die toggleOrb
Funktion jedes Mal auf, wenn ein Orb seinen Status ändert: (1) fügen Sie einen Click-Listener zum Onload-Handler hinzu und (2) fügen Sie ein toggleOrb(i);
Aufruf von clickOrb
. Schließlich (3) definieren Sie ein leeres toggleOrb
.
$(document).ready(function() { ... $('.orb').on('click', function() { var id = $(this).attr('data-id') toggleOrb(id); }); }); function toggleOrb(i) {} function clickOrb(i) { ... toggleOrb(i); }
Der Einfachheit halber verwenden wir unsere Level-Konfiguration, um den Spielstatus anzuzeigen. Verwenden Sie toggleOrb
, on
den Ein-Zustand für die i-te Kugel umzuschalten. toggleOrb
kann zusätzlich einen Endzustand auslösen, wenn alle Orbs eingeschaltet sind.
function toggleOrb(i) { orbs[i].on = !orbs[i].on; if (orbs.every(orb => orb.on)) console.log('Victory!'); }
Überprüfen Sie noch einmal, ob Ihr Code mit unserem Quellcode für Schritt 3 übereinstimmt.
Damit ist der „Einzelspieler“-Modus für das Spiel beendet. An diesem Punkt haben Sie ein voll funktionsfähiges Virtual-Reality-Spiel. Sie müssen jetzt jedoch die Multiplayer-Komponente schreiben und die Zusammenarbeit über die Spielmechanik fördern.
4. Spielerobjekt erstellen
In diesem Schritt erstellen wir eine Abstraktion für einen Spieler mit einer Spieler-ID. Diese Spieler-ID wird später vom Server vergeben.
Im Moment wird dies einfach eine globale Variable sein. Definieren Sie direkt nach dem Definieren von orbs
eine Spieler-ID:
var orbs = ... var current_player_id = 1;
Überprüfe noch einmal, ob dein Code mit unserem Quellcode für Schritt 4 übereinstimmt. Im nächsten Schritt wird diese Spieler-ID dann verwendet, um zu bestimmen, welche Orbs der Spieler steuern kann.
5. Bedingtes Umschalten von Orbs
In diesem Schritt ändern wir das Umschaltverhalten der Kugel. Insbesondere kann Spieler 1 Kugeln mit ungeraden Nummern kontrollieren und Spieler 2 kann Kugeln mit geraden Nummern kontrollieren. Implementieren Sie zunächst diese Logik an beiden Stellen, an denen Kugeln ihren Status ändern:
$('.orb').on('click', function() { var id = ... if (!allowedToToggle(id)) return false; ... } ... function clickOrb(i) { if (!allowedToToggle(id)) return; ... }
Definieren Sie zweitens die allowedToToggle
Funktion direkt nach clickOrb
. Wenn der aktuelle Spieler Spieler 1 ist, werden ungeradzahlige IDs einen Wahrheitswert zurückgeben, und somit wird Spieler 1 erlaubt, ungeradzahlige Kugeln zu kontrollieren. Das Umgekehrte gilt für Spieler 2. Alle anderen Spieler dürfen die Kugeln nicht kontrollieren.
function allowedToToggle(id) { if (current_player_id == 1) { return id % 2; } else if (current_player_id == 2) { return !(id % 2); } return false; }
Überprüfe noch einmal, ob dein Code mit unserem Quellcode für Schritt 5 übereinstimmt. Standardmäßig ist der Spieler Spieler 1. Das bedeutet, dass du als Spieler 1 nur Orbs mit ungerader Zahl in deiner Vorschau steuern kannst. Damit ist der Abschnitt über die Spielmechanik abgeschlossen.
Im nächsten Abschnitt werden wir die Kommunikation zwischen beiden Spielern über einen Server erleichtern.
6. Server mit WebSocket einrichten
In diesem Schritt richten Sie einen einfachen Server ein, um (1) Spieler-IDs zu verfolgen und (2) Nachrichten weiterzuleiten. Diese Nachrichten enthalten den Spielstatus, damit die Spieler sicher sein können, dass jeder sieht, was der andere sieht.
Wir beziehen uns auf Ihre vorherige index.html
als clientseitigen Quellcode. Wir bezeichnen Code in diesem Schritt als serverseitigen Quellcode. Navigiere zu glitch.com, klicke oben rechts auf „Neues Projekt“ und im Dropdown-Menü auf „Hello-Express“.
Wählen Sie im linken Bereich „package.json“ aus und fügen Sie socket-io
zu den dependencies
hinzu. Ihr dependencies
sollte jetzt mit Folgendem übereinstimmen.
"dependencies": { "express": "^4.16.4", "socketio": "^1.0.0" },
Wählen Sie im linken Bereich „index.js“ aus und ersetzen Sie den Inhalt dieser Datei durch die folgende minimale socket.io Hello World:
const express = require("express"); const app = express(); var http = require('http').Server(app); var io = require('socket.io')(http); /** * Run application on port 3000 */ var port = process.env.PORT || 3000; http.listen(port, function(){ console.log('listening on *:', port); });
Das obige richtet socket.io auf Port 3000 für eine einfache Express-Anwendung ein. Definieren Sie als Nächstes zwei globale Variablen, eine zum Verwalten der Liste der aktiven Spieler und eine andere zum Verwalten der kleinsten nicht zugewiesenen Spieler-ID.
/** * Maintain player IDs */ var playerIds = []; var smallestPlayerId = 1;
Definieren Sie als Nächstes die getPlayerId
Funktion, die eine neue Spieler-ID generiert und die neue Spieler-ID als „genommen“ markiert, indem sie sie dem playerIds
Array hinzufügt. Insbesondere markiert die Funktion einfach die smallestPlayerId
und aktualisiert dann die smallestPlayerId
PlayerId, indem sie nach der nächstkleineren nicht genommenen ganzen Zahl sucht.
function getPlayerId() { var playerId = smallestPlayerId; playerIds.push(playerId); while (playerIds.includes(smallestPlayerId)) { smallestPlayerId++; } return playerId; }
Definieren Sie die Funktion removePlayer
, die die smallestPlayerId
PlayerId entsprechend aktualisiert und die bereitgestellte playerId
, damit ein anderer Spieler diese ID übernehmen kann.
function removePlayer(playerId) { if (playerId < smallestPlayerId) { smallestPlayerId = playerId; } var index = playerIds.indexOf(playerId); playerIds.splice(index, 1); }
Definieren Sie schließlich ein Paar von Socket-Ereignishandlern, die neue Spieler registrieren und getrennte Spieler abmelden, indem Sie das obige Methodenpaar verwenden.
/** * Handle socket interactions */ io.on('connection', function(socket) { socket.on('newPlayer', function() { socket.playerId = getPlayerId(); console.log("new player: ", socket.playerId); socket.emit('playerId', socket.playerId); }); socket.on('disconnect', function() { if (socket.playerId === undefined) return; console.log("disconnected player: ", socket.playerId); removePlayer(socket.playerId); }); });
Überprüfen Sie noch einmal, ob Ihr Code mit unserem Quellcode für Schritt 6 übereinstimmt. Damit ist die grundlegende Spielerregistrierung und -abmeldung abgeschlossen. Jeder Client kann nun die vom Server generierte Spieler-ID verwenden.
Im nächsten Schritt ändern wir den Client so, dass er die vom Server ausgegebene Spieler-ID empfängt und verwendet.
7. Spieler-ID anwenden
In diesen nächsten beiden Schritten werden wir eine rudimentäre Version des Multiplayer-Erlebnisses vervollständigen. Integrieren Sie zunächst die clientseitige Spieler-ID-Vergabe. Insbesondere wird jeder Client den Server nach einer Spieler-ID fragen. Navigieren Sie zurück zur clientseitigen index.html
, an der wir in Schritt 4 und davor gearbeitet haben.
socket.io
im head
bei L7 importieren:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
Instanziieren Sie nach dem Document Load Handler den Socket und geben Sie ein newPlayer
Ereignis aus. Als Antwort generiert die Serverseite eine neue Spieler-ID unter Verwendung des playerId
Ereignisses. Verwenden Sie unten die URL für Ihre Glitch-Projektvorschau anstelle von lightful.glitch.me
. Sie können gerne die unten stehende Demo-URL verwenden, aber alle von Ihnen vorgenommenen Codeänderungen werden natürlich nicht wiedergegeben.
$(document).ready(function() { ... }); socket = io("https://lightful.glitch.me"); socket.emit('newPlayer'); socket.on('playerId', function(player_id) { current_player_id = player_id; console.log(" * You are now player", current_player_id); });
Überprüfen Sie, ob Ihr Code mit unserem Quellcode für Schritt 7 übereinstimmt. Jetzt können Sie Ihr Spiel auf zwei verschiedene Browser oder Tabs laden, um zwei Seiten eines Multiplayer-Spiels zu spielen. Spieler 1 kann Kugeln mit ungeraden Nummern und Spieler 2 Kugeln mit geraden Nummern kontrollieren.
Beachten Sie jedoch, dass das Umschalten der Orbs für Spieler 1 den Orb-Status für Spieler 2 nicht beeinflusst. Als Nächstes müssen wir die Spielstatus synchronisieren.
8. Spielstatus synchronisieren
In diesem Schritt werden wir die Spielzustände synchronisieren, sodass Spieler 1 und 2 dieselben Kugelzustände sehen. Wenn Kugel 1 für Spieler 1 eingeschaltet ist, sollte sie auch für Spieler 2 eingeschaltet sein. Auf der Client-Seite werden wir Orb-Toggles sowohl ankündigen als auch darauf hören. Zur Ankündigung übergeben wir einfach die ID der umgeschalteten Kugel.
Fügen Sie vor beiden toggleOrb
Aufrufen den folgenden socket.emit
-Aufruf hinzu.
$(document).ready(function() { ... $('.orb').on('click', function() { ... socket.emit('toggleOrb', id); toggleOrb(id); }); }); ... function clickOrb(i) { ... socket.emit('toggleOrb', i); toggleOrb(i); }
Hören Sie als Nächstes auf Orb Toggles und schalten Sie den entsprechenden Orb um. Fügen Sie direkt unterhalb des playerId
Socket-Ereignis-Listeners einen weiteren Listener für das toggleOrb
Ereignis hinzu.
socket.on('toggleOrb', function(i) { document.querySelector("#container-orb" + i).click(); toggleOrb(i); });
Damit sind Änderungen am clientseitigen Code abgeschlossen. Überprüfen Sie noch einmal, ob Ihr Code mit unserem Quellcode für Schritt 8 übereinstimmt.
Die Serverseite muss nun die umgeschaltete Orb-ID empfangen und senden. Fügen Sie in der serverseitigen index.js
den folgenden Listener hinzu. Dieser Listener sollte direkt unter dem Socket disconnect
Listener platziert werden.
socket.on('toggleOrb', function(i) { socket.broadcast.emit('toggleOrb', i); });
Überprüfen Sie noch einmal, ob Ihr Code mit unserem Quellcode für Schritt 8 übereinstimmt. Jetzt sehen Spieler 1, der in ein Fenster geladen wird, und Spieler 2, der in ein zweites Fenster geladen wird, beide denselben Spielstatus. Damit haben Sie ein Multiplayer-Virtual-Reality-Spiel abgeschlossen. Die beiden Spieler müssen außerdem zusammenarbeiten, um das Ziel zu erreichen. Das Endprodukt entspricht dem Folgenden.
Fazit
Damit ist unser Tutorial zum Erstellen eines Multiplayer-Virtual-Reality-Spiels abgeschlossen. Dabei haben Sie eine Reihe von Themen angesprochen, darunter 3D-Modellierung in A-Frame VR und Echtzeit-Multiplayer-Erlebnisse mit WebSockets.
Wie würden Sie, aufbauend auf den angesprochenen Konzepten, für ein flüssigeres Spielerlebnis für die beiden Spieler sorgen? Dazu könnte gehören, dass überprüft wird, ob der Spielstatus synchronisiert ist, und der Benutzer benachrichtigt wird, falls dies nicht der Fall ist. Sie könnten auch einfache visuelle Anzeigen für den Terminalstatus und den Player-Verbindungsstatus erstellen.
Mit dem von uns erstellten Framework und den eingeführten Konzepten haben Sie jetzt die Werkzeuge, um diese Fragen zu beantworten und viel mehr zu erstellen.
Den fertigen Quellcode finden Sie hier.