Three.js 實(shí)現(xiàn)微信跳一跳小游戲【Three.js入門(mén)必備】

游戲地址:fwjzzz.gitee.io/jump[2] git地址:gitee.com/fwjzzz/Jump[3

前言
跳一跳是微信小程序[4]的一個(gè)小游戲。長(zhǎng)按屏幕讓小人蓄力跳躍,進(jìn)行游玩。按照小人跳躍盒子的數(shù)量,以及特殊盒子加分項(xiàng)計(jì)算得分。

游戲規(guī)則
十分簡(jiǎn)單:長(zhǎng)按鼠標(biāo)蓄力、放手,方塊就會(huì)從一個(gè)盒子跳到另一個(gè)盒子。然而就是這個(gè)小動(dòng)作,讓你一旦開(kāi)始就魔性地停不下來(lái)。

Three.js
Three.js 是一款運(yùn)行在瀏覽器中的 3D 引擎,你可以用它創(chuàng)建各種三維場(chǎng)景,包括了攝影機(jī)、光影、材質(zhì)等各種對(duì)象。

創(chuàng)建一個(gè)場(chǎng)景
設(shè)置光源
創(chuàng)建相機(jī),設(shè)置相機(jī)位置和相機(jī)鏡頭的朝向
創(chuàng)建3D渲染器,使用渲染器把創(chuàng)建的場(chǎng)景渲染出來(lái)
整個(gè)程序的結(jié)構(gòu)

image.png
實(shí)現(xiàn)
html文件引入three.js引擎
<script src="/js/three.min.js"></script>
頁(yè)面結(jié)構(gòu)
<div class="mask">
  <div class="content">
    <div class="score-container">
      <p class="title">本次得分</p>
      <h1 class="score">0</h1>
    </div>
    <button class="restart">
      重新開(kāi)始
    </button>
  </div>
</div>
<div class="info">
 <audio loop="loop" autoplay controls src="https://m801.music.126.net/20220413225245/3060206bc37e3226b7f45fa1
 49b0fb2b/jdymusic/obj/wo3DlMOGwrbDjj7DisKw/13866197954/e351/984c/1f8b/f6d3165d6b04dc78ec0d3c273ce02ff2.mp3">       
 </audio>
  <div class="gaming-score">
    得分:<span class="current-score">0</span>
  </div>
</div>


場(chǎng)景
let scene=new THREE.Scene();
  //創(chuàng)建一個(gè)場(chǎng)景
相機(jī)
常用的相機(jī)有兩種:

透視相機(jī)PerspectiveCamera[5]
符合人心理習(xí)慣,近大遠(yuǎn)小。

正視相機(jī)OrthographicCamera[6]
遠(yuǎn)處和近處一樣大

let camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,1,1000);
  //創(chuàng)建一個(gè)透視相機(jī) 4個(gè)參數(shù)(視覺(jué)范圍,寬高比例,近距離,遠(yuǎn)距離)
  camera.position.z=10;
  camera.position.y=3;
  camera.position.x=8;
  //相機(jī)的xyz場(chǎng)景方向
幾何體
使用CubeGeometry創(chuàng)建一個(gè)立方幾何體,使用MeshLambertMaterial材質(zhì)用來(lái)配置立方體渲染看上去暗淡不光亮的表面,該材質(zhì)會(huì)對(duì)場(chǎng)景中的光源產(chǎn)生反應(yīng),這個(gè)材質(zhì)可以配置一些其他屬性如:顏色等。

 let geometry=new THREE.CubeGeometry(4,2,4);
  //創(chuàng)建一個(gè)幾何體對(duì)象 (寬,高,深度)
  let material=new THREE.MeshLambertMaterial({color:0xbebebe});
  //創(chuàng)建了一個(gè)可以用于立方體的材質(zhì),對(duì)象包含了顏色、透明度等屬性,
  let cube=new THREE.Mesh(geometry,material);
  //結(jié)合在一起
  cube.position.x=16;
  scene.add(cube);
  //添加到場(chǎng)景中
