使用 Three.js 實現跳一跳遊戲

語言: CN / TW / HK

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

前言

跳一跳是 微信小程式 [3] 的一個小遊戲。長按螢幕讓小人蓄力跳躍,進行遊玩。按照小人跳躍盒子的數量,以及特殊盒子加分項計算得分。

遊戲規則

十分簡單:長按滑鼠蓄力、放手,方塊就會從一個盒子跳到另一個盒子。然而就是這個小動作,讓你一旦開始就魔性地停不下來。

Three.js

Three.js 是一款執行在瀏覽器中的 3D 引擎,你可以用它建立各種三維場景,包括了攝影機、光影、材質等各種物件。

  1. 建立一個場景

  2. 設定光源

  3. 建立相機,設定相機位置和相機鏡頭的朝向

  4. 建立3D渲染器,使用渲染器把建立的場景渲染出來

整個程式的結構

實現

html檔案引入three.js引擎

<script src="/js/three.min.js"></script>

頁面結構

<div class="mask">
<div class="content">
<div class="score-container">
<p class="title">本次得分</p>
<h1 class="score">0</h1>
</div>
<button class="restart">
重新開始
</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>

場景

let scene=new THREE.Scene();
//建立一個場景

相機

常用的相機有兩種:

  • 透視相機 PerspectiveCamera [4]

符合人心理習慣,近大遠小。

  • 正視相機 OrthographicCamera [5]

遠處和近處一樣大

let camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,1,1000);
//建立一個透視相機 4個引數(視覺範圍,寬高比例,近距離,遠距離)
camera.position.z=10;
camera.position.y=3;
camera.position.x=8;
//相機的xyz場景方向

幾何體

使用 CubeGeometry 建立一個立方几何體,使用 MeshLambertMaterial 材質用來配置立方體渲染看上去暗淡不光亮的表面,該材質會對場景中的光源產生反應,這個材質可以配置一些其他屬性如:顏色等。

 let geometry=new THREE.CubeGeometry(4,2,4);
//建立一個幾何體物件 (寬,高,深度)
let material=new THREE.MeshLambertMaterial({color:0xbebebe});
//建立了一個可以用於立方體的材質,物件包含了顏色、透明度等屬性,
let cube=new THREE.Mesh(geometry,material);
//結合在一起
cube.position.x=16;
scene.add(cube);
//新增到場景中

光源

場景 Scene 主要是由幾何體模型和光 Light 構成,在實際開發過程中,大多數三維場景往往需要設定光源,通過不同的光源對模型模擬生活中的光照效果,尤其是為了提高Threejs的渲染效果更需要設定好光源,就像攝影師拍照要打燈一樣。

  let directionalLight=new THREE.DirectionalLight(0xffffff,1.1);
//平行光 (顏色,強度)
directionalLight.position.set(3,10,5);
//平行光位置
scene.add(directionalLight);
//在場景中加入平行光
let light=new THREE.AmbientLight(0xffffff,0.4);
//光的材質
scene.add(light);
//把光新增到場景

渲染

直接通過WebGL渲染器 WebGLRenderer [6].setSize() 方法設定渲染尺寸為瀏覽器body區域寬高度。

 let renderer=new THREE.WebGLRenderer({antialias:true});
//建立一個渲染器 (讓邊緣動畫沒有鋸齒感)
renderer.setSize(window.innerWidth,window.innerHeight);
// 畫布寬高
renderer.setClearColor(0x282828);
//修改畫布顏色
renderer.render(scene,camera);
//渲染場景相機 (後續更新也是這裡)
document.body.appendChild(renderer.domElement);
//把當前渲染的畫布放到body裡面
let x=8;
function render() {
//遞迴
x-=0.1;
camera.position.x=x;
renderer.render(scene,camera);
//更新重新渲染
if(x>=-8){
//滿足當前條件
requestAnimationFrame(render)
//迴圈渲染
}
}

目前為止實現了一個雛形

新增第二塊

      _createCube() {
let geometry = new THREE.CubeGeometry(this.config.cubeWidth, this.config.cubeHeight, this.config.cubeDeep);
//建立一個幾何體物件 (寬,高,深度)
let material = new THREE.MeshLambertMaterial({
color: this.config.cubeColor
});
//材質,物件包含了顏色、透明度等屬性,
let cube = new THREE.Mesh(geometry, material); //合併在一起
if (this.cubes.length) {
//從第二塊開始隨機左右方向出現
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); //統一新增塊
if (this.cubes.length > 5) {
//頁面最多看到5個塊
this.scene.remove(this.cubes.shift()); //超過就移除
}
this.scene.add(cube); //新增到場景中
if (this.cubes.length > 1) {
//更新鏡頭位置
this._updateCameraPros();
}
};

定義一個方塊陣列,判斷從第二塊開始向左右兩邊隨機出現。 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
}); //材質,顏色、透明度
this.jumper = new THREE.Mesh(geometry, material); //合併在一起
this.jumper.position.y = 1; //顯示跳塊
geometry.translate(0, 1, 0); //平移
this.scene.add(this.jumper); //新增到場景中
}

