VUE實現(xiàn)一個Flappy Bird游戲的示例代碼
Flappy Bird是一個非常簡單的小游戲,在app上大家都玩過。這里就用VUE來實現(xiàn)一個簡單的PC版Flappy Bird,娛樂一下~~~~~
要實現(xiàn)這個游戲,首先來分析一下游戲界面里哪幾塊東西需要動起來:
1、第一當(dāng)然就是上下移動的小鳥;
2、橫向移動的背景圖,讓小鳥看起來在橫向飛行;
3、從畫面右端進入的一排排管道。
這樣很明確了,我們讓上面3塊內(nèi)容按照規(guī)律運動起來,然后再加上規(guī)則邊界判斷和計分,就可以得到一個完整的游戲。所以就一塊塊來解決。
先來定義一些常量和變量:
let rafId = null; // requestAnimationFrame的ID let startSpeed = 1; const SPEED = 0.04; // 加速度 const UP = 5.0; // 速度累加上限 const UP_SUM = 30; // 按一次跳躍的高度 const BIRD_WIDTH = 50; // 小鳥圖片寬50px const PIPE_DISTANCE = 350; // 管道之間的橫向距離 let id = 0; // 管道唯一id,從0開始計數(shù) ... data() { return { start: false, clientWidth: 0, clientHeight: 0, spaceHeight: [240, 200, 160], // 上管道與下管道之間的距離 pipeArr: [], // 管道數(shù)組 score: 0, // 得分 jumpSum: 0, // 當(dāng)前跳躍相對高度 jumpFlag: false, // true-按下空格鍵跳躍上升階段;false-自由落體階段 dropBirdImg: require("@/assets/img/bird/bird0_0.png"), flyBirdImg: require("@/assets/img/bird/bird0_2.png"), gameOver: false, // 游戲失敗的flag,用于停止動畫幀 }; },
1、上下移動的小鳥
為了分別控制小鳥和管道的位置,元素定位均采用position: absolute
小鳥本身就是個div+背景圖,然后定義一下在界面里的初始位置:
<div class="bird" id="bird" ref="bird"></div> #bird { height: 50px; width: 50px; border-radius: 50%; background: url("~assets/img/bird/bird0_1.png") no-repeat center/contain; // 小鳥初始位置 position: absolute; left: 300px; top: 300px; }
然后,在什么都不操作的情況下,小鳥從初始位置開始"墜落",小鳥的墜落是一個越落越快的過程,在這里我沒有用物理的重力加速度公式,只是簡單模擬了一個曲線加速過程。這是一個持續(xù)的動畫,所以把這個動作放在動畫幀里,即requestAnimationFrame,每一幀的函數(shù)定義為loop()。
所以在loop函數(shù)中,根據(jù)offsetTop和父元素的clientHeight來判斷小鳥是否觸碰到了畫面的上下邊界,是則游戲結(jié)束;否,則增加style.top讓小鳥墜落。
loop() { let _this = this; if (_this.jumpFlag) { // 小鳥跳躍 _this.jump(); } let top = _this.$refs.bird.offsetTop; if (top > _this.clientHeight - BIRD_WIDTH || top <= 0) { // 碰到邊界,游戲結(jié)束 _this.resetGame(); } else if (!_this.jumpFlag) { _this.$refs.bird.style.background = `url('${_this.dropBirdImg}') no-repeat center/contain`; _this.$refs.bird.style.top = top + startSpeed * startSpeed + "px"; // 模擬加速墜落 if (startSpeed < UP) { startSpeed += SPEED; } } _this.pipesMove(); // 管道移動 }
游戲中,玩家按下空格鍵,小鳥會向上跳躍一段距離,用this.jumpFlag[true/false]來記錄這一狀態(tài),當(dāng)按下時,置為true,loop函數(shù)中小鳥jump(),在jump到一定距離后,jumpFlag置為false,小鳥開始墜落。
所以,jump函數(shù)很容易實現(xiàn):
jump() { let _this = this; _this.$refs.bird.style.background = `url('${_this.flyBirdImg}') no-repeat center/contain`; if (_this.jumpSum > UP_SUM) { // 到頂部就落下 _this.jumpFlag = false; _this.jumpSum = 0; startSpeed = 1; } else { _this.$refs.bird.style.top = _this.$refs.bird.offsetTop - 8 + "px"; _this.jumpSum += 8; } }
2、橫向移動的背景圖
這個比較簡單,就是background-position無限循環(huán)切換就行了,位置根據(jù)自己下載的背景圖素材寬度決定。
animation: bgMove 8s linear infinite; @keyframes bgMove { 0% { background-position: 805px 0; } 100% { background-position: 0 0; } }
經(jīng)過這兩步,我們就可以得到一個正在飛行的小鳥了,用document.onkeydown監(jiān)聽空格鍵來切換jumpFlag,如下圖:
3、從右往左一移動進入管道
管道是由上下兩個div組成,每個div通過不同的top: -xx和bottom: -yy實現(xiàn)中間有間隙。
首先實現(xiàn)生成一個隨機間隙管道的函數(shù),管道存放在pipeArr對象數(shù)組中:
addPipe(id) { let obj = {}; let top_num = this.sum(10, 170); let height = this.spaceHeight[ Math.floor(Math.random() * this.spaceHeight.length) ]; // 隨機選取間隙值 let bottom_num = height - top_num; obj.top = top_num; obj.id = id; obj.right = -(PIPE_DISTANCE / 2); obj.bottom = bottom_num; this.pipeArr.push(obj); }, sum(m, n) { // 隨機n-m之間的數(shù)字 return Math.floor(Math.random() * (m - n) + n); }
然后需要將管道移動起來,即loop()中管道移動函數(shù)pipesMove(),整個函數(shù)實現(xiàn)如下:
pipesMove() { let _this = this; if (_this.pipeArr.length === 0) { return; } let right0 = _this.pipeArr[0].right; if (right0 > this.clientWidth + 300) { this.pipeArr.shift(); } let right_last = _this.pipeArr[_this.pipeArr.length - 1].right; if (right_last >= PIPE_DISTANCE / 2) { id++; this.addPipe(id); } for (let i = 0; i < _this.pipeArr.length; i++) { // 判斷一下小鳥是否接觸到了管道,小鳥50*50,left:300px;管道寬100px;管道進入范圍right是width-450到width-300 if ( _this.pipeArr[i].right >= _this.clientWidth - 450 && _this.pipeArr[i].right <= _this.clientWidth - 300 ) { // 該管道進入了小鳥觸碰范圍 let bird_top = _this.$refs.bird.offsetTop; // 12是小鳥圖片素材上下有空白間隙 if ( bird_top <= _this.clientHeight / 2 - _this.pipeArr[i].top - 12 || bird_top >= _this.clientHeight / 2 + _this.pipeArr[i].bottom - BIRD_WIDTH + 12 ) { // 碰到了管道 _this.resetGame(); return; } } if (_this.pipeArr[i].right === _this.clientWidth - 300 && _this.pipeArr[i].right === _this.clientWidth - 301) { // 當(dāng)某個管道剛好在小鳥左邊,證明小鳥通過該管道,根據(jù)管道id算出小鳥得分 _this.score = _this.pipeArr[i].id + 1; } _this.pipeArr[i].right = _this.pipeArr[i].right + 2; // 管道每幀移動2px } }
這里做了五件事:
(1)管道出了左邊畫面后shift()最左的管道;
(2)最右的管道離畫面右側(cè)一定距離后,加入新的一根管道;
(3)循環(huán)遍歷中,判斷小鳥是否進入了某一根管道的范圍,判斷小鳥top是否有觸碰到上下管道,觸碰則輸;
(4)當(dāng)某一個管道剛好位于小鳥左側(cè)時,證明小鳥成功通過,分?jǐn)?shù)+1;
(5)每個管道移動2px像素,數(shù)值記錄在right屬性里。
通過DOM里:style設(shè)置right就可以使得管道橫向移動了
<section class="pipes-wrap" ref="pipes"> <div class="pipe-item" v-for="(item, index) in pipeArr" :key="item.id" :id="'pipe' + index" :style="'right:' + item.right + 'px;'" > <div class="pipe pipe-top" :style="'top:-' + item.top + 'px;'" ></div> <div class="pipe pipe-bottom" :style="'bottom:-' + item.bottom + 'px;'" ></div> </div> </section> .pipes-wrap { position: relative; height: 100%; overflow: hidden; .pipe-item { position: absolute; height: 100%; width: 100px; .pipe { width: 100%; height: 50%; position: relative; } .pipe-top { background: url('"~assets/img/bird/pipe_down.png') no-repeat; background-size: 100px; background-position: bottom; } .pipe-bottom { background: url('"~assets/img/bird/pipe_up.png') no-repeat; background-size: 100px; background-position: top; } } }
以上就是vue實現(xiàn)flappy bird的思路和核心代碼了,總共也就兩百多行代碼。在我看來,難點主要集中在管道的移動、觸碰判定以及分?jǐn)?shù)計算上。當(dāng)然代碼里還有很多可以優(yōu)化的不足點,共勉~~
相關(guān)文章
vue3使用localStorage實現(xiàn)登錄注冊功能實例
這篇文章主要給大家介紹了關(guān)于vue3使用localStorage實現(xiàn)登錄注冊功能的相關(guān)資料, localStorage這個特性主要是用來作為本地存儲來使用的,解決了cookie存儲空間不足的問題,需要的朋友可以參考下2023-06-06Vue利用vue-baidu-map實現(xiàn)獲取經(jīng)緯度和搜索地址
在開發(fā)項目的時候,發(fā)現(xiàn)需要獲取經(jīng)緯度,由于這個項目是用vue寫的,最后決定使用vue-baidu-map來快速獲取經(jīng)緯度,感興趣的可以了解一下2022-09-09Vue el使用el-checkbox-group復(fù)選框進行單選框方式
這篇文章主要介紹了Vue el使用el-checkbox-group復(fù)選框進行單選框方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10