光源
場(chǎng)景Scene主要是由幾何體模型和光Light構(gòu)成,在實(shí)際開(kāi)發(fā)過(guò)程中,大多數(shù)三維場(chǎng)景往往需要設(shè)置光源,通過(guò)不同的光源對(duì)模型模擬生活中的光照效果,尤其是為了提高Threejs的渲染效果更需要設(shè)置好光源,就像攝影師拍照要打燈一樣。

  let directionalLight=new THREE.DirectionalLight(0xffffff,1.1);
  //平行光  (顏色,強(qiáng)度)
  directionalLight.position.set(3,10,5);
  //平行光位置
  scene.add(directionalLight);
  //在場(chǎng)景中加入平行光
  let light=new THREE.AmbientLight(0xffffff,0.4);
  //光的材質(zhì)
  scene.add(light);
  //把光添加到場(chǎng)景
渲染
直接通過(guò)WebGL渲染器WebGLRenderer[7]的.setSize()方法設(shè)置渲染尺寸為瀏覽器body區(qū)域?qū)捀叨取?br>
 let renderer=new THREE.WebGLRenderer({antialias:true});
  //創(chuàng)建一個(gè)渲染器 (讓邊緣動(dòng)畫(huà)沒(méi)有鋸齒感)
  renderer.setSize(window.innerWidth,window.innerHeight);
  // 畫(huà)布寬高
  renderer.setClearColor(0x282828);
  //修改畫(huà)布顏色
  renderer.render(scene,camera);
  //渲染場(chǎng)景相機(jī) (后續(xù)更新也是這里)
  document.body.appendChild(renderer.domElement);
  //把當(dāng)前渲染的畫(huà)布放到body里面
  let x=8;
  function render() {
   //遞歸
    x-=0.1;
    camera.position.x=x;
    renderer.render(scene,camera);
 //更新重新渲染
    if(x>=-8){
 //滿(mǎn)足當(dāng)前條件
      requestAnimationFrame(render)
   //循環(huán)渲染
    }
  }
目前為止實(shí)現(xiàn)了一個(gè)雛形

添加第二塊


      _createCube() {
  let geometry = new THREE.CubeGeometry(this.config.cubeWidth, this.config.cubeHeight, this.config.cubeDeep);
  //創(chuàng)建一個(gè)幾何體對(duì)象 (寬,高,深度)
  let material = new THREE.MeshLambertMaterial({
   color: this.config.cubeColor
  });
  //材質(zhì),對(duì)象包含了顏色、透明度等屬性,
  let cube = new THREE.Mesh(geometry, material); //合并在一起
  if (this.cubes.length) {
   //從第二塊開(kāi)始隨機(jī)左右方向出現(xiàn)
   cube.position.x = this.cubes[this.cubes.length - 1].position.x;
   cube.position.y = this.cubes[this.cubes.length - 1].position.y;
   cube.position.z = this.cubes[this.cubes.length - 1].position.z;
   this.cubeStat.nextDir = Math.random() > 0.5 ? "left" : "right"; //要不左邊要不右邊
   if (this.cubeStat.nextDir == "left") {
    //左邊改變x軸否則y軸
    cube.position.x = cube.position.x - Math.round(Math.random() * 4 + 6);
   } else {
    cube.position.z = cube.position.z - Math.round(Math.random() * 4 + 6);
   }
  }
  this.cubes.push(cube); //統(tǒng)一添加塊
  if (this.cubes.length > 5) {
   //頁(yè)面最多看到5個(gè)塊
   this.scene.remove(this.cubes.shift()); //超過(guò)就移除
  }
  this.scene.add(cube); //添加到場(chǎng)景中
  if (this.cubes.length > 1) {
   //更新鏡頭位置
   this._updateCameraPros();
  }
 };