使用 Geometry 幾何體物件有一系列的頂點屬性和方法,通過 .scale().translate().rotateX() 等方法可以對幾何體本身進行縮放、平移、旋轉等幾何變換。注意本質上都是改變結合體頂點位置座標資料。

滑鼠按下狀態

            this.jumperStat = {
//滑鼠按下速度
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()
})
}
};

滑鼠鬆開彈起狀態

人生不就是這樣嗎?只要你跳對了位置,就能夠“逆襲”!

//滑鼠鬆開談起狀態
_handleMouseUp() {
this.jumperStat.ready = true;
if (this.jumper.position.y >= 1) {
if (this.jumper.scale.y < 1) {
this.jumper.scale.y += 0.1; //壓縮狀態小於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; //上升落下狀態
this._render();
requestAnimationFrame(() => {
//迴圈執行
this._handleMouseUp();
})
} else {
//落下狀態
this.jumperStat.ready = false;
this.jumperStat.xSpeed = 0;
this.jumperStat.ySpeed = 0;
this.jumper.position.y = 1;
this.jumper.scale.y = 1;
this._checkInCube(); //檢測落在哪裡
if (this.falledStat.location == 1) {
//下落後等於1,+分數
this.score++;
this._createCube();
this._updateCamera();
if (this.successCallback) {
//否則失敗
this.successCallback(this.score);
}
} else {
this._falling()
}
}
};

落在哪裡

學會控制速度感是非常奇妙的事情。當你慢下來了,學會控制速度。因為在每一個過程當中,都有你生命中值得停下來瀏覽、欣賞、感受的事物。在我們的認知中,總覺得越快,擁有的時間就越多,效率就越高,生產力就提高。其實並不是。如果你的頭腦常常處在思維高速運轉的狀態,一定會感覺繁忙且毫無頭緒;如果你總是擔心著未來或者掛念過去,就無法專注在當下所做的事,也一定感到時間不夠用,效率大大降低。

                this.falledStat = {
location: -1, //落在哪裡 當前塊塊上
distance: 0, //距離是否倒下
};
this.fallingStat = {
//有沒有落到點
end: false,
speed: 0.2
}
//檢測落在哪裡
//-1 -10從當前盒子掉落
//1 下一個盒子上 10從下一個盒子上掉落
//0沒有落在盒子上
_checkInCube() {
let distanceCur, distanceNext;
//當前盒子距離 下一個盒子距離
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) {
//落在當前塊
this.falledStat.distance = distanceCur;
this.falledStat.location = distanceCur < this.config.cubeWidth / 2 ? -1 : -10;
} else if (distanceNext < should) {
//落在下一個塊上
this.falledStat.distance = distanceNext;
this.falledStat.location = distanceNext < this.config.cubeWidth / 2 ? 1 : 10;
} else {
//落在中間
this.falledStat.location = 0;
}
};

落到方塊上,停上一會兒,放鬆自己,亦會有十分的額外獎勵。人生路上,匆匆忙忙趕路的時候,不要忘了適度休息調整,你會有意外地收穫,命運的魔方會給你別緻的驚喜。人生很短,何須急著走完。

//下落過程
_falling() {
if (this.falledStat.location == 10) {
//從下一個盒子落下
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) {
//從當前盒子落下
if (this.cubeStat.nextDir == "left") {
this._fallingRotate("leftTop")
} else {
this._fallingRotate("rightTop")
}
} else if (this.falledStat.location == 0) {
this._fallingRotate("none")
}
};

結尾

贏也好,輸也罷,人生就是一個起起伏伏的過程,處在巔峰不驕,跌落低谷不餒。 這才是正確的人生姿勢。當然,這裡不僅僅說的是遊戲。有可能是埋頭玩遊戲的 ,也許你早注意到那個小方塊的玩家形象,就是生活中的“ 自己 ”。這個世界就如 “跳一跳” 遊戲:規則和目標都明確的智力遊戲,玩家可以自由地行動,站對位置就可以加分。 時時輕拂拭,勿使惹塵埃 。便可有收穫,享受生活,熱愛人世間的煙火!!!

參考資料

[1]

fwjzzz.gitee.io/jump: h ttps://fwjzzz.gitee.io/jump

[2]

gitee.com/fwjzzz/Jump: https://gitee.com/fwjzzz/Jump

[3]

微信小程式: https://baike.baidu.com/item/微信小程式/20171697

[4]

PerspectiveCamera: http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/cameras/PerspectiveCamera

[5]

OrthographicCamera: http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/cameras/OrthographicCamera

[6]

WebGLRenderer: http://www.yanhuangxueyuan.com/threejs/docs/index.html#api/zh/renderers/WebGLRenderer

作者:奶油小泡芙

https://juejin.cn/post/7086493323136876552

- EOF -

覺得本文對你有幫助?請分享給更多人

關注「大前端技術之路」加星標,提升前端技能

點贊和在看就是最大的支援 :heart: