JS+Canvas實(shí)現(xiàn)貪吃蛇小游戲
今天呢,主要和小伙伴們分享一下一個(gè)貪吃蛇游戲從構(gòu)思到實(shí)現(xiàn)的過程~因?yàn)槲也皇呛芟矚g直接PO代碼,所以只copy代碼的童鞋們請(qǐng)出門左轉(zhuǎn)不謝。
按理說canvas與其應(yīng)用是老生常談了,可我在準(zhǔn)備階段卻搜索不到有用的資料(不是代碼?。?,所以說呢,只能自力更生 -_-
首先是大致要考慮的東西:
1.要有蛇(沒蛇怎么叫貪吃蛇)。
2.然后要有地圖(蛇是不能上天的)。
3.不能水平\垂直掉頭(如果想掉頭,需要至少變換方位并且至少移動(dòng)一格才可)。
4.食物(不然怎么貪吃)。
5.吃了食物要變長(這才是精髓)。
PS:~現(xiàn)在我回想起來,當(dāng)時(shí)的確只想到這么多(⊙﹏⊙)
構(gòu)思完畢,開工!
怎么做呢?從大到小,先畫個(gè)矩形作地圖,可我覺得太丑,于是畫了一張圖出來:
context.beginPath(); var bgImg = new Image(); bgImg.src = "img/background.png"; context.drawImage(bgImg, 0, 0, 600, 600); context.closePath();
現(xiàn)在我們有地圖了

地圖上好像缺點(diǎn)什么……沒錯(cuò)就是禮物,所以我們現(xiàn)在生成禮物,那么問題來了:禮物最多有幾個(gè)、生成位置、何時(shí)生成。
我這里暫時(shí)定義為:最多2個(gè)、隨機(jī)位置生成、當(dāng)禮物個(gè)數(shù)小于2時(shí)生成至2個(gè)。
接下來就很簡單了,上圖中,允許蛇活動(dòng)的范圍是14顆樹(周圍兩顆樹是墻),然后16顆樹=600px,很容易我們得到每格多寬~
所以呢,我們只需要定義一個(gè)隨機(jī)生成1-14整數(shù)的方法就可以很輕松找到應(yīng)該生成的位置:
//隨機(jī)數(shù)
function selectfrom() {
return Math.floor(Math.random() * 14 + 1);
}然后再用求出的數(shù)乘以每一格子的寬度,即可求出生成的具體X坐標(biāo),因?yàn)槭钦叫?,所以Y也一樣:
var x = selectfrom() * (600/16); var y = selectfrom() * (600/16);
并且每得到一組禮物坐標(biāo)后,都需要存儲(chǔ)在一個(gè)數(shù)組內(nèi)(一會(huì)兒有大用處),至于畫矩形太基礎(chǔ)我就不說了。
And Now,我們有了禮物,有了地圖,就差蛇了,那么問題又來了:出生的蛇多長、出生地、死亡方式、移動(dòng)方式、轉(zhuǎn)彎方式、如何判斷吃掉了禮物、吃掉了禮物變長到哪里。
出生蛇長度:實(shí)際編寫過程中,我發(fā)現(xiàn)默認(rèn)長度1和2都不能夠很好的體現(xiàn)“蛇的轉(zhuǎn)彎”,所以定義為3,并且需將蛇身所有坐標(biāo)記錄在數(shù)組內(nèi)。
出生地:地圖中央或者自己定一個(gè)位置(按照格子來分),XY坐標(biāo)求取方式上面已經(jīng)說過不再贅述。
死亡方式:碰到障礙,或者(吃到自己)蛇頭碰到蛇身。
移動(dòng)方式:通過定義一個(gè)全局變量記錄當(dāng)前方向(0、1、2、3,默認(rèn)1),并且使用計(jì)時(shí)器驅(qū)動(dòng)蛇運(yùn)動(dòng)。
轉(zhuǎn)彎方式:加入鍵盤按鍵檢測事件,當(dāng)方向鍵按下的時(shí)候修改-記錄方向的全部變量即可。
如何判斷吃掉了禮物:每次蛇頭移動(dòng)時(shí),都要遍歷下禮物集合(上面有說過),如果蛇頭將要移動(dòng)到的下個(gè)坐標(biāo)與之重合了,則視為吃掉了禮物。
吃掉了禮物變長到哪里:直接加在頭部可能會(huì)導(dǎo)致意外的死亡,所以我決定吃到禮物后的下一次移動(dòng)不消除蛇尾(最后一個(gè)元素)。
有了上面的構(gòu)思,我們可以著手定義一些可能會(huì)用到的公共變量:
var canvas = document.getElementById("mycanvas");//畫布主體
var context = canvas.getContext("2d");
var timer;//計(jì)時(shí)器
const WIDTH = canvas.width;//畫布寬
const HEIGHT = canvas.height;//畫布高
const XSUM = 16; //畫布寬分為幾格
const YSUM = 15; //畫布高分為幾格
const MAXFFOD = 2; //最大食物數(shù)量
var score = 0;//定義記錄游戲得分
var xsplit = WIDTH / XSUM; //x每一格子的寬度
var ysplit = HEIGHT / YSUM; //y每一格子的高度
var foodcount = 0; //當(dāng)前食物數(shù)量
var sinak = []; //貪吃蛇坐標(biāo)集
var get = []; //禮物坐標(biāo)集
var MoveTo = 1; //移動(dòng)方向 默認(rèn)1(右)有了這些變量,是不是發(fā)現(xiàn)很多東西都通了呢?
我們先來畫蛇:
//畫貪吃蛇
function drawsinak(sl) { //sl默認(rèn)長度
context.beginPath();
context.fillStyle = "#000";
var ling = 0; //貪吃蛇被打印長度
for (var r = 0; r < sinak.length; r++) {
context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
ling++;
}
if (ling == 0) {
for (var i = 0; i < sl; i++) {
context.fillRect(xsplit * (7 - i), ysplit * 6, xsplit, ysplit); //默認(rèn)出生點(diǎn):7,6默認(rèn)中心點(diǎn)
sinak.push(xsplit * (7 - i) + ',' + ysplit * 6);
}
}
context.fill();
context.closePath();
}可以看到我將生成的蛇的坐標(biāo)都計(jì)入了數(shù)組內(nèi),生成的禮物自然也要計(jì)入:
context.beginPath();
var x = selectfrom(XSUM - 2) * xsplit;
var y = selectfrom(YSUM - 2) * ysplit;
context.fillStyle = "red";
for (var i = 0; i < get.length; i++) {
context.fillRect(get[i].split(',')[0], get[i].split(',')[1], xsplit, ysplit);
context.fill();
foodcount++;
}
if (MAXFFOD > foodcount) {
context.fillRect(x, y, xsplit, ysplit);
context.fill();
foodcount++;
get.push(x + ',' + y);
}
context.closePath();接下來比較重要了,蛇的移動(dòng),以及吃到禮物和觸發(fā)死亡判斷:
//移動(dòng)方法
//[c]移動(dòng)方向 上右下左 0123
function sinakMove(c) {
context.beginPath();
//默認(rèn)右側(cè)為頭
var tou = sinak[0]; //頭
var weiba = sinak[sinak.length - 1]; //尾巴
var oldX = tou.split(',')[0]; //頭部舊X坐標(biāo)
var oldY = tou.split(',')[1]; //頭部舊Y坐標(biāo)
var newX = 0; //頭部最新X坐標(biāo)
var newY = 0; //頭部最新Y坐標(biāo)
//計(jì)算頭部最新XY坐標(biāo)
switch (c) {
case 0:
newX = oldX;
newY = oldY - ysplit;
break;
case 1:
newX = (oldX - 0) + xsplit;
newY = oldY;
break;
case 2:
newX = oldX;
newY = (oldY - 0) + ysplit;
break;
case 3:
newX = oldX - xsplit;
newY = oldY;
break;
}
var flag = 0; //有沒有吃到禮物 0沒有1有
//如果吃到了禮物,則不消減尾部最后元素
for (var i = 0; i < get.length; i++) {
if (newX == get[i].split(',')[0] && newY == get[i].split(',')[1]) {
sinak.unshift(newX + ',' + newY);
foodcount--; //禮物計(jì)數(shù)減少1個(gè)
get.splice(i, 1); //清空禮物
flag = 1;
}
}
//如果沒有吃到禮物,則判斷是否碰到障礙或吃到自己
if (flag == 0) {
for (var i = 0; i < sinak.length; i++) {
if (newX == sinak[i].split(',')[0] && newY == sinak[i].split(',')[1]) {
if (confirm('吃掉了自己,游戲失?。∈欠裰匦麻_始?')) {
location.reload(true);
} else {
context.clearRect(0, 0, WIDTH, HEIGHT);
}
}
}
if (xsplit * (XSUM - 2) < newX || ysplit * (YSUM - 2) < newY || newX == 0 || newY == 0) {
if (confirm('撞墻了,游戲失?。∈欠裰匦麻_始?')) {
location.reload(true);
}
}
}
//如果沒有吃到禮物,那么進(jìn)行普通移動(dòng)
if (flag == 0) {
sinak.unshift(newX + ',' + newY);
sinak.splice(sinak.length - 1, 1);
}
//畫蛇
for (var r = 0; r < sinak.length; r++) {
context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
}
context.closePath();
}控制蛇的方向:
//鍵盤事件
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var move = 0; //移動(dòng)方向
if (e && e.keyCode == 37) { //左
move = (MoveTo == 1 ? 1 : 3);
} else if (e && e.keyCode == 38) { //上
move = (MoveTo == 2 ? 2 : 0);
} else if (e && e.keyCode == 39) { //右
move = (MoveTo == 3 ? 3 : 1);
} else if (e && e.keyCode == 40) { //下
move = (MoveTo == 0 ? 0 : 2);
} else if (e && e.keyCode == 32) {//暫停游戲
clearInterval(timer);
}
MoveTo = move; //修改當(dāng)前移動(dòng)方向
};這里做了防誤操作,當(dāng)蛇正在朝向某方向移動(dòng)時(shí),直接輸入反方向是無效的。如:蛇正向右走,這時(shí)直接按←鍵是無效的,仍然往右走。
一路跟著做到這里,相信大家的貪吃蛇已經(jīng)可以正常游戲了,不過我這個(gè)做的很糙,大家可以加入一些自己的想法,比如:
計(jì)分通關(guān),通關(guān)之后通過加快蛇的移動(dòng)速度來增加難度。
隨機(jī)生成多種果實(shí),比如加速果實(shí),雙倍成長果實(shí)等。
加入WebSocket,實(shí)現(xiàn)網(wǎng)絡(luò)版貪吃蛇。
我寫過關(guān)于WebSocket的實(shí)現(xiàn),有興趣的也可以去看看,下面是鏈接:
基于SuperSocket實(shí)現(xiàn)的WebSocket(后端)
基于SuperSocket實(shí)現(xiàn)的WebSocket(前端)
到此這篇關(guān)于JS+Canvas實(shí)現(xiàn)貪吃蛇小游戲的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ES6 javascript中class類的get與set用法實(shí)例分析
這篇文章主要介紹了ES6 javascript中class類的get與set用法,結(jié)合具體實(shí)例形式分析了ES6中類的get與set關(guān)鍵字使用方法,需要的朋友可以參考下2017-10-10
Javascript設(shè)計(jì)模式之發(fā)布訂閱模式
發(fā)布---訂閱模式又叫觀察者模式,它定義了對(duì)象間的一種一對(duì)多的關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知2022-12-12
js實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
JS實(shí)現(xiàn)模擬風(fēng)力的雪花飄落效果
這篇文章主要介紹了JS實(shí)現(xiàn)模擬風(fēng)力的雪花飄落效果,可在右側(cè)填入風(fēng)力值點(diǎn)擊按鈕即可看到伴隨風(fēng)力的雪花飄落效果,同時(shí)右側(cè)有實(shí)時(shí)雪花數(shù)量統(tǒng)計(jì)功能,需要的朋友可以參考下2015-05-05
javascript針對(duì)cookie的基本操作實(shí)例詳解
這篇文章主要介紹了javascript針對(duì)cookie的基本操作,結(jié)合實(shí)例形式較為詳細(xì)的分析了JavaScript操作cookie的基本技巧,并給出了一個(gè)JavaScript操作cookie的完整類,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
解決layui的table插件無法多層級(jí)獲取json數(shù)據(jù)的問題
今天小編就為大家分享一篇解決layui的table插件無法多層級(jí)獲取json數(shù)據(jù)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-09-09

