原生js開發(fā)【貪吃蛇】小游戲

  • 萬里歸一疟呐,情歸深處脚曾,萬變不離其宗,兜兜轉(zhuǎn)轉(zhuǎn)我們回到原生js開發(fā)中來启具,下面我們來使用原生js來開發(fā)一個小游戲==>【貪吃蛇】
  • 下面是游戲截圖
image.png
    1. 首先先來創(chuàng)建項目本讥,創(chuàng)建三個目錄,js鲁冯、css囤踩、image,和一個index.html
image.png
    1. css目錄內(nèi)新建index.css
    1. js目錄內(nèi)新建index.js和config.js
    1. index.html內(nèi)先引入必要的文件晓褪、創(chuàng)建必要的dom
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>貪吃??</title>
        <link rel="stylesheet" href="./css/index.css">
    </head>
    <body>
        <div class="container"></div>
        <script src="./js/config.js"></script>
        <script src="./js/index.js"></script>
    </body>
</html>
  • index.css
.container{
    width: 600px;
    height: 600px;
    background: #225675;
    border: 20px solid #7dd9ff;
    margin: 0 auto;
    position: relative;
}
    1. 首先來創(chuàng)建一個游戲的主方法堵漱,并且調(diào)用
  • index.js
/**
 * 游戲的主方法
 */
function main() {
  
}
main();
    1. 在主方法內(nèi),調(diào)用一個初始化游戲的方法
/**
 * 游戲的主方法
 */
function main() {
  initGame();
}
main();
    1. 下面創(chuàng)建這個初始化的游戲方法
function initGame() {
  
}
    1. 初始化的游戲方法大概分三步
  • 8.1 第一步:繪制地圖
  • 8.2 第二步:繪制蛇
  • 8.3 第三步:繪制食物
  • 繪制地圖前涣仿,要先在config.js內(nèi)寫一些游戲的相關(guān)配置
    1. config.js部分相關(guān)配置
// 游戲相關(guān)的配置
// 存儲地圖對象
var gridData = [];
// 整個地圖的行與列
var tr = 30;
var td = 30;
// 蛇的身體大小
var snakeBody = 20;
// 蛇相關(guān)的配置信息
var snake = {
  snakePos: [
    { x: 0, y: 0, domContent: "", flag: "body" },
    { x: 1, y: 0, domContent: "", flag: "body" },
    { x: 2, y: 0, domContent: "", flag: "body" },
    { x: 3, y: 0, domContent: "", flag: "head" },
  ]
}
  • 8.1 繪制地圖
function initGame() {
  // 1.繪制地圖
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
}
  • 8.2 繪制蛇
function initGame() {
  // 1.繪制地圖
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
  // 2.繪制蛇勤庐,寫一個公共方法
  drawSnake(snake);
}
  • 下面是繪制蛇的公共方法 drawSnake
/**
 * 繪制蛇的方法
 */
function drawSnake(snake) {
  for (let i = 0; i < snake.snakePos.length; i++) {
    if (!snake.snakePos[i].domContent) {
      // 如果進入進入此if,說明是第一次創(chuàng)建蛇
      // 先創(chuàng)建一個div
      snake.snakePos[i].domContent = document.createElement('div')
      // 設(shè)置下絕對定位
      snake.snakePos[i].domContent.style.position = 'absolute'
      // 設(shè)置下蛇身體的寬高
      snake.snakePos[i].domContent.style.width = snakeBody + 'px'
      snake.snakePos[i].domContent.style.height = snakeBody + 'px'
      // 設(shè)置下每節(jié)蛇身體的位置
      snake.snakePos[i].domContent.style.left = snake.snakePos[i].x * snakeBody + 'px'
      snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody + 'px'
      if (snake.snakePos[i].flag === 'head') {
        // 說明是蛇頭
        snake.snakePos[i].domContent.style.background = `
        url("./image/head.png") center/contain no-repeat
        `
      } else {
        // 說明是蛇身
        snake.snakePos[i].domContent.style.background = '#9ddbb1'
        snake.snakePos[i].domContent.style.borderRadius = '50%'
      }
    }
    // 需要講創(chuàng)建的DOM元素添加到container容器上面
    document.querySelector('.container').append(snake.snakePos[i].domContent)

  }
}
  • 保存看一下效果
image.png
  • 8.3 繪制食物
