This is an iterative review of this question: 3D 1st Person Snake Game #1 The next iteration can be found here: 3D 1st Person Snake Game #3 Here is a snippet of the code:
<html> <style> canvas{ position:fixed; left:0; top:0; width:99%; height:99%; } * { padding:0px; margin:0px } #score,#speed,#highscore,#maxspeed,#lag,#debug{ position: fixed; z-index: 100; font-size:20px; font-family:Verdana; left:15px; width: 100%; } #gameover{ position:absolute; z-index: 100; font-size:60px; font-family:Verdana; margin: 0; top: 50%; left: 50%; opacity:0; transform: translate(-50%, -50%); } #maxspeed, #score, #josh, #speed, #highscore, #lag { display:block; } #maxspeed { width: 100%; top:50px; display:none; } #score { top: 10px; } #speed { top: 30px; } #highscore { top:30px; } #lag { top: 70px; } </style> <div id="stats"> <div id="debug"></div> <div id="score">Score: 0</div> <div id="speed">Speed: 1</div> <div id="highscore">Highscore: 1</div> <div id="maxspeed">Highest Speed: 1</div> <span id="lag">Lag: 0ms</span> <div id="gameover" align="center">Game Over</div> </div> <div id="canvas"></div> <p id="p"></p> <p id="e"></p> <script type="text/javascript" src="https://cdn.rawgit.com/alexgibson/shake.js/master/shake.js"></script> <script src="https://threejs.org/build/three.min.js"></script> <script> //======================== // One times //======================== const random = Math.random, floor = function(a){ return ~~a}, newGeometry = THREE.Geometry, newBufferGeometry = THREE.BufferGeometry, newMesh = THREE.Mesh, newLineSegments = THREE.LineSegments, newMeshBasicMaterial = THREE.MeshBasicMaterial, newVector3 = THREE.Vector3, newLineBasicMaterial = THREE.LineBasicMaterial, newBoxGeometry = THREE.BoxGeometry, newBoxBufferGeometry = THREE.BoxBufferGeometry, arena = new THREE.Group(), applegeometry = new THREE.SphereBufferGeometry( 0.5, 32, 32 ), superapplematerial= new newMeshBasicMaterial({color:"gold"}), applematerial= new newMeshBasicMaterial({color:"limegreen"}), floormaterial = new newMeshBasicMaterial({color:"lightgrey"}), wallmaterial = new newMeshBasicMaterial( { color: "grey" } ), linecube = new newBoxBufferGeometry( 1,1,1 ), edges = new THREE.EdgesGeometry( linecube ), geometry = new newBoxBufferGeometry( 1,1,1 ), stringify = JSON.stringify </script> <script id="script"> (function(THREE,ArenaWidth, ArenaLength){ 'use strict' const arenaLines = new THREE.Group(), arenaWall = new newGeometry(), arenaFloor = new newGeometry(), wall1 = new newBoxGeometry( ArenaWidth+1, 1, 1 ) .translate((ArenaWidth/2)-2,1,-2); arenaWall.merge(wall1); arenaWall.merge(wall1.translate(0,0, ArenaLength)); const wall2 = new newBoxGeometry( 1, 1, ArenaLength+1 ).translate(ArenaWidth-2,1,(ArenaLength/2)-2); arenaWall.merge(wall2); arenaWall.merge(wall2.translate(-ArenaWidth,0,0)); arenaFloor.merge(new newBoxGeometry( ArenaWidth+1, 1, ArenaLength+1 ).translate((ArenaWidth/2)-2,0,(ArenaLength/2)-2)) arenaWall.mergeVertices() arenaFloor.mergeVertices() // Get the meshs window.arenaWallMesh = new newMesh(arenaWall, wallmaterial); window.arenaFloorMesh = new newMesh(arenaFloor, floormaterial); var speed = 100, maxwidth = 40, maxdepdth = 40, size = 0.5, score = 0, maxheight = 0, lagNum = 0, highscoreNum, showlag, maxspeedNum, showmaxspeed, showhighscore //======================== // Threejs //======================== var scene = new THREE.Scene(), camera= new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,60), renderer = new THREE.WebGLRenderer(), extrasLookup = { 1:30, 2:50, 3:70, 4:90 } //======================== // Reductions //======================== const newScene = THREE.Scene if(true /*localStorage.getItem("3dhighscore")==undefined*/){ highscoreNum=0 } else { highscoreNum = localStorage.getItem("3dhighscore") } if(true /*localStorage.getItem("showlag")==undefined*/){ showlag=false } else { showlag = localStorage.getItem("showlag") } if(true /*localStorage.getItem("3dmaxspeed")==undefined*/){ maxspeedNum=100 } else { maxspeedNum = localStorage.getItem("3dmaxspeed") } if(showmaxspeed==undefined){ showmaxspeed=false } if(showhighscore==undefined){ showhighscore=false } renderer.setSize( window.innerWidth, window.innerHeight ); document.getElementById("canvas").appendChild( renderer.domElement ); function animate() { requestAnimationFrame( animate ) renderer.render( scene, camera ); } class Location{ constructor(x,y,z){ return [x,y,z] } } //======================== // Dom //======================== const maxSpeedDom = document.getElementById("maxspeed"), maxSpeedDomStyle = maxSpeedDom.style, lagdom = document.getElementById("lag"), lagdomstyle = lagdom.style, highScoreDom = document.getElementById("highscore"), highScoreDomStyle = highScoreDom.style, scoredom = document.getElementById("score"), speeddom = document.getElementById("speed"), gameOverDom = document.getElementById("gameover"), gameOverDomStyle = gameOverDom.style; //======================== // Snake //======================== class Snake { constructor(){ this.segments = [ new Location(7,1,5), new Location(6,1,5), new Location(5,1,5) ]; this.direction = "forwards"; this.nextDirection = "forwards"; this.nextHead = new Location(8,1,5); } draw(){ let snakeobject = new THREE.Group(); let j = this.segments.length this.segments.forEach((item,index) => { let i = index let segmentColor=`rgb(${255-(255/j|0)*index+randomNumberBetween(-(j-index),j-index)},00,00)` const cube = new newMesh(geometry, new newMeshBasicMaterial({ color: segmentColor})), x = item[0], y = item[1], z = item[2]; let pos = new newVector3(x,y,z) const line = new newLineSegments(edges, new newLineBasicMaterial({ color: segmentColor,linewidth:2 })); cube.position.copy(pos); line.position.copy(pos); snakeobject.add(line,cube); }) scene.add(snakeobject); } move() { this.segments.unshift(this.nextHead); if(this.checkCollision()) { this.segments.shift(); gameOver(); return; }; var nextHeadPos = this.nextHead; this.direction = this.nextDirection; (directionSwitch[this.direction])(nextHeadPos) if(stringify(this.segments[0]) === stringify(apple.pos)) { speed=0.95*speed; score++; if(apple.type === "superapple") { speed = 1.10*speed; score += 4; } apple.move(); } else { this.segments.pop(); }; } changeDirection(key) { switch(key){ case 39: case 68: case 37: case 65: keyLookup[key][this.direction]() break; case 192: case 55: case 56: keyLookup[key]() break; } } checkCollision() { const head = this.segments[0] const headz = head[2]; const headx = head[0]; const wallCollision = headz === -2 || headz === ArenaLength-2 || headx === -2 || headx === ArenaWidth-2; var selfCollision = false; this.segments.forEach(function(item, index) { if(index) { if(item[0] === head[0] && item[2] === head[2]) { selfCollision = true; }; }; }); const collision = selfCollision || wallCollision; return collision; } } //======================== // Drawing //======================== function drawStats() { var extras = 1; if(score > highscoreNum) { highscoreNum = score; //localStorage.setItem("3dhighscore", highscore); } if(speed < maxspeed) { maxspeedNum = speed; //localStorage.setItem("3dmaxspeed", maxspeed); } scoredom.innerHTML=`Score: ${score}`; speeddom.innerHTML=`Speed: ${(((100/speed*100)|0)/100)}`; if(showhighscore) { extras++; highscore.style.display = "block"; highscore.style.top = extrasLookup[extras] highscore.innerHTML = `Highscore: ${highscoreNum}`; } else { highScoreDomStyle.display = "none"; }; if(showmaxspeed) { extras++; maxspeed.style.display = "block"; maxspeed.style.top = extrasLookup[extras] maxspeed.innerHTML = `Highest Speed: ${(((100/maxspeed*100)|0)/100)}`; } else { maxspeed.style.display = "none"; }; if(showlag) { extras++; lag.style.display = "block"; lag.style.top = extrasLookup[extras] lag.innerHTML = `Lag: ${lagNum}ms`; } else { lagdomstyle.display = "none"; }; } function renderArena() { ; } //======================== // Apple //======================== class Apple { constructor(){ this.pos = new Location(2,1,2) } move(){ // Set the location to a random x and z. var x = randomNumberBetween(0,ArenaWidth-4) var z = randomNumberBetween(0,ArenaLength-4) this.pos = new Location(x,1,z) if(randomNumberBetween(1,11)===1&&score>15){ apple.type="superapple" } else { apple.type="apple" } } draw() { var sphere // Generate a new sphere. if(this.type=="superapple") { sphere = new newMesh( applegeometry, superapplematerial ); } else { sphere = new newMesh( applegeometry, applematerial ); } // Get the data location. var applepos=apple.pos // Set the location of the visual apple to the data location. sphere.position.set(applepos[0],applepos[1],applepos[2]) scene.add(sphere); } } //======================== // Misc //======================== function newGame(key) { if(key==82) { // Stop the game. clearTimeout(gameLoop); s=function(){}; // Start a new game. var game = document.getElementById("canvas"); game.removeChild(game.childNodes[0]); eval(document.getElementById("script").innerHTML); } } gameOverDomStyle.opacity=0; function gameOver() { s = function() { camera.position.copy(new newVector3((ArenaWidth/2)-2,Math.sqrt( Math.pow(ArenaWidth/2,2)+Math.pow(ArenaLength/2,2)),(ArenaLength/2)-2)); camera.lookAt(new newVector3((ArenaWidth/2)-2,0,(ArenaLength/2)-2)); }; document.onkeydown=function(e) { newGame(e.keyCode); } //listen to shake event var shakeEvent = new Shake({threshold: 15}); shakeEvent.start(); window.addEventListener('shake', function(){ newGame(82) }, false); //stop listening function stopShake(){ shakeEvent.stop(); } //check if shake is supported or not. if(!("ondevicemotion" in window)){alert("Not Supported");} gameOverDomStyle.opacity = 1; } function randomNumberBetween(a,b) { return a+floor(random()*(b-a)); } var apple = new Apple(), snake = new Snake() var directionSwitch={ "forwards":function(a){this.nextHead = new Location(a[0]+1, a[1], a[2])}, "backwards":function(a){this.nextHead = new Location(a[0]-1, a[1], a[2])}, "left":function(a){this.nextHead = new Location(a[0], a[1], a[2]-1)}, "right":function(a){this.nextHead = new Location(a[0], a[1], a[2]+1)} } for(var x in directionSwitch){ directionSwitch[x] = directionSwitch[x].bind(snake) } var right = { "right":function(){this.nextDirection = "backwards"}, "backwards":function(){this.nextDirection = "left"}, "forwards":function(){this.nextDirection = "right"}, "left":function(){this.nextDirection = "forwards"} } for(var x in right){ right[x] = right[x].bind(snake) } var left = { "right":function(){snake.nextDirection = "forwards"}, "forwards":function(){snake.nextDirection = "left"}, "left":function(){snake.nextDirection = "backwards"}, "backwards":function(){snake.nextDirection = "right"} } for(var x in left){ left[x] = left[x].bind(snake) } var keyLookup = { "39":right, "68":right, "37":left, "65":left, "192":function(){showlag=!showlag}, "55":function(){showhighscore=!showhighscore}, "56":function(){showmaxspeed = !showmaxspeed} } snake.draw(); apple.move(); var background = new THREE.Color( "white" ), fog = new THREE.FogExp2( "random", 0.02625 ); let s = function() { // Start for lag detection. var start = performance.now(); // Make a fresh scene. scene = new newScene(); // Move the snake. snake.move(); // Render various things onto the scene. scene.add(arenaWallMesh,arenaFloorMesh) snake.draw(); apple.draw(); drawStats(); // Get relevent snake parts. let head = snake.segments[0]; let nextHead = snake.nextHead; // Set camera position and target. camera.position.set(head[0],head[1],head[2]); camera.lookAt(new newVector3(nextHead[0],nextHead[1],nextHead[2])); // Set background and fog. scene.background = background; scene.fog = fog; let end = performance.now(); // Reschedule the next update gameLoop = setTimeout(function(){ requestAnimationFrame(s) },speed) // End for lag detection and calculation of lag. lagNum = floor((end-start)) } animate() var gameLoop = setTimeout(function(){ requestAnimationFrame(s) },speed); document.onkeydown = function(e) { snake.changeDirection(e.keyCode); } })(window.THREE, (((window.innerWidth/10)|0)*10-2*10)/10, (((window.innerHeight/10)|0)*10-2*10)/10) </script>