Created
June 10, 2021 15:23
-
-
Save foopis23/2f335a3e2c257787de72242a92bb36a6 to your computer and use it in GitHub Desktop.
Simple Multiplayer with THREE JS & Firebase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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