-
萬里歸一疟呐,情歸深處脚曾,萬變不離其宗,兜兜轉(zhuǎn)轉(zhuǎn)我們回到原生js開發(fā)中來启具,下面我們來使用原生js來開發(fā)一個小游戲==>【貪吃蛇】
-
下面是游戲截圖
- 首先先來創(chuàng)建項目本讥,創(chuàng)建三個目錄,js鲁冯、css囤踩、image,和一個index.html
- css目錄內(nèi)新建index.css
- js目錄內(nèi)新建index.js和config.js
- 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>
.container{
width: 600px;
height: 600px;
background: #225675;
border: 20px solid #7dd9ff;
margin: 0 auto;
position: relative;
}
- 首先來創(chuàng)建一個游戲的主方法堵漱,并且調(diào)用
- index.js
/**
* 游戲的主方法
*/
function main() {
}
main();
- 在主方法內(nèi),調(diào)用一個初始化游戲的方法
/**
* 游戲的主方法
*/
function main() {
initGame();
}
main();
function initGame() {
}
- 初始化的游戲方法大概分三步
- 8.1 第一步:繪制地圖
- 8.2 第二步:繪制蛇
- 8.3 第三步:繪制食物
- 繪制地圖前涣仿,要先在config.js內(nèi)寫一些游戲的相關(guān)配置
- 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" },
]
}
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);
}
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);
}
/**
* 繪制蛇的方法
*/
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)
}
}
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();
}
/**
* 繪制食物的方法
*/
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)的食物的坐標都是隨機的
function main() {
// 一 初始化游戲
initGame()
// 二 綁定事件
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)
}
- 但是還存在幾個問題
- 第一塘秦,就是蛇頭的方向不對讼渊,要根據(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;
}
}
- 第二爪幻,就是要做碰撞檢測,在生成新的蛇頭后须误,要檢測蛇頭有沒有碰到食物挨稿、或者蛇身體、或者墻壁
- 要在哪做這個碰撞檢測呢京痢?很明顯要在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)
}
/**
* 碰撞檢測
* @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
// 判斷是否吃到食物
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()
/**
* 蛇自己動
*/
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