VUE實(shí)現(xiàn)一個(gè)Flappy Bird游戲的示例代碼
Flappy Bird是一個(gè)非常簡(jiǎn)單的小游戲,在app上大家都玩過(guò)。這里就用VUE來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的PC版Flappy Bird,娛樂(lè)一下~~~~~
要實(shí)現(xiàn)這個(gè)游戲,首先來(lái)分析一下游戲界面里哪幾塊東西需要?jiǎng)悠饋?lái):
1、第一當(dāng)然就是上下移動(dòng)的小鳥(niǎo);
2、橫向移動(dòng)的背景圖,讓小鳥(niǎo)看起來(lái)在橫向飛行;
3、從畫(huà)面右端進(jìn)入的一排排管道。
這樣很明確了,我們讓上面3塊內(nèi)容按照規(guī)律運(yùn)動(dòng)起來(lái),然后再加上規(guī)則邊界判斷和計(jì)分,就可以得到一個(gè)完整的游戲。所以就一塊塊來(lái)解決。
先來(lái)定義一些常量和變量:
let rafId = null; // requestAnimationFrame的ID
let startSpeed = 1;
const SPEED = 0.04; // 加速度
const UP = 5.0; // 速度累加上限
const UP_SUM = 30; // 按一次跳躍的高度
const BIRD_WIDTH = 50; // 小鳥(niǎo)圖片寬50px
const PIPE_DISTANCE = 350; // 管道之間的橫向距離
let id = 0; // 管道唯一id,從0開(kāi)始計(jì)數(shù)
...
data() {
return {
start: false,
clientWidth: 0,
clientHeight: 0,
spaceHeight: [240, 200, 160], // 上管道與下管道之間的距離
pipeArr: [], // 管道數(shù)組
score: 0, // 得分
jumpSum: 0, // 當(dāng)前跳躍相對(duì)高度
jumpFlag: false, // true-按下空格鍵跳躍上升階段;false-自由落體階段
dropBirdImg: require("@/assets/img/bird/bird0_0.png"),
flyBirdImg: require("@/assets/img/bird/bird0_2.png"),
gameOver: false, // 游戲失敗的flag,用于停止動(dòng)畫(huà)幀
};
},
1、上下移動(dòng)的小鳥(niǎo)
為了分別控制小鳥(niǎo)和管道的位置,元素定位均采用position: absolute
小鳥(niǎo)本身就是個(gè)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;
// 小鳥(niǎo)初始位置
position: absolute;
left: 300px;
top: 300px;
}
然后,在什么都不操作的情況下,小鳥(niǎo)從初始位置開(kāi)始"墜落",小鳥(niǎo)的墜落是一個(gè)越落越快的過(guò)程,在這里我沒(méi)有用物理的重力加速度公式,只是簡(jiǎn)單模擬了一個(gè)曲線(xiàn)加速過(guò)程。這是一個(gè)持續(xù)的動(dòng)畫(huà),所以把這個(gè)動(dòng)作放在動(dòng)畫(huà)幀里,即requestAnimationFrame,每一幀的函數(shù)定義為loop()。
所以在loop函數(shù)中,根據(jù)offsetTop和父元素的clientHeight來(lái)判斷小鳥(niǎo)是否觸碰到了畫(huà)面的上下邊界,是則游戲結(jié)束;否,則增加style.top讓小鳥(niǎo)墜落。
loop() {
let _this = this;
if (_this.jumpFlag) {
// 小鳥(niǎo)跳躍
_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(); // 管道移動(dòng)
}
游戲中,玩家按下空格鍵,小鳥(niǎo)會(huì)向上跳躍一段距離,用this.jumpFlag[true/false]來(lái)記錄這一狀態(tài),當(dāng)按下時(shí),置為true,loop函數(shù)中小鳥(niǎo)jump(),在jump到一定距離后,jumpFlag置為false,小鳥(niǎo)開(kāi)始?jí)嬄洹?/p>
所以,jump函數(shù)很容易實(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、橫向移動(dòng)的背景圖
這個(gè)比較簡(jiǎn)單,就是background-position無(wú)限循環(huán)切換就行了,位置根據(jù)自己下載的背景圖素材寬度決定。
animation: bgMove 8s linear infinite;
@keyframes bgMove {
0% {
background-position: 805px 0;
}
100% {
background-position: 0 0;
}
}
經(jīng)過(guò)這兩步,我們就可以得到一個(gè)正在飛行的小鳥(niǎo)了,用document.onkeydown監(jiān)聽(tīng)空格鍵來(lái)切換jumpFlag,如下圖:

3、從右往左一移動(dòng)進(jìn)入管道
管道是由上下兩個(gè)div組成,每個(gè)div通過(guò)不同的top: -xx和bottom: -yy實(shí)現(xiàn)中間有間隙。
首先實(shí)現(xiàn)生成一個(gè)隨機(jī)間隙管道的函數(shù),管道存放在pipeArr對(duì)象數(shù)組中:
addPipe(id) {
let obj = {};
let top_num = this.sum(10, 170);
let height = this.spaceHeight[
Math.floor(Math.random() * this.spaceHeight.length)
]; // 隨機(jī)選取間隙值
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) {
// 隨機(jī)n-m之間的數(shù)字
return Math.floor(Math.random() * (m - n) + n);
}
然后需要將管道移動(dòng)起來(lái),即loop()中管道移動(dòng)函數(shù)pipesMove(),整個(gè)函數(shù)實(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++) {
// 判斷一下小鳥(niǎo)是否接觸到了管道,小鳥(niǎo)50*50,left:300px;管道寬100px;管道進(jìn)入范圍right是width-450到width-300
if (
_this.pipeArr[i].right >= _this.clientWidth - 450 &&
_this.pipeArr[i].right <= _this.clientWidth - 300
) {
// 該管道進(jìn)入了小鳥(niǎo)觸碰范圍
let bird_top = _this.$refs.bird.offsetTop;
// 12是小鳥(niǎo)圖片素材上下有空白間隙
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)某個(gè)管道剛好在小鳥(niǎo)左邊,證明小鳥(niǎo)通過(guò)該管道,根據(jù)管道id算出小鳥(niǎo)得分
_this.score = _this.pipeArr[i].id + 1;
}
_this.pipeArr[i].right = _this.pipeArr[i].right + 2; // 管道每幀移動(dòng)2px
}
}
這里做了五件事:
(1)管道出了左邊畫(huà)面后shift()最左的管道;
(2)最右的管道離畫(huà)面右側(cè)一定距離后,加入新的一根管道;
(3)循環(huán)遍歷中,判斷小鳥(niǎo)是否進(jìn)入了某一根管道的范圍,判斷小鳥(niǎo)top是否有觸碰到上下管道,觸碰則輸;
(4)當(dāng)某一個(gè)管道剛好位于小鳥(niǎo)左側(cè)時(shí),證明小鳥(niǎo)成功通過(guò),分?jǐn)?shù)+1;
(5)每個(gè)管道移動(dòng)2px像素,數(shù)值記錄在right屬性里。
通過(guò)DOM里:style設(shè)置right就可以使得管道橫向移動(dòng)了
<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實(shí)現(xiàn)flappy bird的思路和核心代碼了,總共也就兩百多行代碼。在我看來(lái),難點(diǎn)主要集中在管道的移動(dòng)、觸碰判定以及分?jǐn)?shù)計(jì)算上。當(dāng)然代碼里還有很多可以?xún)?yōu)化的不足點(diǎn),共勉~~
- 純JavaScript 實(shí)現(xiàn)flappy bird小游戲?qū)嵗a
- C++版本簡(jiǎn)易Flappy bird
- C語(yǔ)言簡(jiǎn)易版flappy bird小游戲
- python實(shí)現(xiàn)Flappy Bird源碼
- C語(yǔ)言實(shí)現(xiàn)flappy bird游戲
- Unity實(shí)現(xiàn)Flappy Bird游戲開(kāi)發(fā)實(shí)戰(zhàn)
- java實(shí)現(xiàn)Flappy Bird游戲源代碼
- C語(yǔ)言實(shí)現(xiàn)Flappy Bird小游戲
- 使用pygame編寫(xiě)Flappy bird小游戲
- pygame面向?qū)ο蟮娘w行小鳥(niǎo)實(shí)現(xiàn)(Flappy bird)
相關(guān)文章
vue3使用localStorage實(shí)現(xiàn)登錄注冊(cè)功能實(shí)例
這篇文章主要給大家介紹了關(guān)于vue3使用localStorage實(shí)現(xiàn)登錄注冊(cè)功能的相關(guān)資料, localStorage這個(gè)特性主要是用來(lái)作為本地存儲(chǔ)來(lái)使用的,解決了cookie存儲(chǔ)空間不足的問(wèn)題,需要的朋友可以參考下2023-06-06
Vue.js組件tabs實(shí)現(xiàn)選項(xiàng)卡切換效果
這篇文章主要為大家詳細(xì)介紹了Vue.js組件tabs實(shí)現(xiàn)選項(xiàng)卡切換效果的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
Vue利用vue-baidu-map實(shí)現(xiàn)獲取經(jīng)緯度和搜索地址
在開(kāi)發(fā)項(xiàng)目的時(shí)候,發(fā)現(xiàn)需要獲取經(jīng)緯度,由于這個(gè)項(xiàng)目是用vue寫(xiě)的,最后決定使用vue-baidu-map來(lái)快速獲取經(jīng)緯度,感興趣的可以了解一下2022-09-09
Vue el使用el-checkbox-group復(fù)選框進(jìn)行單選框方式
這篇文章主要介紹了Vue el使用el-checkbox-group復(fù)選框進(jìn)行單選框方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10