function initGame() {
  // 1.繪制地圖
for (let i = 0; i < tr; i++) {
    for (let j = 0; j < td; j++) {
      gridData.push({
        x: j,
        y: i
      })
    }
  }
  // console.log(gridData);
  // 2.繪制蛇,寫一個公共方法
  drawSnake(snake);
  // 繪制食物,寫一個公共方法
  drawFood();
}
  • drawFood 方法
/** 
 * 繪制食物的方法
 */
function drawFood() {
  // 1. 食物的坐標是隨機的
  // 2. 食物不能生成在蛇頭或蛇身上面
  while (true) {
    // 構(gòu)成一個死循環(huán)纵装,直到生成符合要求的食物坐標才能退出該循環(huán)
    var isRepeat = false; //默認生成的坐標是符合要求的
    // 隨機生成一個坐標
    food.x = Math.floor(Math.random() * tr)
    food.y = Math.floor(Math.random() * tr)
    // console.log(food);
    // 查看坐標是否符合要求(遍歷蛇)
    for (let i = 0; i < snake.snakePos.length; i++) {
      if (snake.snakePos[i].x === food.x && snake.snakePos[i].y === food.y) {
        // 進入此if,說明當前生成的食物坐標和蛇的身體或者頭的坐標沖突了
        isRepeat = true
        break;
      }
    }
    if (!isRepeat) {
      // 跳出while循環(huán)
      break;
    }
  }
  // 整個while循環(huán)跳出來之后丈探,食物的坐標一定是ok的
  // console.log(food);
  // console.log(snake.snakePos);
  if (!food.domContent) {
    food.domContent = document.createElement('div')
    food.domContent.style.width = snakeBody + 'px'
    food.domContent.style.height = snakeBody + 'px'
    food.domContent.style.position = 'absolute'
    food.domContent.style.background = `
    url("./image/apple.png") center/contain no-repeat
    `
    document.querySelector('.container').append(food.domContent)
  }
  food.domContent.style.left = food.x * snakeBody + 'px'
  food.domContent.style.top = food.y * snakeBody + 'px'
}
  • 下面就是效果,生產(chǎn)的食物的坐標都是隨機的
image.png

image.png

  • 下面就是要綁定事件
function main() {
  // 一 初始化游戲
  initGame()
  // 二 綁定事件
  bindEvent()
}
  • bindEvent
/**
 * 綁定事件的方法
 */
function bindEvent() {
  // 1. 首先是鍵盤事件 用戶按下上下左右拔莱,蛇能夠移動
  document.onkeydown = (e) => {
    // ArrowUp     上
    // ArrowRight  右
    // ArrowDown   下
    // ArrowLeft   左
    // console.log(e.key);
    // 先來明確下碗降,新的蛇頭坐標和舊的蛇頭坐標的關(guān)系,先在config.js內(nèi)配置下位置關(guān)系
    if (e.key == 'ArrowUp' || e.key == 'w') {
      // 用戶按的是上
      snake.direction = directionNum.top
    }
    if (e.key == 'ArrowRight' || e.key == 'd') {
      // 用戶按的是右
      snake.direction = directionNum.right
    }
    if (e.key == 'ArrowDown' || e.key == 's') {
      // 用戶按的是下
      snake.direction = directionNum.bottom
    }
    if (e.key == 'ArrowLeft' || e.key == 'a') {
      // 用戶按的是左
      snake.direction = directionNum.left
    }
    snakeMove()
  }
}
/**
 * 蛇移動的方法
 */
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根據(jù)方向計算出新的蛇頭的坐標
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }
  //把舊蛇頭改成身體
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  //把新蛇頭push到數(shù)組內(nèi)去
  snake.snakePos.push(newHead)
  // 重新繪制蛇
  drawSnake(snake)
}
  • 這樣就實現(xiàn)了蛇的上下左右移動
image.png
  • 但是還存在幾個問題
  • 第一塘秦,就是蛇頭的方向不對讼渊,要根據(jù)方向旋轉(zhuǎn)下蛇頭,在繪制蛇的方法drawSnake里完善尊剔,代碼如下
