Skip to content

Commit ba1c7a4

Browse files
authored
virtual-gamepad-controls component for mobile 2d locomotion (Hubs-Foundation#9)
Virtual gamepad controls component.
1 parent 00b8f45 commit ba1c7a4

File tree

10 files changed

+1290
-31
lines changed

10 files changed

+1290
-31
lines changed

.eslintrc.json

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
{
2-
"plugins": ["prettier"],
2+
"parserOptions": {
3+
"ecmaVersion": 2017,
4+
"sourceType": "module"
5+
},
6+
"plugins": [
7+
"prettier"
8+
],
39
"rules": {
4-
"prettier/prettier": "error"
10+
"prettier/prettier": "error",
11+
"prefer-const": "error",
12+
"no-var": "error"
513
},
6-
"extends": ["prettier"]
7-
}
14+
"extends": [
15+
"prettier"
16+
]
17+
}

.vscode/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
// Format on save for Prettier
3+
"editor.formatOnSave": true,
4+
// Disable html formatting for now
5+
"html.format.enable": false,
6+
// Disable the default javascript formatter
7+
"javascript.format.enable": false,
8+
// Use 'prettier-eslint' instead of 'prettier'. Other settings will only be fallbacks in case they could not be inferred from eslint rules.
9+
"prettier.eslintIntegration": true,
10+
// Enable eslint for JavaScript files.
11+
"eslint.enable": true
12+
}

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin",
1818
"naf-janus-adapter": "^0.1.4",
1919
"networked-aframe": "https://github.com/netpro2k/networked-aframe#bugfix/chrome/audio",
20+
"nipplejs": "^0.6.7",
2021
"pleasejs": "^0.4.2",
2122
"query-string": "^5.0.1"
2223
},
@@ -25,11 +26,14 @@
2526
"babel-loader": "^7.1.2",
2627
"babel-minify-webpack-plugin": "^0.2.0",
2728
"babel-preset-env": "^1.6.1",
29+
"css-loader": "^0.28.7",
30+
"eslint": "^4.10.0",
2831
"eslint-config-prettier": "^2.6.0",
2932
"eslint-plugin-prettier": "^2.3.1",
3033
"now": "^8.3.11",
3134
"pre-commit": "^1.2.2",
3235
"prettier": "^1.7.0",
36+
"style-loader": "^0.19.0",
3337
"webpack": "^3.6.0",
3438
"webpack-dev-server": "^2.9.3",
3539
"webpack-merge": "^4.1.0"

public/index.html

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@
44
<title>Mozilla Mixed Reality Social Client</title>
55
<script src="https://aframe.io/releases/0.7.0/aframe.js"></script>
66
<script src="./app.bundle.js"></script>
7+
<style>
8+
.a-enter-vr {
9+
top: 90px;
10+
bottom: auto;
11+
}
12+
</style>
713
</head>
814

915
<body>
1016
<a-scene
1117
networked-scene="adapter: janus;
12-
room: 2;
13-
serverURL: wss://quander.me:8989;
14-
audio: true;
15-
debug: true;
16-
connectOnLoad: false;"
18+
room: 2;
19+
serverURL: wss://quander.me:8989;
20+
audio: true;
21+
debug: true;
22+
connectOnLoad: false;"
1723
mute-mic="eventSrc: a-scene; toggleEvents: action_mute">
1824

