-
万里归一,情归深处,万变不离其宗,兜兜转转我们回到原生js开发中来,下面我们来使用原生js来开发一个小游戏==>【贪吃蛇】
-
下面是游戏截图
image.png
- 首先先来创建项目,创建三个目录,js、css、image,和一个index.html
image.png
- css目录内新建index.css
- js目录内新建index.js和config.js
- index.html内先引入必要的文件、创建必要的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;
}
- 首先来创建一个游戏的主方法,并且调用
- index.js
/**
* 游戏的主方法
*/
function main() {
}
main();
/**
* 游戏的主方法
*/
function main() {
initGame();
}
main();
function initGame() {
}
- 初始化的游戏方法大概分三步
- 8.1 第一步:绘制地图
- 8.2 第二步:绘制蛇
- 8.3 第三步:绘制食物
- 绘制地图前,要先在config.js内写一些游戏的相关配置
- config.js部分相关配置
// 游戏相关的配置
// 存储地图对象
var gridData = [];
// 整个地图的行与列
var tr = 30;
var td = 30;
// 蛇的身体大小
var snakeBody = 20;
// 蛇相关的配置信息
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,说明是第一次创建蛇
// 先创建一个div
snake.snakePos[i].domContent = document.createElement('div')
// 设置下绝对定位
snake.snakePos[i].domContent.style.position = 'absolute'
// 设置下蛇身体的宽高
snake.snakePos[i].domContent.style.width = snakeBody + 'px'
snake.snakePos[i].domContent.style.height = snakeBody + 'px'
// 设置下每节蛇身体的位置
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%'
}
}
// 需要讲创建的DOM元素添加到container容器上面
document.querySelector('.container').append(snake.snakePos[i].domContent)
}
}
image.png
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) {
// 构成一个死循环,直到生成符合要求的食物坐标才能退出该循环
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循环
break;
}
}
// 整个while循环跳出来之后,食物的坐标一定是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'
}
image.png
image.png
function main() {
// 一 初始化游戏
initGame()
// 二 绑定事件
bindEvent()
}
/**
* 绑定事件的方法
*/
function bindEvent() {
// 1. 首先是键盘事件 用户按下上下左右,蛇能够移动
document.onkeydown = (e) => {
// ArrowUp 上
// ArrowRight 右
// ArrowDown 下
// ArrowLeft 左
// console.log(e.key);
// 先来明确下,新的蛇头坐标和旧的蛇头坐标的关系,先在config.js内配置下位置关系
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]
// 根据方向计算出新的蛇头的坐标
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到数组内去
snake.snakePos.push(newHead)
// 重新绘制蛇
drawSnake(snake)
}
image.png
- 但是还存在几个问题
- 第一,就是蛇头的方向不对,要根据方向旋转下蛇头,在绘制蛇的方法drawSnake里完善,代码如下
// 设置下蛇身和蛇头
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]
// 根据方向计算出新的蛇头的坐标
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]
// 根据方向计算出新的蛇头的坐标
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,说明越界了或者碰到自己了,游戏结束
alert("游戏结束")
}
// 将旧的蛇头变成身体
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)
}
- 下面在做一个分数自增,计算成绩
- 在config.js新建一个变量score
var score = 0
// 判断是否吃到食物
if (collideCheckResult.isEat) {
// 吃到了,就重新生成新的食物
score++
drawFood()
}
- 游戏结束后,刷新页面,重新再来,直接刷新浏览器,简单粗暴
if (collideCheckResult.isCollide) {
// 进入此if,说明越界了或者碰到自己了,游戏结束
alert("游戏结束")
location.reload()
}
- 下面再来解决下 向左(上)走的时候,按右(下)键导致游戏结束的问题
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
}
- 现在已经可以玩了,接下来就是让他自己动起来
- 在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
if (collideCheckResult.isCollide) {
// 进入此if,说明越界了或者碰到自己了,游戏结束
clearInterval(timer)
alert("游戏结束")
location.reload()
}
- 还有很多可以优化和功能拓展,感兴趣的同学可以试试~
- 比如吃到苹果加音效、添加开始、暂停功能等等
- over
网友评论