// 設(shè)置下蛇身和蛇頭
      if (snake.snakePos[i].flag === 'head') {
        // 說明是蛇頭
        snake.snakePos[i].domContent.style.background = `
          url("./image/head.png")   center no-repeat
        `
        snake.snakePos[i].domContent.style.backgroundSize = `100%`
       switch (snake.direction.flag) {
          case "left":
            snake.snakePos[i].domContent.style.transform = "rotate(180deg)"
            break;
          case "top":
            snake.snakePos[i].domContent.style.transform = "rotate(-90deg)"
            break;
          case "right":
            snake.snakePos[i].domContent.style.transform = "rotate(0deg)"
            break;
          case "bottom":
            snake.snakePos[i].domContent.style.transform = "rotate(90deg)"
            break;

          default:
            break;
        }
      }
  • 這樣蛇頭就正常了
image.png
  • 第二爪幻,就是要做碰撞檢測,在生成新的蛇頭后须误,要檢測蛇頭有沒有碰到食物挨稿、或者蛇身體、或者墻壁
  • 要在哪做這個碰撞檢測呢京痢?很明顯要在snakeMove方法里奶甘,生成新蛇頭后面,就要做碰撞檢測历造,在snakeMove方法里也封裝一個碰撞檢測方法isCollide
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根據(jù)方向計算出新的蛇頭的坐標
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }

  // 接下來就是要做碰撞檢測
  // 檢測新蛇頭有沒有碰到食物甩十、墻壁或者蛇身體
  // 也要封裝一個碰撞檢測方法 isCollide
  isCollide(newHead)
  // 將舊的蛇頭變成身體
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  snake.snakePos.push(newHead)
  // 重新繪制蛇
  drawSnake(snake)
}
  • isCollide碰撞檢測方法
/**
 * 碰撞檢測
 * @param {新計算出來的蛇頭坐標} newHead 
 */
function isCollide(newHead) {
  // 先定義一個對象船庇,默認是否碰撞了
  var collideCheckInfo = {
    isCollide: false,//默認沒有碰撞到墻壁吭产、蛇身
    isEat: false //默認沒有吃到食物
  }
  // 1. 檢測是否碰到墻壁 新蛇頭的x坐標小于0或者大于等于td了侣监,或者新蛇頭的y坐標小于0或者大于等于tr了,就是越界了
  if (newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr) {
    collideCheckInfo.isCollide = true
    return collideCheckInfo
  }
  // 2. 檢測是否碰到自己了 遍歷蛇 新蛇頭的x坐標==蛇的每個x坐標并且新蛇頭的y坐標==每個y坐標 就是碰到自己了
  for (let i = 0; i < snake.snakePos.length; i++) {
    const element = snake.snakePos[i];
    if (element.x === newHead.x && element.y === newHead.y) {
      collideCheckInfo.isCollide = true
      return collideCheckInfo
    }
  }
  // 3. 檢測是否吃到食物了
  if(newHead.x===food.x&&newHead.y===food.y){
    collideCheckInfo.isEat = true
  }
  return collideCheckInfo
}
  • 然后在完善下snakeMove方法臣淤,接收碰撞檢測方法的返回值
/**
 * 蛇移動的方法
 */
function snakeMove() {
  var oldHead = snake.snakePos[snake.snakePos.length - 1]
  // 根據(jù)方向計算出新的蛇頭的坐標
  var newHead = {
    x: oldHead.x + snake.direction.x,
    y: oldHead.y + snake.direction.y,
    domContent: "",
    flag: 'head'
  }

  // 接下來就是要做碰撞檢測
  // 檢測新蛇頭有沒有碰到食物橄霉、墻壁或者蛇身體
  // 也要封裝一個碰撞檢測方法 isCollide
  let collideCheckResult = isCollide(newHead)
  if (collideCheckResult.isCollide) {
    // 進入此if,說明越界了或者碰到自己了邑蒋,游戲結(jié)束
    alert("游戲結(jié)束")
  }
  // 將舊的蛇頭變成身體
  oldHead.flag = 'body'
  oldHead.domContent.style.background = '#9ddbb1'
  oldHead.domContent.style.borderRadius = '50%'
  snake.snakePos.push(newHead)
  // 判斷是否吃到食物
  if (collideCheckResult.isEat) {
    // 吃到了姓蜂,就重新生成新的食物
    drawFood()
  } else {
    // 沒吃到 就移除最后一個游戲
    document.querySelector('.container').removeChild(snake.snakePos[0].domContent)
    snake.snakePos.shift()
  }
  // 重新繪制蛇
  drawSnake(snake)
}
  • 下面在做一個分數(shù)自增,計算成績
  • 在config.js新建一個變量score
var score = 0
  • 在吃到食物后医吊,將score++
// 判斷是否吃到食物
  if (collideCheckResult.isEat) {
    // 吃到了钱慢,就重新生成新的食物
    score++
    drawFood()
  }
  • 游戲結(jié)束后,刷新頁面卿堂,重新再來束莫,直接刷新瀏覽器,簡單粗暴
if (collideCheckResult.isCollide) {
    // 進入此if草描,說明越界了或者碰到自己了览绿,游戲結(jié)束
    alert("游戲結(jié)束")
    location.reload()
  }
  • 下面再來解決下 向左(上)走的時候,按右(下)鍵導(dǎo)致游戲結(jié)束的問題
if ((e.key == 'ArrowUp' || e.key == 'w')&&snake.direction.flag!='bottom') {
      // 用戶按的是上
      snake.direction = directionNum.top
    }
    if ((e.key == 'ArrowRight' || e.key == 'd')&&snake.direction.flag!='left') {
      // 用戶按的是右
      snake.direction = directionNum.right
    }
    if ((e.key == 'ArrowDown' || e.key == 's')&&snake.direction.flag!='top') {
      // 用戶按的是下
      snake.direction = directionNum.bottom
    }
    if ((e.key == 'ArrowLeft' || e.key == 'a')&&snake.direction.flag!='right') {
      // 用戶按的是左
      snake.direction = directionNum.left
    }
  • 現(xiàn)在已經(jīng)可以玩了穗慕,接下來就是讓他自己動起來
  • 在bindEvent方法里
 if ((e.key == 'ArrowLeft' || e.key == 'a') && snake.direction.flag != 'right') {
      // 用戶按的是左
      snake.direction = directionNum.left
    }
    clearInterval(timer)
    startGame()
  • startGame
/**
 * 蛇自己動
 */
function startGame() {
  timer = setInterval(() => {
    snakeMove()
  }, time);
}
  • timer 和time 在config.js里要配置下
var timer = null
var time = 200
  • 碰墻后饿敲,一直彈alert,清除下定時器 就好了
if (collideCheckResult.isCollide) {
    // 進入此if逛绵,說明越界了或者碰到自己了怀各,游戲結(jié)束
    clearInterval(timer)
    alert("游戲結(jié)束")
    location.reload()
  }
  • 還有很多可以優(yōu)化和功能拓展,感興趣的同學(xué)可以試試~
  • 比如吃到蘋果加音效术浪、添加開始渠啤、暫停功能等等
  • over
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市添吗,隨后出現(xiàn)的幾起案子沥曹,更是在濱河造成了極大的恐慌,老刑警劉巖碟联,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妓美,死亡現(xiàn)場離奇詭異,居然都是意外死亡鲤孵,警方通過查閱死者的電腦和手機壶栋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來普监,“玉大人贵试,你說我怎么就攤上這事琉兜。” “怎么了毙玻?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵豌蟋,是天一觀的道長。 經(jīng)常有香客問我桑滩,道長梧疲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任运准,我火速辦了婚禮幌氮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胁澳。我一直安慰自己该互,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布韭畸。 她就那樣靜靜地躺著宇智,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陆盘。 梳的紋絲不亂的頭發(fā)上普筹,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音隘马,去河邊找鬼太防。 笑死,一個胖子當著我的面吹牛酸员,可吹牛的內(nèi)容都是我干的蜒车。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幔嗦,長吁一口氣:“原來是場噩夢啊……” “哼酿愧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邀泉,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤嬉挡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汇恤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庞钢,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年因谎,在試婚紗的時候發(fā)現(xiàn)自己被綠了基括。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡财岔,死狀恐怖风皿,靈堂內(nèi)的尸體忽然破棺而出河爹,到底是詐尸還是另有隱情,我是刑警寧澤桐款,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布咸这,位于F島的核電站,受9級特大地震影響鲁僚,放射性物質(zhì)發(fā)生泄漏炊苫。R本人自食惡果不足惜裁厅,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一冰沙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧执虹,春花似錦拓挥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茬故,卻和暖如春盖灸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磺芭。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工赁炎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钾腺。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓徙垫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親放棒。 傳聞我的和親對象是個殘疾皇子姻报,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容