Skip to content

Instantly share code, notes, and snippets.

@foopis23
Created June 10, 2021 15:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save foopis23/2f335a3e2c257787de72242a92bb36a6 to your computer and use it in GitHub Desktop.
Save foopis23/2f335a3e2c257787de72242a92bb36a6 to your computer and use it in GitHub Desktop.
Simple Multiplayer with THREE JS & Firebase
// Option 1: Import the entire three.js core library.
import * as THREE from 'three';
import { BoxBufferGeometry, Mesh, MeshPhongMaterial } from 'three';
import { BoxLineGeometry } from 'three/examples/jsm/geometries/BoxLineGeometry.js';
import { VRButton } from 'three/examples/jsm/webxr/VRButton.js';
import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
let DB;
let storage;
let user;
let connected = false;
let camera, scene, renderer;
let controller1, controller2;
let controllerGrip1, controllerGrip2;
const otherPlayers = {};
let testCube;
InitVRApp();
start();
function InitVRApp() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x505050);
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 10);
camera.position.set(0, 0, 0);
scene.add(new THREE.HemisphereLight(0x606060, 0x404040));
const light = new THREE.DirectionalLight(0xffffff);
light.position.set(1, 1, 1).normalize();
scene.add(light);
testCube = new Mesh(new BoxBufferGeometry(), new MeshPhongMaterial({ color: 0xffcc00 }));
testCube.position.z = -2
scene.add(testCube);
window.testCube = testCube;
//
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.xr.enabled = true;
document.body.appendChild(renderer.domElement);
//
document.body.appendChild(VRButton.createButton(renderer));
// controllers
function onSelectStart() {
this.userData.isSelecting = true;
}
function onSelectEnd() {
this.userData.isSelecting = false;
}
controller1 = renderer.xr.getController(0);
controller1.addEventListener('selectstart', onSelectStart);
controller1.addEventListener('selectend', onSelectEnd);
controller1.addEventListener('connected', function (event) {
this.add(buildController(event.data));
});
controller1.addEventListener('disconnected', function () {
this.remove(this.children[0]);
});
scene.add(controller1);
controller2 = renderer.xr.getController(1);
controller2.addEventListener('selectstart', onSelectStart);
controller2.addEventListener('selectend', onSelectEnd);
controller2.addEventListener('connected', function (event) {
this.add(buildController(event.data));
});
controller2.addEventListener('disconnected', function () {
this.remove(this.children[0]);
});
scene.add(controller2);
// The XRControllerModelFactory will automatically fetch controller models
// that match what the user is holding as closely as possible. The models
// should be attached to the object returned from getControllerGrip in
// order to match the orientation of the held device.
const controllerModelFactory = new XRControllerModelFactory();
controllerGrip1 = renderer.xr.getControllerGrip(0);
controllerGrip1.add(controllerModelFactory.createControllerModel(controllerGrip1));
scene.add(controllerGrip1);
controllerGrip2 = renderer.xr.getControllerGrip(1);
controllerGrip2.add(controllerModelFactory.createControllerModel(controllerGrip2));
scene.add(controllerGrip2);
//
window.addEventListener('resize', onWindowResize, false);
}
function buildController(data) {
let geometry, material;
switch (data.targetRayMode) {
case 'tracked-pointer':
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 0, - 1], 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute([0.5, 0.5, 0.5, 0, 0, 0], 3));
material = new THREE.LineBasicMaterial({ vertexColors: true, blending: THREE.AdditiveBlending });
return new THREE.Line(geometry, material);
case 'gaze':
geometry = new THREE.RingBufferGeometry(0.02, 0.04, 32).translate(0, 0, - 1);
material = new THREE.MeshBasicMaterial({ opacity: 0.5, transparent: true });
return new THREE.Mesh(geometry, material);
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function handleController(controller) {
if (controller.userData.isSelecting) {
}
}
//
function start() {
renderer.setAnimationLoop(render);
}
export function StartVRApp() {
connected = true;
storage = firebase.storage();
var pathReference = storage.ref('room.json');
const loader = new THREE.ObjectLoader();
pathReference.getDownloadURL().then((url) => {
loader.load(
// resource URL
'minecraft.json',
// onLoad callback
// Here the loaded data is assumed to be an object
function (obj) {
// Add the loaded object to the scene
scene.add(obj);
obj.position.y = -1.6;
},
// onProgress callback
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// onError callback
function (err) {
console.error('An error happened');
}
);
})
DB = firebase.database();
user = firebase.auth().currentUser;
const userref = DB.ref(`users/${user.uid}`);
userref.update({ status: "online" })
userref.onDisconnect().remove();
const testRef = DB.ref('test');
testRef.on('value', (snapshot) => {
const data = snapshot.val();
testCube.position.x = data.x;
testCube.position.y = data.y;
testCube.position.z = data.z;
})
//handle other players
const usersListRef = firebase.database().ref("users");
function updatePlayerData(key, data) {
otherPlayers[key].headMesh.position.x = data.head.x;
otherPlayers[key].headMesh.position.y = data.head.y;
otherPlayers[key].headMesh.position.z = data.head.z;
otherPlayers[key].headMesh.rotation.x = data.head.rx;
otherPlayers[key].headMesh.rotation.y = data.head.ry;
otherPlayers[key].headMesh.rotation.z = data.head.rz;
otherPlayers[key].hand1mesh.visible = data.hands[0].visible;
otherPlayers[key].hand1mesh.position.x = data.hands[0].x;
otherPlayers[key].hand1mesh.position.y = data.hands[0].y;
otherPlayers[key].hand1mesh.position.z = data.hands[0].z;
otherPlayers[key].hand2mesh.visible = data.hands[1].visible;
otherPlayers[key].hand2mesh.position.x = data.hands[1].x;
otherPlayers[key].hand2mesh.position.y = data.hands[1].y;
otherPlayers[key].hand2mesh.position.z = data.hands[1].z;
}
usersListRef.on("child_added", (data) => {
//don't need to render the mesh of the current user, only of other users
if (data.key == user.uid) return;
const material = new MeshPhongMaterial({ color: 0xffffff });
const headMesh = new Mesh(new BoxBufferGeometry(0.2, 0.2, 0.2), material);
scene.add(headMesh);
const hand1mesh = new Mesh(new BoxBufferGeometry(0.1, 0.1, 0.1), material);
scene.add(hand1mesh);
const hand2mesh = new Mesh(new BoxBufferGeometry(0.1, 0.1, 0.1), material);
scene.add(hand2mesh);
otherPlayers[data.key] = {
headMesh,
hand1mesh,
hand2mesh
};
updatePlayerData(data.key, data.val());
});
usersListRef.on("child_changed", (data) => {
//don't need to render the mesh of the current user, only of other users
if (data.key == user.uid) return;
if (otherPlayers[data.key] == undefined) return;
updatePlayerData(data.key, data.val());
});
usersListRef.on("child_removed", (data) => {
//don't need to render the mesh of the current user, only of other users
if (data.key == user.uid) return;
scene.remove(otherPlayers[data.key].headMesh);
scene.remove(otherPlayers[data.key].hand1mesh);
scene.remove(otherPlayers[data.key].hand2mesh);
delete otherPlayers[data.key];
});
}
function updateUserPos() {
const position = new THREE.Vector3();
position.setFromMatrixPosition(camera.matrixWorld);
const rotation = new THREE.Euler();
rotation.setFromRotationMatrix(camera.matrixWorld);
const userref = DB.ref(`users/${user.uid}`);
userref.update({
head: {
x: position.x,
y: position.y,
z: position.z,
rx: rotation.x,
ry: rotation.y,
rz: rotation.z,
visible: true,
},
hands: [
{
x: controller1.position.x,
y: controller1.position.y,
z: controller1.position.z,
visible: controller1.visible
},
{
x: controller2.position.x,
y: controller2.position.y,
z: controller2.position.z,
visible: controller2.visible
}
]
})
}
function render() {
if (connected) {
updateUserPos();
}
handleController(controller1);
handleController(controller2);
renderer.render(scene, camera);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment