Cleanups + comments

This commit is contained in:
Arne Keller 2022-01-31 13:26:35 +01:00
parent 8c2a8b6272
commit 711bc739ec
3 changed files with 76 additions and 106 deletions

View File

@ -4,7 +4,7 @@
<title>three.js webgl - tunnel racer</title> <title>three.js webgl - tunnel racer</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css"> <link type="text/css" rel="stylesheet" href="./main.css">
<link rel="icon" href="./favicon.png"> <link rel="icon" href="./favicon.png">
<script src="./gyronorm.complete.min.js"></script> <script src="./gyronorm.complete.min.js"></script>
</head> </head>
@ -18,6 +18,6 @@
<button id="start" disabled>Start</button> <button id="start" disabled>Start</button>
<script src="./three.min.js"></script> <script src="./three.min.js"></script>
<script src="./game.js"></script> <script src="./game.js"></script>
</body> </body>
</html> </html>

111
game.js
View File

@ -1,3 +1,9 @@
// ----------------------
// | setup. of three.js |
// ----------------------
// shaders for sphere outline
// https://stemkoski.github.io/Three.js/Shader-Glow.html
const vertexShaderGlow = ` const vertexShaderGlow = `
uniform vec3 viewVector; uniform vec3 viewVector;
uniform float c; uniform float c;
@ -17,7 +23,7 @@ void main()
vec3 vNormel = normalize( normalMatrix * viewVector ); vec3 vNormel = normalize( normalMatrix * viewVector );
intensity = pow( c - dot(vNormal, vNormel), p ); intensity = pow( c - dot(vNormal, vNormel), p );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
vColor = hsv2rgb(vec3(fract(gl_Position[2] / 270.0), 0.7, 0.7)); vColor = hsv2rgb(vec3(fract(gl_Position[2] / 270.0), 0.7, 0.7));
//vColor = vec3((sin(position[2] / 360.0) + 1.0) * 0.5, (sin(position[2] / 1000.0) + 1.0) * 0.5, (sin(position[2] / 70.0) + 1.0) * 0.5); //vColor = vec3((sin(position[2] / 360.0) + 1.0) * 0.5, (sin(position[2] / 1000.0) + 1.0) * 0.5, (sin(position[2] / 70.0) + 1.0) * 0.5);
//vColor = vec3(0.5, 0, 0.5); //vColor = vec3(0.5, 0, 0.5);
@ -34,6 +40,7 @@ void main()
} }
`; `;
// shaders for tunnel border
const fragmentShader = ` const fragmentShader = `
#include <common> #include <common>
@ -80,20 +87,33 @@ const vertexShader = `
} }
`; `;
const diameterOfTunnel = 100; // size of the tunnel
const tunnelRadius = 100;
// z interval of new obstacles
const spawnInterval = 40; const spawnInterval = 40;
// coordinate of newest obstacle
let lastSpawned = 0; let lastSpawned = 0;
// gyroscope variables
var gn; var gn;
let headSet = false; let headSet = false;
let gammaReference = 0.0; let gammaReference = 0.0;
let leftRightMove = 0.0; let leftRightMove = 0.0;
let upDownMove = 0.0; let upDownMove = 0.0;
var speed = 5.0; // current player speed
const initialSpeed = 5.0;
let speed = initialSpeed;
// score points
let score = 0; let score = 0;
// amount of sphere objects removed
let removed = 0; let removed = 0;
// player is actively racing
let running = false; let running = false;
// obstacles
const spheres = [];
// wall segments
const borders = [];
let frameCount = 0; let frameCount = 0;
function logger(text) { function logger(text) {
@ -101,14 +121,14 @@ function logger(text) {
} }
function init_gn() { function init_gn() {
var args = { const args = {
logger: logger logger: logger
}; };
gn = new GyroNorm(); gn = new GyroNorm();
gn.init(args).then(function() { gn.init(args).then(function() {
var isAvailable = gn.isAvailable(); const isAvailable = gn.isAvailable();
if (!isAvailable.deviceOrientationAvailable) { if (!isAvailable.deviceOrientationAvailable) {
console.log({ console.log({
message: 'Device orientation is not available.' message: 'Device orientation is not available.'
@ -196,11 +216,13 @@ document.getElementById("set_head").addEventListener("click", () => {
init_gn(); init_gn();
// setup three.js: materials, geometries, scene, lighting and camera
let scene, renderer; let scene, renderer;
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000); const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
// obstacle geometry and material
const geometrySphere = new THREE.SphereGeometry(11, 32, 32); const geometrySphere = new THREE.SphereGeometry(11, 32, 32);
const materialCube = new THREE.MeshBasicMaterial(); const materialSphere = new THREE.MeshBasicMaterial();
const customMaterial = new THREE.ShaderMaterial({ const customMaterial = new THREE.ShaderMaterial({
uniforms: { uniforms: {
"c": { "c": {
@ -217,14 +239,13 @@ const customMaterial = new THREE.ShaderMaterial({
} }
}, },
vertexShader: vertexShaderGlow, vertexShader: vertexShaderGlow,
//vertexColors: true,
fragmentShader: fragmentShaderGlow, fragmentShader: fragmentShaderGlow,
side: THREE.FrontSide, side: THREE.FrontSide,
blending: THREE.AdditiveBlending, blending: THREE.AdditiveBlending,
transparent: true transparent: true
}); });
// wall texture
const loader = new THREE.TextureLoader(); const loader = new THREE.TextureLoader();
const texture = loader.load('./bayer.png'); const texture = loader.load('./bayer.png');
texture.minFilter = THREE.NearestFilter; texture.minFilter = THREE.NearestFilter;
@ -249,9 +270,6 @@ const material = new THREE.ShaderMaterial({
}); });
const borderGeometry = new THREE.PlaneGeometry(1, 1); const borderGeometry = new THREE.PlaneGeometry(1, 1);
const cubes = [];
const borders = [];
init(); init();
animate(); animate();
@ -264,12 +282,13 @@ document.getElementById("start").onclick = () => {
} }
document.getElementById("start").style.zIndex = -10; document.getElementById("start").style.zIndex = -10;
document.getElementById("start").style.visibility = "hidden"; document.getElementById("start").style.visibility = "hidden";
speed = 5.0; // reset variables
speed = initialSpeed;
score = 0; score = 0;
for (let i = 0; i < cubes.length; i++) { for (let i = 0; i < spheres.length; i++) {
scene.remove(cubes[i]); scene.remove(spheres[i]);
} }
cubes.length = 0; spheres.length = 0;
removed = 0; removed = 0;
running = true; running = true;
} }
@ -284,8 +303,6 @@ function gameOver() {
function init() { function init() {
//
scene = new THREE.Scene(); scene = new THREE.Scene();
camera.position.set(0, 10, 25); camera.position.set(0, 10, 25);
@ -318,6 +335,7 @@ function onWindowResize() {
} }
// main render/update function called once per frame
function animate() { function animate() {
requestAnimationFrame(animate); requestAnimationFrame(animate);
@ -327,18 +345,19 @@ function animate() {
if (running) { if (running) {
if (Math.abs(camera.position.x) > diameterOfTunnel || Math.abs(camera.position.y) > diameterOfTunnel) { if (Math.abs(camera.position.x) > tunnelRadius || Math.abs(camera.position.y) > tunnelRadius) {
// out of bounds // out of bounds
gameOver(); gameOver();
} }
for (let i = 0; i < cubes.length; i++) { // collision checks
if (cubes[i].material !== materialCube) { for (let i = 0; i < spheres.length; i++) {
if (spheres[i].material !== materialSphere) {
continue; continue;
} }
let x1 = cubes[i].position.x; let x1 = spheres[i].position.x;
let y1 = cubes[i].position.y; let y1 = spheres[i].position.y;
let z1 = cubes[i].position.z; let z1 = spheres[i].position.z;
let x2 = camera.position.x; let x2 = camera.position.x;
let y2 = camera.position.y; let y2 = camera.position.y;
let z2 = camera.position.z; let z2 = camera.position.z;
@ -348,7 +367,7 @@ function animate() {
z2 = Math.max(z1, z2 - speed); z2 = Math.max(z1, z2 - speed);
} }
let dist_squared = (x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2; let dist_squared = (x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2;
if (dist_squared <= (cubes[i].geometry.parameters.radius * cubes[i].scale.x) ** 2) { if (dist_squared <= (spheres[i].geometry.parameters.radius * spheres[i].scale.x) ** 2) {
gameOver(); gameOver();
break; break;
} }
@ -357,6 +376,7 @@ function animate() {
if (running) { if (running) {
// advance player position, increase speed, handle movement input
camera.position.z -= 0.5 * speed; camera.position.z -= 0.5 * speed;
speed += 0.01; speed += 0.01;
if (!Number.isNaN(leftRightMove)) { if (!Number.isNaN(leftRightMove)) {
@ -366,16 +386,17 @@ function animate() {
camera.position.y += 0.1 * upDownMove; camera.position.y += 0.1 * upDownMove;
} }
// create new obstacles as needed
while (camera.position.z < lastSpawned - spawnInterval) { while (camera.position.z < lastSpawned - spawnInterval) {
lastSpawned -= spawnInterval; lastSpawned -= spawnInterval;
// randomly spawn large spheres // randomly spawn large spheres
let scale = Math.random() < 0.1 ? 5.0 : 1.0; let scale = Math.random() < 0.1 ? 5.0 : 1.0;
const meshCube = new THREE.Mesh(geometrySphere, materialCube); const meshCube = new THREE.Mesh(geometrySphere, materialSphere);
meshCube.position.z = camera.position.z - speed * 210; meshCube.position.z = camera.position.z - speed * 210;
meshCube.position.x = (2 * Math.random() - 1.0) * diameterOfTunnel; meshCube.position.x = (2 * Math.random() - 1.0) * tunnelRadius;
meshCube.position.y = (2 * Math.random() - 1.0) * 0.9 * diameterOfTunnel; meshCube.position.y = (2 * Math.random() - 1.0) * 0.9 * tunnelRadius;
meshCube.scale.multiplyScalar(scale); meshCube.scale.multiplyScalar(scale);
const outlineMesh = new THREE.Mesh(geometrySphere, customMaterial); const outlineMesh = new THREE.Mesh(geometrySphere, customMaterial);
@ -385,19 +406,20 @@ function animate() {
outlineMesh.position.y = meshCube.position.y; outlineMesh.position.y = meshCube.position.y;
meshCube.add(outlineMesh); meshCube.add(outlineMesh);
scene.add(outlineMesh); scene.add(outlineMesh);
cubes.push(outlineMesh); spheres.push(outlineMesh);
scene.add(meshCube); scene.add(meshCube);
cubes.push(meshCube); spheres.push(meshCube);
} }
} }
renderer.render(scene, camera); renderer.render(scene, camera);
for (let i = 0; i < cubes.length; i++) { // clean up game elements behind the camera
if (cubes[i].position.z > camera.position.z + cubes[i].geometry.parameters.radius * cubes[i].scale.x + 20) { for (let i = 0; i < spheres.length; i++) {
scene.remove(cubes[i]); if (spheres[i].position.z > camera.position.z + spheres[i].geometry.parameters.radius * spheres[i].scale.x + 20) {
cubes.splice(i, 1); scene.remove(spheres[i]);
spheres.splice(i, 1);
i--; i--;
removed++; removed++;
} }
@ -407,44 +429,47 @@ function animate() {
removed = 0; removed = 0;
document.getElementById("score").innerText = score; document.getElementById("score").innerText = score;
} }
// create new wall segments on demand
if (borders.length == 0 || borders[borders.length - 1].position.z - camera.position.z > -1200) { if (borders.length == 0 || borders[borders.length - 1].position.z - camera.position.z > -1200) {
const newZ = borders.length == 0 ? -200 : borders[borders.length - 1].position.z - diameterOfTunnel * 2; const newZ = borders.length == 0 ? -200 : borders[borders.length - 1].position.z - tunnelRadius * 2;
let border = new THREE.Mesh(borderGeometry, material); let border = new THREE.Mesh(borderGeometry, material);
//border.position.x = -70; //border.position.x = -70;
border.position.y = -diameterOfTunnel; border.position.y = -tunnelRadius;
border.rotation.x = -Math.PI / 2; border.rotation.x = -Math.PI / 2;
border.position.z = newZ; border.position.z = newZ;
border.scale.multiplyScalar(diameterOfTunnel * 2); border.scale.multiplyScalar(tunnelRadius * 2);
scene.add(border); scene.add(border);
borders.push(border); borders.push(border);
border = new THREE.Mesh(borderGeometry, material); border = new THREE.Mesh(borderGeometry, material);
border.position.y = diameterOfTunnel; border.position.y = tunnelRadius;
border.rotation.x = Math.PI / 2; border.rotation.x = Math.PI / 2;
border.position.z = newZ; border.position.z = newZ;
border.scale.multiplyScalar(diameterOfTunnel * 2); border.scale.multiplyScalar(tunnelRadius * 2);
scene.add(border); scene.add(border);
borders.push(border); borders.push(border);
border = new THREE.Mesh(borderGeometry, material); border = new THREE.Mesh(borderGeometry, material);
border.position.x = -diameterOfTunnel; border.position.x = -tunnelRadius;
border.rotation.y = Math.PI / 2; border.rotation.y = Math.PI / 2;
border.position.z = newZ; border.position.z = newZ;
border.scale.multiplyScalar(diameterOfTunnel * 2); border.scale.multiplyScalar(tunnelRadius * 2);
scene.add(border); scene.add(border);
borders.push(border); borders.push(border);
border = new THREE.Mesh(borderGeometry, material); border = new THREE.Mesh(borderGeometry, material);
border.position.x = diameterOfTunnel; border.position.x = tunnelRadius;
border.rotation.y = -Math.PI / 2; border.rotation.y = -Math.PI / 2;
border.position.z = newZ; border.position.z = newZ;
border.scale.multiplyScalar(diameterOfTunnel * 2); border.scale.multiplyScalar(tunnelRadius * 2);
scene.add(border); scene.add(border);
borders.push(border); borders.push(border);
} }
// clean up wall segments behind the camera
for (let i = 0; i < borders.length; i++) { for (let i = 0; i < borders.length; i++) {
if (borders[i].position.z > camera.position.z + diameterOfTunnel + 50) { if (borders[i].position.z > camera.position.z + tunnelRadius + 50) {
scene.remove(borders[i]); scene.remove(borders[i]);
borders.splice(i, 1); borders.splice(i, 1);
i--; i--;

View File

@ -1,7 +1,7 @@
body { body {
margin: 0; margin: 0;
background-color: #000; background-color: #000;
font-family: Monospace; font-family: monospace;
overscroll-behavior: none; overscroll-behavior: none;
font-size: xxx-large; font-size: xxx-large;
@ -12,23 +12,17 @@ body {
-0.07em 0 black, -0.07em 0 black,
0 -0.07em black; 0 -0.07em black;
} }
#start { #start {
width: 50vw; width: 50vw;
height: 20vh; height: 20vh;
font-size: 15vh;
/* center button */
position: absolute; position: absolute;
top: 50vh; top: 50vh;
left: 50vw; left: 50vw;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
font-size: 15vh;
}
a {
color: #ff0;
text-decoration: none;
}
a:hover {
text-decoration: underline;
} }
button { button {
@ -43,56 +37,7 @@ button {
padding: 10px; padding: 10px;
box-sizing: border-box; box-sizing: border-box;
text-align: center; text-align: center;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
pointer-events: none; pointer-events: none;
z-index: 1; /* TODO Solve this in HTML */ z-index: 1;
} }
a, button, input, select {
pointer-events: auto;
}
.dg.ac {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
z-index: 2 !important; /* TODO Solve this in HTML */
}
#overlay {
position: absolute;
font-size: 16px;
z-index: 2;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: rgba(0,0,0,0.7);
}
#overlay button {
background: transparent;
border: 0;
border: 1px solid rgb(255, 255, 255);
border-radius: 4px;
color: #ffffff;
padding: 12px 18px;
text-transform: uppercase;
cursor: pointer;
}
#notSupported {
width: 50%;
margin: auto;
background-color: #f00;
margin-top: 20px;
padding: 10px;
}