1925
<a-assets>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
:local(.touchZone) {
2+
position: absolute;
3+
top: 0;
4+
bottom: 0;
5+
}
6+
7+
:local(.touchZone.left) {
8+
left: 0;
9+
right: 50%;
10+
}
11+
12+
:local(.touchZone.right) {
13+
left: 50%;
14+
right: 0;
15+
}
16+
17+
:local(.touchZone) .nipple {
18+
margin: 5vh 5vw;
19+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import nipplejs from "nipplejs";
2+
import styles from "./virtual-gamepad-controls.css";
3+
4+
const THREE = AFRAME.THREE;
5+
const DEGREES = Math.PI / 180;
6+
const HALF_PI = Math.PI / 2;
7+
8+
AFRAME.registerComponent("virtual-gamepad-controls", {
9+
schema: {
10+
movementSpeed: { default: 2 },
11+
lookSpeed: { default: 60 }
12+
},
13+
14+
init() {
15+
// Setup gamepad elements
16+
const leftTouchZone = document.createElement("div");
17+
leftTouchZone.classList.add(styles.touchZone, styles.left);
18+
document.body.appendChild(leftTouchZone);
19+
20+
const rightTouchZone = document.createElement("div");
21+
rightTouchZone.classList.add(styles.touchZone, styles.right);
22+
document.body.appendChild(rightTouchZone);
23+
24+
const leftStick = nipplejs.create({
25+
zone: leftTouchZone,
26+
mode: "static",
27+
color: "white",
28+
position: { left: "50px", bottom: "50px" }
29+
});
30+
31+
const rightStick = nipplejs.create({
32+
zone: rightTouchZone,
33+
mode: "static",
34+
color: "white",
35+
position: { right: "50px", bottom: "50px" }
36+
});
37+
38+
this.onJoystickChanged = this.onJoystickChanged.bind(this);
39+
40+
rightStick.on("move end", this.onJoystickChanged);
41+
leftStick.on("move end", this.onJoystickChanged);
42+
43+
this.leftTouchZone = leftTouchZone;
44+
this.rightTouchZone = rightTouchZone;
45+
this.leftStick = leftStick;
46+
this.rightStick = rightStick;
47+
48+
// Define initial state
49+
this.velocity = new THREE.Vector3();
50+
this.yaw = 0;
51+
52+
// Allocate matrices and vectors
53+
this.move = new THREE.Matrix4();
54+
this.trans = new THREE.Matrix4();
55+
this.transInv = new THREE.Matrix4();
56+
this.pivotPos = new THREE.Vector3();
57+
this.rotationAxis = new THREE.Vector3(0, 1, 0);
58+
this.yawMatrix = new THREE.Matrix4();
59+
this.rotationMatrix = new THREE.Matrix4();
60+
this.rotationInvMatrix = new THREE.Matrix4();
61+
this.camRotationMatrix = new THREE.Matrix4();
62+
this.camRotationInvMatrix = new THREE.Matrix4();
63+
64+
this.cameraEl = document.querySelector("[camera]");
65+
66+
this.onEnterVr = this.onEnterVr.bind(this);
67+
this.onExitVr = this.onExitVr.bind(this);
68+
this.el.sceneEl.addEventListener("enter-vr", this.onEnterVr);
69+
this.el.sceneEl.addEventListener("exit-vr", this.onExitVr);
70+
},
71+
72+
onJoystickChanged(event, joystick) {
73+
if (event.target.id === this.leftStick.id) {
74+
if (event.type === "move") {
75+
// Set velocity vector on left stick move
76+
const angle = joystick.angle.radian;
77+
const force = joystick.force < 1 ? joystick.force : 1;
78+
const x = Math.cos(angle) * force;
79+
const z = Math.sin(angle) * -force;
80+
this.velocity.set(x, 0, z);
81+
} else {
82+
this.velocity.set(0, 0, 0);
83+
}
84+
} else {
85+
if (event.type === "move") {
86+
// Set yaw angle on right stick move
87+
const angle = joystick.angle.radian;
88+
const force = joystick.force < 1 ? joystick.force : 1;
89+
this.yaw = Math.cos(angle) * -force;
90+
} else {
91+
this.yaw = 0;
92+
}
93+
}
94+
},
95+
96+
tick(t, dt) {
97+
const deltaSeconds = dt / 1000;
98+
const lookSpeed = THREE.Math.DEG2RAD * this.data.lookSpeed * deltaSeconds;
99+
const obj = this.el.object3D;
100+
const pivot = this.cameraEl.object3D;
101+
const distance = this.data.movementSpeed * deltaSeconds;
102+
103+
this.pivotPos.copy(pivot.position);
104+
this.pivotPos.applyMatrix4(obj.matrix);
105+
this.trans.setPosition(this.pivotPos);
106+
this.transInv.makeTranslation(
107+
-this.pivotPos.x,
108+
-this.pivotPos.y,
109+
-this.pivotPos.z
110+
);
111+
this.rotationMatrix.makeRotationAxis(this.rotationAxis, obj.rotation.y);
112+
this.rotationInvMatrix.makeRotationAxis(this.rotationAxis, -obj.rotation.y);
113+
this.camRotationMatrix.makeRotationAxis(
114+
this.rotationAxis,
115+
pivot.rotation.y
116+
);
117+
this.camRotationInvMatrix.makeRotationAxis(
118+
this.rotationAxis,
119+
-pivot.rotation.y
120+
);
121+
this.move.makeTranslation(
122+
this.velocity.x * distance,
123+
this.velocity.y * distance,
124+
this.velocity.z * distance
125+
);
126+
127+
this.yawMatrix.makeRotationAxis(this.rotationAxis, lookSpeed * this.yaw);
128+
129+
// Translate to middle of playspace (player rig)
130+
obj.applyMatrix(this.transInv);
131+
// Zero playspace (player rig) rotation
132+
obj.applyMatrix(this.rotationInvMatrix);
133+
// Zero camera (head) rotation
134+
obj.applyMatrix(this.camRotationInvMatrix);
135+
// Apply joystick translation
136+
obj.applyMatrix(this.move);
137+
// Apply joystick yaw rotation
138+
obj.applyMatrix(this.yawMatrix);
139+
// Reapply camera (head) rotation
140+
obj.applyMatrix(this.camRotationMatrix);
141+
// Reapply playspace (player rig) rotation
142+
obj.applyMatrix(this.rotationMatrix);
143+
// Reapply playspace (player rig) translation
144+
obj.applyMatrix(this.trans);
145+
146+
this.el.setAttribute("rotation", {
147+
x: obj.rotation.x * THREE.Math.RAD2DEG,
148+
y: obj.rotation.y * THREE.Math.RAD2DEG,
149+
z: obj.rotation.z * THREE.Math.RAD2DEG
150+
});
151+
this.el.setAttribute("position", obj.position);
152+
},
153+
154+
onEnterVr() {
155+
// Hide the joystick controls
156+
this.leftTouchZone.style.display = "none";
157+
this.rightTouchZone.style.display = "none";
158+
},
159+
160+
onExitVr() {
161+
// Show the joystick controls
162+
this.leftTouchZone.style.display = "block";
163+
this.rightTouchZone.style.display = "block";
164+
},
165+
166+
remove() {
167+
this.el.sceneEl.removeEventListener("entervr", this.onEnterVr);
168+
this.el.sceneEl.removeEventListener("exitvr", this.onExitVr);
169+
document.body.removeChild(this.leftTouchZone);
170+
document.body.removeChild(this.rightTouchZone);
171+
}
172+
});

src/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import "./components/nametag-transform";
1313
import "./components/avatar-customization";
1414
import "./components/mute-state-indicator";
1515
import "./components/hand-controls-visibility";
16+
import "./components/virtual-gamepad-controls";
1617

1718
import "./systems/personal-space-bubble";
1819

@@ -32,7 +33,12 @@ window.onSceneLoad = function() {
3233
}
3334

3435
if (!qs.stats || !/off|false|0/.test(qs.stats)) {
35-
scene.setAttribute('stats', true);
36+
scene.setAttribute("stats", true);
37+
}
38+
39+
if (AFRAME.utils.device.isMobile() || qs.gamepad) {
40+
const playerRig = document.querySelector("#player-rig");
41+
playerRig.setAttribute("virtual-gamepad-controls", {});
3642
}
3743

3844
let username = qs.name;

webpack.common.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ module.exports = {
1515
include: [path.resolve(__dirname, "src")],
1616
exclude: [path.resolve(__dirname, "node_modules")],
1717
loader: "babel-loader"
18+
},
19+
{
20+
test: /\.css$/,
21+
use: ["style-loader", "css-loader"]
1822
}
1923
]
20-
},
21-
resolve: {
22-
extensions: [".js"]
2324
}
2425
};

webpack.dev.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const common = require("./webpack.common");
55
module.exports = merge(common, {
66
devtool: "inline-source-map",
77
devServer: {
8-
contentBase: path.resolve(__dirname, "public")
8+
contentBase: path.resolve(__dirname, "public"),
9+
disableHostCheck: true
910
}
1011
});

0 commit comments

Comments
 (0)