定義一個(gè)方塊數(shù)組,判斷從第二塊開(kāi)始向左右兩邊隨機(jī)出現(xiàn)。this.cubeStat.nextDir = Math.random() > 0.5 ? "left" : "right" 如上圖:(這是由兩張圖組成的)

跳塊

_createJumper() {
  let geometry = new THREE.CubeGeometry(this.config.jumperWidth, this.config.jumperHeight, this.config
   .jumperDeep); // (寬,高,深度)   
  let material = new THREE.MeshLambertMaterial({
   color: this.config.jumperColor
  }); //材質(zhì),顏色、透明度
  this.jumper = new THREE.Mesh(geometry, material); //合并在一起
  this.jumper.position.y = 1; //顯示跳塊
  geometry.translate(0, 1, 0); //平移
  this.scene.add(this.jumper); //添加到場(chǎng)景中
 }
使用Geometry幾何體對(duì)象有一系列的頂點(diǎn)屬性和方法,通過(guò).scale()、.translate()、.rotateX()等方法可以對(duì)幾何體本身進(jìn)行縮放、平移、旋轉(zhuǎn)等幾何變換。注意本質(zhì)上都是改變結(jié)合體頂點(diǎn)位置坐標(biāo)數(shù)據(jù)。

鼠標(biāo)按下?tīng)顟B(tài)
            this.jumperStat = {
   //鼠標(biāo)按下速度
   ready: false,
   xSpeed: 0,
   ySpeed: 0
  };
    _handleMouseDown() {
  if (!this.jumperStat.ready && this.jumper.scale.y > 0.02) {
   this.jumper.scale.y -= 0.01; //壓縮塊
   this.jumperStat.xSpeed += 0.004;
   this.jumperStat.ySpeed += 0.008;
   this._render();
   requestAnimationFrame(() => {
    this._handleMouseDown()
   })
  }
 };


鼠標(biāo)松開(kāi)彈起狀態(tài)
人生不就是這樣嗎?只要你跳對(duì)了位置,就能夠“逆襲”!

//鼠標(biāo)松開(kāi)談起狀態(tài)
 _handleMouseUp() {
  this.jumperStat.ready = true;
  if (this.jumper.position.y >= 1) {
   if (this.jumper.scale.y < 1) {
    this.jumper.scale.y += 0.1; //壓縮狀態(tài)小于1就+
   }
   if (this.cubeStat.nextDir == "left") {
    //挑起盒子落在哪里
    this.jumper.position.x -= this.jumperStat.xSpeed;
   } else {
    this.jumper.position.z -= this.jumperStat.xSpeed;
   }
   this.jumper.position.y += this.jumperStat.ySpeed;
   this.jumperStat.ySpeed -= 0.01; //上升落下?tīng)顟B(tài)
   this._render();
   requestAnimationFrame(() => {
    //循環(huán)執(zhí)行
    this._handleMouseUp();
   })
  } else {
   //落下?tīng)顟B(tài)
   this.jumperStat.ready = false;
   this.jumperStat.xSpeed = 0;
   this.jumperStat.ySpeed = 0;
   this.jumper.position.y = 1;
   this.jumper.scale.y = 1;
   this._checkInCube(); //檢測(cè)落在哪里
   if (this.falledStat.location == 1) {
    //下落后等于1,+分?jǐn)?shù)
    this.score++;
    this._createCube();
    this._updateCamera();
    if (this.successCallback) {
     //否則失敗
     this.successCallback(this.score);
    }
   } else {
    this._falling()
   }
  }
 };


