hello,今天给大家用three.js开发了一个手机太空穿越VR游戏,确实不容易,小编的头发又少了一大截。Ok,废话少说,先看效果。
效果图
首页index.html
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <title>Three.js Mobile VR Sonic</title> <link rel="stylesheet" href="css/style.css"> </head> <body> <script src="js/jquery-1.12.4.min.js"></script> <script src="js/d3.v4.min.js"></script> <script src="js/three.min.js"></script> <script src="js/GLTFLoader.js"></script> <script src="js/TweenMax.min.js"></script> <script src="js/CSSPlugin.min.js"></script> <script src="js/EasePack.min.js"></script> <script src='js/AssimpJSONLoader.js'></script> <script src="js/DeviceOrientationControls.js"></script> <script src="js/OrbitControls.js"></script> <script src="js/StereoEffect.js"></script> <script src="js/tween.min.js"></script> <script src="js/dat.gui.min.js"></script> <!-- glowing effect scripts --> <script src="js/EffectComposer.js"></script> <script src="js/RenderPass.js"></script> <script src="js/MaskPass.js"></script> <script src="js/ShaderPass.js"></script> <script src="js/CopyShader.js"></script> <script src="js/FXAAShader.js"></script> <script src="js/ConvolutionShader.js"></script> <script src="js/LuminosityHighPassShader.js"></script> <!-- unreal bloom --> <script src="js/UnrealBloomPass.js"></script> <!-- VR Button --> <button id='VR' class='button toggleVR' onclick='toggleVR()' title='Toggle VR Mode for Mobile Devices Only'> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 62.7 52.375" enable-background="new 0 0 62.7 41.9" xml:space="preserve"><path d="M53.4,5.5h-44c-2.1,0-3.7,1.7-3.7,3.7v22.6c0,2.1,1.7,3.7,3.7,3.7h13.4c1.1,0,2.1-0.6,2.5-1.6l3-7.5c1.2-2.6,4.9-2.5,6,0.1 l2.6,7.3c0.4,1,1.4,1.7,2.5,1.7h13.9c2.1,0,3.7-1.7,3.7-3.7V9.3C57.2,7.2,55.5,5.5,53.4,5.5z M20.4,27c-3.2,0-5.7-2.6-5.7-5.7 s2.6-5.7,5.7-5.7s5.7,2.6,5.7,5.7S23.6,27,20.4,27z M42.4,27c-3.2,0-5.7-2.6-5.7-5.7s2.6-5.7,5.7-5.7s5.7,2.6,5.7,5.7 S45.6,27,42.4,27z"/><text x="0" y="56.9" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Nick Bluth</text><text x="0" y="61.9" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg> </button> <div id="info">SUPER!</div> <audio id="bflat" src="sonic_ring_sound.mp3"></audio> <script src="js/index.js"></script> </body> </html>
核心代码js
//===================================================== full screen var requestFullscreen = function(ele) { if (ele.requestFullscreen) { ele.requestFullscreen(); } else if (ele.webkitRequestFullscreen) { ele.webkitRequestFullscreen(); } else if (ele.mozRequestFullScreen) { ele.mozRequestFullScreen(); } else if (ele.msRequestFullscreen) { ele.msRequestFullscreen(); } else { console.log("Fullscreen API is not supported."); } }; var exitFullscreen = function(ele) { if (ele.exitFullscreen) { ele.exitFullscreen(); } else if (ele.webkitExitFullscreen) { ele.webkitExitFullscreen(); } else if (ele.mozCancelFullScreen) { ele.mozCancelFullScreen(); } else if (ele.msExitFullscreen) { ele.msExitFullscreen(); } else { console.log("Fullscreen API is not supported."); } }; //===================================================== add canvas let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0xd8e7ff, 0); document.body.appendChild(renderer.domElement); //===================================================== resize window.addEventListener("resize", function() { let width = window.innerWidth; let height = window.innerHeight; renderer.setSize(width, height); camera.aspect = width / height; camera.updateProjectionMatrix(); }); //===================================================== add Scene let scene = new THREE.Scene(); //===================================================== add Camera let camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); let cameraTarget = new THREE.Vector3(0, 0, 0); //===================================================== add Group group = new THREE.Group(); scene.add(group); //===================================================== add Controls var controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.enableZoom = true; //How far you can orbit vertically, upper and lower limits. The maximum is Pi / 2 (90deg). You wont see below the below the line of the horizon controls.maxPolarAngle = Math.PI / 2.1; //===================================================== add VR renderer.setPixelRatio(window.devicePixelRatio); //VR effect = new THREE.StereoEffect(renderer); //VR effect.setSize(window.innerWidth, window.innerHeight); //VR var VR = false; function toggleVR() { if (VR) { VR = false; controls = new THREE.OrbitControls(camera, renderer.domElement); } else { VR = true; controls = new THREE.DeviceOrientationControls(camera); requestFullscreen(document.documentElement); } renderer.setSize(window.innerWidth, window.innerHeight); } //===================================================== curve points exported from blender using python var points = [ /*[2.873088836669922, 1.886053442955017, 2.807666063308716] , [2.8677191734313965, 1.901498556137085, 0.9702248573303223] , [4.261392116546631, 1.1370995044708252, 2.199495315551758] , [5.4436726570129395, -0.46564579010009766, 1.1223225593566895] , [4.683673858642578, -5.823459148406982, 0.29172343015670776] , [4.683673858642578, -5.823459148406982, 0.29172343015670776] , [1.0320820808410645, -8.896514892578125, -0.25123584270477295] , [-6.009271621704102, -5.854086875915527, -1.619685411453247] , [-5.8719940185546875, 1.664353609085083, 1.4598760604858398] , [-3.664644718170166, 5.1350908279418945, 0.8891280889511108] , [0.3946661949157715, 9.023353576660156, 2.472759246826172] , [6.545413017272949, 7.975126266479492, 4.807941913604736] , [9.380702018737793, 3.8875434398651123, -1.2000198364257812] , [3.980130672454834, -3.3519601821899414, -1.5907882452011108] , [0.10054226964712143, -4.0724077224731445, -2.6255977153778076] , [0.11306309700012207, -4.062130451202393, -2.625786781311035] , [-1.5915921926498413, 2.2223830223083496, 1.9054492712020874] , [-0.7800552845001221, 2.729933738708496, 2.711679458618164] , [2.2788195610046387, 1.5061609745025635, 3.6167585849761963] , [3.330962896347046, -1.753382682800293, 3.5841569900512695] , [3.3268532752990723, -1.7394065856933594, 3.584031343460083] , [1.8003381490707397, -2.6099541187286377, 4.496334552764893] , [1.809736728668213, -2.6042258739471436, 4.490817546844482] , [-1.5139055252075195, -2.2446377277374268, 3.513293743133545] , [-1.5139055252075195, -2.2446377277374268, 3.513293743133545] , [-1.5139055252075195, -2.2446377277374268, 1.7960444688796997] , [-1.9393141269683838, -0.623103678226471, 1.3167498111724854] , [-1.5, 0.0, 0.0] , [-1.0, 1.0, 0.0] , [1.0, 1.0, 0.0] , [1.5, 0.0, 0.0] ,*/ [1.8117204904556274, 5.987488269805908, 0.29106736183166504], [6.005367279052734, 1.7647128105163574, -1.5591322183609009], [1.435487985610962, -6.016839504241943, 2.1336286067962646], [-4.118395805358887, -6.886471271514893, -0.7294682264328003], [-4.732693195343018, 3.405961036682129, 3.1304938793182373], [8.304193496704102, 7.593861103057861, 0.3412821292877197], [8.038525581359863, -4.391696453094482, 2.687108278274536], [1.488401174545288, -9.993440628051758, -2.2956111431121826], [-5.277090549468994, -8.481210708618164, -0.719127893447876], [-7.250330448150635, -0.9653520584106445, -0.3089699447154999], [-6.526705741882324, 5.678538799285889, 0.15560221672058105], [-0.885545015335083, 6.678538799285889, 1.5724562406539917], [1.614454984664917, 5.678538799285889, 0.24559785425662994], [1.8117204904556274, 5.987488269805908, 0.29106736183166504] ]; var scale = 5; //Convert the array of points into vertices for (var i = 0; i < points.length; i++) { var x = points[i][0] * scale; var y = points[i][1] * scale; var z = points[i][2] * scale; points[i] = new THREE.Vector3(x, z, -y); } //Create a path from the points var carPath = new THREE.CatmullRomCurve3(points); var radius = 0.25; var geometry = new THREE.TubeGeometry(carPath, 600, radius, 10, false); //Set a different color on each face for (var i = 0, j = geometry.faces.length; i < j; i++) { geometry.faces[i].color = new THREE.Color( "hsl(" + Math.floor(Math.random() * 290) + ",50%,50%)" ); } var material = new THREE.MeshBasicMaterial({ side: THREE.BackSide, vertexColors: THREE.FaceColors, side: THREE.DoubleSide, transparent: true, opacity: 1 }); var tube = new THREE.Mesh(geometry, material); scene.add(tube); //===================================================== add lighta var light = new THREE.DirectionalLight(0xefefff, 1.25); light.position.set(1, 1, 1).normalize(); scene.add(light); var light = new THREE.DirectionalLight(0xffefef, 1.25); light.position.set(-1, -1, -1).normalize(); scene.add(light); //Create a point light in our scene. Makes everything gloomy var light = new THREE.PointLight(0xffffff, 1, 100); scene.add(light); //===================================================== particles var mergedGeometry = new THREE.Geometry(); var boxGeometry = new THREE.TetrahedronGeometry(0.25, 0); var material = new THREE.MeshNormalMaterial({ color: new THREE.Color("white") }); for (var i = 0; i < 1000; i++) { var x = Math.random() * 125 - 75; var y = Math.random() * 125 - 75; var z = Math.random() * 125 - 75; boxGeometry.translate(x, y, z); mergedGeometry.merge(boxGeometry); boxGeometry.translate(-x, -y, -z); } var cubes = new THREE.Mesh(mergedGeometry, material); scene.add(cubes); //===================================================== Loader //3d model from var loader = new THREE.GLTFLoader(); var model; loader.load( "sky-island.glb", function(gltf) { gltf.scene.traverse(function(node) { if (node instanceof THREE.Mesh) { node.castShadow = true; node.material.side = THREE.DoubleSide; } }); model = gltf.scene; model.scale.set(3, 3, 3); model.position.set(0, -20, -10); //model.rotateY(Math.PI); scene.add(model); } ); //===================================================== Loader //3d model from https://3dwarehouse.sketchup.com/user/0438052632930067253040161/wingedkoopa67?nav=models var clock = new THREE.Clock(); var mixer = null; var firstObject; var loader = new THREE.GLTFLoader(); loader.load( "sonic.glb", function(gltf) { gltf.scene.traverse(function(node) { if (node instanceof THREE.Mesh) { node.castShadow = true; node.material.side = THREE.DoubleSide; } }); firstObject = gltf.scene; firstObject.scale.set(0.65, 0.65, 0.65); group.add(firstObject); console.log(gltf.animations); //shows all animations imported into the dopesheet in blender mixer = new THREE.AnimationMixer(firstObject); mixer.clipAction(gltf.animations[0]).play(); document.body.addEventListener("click", jump); function jump() { mixer.clipAction(gltf.animations[0]).stop(); mixer.clipAction(gltf.animations[1]).play(); setTimeout(function() { mixer.clipAction(gltf.animations[1]).stop(); mixer.clipAction(gltf.animations[0]).play(); }, 900); } } ); //===================================================== add model var size = 0.05; var meshList = []; for (var i = 0; i < carPath.points.length; i++) { var x = carPath.points[i].x; var y = carPath.points[i].y; var z = carPath.points[i].z; var geometry = new THREE.TorusGeometry(3, 0.5, 8, 50); var material = new THREE.MeshBasicMaterial({ color: new THREE.Color("yellow") }); var secondObject = new THREE.Mesh(geometry, material); secondObject.position.set(x, y + 0.75, z); secondObject.scale.set(size, size, size); meshList.push(secondObject); scene.add(secondObject); } //===================================================== Collision Detection function PlaySound() { bflat.play(); } //calculate distance of the main object firstBB = new THREE.Box3().setFromObject(group); //calculate distance for all other objects for (var i = 0; i < meshList.length; i++) { secondBB = new THREE.Box3().setFromObject(meshList[i]); } var count = 0; function hit() { //recalculate distance for the main object firstBB = new THREE.Box3().setFromObject(group); //recalcuate distance for all the other objects for (var i = 0; i < meshList.length; i++) { secondBB = new THREE.Box3().setFromObject(meshList[i]); if (firstBB.isIntersectionBox(secondBB)) { PlaySound(); info.style.color = "hsl(" + Math.floor(Math.random() * 290) + ",50%,50%)"; info.innerHTML = Math.random() > 0.25 ? "Superb!" : Math.random() > 0.25 ? "Oustanding!" : "Awesome!"; TweenLite.to(info, 0.75, { css: { fontSize: "50px", opacity: 1 }, ease: Power4.easeOut, onComplete: function() { TweenLite.to(info, 0.75, { css: { fontSize: "14px", opacity: 0 }, ease: Power4.easeOut }); //end tween } //end onComplete }); //end tween } //end if } //end for } //end hit //===================================================== bloom var renderScene = new THREE.RenderPass(scene, camera); var shaderActive = "none"; var gui = new dat.GUI(); dat.GUI.toggleHide(); var composer; var parameters = { x: 0, y: 30, z: 0, bloomStrength: 1.0, bloomRadius: 1.0, bloomThreshold: 0.45, useShaderNone: function() { setupShaderNone(); }, useShaderBloom: function() { setupShaderBloom(); } }; gui.add(parameters, "useShaderNone").name("Display Original Scene"); var folderBloom = gui.addFolder("Bloom"); var bloomStrengthGUI = folderBloom .add(parameters, "bloomStrength") .min(0.0) .max(2.0) .step(0.01) .name("Strength") .listen(); bloomStrengthGUI.onChange(function(value) { setupShaderBloom(); }); var bloomRadiusGUI = folderBloom .add(parameters, "bloomRadius") .min(0.0) .max(5.0) .step(0.01) .name("Radius") .listen(); bloomRadiusGUI.onChange(function(value) { setupShaderBloom(); }); var bloomThresholdGUI = folderBloom .add(parameters, "bloomThreshold") .min(0) .max(0.99) .step(0.01) .name("Threshold") .listen(); bloomThresholdGUI.onChange(function(value) { setupShaderBloom(); }); folderBloom.add(parameters, "useShaderBloom").name("Use Bloom Shader"); folderBloom.open(); //===================================================== functions function setupShaderNone() { shaderActive = "none"; } function setupShaderBloom() { composer = new THREE.EffectComposer(renderer); composer.addPass(new THREE.RenderPass(scene, camera)); /*unreal bloom*/ var effectFXAA = new THREE.ShaderPass(THREE.FXAAShader); effectFXAA.uniforms["resolution"].value.set( 1 / window.innerWidth, 1 / window.innerHeight ); var copyShader = new THREE.ShaderPass(THREE.CopyShader); copyShader.renderToScreen = true; var bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), parameters.bloomStrength, parameters.bloomRadius, parameters.bloomThreshold ); composer = new THREE.EffectComposer(renderer); composer.setSize(window.innerWidth, window.innerHeight); composer.addPass(renderScene); composer.addPass(effectFXAA); composer.addPass(bloomPass); composer.addPass(copyShader); shaderActive = "bloom"; } function isShaderActive() { if (shaderActive == "none") { renderer.render(scene, camera); } else { composer.render(); } } //active bloom on load setupShaderBloom(); //===================================================== Animate var percentage = 0; var prevTime = Date.now(); function POV() { percentage += 0.00045; var p1 = carPath.getPointAt(percentage % 1); var p2 = carPath.getPointAt((percentage + 0.01) % 1); var p3 = carPath.getPointAt((percentage + 0.01 / 2) % 1); var p4 = carPath.getPointAt((percentage + 0.01 / 4) % 1); camera.lookAt(p2); group.position.set(p3.x, p3.y + 0.25, p3.z); group.lookAt(p2); camera.position.x = p4.x + 2; camera.position.y = p4.y + 1; camera.position.z = p4.z + 2; camera.lookAt(group.position); } function animate() { POV(); hit(); var delta = clock.getDelta(); if (mixer != null) mixer.update(delta); //VR if (VR) { effect.render(scene, camera); } else { //renderer.render( scene, camera ); isShaderActive(); } requestAnimationFrame(animate); controls.update(); } animate(); //set camera position camera.position.x = 40; camera.position.y = 50; camera.position.z = 50;
更多学习内容:请关注我的知乎专栏 高级前端工程师前端学习教程,从基础到进阶,看完保证让你的薪资上升一个台阶,你也能成为阿里人(持续更新)