Open Source WebXR game using aframe.io (webVR framework) Full Code available on Github Page.
Live Demo - https://chessvr.glitch.me/
- Web Browser (Chrome)
- Set up Glitch Account - www.glitch.com
- Add 3D models in GLTF or glb format in the asset folder. Also, you can add any Images, videos, materials in asset folder for reference (Check File Size limit)
- Inside
/public
folderindex.html
is where all the code will go and/js
folder for all javascript functions / components.
Let's define our goal for the project and scene. In order visualize our concept, some of following assets we need
-
Environment
-
3D Models of Chess
-
Controllers
Similar to normal Web HTML Pages. We have
<head>
tag (To import all our dependencies)<body>
tag(All entities and assets)
- Let's set up our floor / ground
In a-frame, we can define a <a-plane>
For example we can follow the docs from a-frame Getting Started
<a-plane
position="0 0 -4"
rotation="-90 0 0"
width="4"
height="4"
color="#7BC8A4"
></a-plane>
Watch Tutorial
- For our project we need a real ground, We decided use a Ground texture
<html>
<head>
<title>Chess VR - Let's play chess in WEBXR</title>
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<img
id="ground"
src="https://cdn.glitch.com/97890fbd-889b-466f-a357-23627a83de50%2FTexturesCom_Carpet0020_1_seamless_S.jpg?v=1612295412711"
/>
</a-assets>
<!-- Ground -->
<a-plane
material="color: #FFFFFFF; src: #ground; repeat : 5 5;"
rotation="-90 0 0"
scale="10 10 1"
>
</a-plane>
</a-scene>
</body>
</html>
- Let's add a open-source
aframe-environment compoment
from Diego F. Goberna to easily create background scene for us!
We used a environment="preset forest"
forest envrionement but we can do a bit more to add more customization to match our scene. For example, adding and removing different aspects of the scene
<!-- Environment -->
<a-entity
environment="preset: forest; playArea: 2; active: true; seed: 8;
skyType: gradient;
skyColor: #ffffff; // Change the color of the sky
horizonColor: #87CEEB;
fog: 0.5;
groundYScale: 4.18; groundTexture: squares; groundColor: #43994b; //Change ground color
groundColor2: #43994b; dressing: trees; dressingAmount: 500;
dressingColor: #43994b; dressingScale: 1;
gridColor: #43994b"
></a-entity>
A Tip to easily change parameters
- Use A-frame Inspector Please
ctrl+alt+i
on a scene and it will open an inspector, then on the right side change the parameter. Copy the changes and paste it to your html.
- Let's add 3d Objects, we can use models our custom assets or use Sketchfab
We have a bench and our chess board, For WebVR we need .gltf
or .glb
file
Similar to the code from aframe docs
<a-scene>
<a-assets>
<a-asset-item id="tree" src="tree.gltf"></a-asset-item>
</a-assets>
<!-- Using the asset management system. -->
<a-gltf-model src="#tree"></a-gltf-model>
<!-- Defining the URL inline. Not recommended but more comfortable for web developers. -->
<a-gltf-model src="tree.gltf"></a-gltf-model>
</a-scene>
- By using the
Aframe inspector
we position each chess pieces into proper spaces
- Adding Quest Controllers to move and Teleport in our scene
We import aframe-teleport-controls
by Fernando Serrano Github link
Inside the <head>
tag we simply copy and past this cdn script
<script src="https://rawgit.com/fernandojsg/aframe-teleport-controls/master/dist/aframe-teleport-controls.min.js"></script>
WARNING
The implementation for quest controllers doesn't quite work from the github page.
Luckily Takashi Yoshinaga create a great implementation and showed how to implement in his demo - https://quest-demo.glitch.me
Using Takashi implementation, since we already imported the aframe-teleport
we add cameraRig entity
<a-entity id="cameraRig">
<a-entity id="head" camera wasd-controls look-controls position="0 1.6 0">
</a-entity>
<a-entity id="ctlL"
teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; startEvents: teleportstart; endEvents: teleportend"
raycaster="objects: .collidable; far:1.2; " oculus-touch-controls="hand: left" input-listen>
<a-text value="Press and let go \nX: to Move" position="0 0.08 0"
rotation="-90 0 0" scale="0.1 0.1 0.1" align="center" color="#FFFFFF"></a-text>
<a-sphere radius="0.01" position="0 0 -1.2" color="#FFFFFF"></a-sphere>
</a-entity>
<a-entity id="ctlR" raycaster="objects: .collidable; far:1.2; " oculus-touch-controls="hand: right" input-listen>
<a-text value="Use Grip to interact"
position="0 0.08 0" rotation="-90 0 0" scale="0.1 0.1 0.1" align="center" color="#FFFFFF"></a-text>
<a-sphere radius="0.01" position="0 0 -1.2" color="#FFFFFF"></a-sphere>
</a-entity>
</a-entity>
And we need to add EventListener script
<script>
AFRAME.registerComponent('input-listen', {
init:
function () {
//Declaration and initialization of flag
//which exprains grip button is pressed or not.
//"this.el" reffers ctlR or L in this function
this.el.grip = false;
//Grip Pressed
this.el.addEventListener('gripdown', function (e) {
//Setting grip flag as true.
this.grip = true;
});
//Grip Released
this.el.addEventListener('gripup', function (e) {
//Setting grip flag as false.
this.grip = false;
});
//Raycaster intersected with something.
this.el.addEventListener('raycaster-intersection', function (e) {
//Store first selected object as selectedObj
this.selectedObj = e.detail.els[0];
});
//Raycaster intersection is finished.
this.el.addEventListener('raycaster-intersection-cleared', function (e) {
//Clear information of selectedObj
this.selectedObj = null;
});
//A-buttorn Pressed
this.el.addEventListener('abuttondown', function (e) {
//Start pointing position to teleport
this.emit('teleportstart');
});
this.el.addEventListener('abuttonup', function (e) {
//Jump to pointed position
this.emit('teleportend');
});
//X-buttorn Pressed
this.el.addEventListener('xbuttondown', function (e) {
//Start pointing position to teleport
this.emit('teleportstart');
});
//X-buttorn Released
this.el.addEventListener('xbuttonup', function (e) {
//Jump to pointed position
this.emit('teleportend');
});
//console.log(this);
},
//called evry frame.
tick: function () {
if (!this.el.selectedObj) { return; }
if (!this.el.grip) { return; }
//Getting raycaster component which is attatched to ctlR or L
//this.el means entity of ctlR or L in this function.
var ray = this.el.getAttribute("raycaster").direction;
//setting tip of raycaster as 1.1m forward of controller.
var p = new THREE.Vector3(ray.x, ray.y, ray.z);
p.normalize();
p.multiplyScalar(1.2);
//Convert local position into world coordinate.
this.el.object3D.localToWorld(p);
//Move selected object to follow the tip of raycaster.
this.el.selectedObj.object3D.position.set(p.x, p.y, p.z);
}
});
</script>
Since we need to add hands and being able to pick up chess pieces we need aframe-physics
and hands
- @Will Murphy to the rescue with aframe-super-hands-component
Also - Kyle Baker helped us simplify the implementation - > c-frame/aframe-super-hands-component#188
Per Kyle instruction
-
Import the components in head
<script src="https://unpkg.com/super-hands/dist/super-hands.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/n5ro/aframe-physics-system@v4.0.1/dist/aframe-physics-system.js"></script> <script src="https://unpkg.com/aframe-event-set-component@^4.1.1/dist/aframe-event-set-component.min.js"></script> <script src="https://unpkg.com/aframe-physics-extras/dist/aframe-physics-extras.min.js"></script>
-
Then add Script
<script>
AFRAME.registerComponent("phase-shift", {
init: function() {
var el = this.el;
el.addEventListener("gripdown", function() {
el.setAttribute("collision-filter", { collisionForces: true });
});
el.addEventListener("gripup", function() {
el.setAttribute("collision-filter", { collisionForces: false });
});
}
});
</script>
- Add Mixin inside your
<assets>
tag
<a-mixin
id="all-interactions"
hoverable
grabbable
stretchable
draggable
event-set__hoveron="_event: hover-start; material.opacity: 0.7; transparent: true"
event-set__hoveroff="_event: hover-end; material.opacity: 1; transparent: false"
dynamic-body
></a-mixin>
<a-mixin
id="grab-move"
hoverable
grabbable
draggable
event-set__hoveron="_event: hover-start; material.opacity: 0.7; transparent: true"
event-set__hoveroff="_event: hover-end; material.opacity: 1; transparent: false"
dynamic-body="shape: box; sphereRadius: 0.1"
></a-mixin>
<a-mixin
id="physics-hands"
physics-collider
phase-shift
collision-filter="collisionForces: false"
static-body="shape: sphere; sphereRadius: 0.02"
super-hands="colliderEvent: collisions;
colliderEventProperty: els;
colliderEndEvent: collisions;
colliderEndEventProperty: clearedEls;"
></a-mixin>
- Now we need to tell our models to get mixins
<a-gltf-model
mixin="all-interactions"
class="collidable"
src="#knight"
scale="0.03 0.03 0.03"
position="-0.38701 1.08131 -2.44533"
>
</a-gltf-model>
<a-gltf-model
mixin="all-interactions"
src="#queen"
scale="0.03 0.03 0.03"
position="0.07661 1.08131 -2.44533"
>
- Lastly we need to mixin, hands and script to the CameraRig
<a-entity
id="ctlL"
teleport-controls="cameraRig: #cameraRig; teleportOrigin: #head; startEvents: teleportstart; endEvents: teleportend"
raycaster="objects: .collidable; far:1.2; "
hand-controls="hand: left"
mixin="physics-hands" <-updated
input-listen
phase-shift <- updated
>
- Adding
aframe-physics
to interact with chess pieces - Multiplayer