今天呢,主要和小夥伴們分享一下一個貪吃蛇遊戲從構思到實現的過程~由於我不是很喜歡直接PO代碼,因此只copy代碼的童鞋們請出門左轉不謝。javascript
按理說canvas與其應用是老生常談了,可我在準備階段卻搜索不到有用的資料(不是代碼!),因此說呢,只能自力更生 -_- html
首先是大體要考慮的東西:前端
1.要有蛇(沒蛇怎麼叫貪吃蛇)。java
2.而後要有地圖(蛇是不能上天的)。canvas
3.不能水平\垂直掉頭(若是想掉頭,須要至少變換方位而且至少移動一格纔可)。後端
4.食物(否則怎麼貪吃)。數組
5.吃了食物要變長(這纔是精髓)。網絡
PS:~如今我回想起來,當時的確只想到這麼多(⊙﹏⊙)dom
構思完畢,開工!spa
怎麼作呢?從大到小,先畫個矩形做地圖,可我以爲太醜,因而花了一張圖出來:
context.beginPath(); var bgImg = new Image(); bgImg.src = "img/background.png"; context.drawImage(bgImg, 0, 0, 600, 600); context.closePath();
如今咱們有地圖了
地圖上好像缺點什麼……沒錯就是禮物,因此咱們如今生成禮物,那麼問題來了:禮物最多有幾個、生成位置、什麼時候生成。
我這裏暫時定義爲:最多2個、隨機位置生成、當禮物個數小於2時生成至2個。
接下來就很簡單了,上圖中,容許蛇活動的範圍是14顆樹(周圍兩顆樹是牆),而後16顆樹=600px,很容易咱們獲得每格多寬~
因此呢,咱們只須要定義一個隨機生成1-14整數的方法就能夠很輕鬆找到應該生成的位置:
//隨機數 function selectfrom() { return Math.floor(Math.random() * 14 + 1); }
而後再用求出的數乘以每一格子的寬度,便可求出生成的具體X座標,由於是正方形,因此Y也同樣:
var x = selectfrom() * (600/16); var y = selectfrom() * (600/16);
而且每獲得一組禮物座標後,都須要存儲在一個數組內(一下子有大用處),至於畫矩形太基礎我就不說了。
And Now,咱們有了禮物,有了地圖,就差蛇了,那麼問題又來了:出生的蛇多長、出生地、死亡方式、移動方式、轉彎方式、如何判斷吃掉了禮物、吃掉了禮物變長到哪裏。
出生蛇長度:實際編寫過程當中,我發現默認長度1和2都不可以很好的體現「蛇的轉彎」,因此定義爲3,而且需將蛇身全部座標記錄在數組內。
出生地:地圖中央或者本身定一個位置(按照格子來分),XY座標求取方式上面已經說過再也不贅述。
死亡方式:碰到障礙,或者(吃到本身)蛇頭碰到蛇身。
移動方式:經過定義一個全局變量記錄當前方向(0、一、二、3,默認1),而且使用計時器驅動蛇運動。
轉彎方式:加入鍵盤按鍵檢測事件,當方向鍵按下的時候修改-記錄方向的所有變量便可。
如何判斷吃掉了禮物:每次蛇頭移動時,都要遍歷下禮物集合(上面有說過),若是蛇頭將要移動到的下個座標與之重合了,則視爲吃掉了禮物。
吃掉了禮物變長到哪裏:直接加在頭部可能會致使意外的死亡,因此我決定吃到禮物後的下一次移動不消除蛇尾(最後一個元素)。
有了上面的構思,咱們能夠着手定義一些可能會用到的公共變量:
var canvas = document.getElementById("mycanvas");//畫布主體 var context = canvas.getContext("2d"); var timer;//計時器 const WIDTH = canvas.width;//畫布寬 const HEIGHT = canvas.height;//畫布高 const XSUM = 16; //畫布寬分爲幾格 const YSUM = 15; //畫布高分爲幾格 const MAXFFOD = 2; //最大食物數量 var score = 0;//定義記錄遊戲得分 var xsplit = WIDTH / XSUM; //x每一格子的寬度 var ysplit = HEIGHT / YSUM; //y每一格子的高度 var foodcount = 0; //當前食物數量 var sinak = []; //貪吃蛇座標集 var get = []; //禮物座標集 var MoveTo = 1; //移動方向 默認1(右)
有了這些變量,是否是發現不少東西都通了呢?
咱們先來畫蛇:
//畫貪吃蛇 function drawsinak(sl) { //sl默認長度 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); //默認出生點:7,6默認中心點 sinak.push(xsplit * (7 - i) + ',' + ysplit * 6); } } context.fill(); context.closePath(); }
能夠看到我將生成的蛇的座標都計入了數組內,生成的禮物天然也要計入:
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();
接下來比較重要了,蛇的移動,以及吃到禮物和觸發死亡判斷:
//移動方法 //[c]移動方向 上右下左 0123 function sinakMove(c) { context.beginPath(); //默認右側爲頭 var tou = sinak[0]; //頭 var weiba = sinak[sinak.length - 1]; //尾巴 var oldX = tou.split(',')[0]; //頭部舊X座標 var oldY = tou.split(',')[1]; //頭部舊Y座標 var newX = 0; //頭部最新X座標 var newY = 0; //頭部最新Y座標 //計算頭部最新XY座標 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--; //禮物計數減小1個 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); } } } //若是沒有吃到禮物,那麼進行普通移動 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; //移動方向 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; //修改當前移動方向 };
這裏作了防誤操做,當蛇正在朝向某方向移動時,直接輸入反方向是無效的。如:蛇正向右走,這時直接按←鍵是無效的,仍然往右走。
一路跟着作到這裏,相信你們的貪吃蛇已經能夠正常遊戲了,不過我這個作的很糙,你們能夠加入一些本身的想法,好比:
計分通關,通關以後經過加快蛇的移動速度來增長難度。
隨機生成多種果實,好比加速果實,雙倍成長果實等。
加入WebSocket,實現網絡版貪吃蛇。
我寫過關於WebSocket的實現,有興趣的也能夠去看看,下面是連接:
歡迎你們討論、提問~