落在哪里
學(xué)會(huì)控制速度感是非常奇妙的事情。當(dāng)你慢下來(lái)了,學(xué)會(huì)控制速度。因?yàn)樵诿恳粋€(gè)過(guò)程當(dāng)中,都有你生命中值得停下來(lái)瀏覽、欣賞、感受的事物。在我們的認(rèn)知中,總覺(jué)得越快,擁有的時(shí)間就越多,效率就越高,生產(chǎn)力就提高。其實(shí)并不是。如果你的頭腦常常處在思維高速運(yùn)轉(zhuǎn)的狀態(tài),一定會(huì)感覺(jué)繁忙且毫無(wú)頭緒;如果你總是擔(dān)心著未來(lái)或者掛念過(guò)去,就無(wú)法專(zhuān)注在當(dāng)下所做的事,也一定感到時(shí)間不夠用,效率大大降低。

                this.falledStat = {
   location: -1, //落在哪里 當(dāng)前塊塊上
   distance: 0, //距離是否倒下
  };
  this.fallingStat = {
   //有沒(méi)有落到點(diǎn)
   end: false,
   speed: 0.2
  }
//檢測(cè)落在哪里
 //-1   -10從當(dāng)前盒子掉落
 //1 下一個(gè)盒子上 10從下一個(gè)盒子上掉落
 //0沒(méi)有落在盒子上
 _checkInCube() {
  let distanceCur, distanceNext;
  //當(dāng)前盒子距離    下一個(gè)盒子距離
  let should = (this.config.jumperWidth + this.config.cubeWidth) / 2;
  //
  if (this.cubeStat.nextDir == "left") {
   //往左走了
   distanceCur = Math.abs(this.jumper.position.x - this.cubes[this.cubes.length - 2].position.x);
   distanceNext = Math.abs(this.jumper.position.x - this.cubes[this.cubes.length - 1].position.x);
  } else {
   //往右走了
   distanceCur = Math.abs(this.jumper.position.z - this.cubes[this.cubes.length - 2].position.z);
   distanceNext = Math.abs(this.jumper.position.z - this.cubes[this.cubes.length - 1].position.z);
  }
  if (distanceCur < should) {
   //落在當(dāng)前塊
   this.falledStat.distance = distanceCur;
   this.falledStat.location = distanceCur < this.config.cubeWidth / 2 ? -1 : -10;
  } else if (distanceNext < should) {
   //落在下一個(gè)塊上
   this.falledStat.distance = distanceNext;
   this.falledStat.location = distanceNext < this.config.cubeWidth / 2 ? 1 : 10;
  } else {
   //落在中間
   this.falledStat.location = 0;
  }
 };


落到方塊上,停上一會(huì)兒,放松自己,亦會(huì)有十分的額外獎(jiǎng)勵(lì)。人生路上,匆匆忙忙趕路的時(shí)候,不要忘了適度休息調(diào)整,你會(huì)有意外地收獲,命運(yùn)的魔方會(huì)給你別致的驚喜。人生很短,何須急著走完。

//下落過(guò)程
 _falling() {
  if (this.falledStat.location == 10) {
   //從下一個(gè)盒子落下
   if (this.cubeStat.nextDir == "left") {
    //判斷左方向
    if (this.jumper.position.x > this.cubes[this.cubes.length - 1].position.x) {
     this._fallingRotate("leftBottom")
    } else {
     this._fallingRotate("leftTop")
    }
   } else {
    //判斷右方向
    if (this.jumper.position.z > this.cubes[this.cubes.length - 1].position.z) {
     this._fallingRotate("rightBottom")
    } else {
     this._fallingRotate("rightTop")
    }
   }
  } else if (this.falledStat.location == -10) {
   //從當(dāng)前盒子落下
   if (this.cubeStat.nextDir == "left") {
    this._fallingRotate("leftTop")
   } else {
    this._fallingRotate("rightTop")
   }
  } else if (this.falledStat.location == 0) {
   this._fallingRotate("none")
  }
 };




作者:奶油小泡芙  

原文鏈接:https://juejin.cn/post/7086493323136876552

作者:奶油小泡芙


歡迎關(guān)注微信公眾號(hào) :前端開(kāi)發(fā)愛(ài)好者


添加好友備注【進(jìn)階學(xué)習(xí)】拉你進(jìn)技術(shù)交流群