VUE+Canvas實(shí)現(xiàn)財(cái)神爺接元寶小游戲
之前的canvas小游戲系列歡迎大家戳:
《VUE實(shí)現(xiàn)一個(gè)Flappy Bird~~~》
《VUE+Canvas 實(shí)現(xiàn)桌面彈球消磚塊小游戲》
《VUE+Canvas實(shí)現(xiàn)雷霆戰(zhàn)機(jī)打字類小游戲》
如標(biāo)題,這個(gè)游戲大家也玩過(guò),隨處可見(jiàn),左右方向鍵控制財(cái)神移動(dòng),接住從天而降的金元寶等,時(shí)間一到,則游戲結(jié)束。先來(lái)看一下效果:

相比于之前的雷霆戰(zhàn)機(jī)要打出四處飛的子彈,這次元素的運(yùn)動(dòng)軌跡就很單一了,垂直方向的珠寶和水平移動(dòng)的財(cái)神爺,類似于之前的代碼,這里就說(shuō)一下關(guān)鍵步驟點(diǎn)吧:
1、鍵盤(pán)控制水平移動(dòng)的財(cái)神爺
這個(gè)很簡(jiǎn)單,同理于《VUE+Canvas 實(shí)現(xiàn)桌面彈球消磚塊小游戲》滑塊的控制:
drawCaishen() {
let _this = this;
_this.ctx.save();
_this.ctx.drawImage(
_this.caishenImg,
_this.caishen.x,
_this.caishen.y,
120,
120
);
_this.ctx.restore();
},
moveCaishen() {
this.caishen.x += this.caishen.dx;
if (this.caishen.x > this.clientWidth - 120) {
this.caishen.x = this.clientWidth - 120;
} else if (this.caishen.x < 0) {
this.caishen.x = 0;
}
}
2、從天而降的珠寶
這個(gè)也很簡(jiǎn)單,但要注意的是,珠寶的初始x值不能隨機(jī)取0~clientWidth了,因?yàn)檫@樣很容易造成珠寶堆積在一起,影響了游戲的可玩性,所以珠寶最好是分散在不同的軌道上,這里我們把畫(huà)布寬度分為5條軌道,初始珠寶的時(shí)候,我們就把珠寶分散在軌道上,并且y值隨機(jī)在一定高度造成參差。而后新生成的珠寶都依據(jù)軌道分布來(lái)生成,避免珠寶擠在一起。
generateTreasure() {
let _this = this;
if (_this.treasureArr.length < MaxNum) {
let random = Math.floor(Math.random() * TreasureNames.length);
let channel = _this.getRandomArbitrary(1, 5);
_this.treasureArr.push({
x: _this.channelWidth * (1 / 2 + (channel - 1)) - 30,
y: 0,
name: TreasureNames[random],
speed: _this.getRandomArbitrary(2, 4)
});
}
},
filterTreasure(item) {
let _this = this;
if (
item.x <= _this.caishen.x + 110 &&
item.x >= _this.caishen.x &&
item.y > _this.caishen.y
) {
// 判斷和財(cái)神的觸碰范圍
_this.score += _this.treasureObj[item.name].score;
return false;
}
if (item.y >= _this.clientHeight) {
return false;
}
return true;
},
drawTreasure() {
let _this = this;
_this.treasureArr = _this.treasureArr.filter(_this.filterTreasure);
_this.treasureArr.forEach(item => {
_this.ctx.drawImage(
_this.treasureObj[item.name].src,
item.x,
item.y,
60,
60
);
item.y += item.speed;
});
},
getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
這里用filter函數(shù)過(guò)濾掉應(yīng)該消失的珠寶,如果用for+splice+i--的方法會(huì)造成抖動(dòng)。
然后給予每個(gè)珠寶隨機(jī)的運(yùn)動(dòng)速度,當(dāng)珠寶進(jìn)入財(cái)神爺?shù)膱D片范圍時(shí)則累加相應(yīng)分?jǐn)?shù)。
3、倒計(jì)時(shí)圓環(huán)
設(shè)置倒計(jì)時(shí)30s,那么在requestAnimationFrame的回調(diào)里計(jì)算當(dāng)前時(shí)間與上次時(shí)間戳毫秒差值是否大于1000,實(shí)現(xiàn)秒的計(jì)算,然后取另一時(shí)間戳累加progress,實(shí)現(xiàn)圓環(huán)的平滑移動(dòng)。
drawCountDown() {
// 畫(huà)進(jìn)度環(huán)
let _this = this;
_this.progress += Date.now() - _this.timeTag2;
_this.timeTag2 = Date.now();
_this.ctx.beginPath();
_this.ctx.moveTo(50, 50);
_this.ctx.arc(
50,
50,
40,
Math.PI * 1.5,
Math.PI * (1.5 + 2 * (_this.progress / (countDownInit * 1000))),
false
);
_this.ctx.closePath();
_this.ctx.fillStyle = "yellow";
_this.ctx.fill();
// 畫(huà)內(nèi)填充圓
_this.ctx.beginPath();
_this.ctx.arc(50, 50, 30, 0, Math.PI * 2);
_this.ctx.closePath();
_this.ctx.fillStyle = "#fff";
_this.ctx.fill();
// 填充文字
_this.ctx.font = "bold 16px Microsoft YaHei";
_this.ctx.fillStyle = "#333";
_this.ctx.textAlign = "center";
_this.ctx.textBaseline = "middle";
_this.ctx.moveTo(50, 50);
_this.ctx.fillText(_this.countDown + "s", 50, 50);
}
(function animloop() {
_this.ctx.clearRect(0, 0, _this.clientWidth, _this.clientHeight);
_this.loop();
animationId = window.requestAnimationFrame(animloop);
if (_this.countDown === 0) {
_this.gameOver = true;
window.cancelAnimationFrame(animationId);
}
if (Date.now() - _this.timeTag >= 1000) {
_this.countDown--;
_this.timeTag = Date.now();
}
})();
至此,一個(gè)非常簡(jiǎn)單的財(cái)神爺接元寶的小游戲就完成了,當(dāng)然可以為了增加難度,設(shè)置不間斷地丟炸彈這一環(huán)節(jié),原理同珠寶的運(yùn)動(dòng)是一樣的。
下面還是附上全部代碼,供大家參考學(xué)習(xí):
<template>
<div class="caishen">
<canvas id="caishen" width="1200" height="750"></canvas>
<div class="container" v-if="gameOver">
<div class="dialog">
<p class="once-again">恭喜!</p>
<p class="once-again">本回合奪寶:{{ score }}分</p>
</div>
</div>
</div>
</template>
<script>
const TreasureNames = [
"yuanbao",
"tongqian",
"jintiao",
"shuijin_red",
"shuijin_blue",
"fudai"
];
let animationId = null;
let countDownInit = 0;
const MaxNum = 5;
export default {
name: "CaiShen",
data() {
return {
score: 0,
ctx: null,
caishenImg: null,
clientWidth: 0,
clientHeight: 0,
channelWidth: 0,
caishen: {
x: 0,
y: 0,
speed: 8,
dx: 0
},
progress: 0,
countDown: 30,
timeTag: Date.now(),
timeTag2: Date.now(),
treasureArr: [],
gameOver: false,
treasureObj: {
yuanbao: {
score: 5,
src: null
},
tongqian: {
score: 2,
src: null
},
jintiao: {
score: 10,
src: null
},
shuijin_red: {
score: 20,
src: null
},
shuijin_blue: {
score: 15,
src: null
},
fudai: {
score: 8,
src: null
}
}
};
},
mounted() {
let _this = this;
let container = document.getElementById("caishen");
_this.ctx = container.getContext("2d");
_this.clientWidth = container.width;
_this.clientHeight = container.height;
_this.channelWidth = Math.floor(_this.clientWidth / 5);
_this.caishenImg = new Image();
_this.caishenImg.src = require("@/assets/img/caishen/財(cái)神爺.png");
_this.initTreasures();
countDownInit = _this.countDown;
_this.caishen.x = _this.clientWidth / 2 - 60;
_this.caishen.y = _this.clientHeight - 120;
document.onkeydown = function(e) {
let key = window.event.keyCode;
if (key === 37) {
// 左鍵
_this.caishen.dx = -_this.caishen.speed;
} else if (key === 39) {
// 右鍵
_this.caishen.dx = _this.caishen.speed;
}
};
document.onkeyup = function(e) {
_this.caishen.dx = 0;
};
_this.caishenImg.onload = function() {
(function animloop() {
_this.ctx.clearRect(0, 0, _this.clientWidth, _this.clientHeight);
_this.loop();
animationId = window.requestAnimationFrame(animloop);
if (_this.countDown === 0) {
_this.gameOver = true;
window.cancelAnimationFrame(animationId);
}
if (Date.now() - _this.timeTag >= 1000) {
_this.countDown--;
_this.timeTag = Date.now();
}
})();
};
},
methods: {
initTreasures() {
let _this = this;
Object.keys(_this.treasureObj).forEach(key => {
_this.treasureObj[key].src = new Image();
_this.treasureObj[
key
].src.src = require(`@/assets/img/caishen/${key}.png`);
});
for (let i = 0; i < MaxNum; i++) {
let random = Math.floor(Math.random() * TreasureNames.length);
_this.treasureArr.push({
x: _this.channelWidth * (1 / 2 + i) - 30,
y: _this.getRandomArbitrary(0, 20),
name: TreasureNames[random],
speed: _this.getRandomArbitrary(2, 4)
});
}
},
loop() {
let _this = this;
_this.drawCountDown();
_this.drawCaishen();
_this.moveCaishen();
_this.generateTreasure();
_this.drawTreasure();
_this.drawScore();
},
drawCaishen() {
let _this = this;
_this.ctx.save();
_this.ctx.drawImage(
_this.caishenImg,
_this.caishen.x,
_this.caishen.y,
120,
120
);
_this.ctx.restore();
},
moveCaishen() {
this.caishen.x += this.caishen.dx;
if (this.caishen.x > this.clientWidth - 120) {
this.caishen.x = this.clientWidth - 120;
} else if (this.caishen.x < 0) {
this.caishen.x = 0;
}
},
drawScore() {
let _this = this;
_this.ctx.beginPath();
_this.ctx.fillStyle = "#fff";
_this.ctx.textAlign = "center";
_this.ctx.textBaseline = "middle";
_this.ctx.fillText(_this.score + "分", 30, _this.clientHeight - 10);
_this.ctx.closePath();
},
drawCountDown() {
// 畫(huà)進(jìn)度環(huán)
let _this = this;
_this.progress += Date.now() - _this.timeTag2;
_this.timeTag2 = Date.now();
_this.ctx.beginPath();
_this.ctx.moveTo(50, 50);
_this.ctx.arc(
50,
50,
40,
Math.PI * 1.5,
Math.PI * (1.5 + 2 * (_this.progress / (countDownInit * 1000))),
false
);
_this.ctx.closePath();
_this.ctx.fillStyle = "yellow";
_this.ctx.fill();
// 畫(huà)內(nèi)填充圓
_this.ctx.beginPath();
_this.ctx.arc(50, 50, 30, 0, Math.PI * 2);
_this.ctx.closePath();
_this.ctx.fillStyle = "#fff";
_this.ctx.fill();
// 填充文字
_this.ctx.font = "bold 16px Microsoft YaHei";
_this.ctx.fillStyle = "#333";
_this.ctx.textAlign = "center";
_this.ctx.textBaseline = "middle";
_this.ctx.moveTo(50, 50);
_this.ctx.fillText(_this.countDown + "s", 50, 50);
},
filterTreasure(item) {
let _this = this;
if (
item.x <= _this.caishen.x + 110 &&
item.x >= _this.caishen.x &&
item.y > _this.caishen.y
) {
// 判斷和財(cái)神的觸碰范圍
_this.score += _this.treasureObj[item.name].score;
return false;
}
if (item.y >= _this.clientHeight) {
return false;
}
return true;
},
drawTreasure() {
let _this = this;
_this.treasureArr = _this.treasureArr.filter(_this.filterTreasure);
_this.treasureArr.forEach(item => {
_this.ctx.drawImage(
_this.treasureObj[item.name].src,
item.x,
item.y,
60,
60
);
item.y += item.speed;
});
},
getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
},
generateTreasure() {
let _this = this;
if (_this.treasureArr.length < MaxNum) {
let random = Math.floor(Math.random() * TreasureNames.length);
let channel = _this.getRandomArbitrary(1, 5);
_this.treasureArr.push({
x: _this.channelWidth * (1 / 2 + (channel - 1)) - 30,
y: 0,
name: TreasureNames[random],
speed: _this.getRandomArbitrary(2, 4)
});
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
#caishen {
background-color: #b00600;
background-image: url("~assets/img/caishen/brick-wall.png");
}
.container {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.3);
text-align: center;
font-size: 0;
white-space: nowrap;
overflow: auto;
}
.container:after {
content: "";
display: inline-block;
height: 100%;
vertical-align: middle;
}
.dialog {
width: 400px;
height: 300px;
background: rgba(255, 255, 255, 0.5);
box-shadow: 3px 3px 6px 3px rgba(0, 0, 0, 0.3);
display: inline-block;
vertical-align: middle;
text-align: left;
font-size: 28px;
color: #fff;
font-weight: 600;
border-radius: 10px;
white-space: normal;
text-align: center;
.once-again-btn {
background: #1f9a9a;
border: none;
color: #fff;
}
}
</style>
到此這篇關(guān)于VUE+Canvas實(shí)現(xiàn)財(cái)神爺接元寶小游戲的文章就介紹到這了,更多相關(guān)vue接元寶游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue使用Canvas生成隨機(jī)大小且不重疊圓
- Vue使用canvas實(shí)現(xiàn)圖片壓縮上傳
- vue+canvas繪制時(shí)間軸的方法
- VUE+Canvas實(shí)現(xiàn)簡(jiǎn)單五子棋游戲的全過(guò)程
- 如何用VUE和Canvas實(shí)現(xiàn)雷霆戰(zhàn)機(jī)打字類小游戲
- VUE+Canvas 實(shí)現(xiàn)桌面彈球消磚塊小游戲的示例代碼
- Vue使用鼠標(biāo)在Canvas上繪制矩形
- vue使用canvas實(shí)現(xiàn)移動(dòng)端手寫(xiě)簽名
- vue+canvas實(shí)現(xiàn)拼圖小游戲
- vue使用canvas手寫(xiě)輸入識(shí)別中文
相關(guān)文章
利用vue實(shí)現(xiàn)打印頁(yè)面的幾種方法總結(jié)
在項(xiàng)目里有個(gè)打印功能,將頁(yè)面的部分內(nèi)容打印出來(lái),所以下面這篇文章主要給大家介紹了關(guān)于利用vue實(shí)現(xiàn)打印頁(yè)面的幾種方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11
uniApp?h5項(xiàng)目如何通過(guò)命令行打包并生成指定路徑及文件名稱
用uni-app來(lái)寫(xiě)安卓端,近日需要將程序打包為H5放到web服務(wù)器上,經(jīng)過(guò)一番折騰,這里給大家分享下,這篇文章主要給大家介紹了關(guān)于uniApp?h5項(xiàng)目如何通過(guò)命令行打包并生成指定路徑及文件名稱的相關(guān)資料,需要的朋友可以參考下2024-02-02
vue前端img訪問(wèn)鑒權(quán)后端進(jìn)行攔截的代碼示例
路由攔截是一種在用戶訪問(wèn)特定頁(yè)面之前對(duì)其進(jìn)行攔截和處理的機(jī)制,下面這篇文章主要給大家介紹了關(guān)于vue前端img訪問(wèn)鑒權(quán)后端進(jìn)行攔截的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue實(shí)現(xiàn)循環(huán)滾動(dòng)圖片
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)循環(huán)滾動(dòng)圖片,多圖片輪播,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
Vue 數(shù)組和對(duì)象更新,但是頁(yè)面沒(méi)有刷新的解決方式
今天小編就為大家分享一篇Vue 數(shù)組和對(duì)象更新,但是頁(yè)面沒(méi)有刷新的解決方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
vue數(shù)組對(duì)象排序的實(shí)現(xiàn)代碼
這篇文章主要介紹了vue數(shù)組對(duì)象排序的實(shí)現(xiàn)代碼,這里整理了詳細(xì)的代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-06-06
vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決
這篇文章主要介紹了vue長(zhǎng)按事件和點(diǎn)擊事件沖突的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
在VUE中實(shí)現(xiàn)文件下載并判斷狀態(tài)的方法
今天小編就為大家分享一篇在VUE中實(shí)現(xiàn)文件下載并判斷狀態(tài)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
vue父組件傳值子組件報(bào)錯(cuò)Avoid?mutating?a?prop?directly解決
這篇文章主要為大家介紹了vue父組件傳值子組件報(bào)錯(cuò)Avoid?mutating?a?prop?directly解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

