文章目录
一、html文件
html文件要写的东西很少
- 游戏的容器
- 开始按钮
- 暂停按钮
<body>
<!-- 整个游戏容器 -->
<div class="container">
<!-- 开始游戏按钮 -->
<button class="startBtn"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn"></button>
</div>
</body>
二、CSS文件
1. 将标签初始化
*{
margin:0;
padding:0;
}
2. 游戏容器
- 长度和高度为600px;
- 背景颜色为#225675;
- 边框20px实线颜色为#7dd9ff;
- 上下左右距离20px 居中;
.container{
width:600px;
height:600px;
background:#225675;
border:20px solid #7dd9ff;
margin:20px auto;
}
3.开始和暂停按钮
1. 按钮的公共样式
- 去除按钮默认的边框
- 去除按钮的轮廓
.container button{
border:none;
outline:none;
}
2. 开始按钮
- 宽200px
- 高80px;
- 设置背景图片 startGame.png 居中 铺满 不重复
.startBtn{
height:80px;
width:200px;
background:url(./img/开始游戏.png) center/contain no-repeat
}
3. 暂停按钮
- 宽高都为70px
.pauseBtn{
display: none;
width: 70px;
height: 70px;
background:url(./img/暂停.png) center/contain no-repeat
}
4.按钮的位置问题
这时候生成的样式为:
- 问题:那么怎么让按钮在中间的位置?
- 思路:flex布局 然后主轴元素居中(x轴),侧轴元素居中(y轴)
- 解决:
由于flex的触发条件是父元素设置成flex 故我们在.container将display修改为flex,在将justify-content(设置主轴上元素的排列方式 即x轴)属性修改为center,align-items(设置侧轴上的元素的排列方式 即y轴)
.container{
display: flex;
justify-content: center;
align-items: center;
width:600px;
height: 600px;
background: #225675;
border: 20px solid #7dd9ff;
margin: 20px auto;
position: relative;
}
这里多设置了一个position为relative(相对定位) 为什么要这么做?
由于后期蛇的身体其实是一个个div,我们将这一个个div设置成absolute那么子元素的参考点就是父元素了,通过设置left和top属性就可以控制移动了。
5.按钮的显示问题
样式截图:
- 问题:现在有两个按钮,但是需要一次性只能显示一个
- 思路:通过修改开始和暂停按钮的display属性来解决(后期也是通过修改这两个按钮的display属性为block和none进行显示切换)
- 具体如下
- 开始按钮
.startBtn{
display: block;
width: 200px;
height: 80px;
background:url(./img/开始游戏.png) center/contain no-repeat
}
- 暂停按钮
.pauseBtn{
display: none;
width: 70px;
height: 70px;
background:url(./img/开始.png) center/contain no-repeat
}
三、js文件
3.1文件分类
将js分为两个文件
- index.js 用于书写业务逻辑的文件
- config.js 用于存放游戏配置相关的文件
3.2文件引入
在html文件body标签的最后面需要引入这两个js文件,由于index.js需要用到config.js中的内容故需要在index.js文件之前引入config.js否则会报错
<body>
<!-- 引入js文件 -->
<script src="../js/config.js"></script>
<script src="../js/index.js"></script>
</body>
3.3面对对象思想
做游戏最好采用面对对象思维。定义一个main函数,游戏执行只需要执行main函数即可
3.4步骤
1.主函数
首先我们在index.js中写一个主函数 在执行
注意:后续如果没有专门提到config.js那么书写的东西都是在index.js文件夹中
//定义游戏的主函数
function main(){
}
//执行游戏的主函数
main()
2. 初始化游戏
第一步我们需要在main函数中调用初始化游戏的方法
function main(){
//1.第一步:初始化游戏
initGame()
}
- 思考:我们需要初始化游戏的什么东西?
- 步骤:
- 初始化地图
- 绘制蛇
- 绘制食物
- 如下:
- 步骤:
//初始化游戏的方法
function initGame(){
//1.初始化地图
//2.绘制蛇
//3.绘制食物
}
1.1初始化地图
- 问题:那么初始化游戏需要用到什么参数配置?
- 思考:
- 存储地图对象 将container容器变为一个二维数组(蛇移动即通过坐标表示比如蛇头从(0,0)移动到(1,0))
- 行和列
- 蛇身体的大小
- 参数创建:
在config.js文件中
- 思考:
var gridData = [] //存储地图对象
//整个网格的行列
var tr = 30 //行 y
var td = 30 //列 x
//蛇的身体大小 一个格子的大小width/tr
var snakeBody = parseInt(window.getComputedStyle(document.getElementsByClassName('container')[0], null).width.split('px')) / tr
当时我们设置的container是600px宽高那么每一个小格子就是20px
- 问题1:那么为什么不直接将蛇的身体设置成20px?
- 解答:为了方便我们后期修改地图大小这里利用了一些方法进行自动计算蛇身体的大小。
- 步骤解释:
- 先获取container元素
- 在通过container元素获取width 但是此时带有px单位
- 通过split方法去掉px
- 在通过parseInt转化为数字 就获取了width大小
- 问题2:为什么要绕这么一大圈获取container的长和宽?
- 解答:当时第一个想法是通过获取container游戏容器的dom元素,在通过.style的方式直接获取游戏容器的长和宽,但是获取失败,显示的是空值,思考了下造成空值的原因是通过style.属性方式获取元素属性值有一个前提就是设置的css属性要是直接写在行内的,而我们当时写这些css属性是通过外链的形式,故获取不到。
function initGame(){
//1.初始化地图
drawMap()
}
function drawMap(){
}
- 思考:初始化地图是初始化什么东西?
- 解答:初始化地图就是将地图每个格子变为一个二维坐标
- 操作:用双重for循环即可
fucntion drawMap(){ for(var i = 0 ; i<tr;i++){ //tr是y for(var j = 0 ;j<td;j++){//td是x gridData.push({ x:j, y:i, }) } } }
第一列(0,0)(1,0)(2,0) y是不变的所以x是j ,y是i
1.2绘制蛇
- 思考:蛇需要有什么?
- 步骤
- 蛇头的初始位置 (用数组定义,里面存放对象:蛇头蛇身,蛇头和舍身对象的属性:x,y,domContent 用于存放dom元素,flag用于标记是头还是身体)
- 蛇一开始移动的方向或者可以说蛇目前移动的方向(在绑定事件时会用到)
- 参数实现:
config.js文件中写配置文件//蛇相关的配置 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(){
//2.绘制蛇
drawSnake(snake);
}
function drawSnake(snake){
}
- 问题1:绘制蛇需要怎么实现?
- 思路:
- 首先判断蛇是否有domContent元素,如果为空则说明是新增加的,需要创建一个div元素,设置他的position为absolute,设置width和height属性为snakeBody 加一个”px“,设置它的left和top属性。left和top属性需要经过计算我们要将其放在左上角的位置。
- 在第一个判断条件里判断它的flag是头还是身体,如果是身体则添加背景颜色为#9ddbb1以及百分之五十圆,如果是头则使用蛇头图片 居中 铺满 不重复。
- 将设置好的domContent元素添加进container容器里
- 绘制蛇实现
//绘制蛇的方法 function drawSnake(snake) { for (var i = 0; i < snake.snakePos.length; i++) { if (!snake.snakePos[i].domContent) { //如果进入此if说明是第一次创建蛇 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("../img/蛇头.png") center/contain no-repeat` } else { //说明是蛇身 snake.snakePos[i].domContent.style.background = '#9ddbb1' snake.snakePos[i].domContent.style.borderRadius = '50%' } } //需要将创建dom元素添加到container容器上面 var container = document.getElementsByClassName('container')[0] container.appendChild(snake.snakePos[i].domContent) } }
- 完成图:
- 思路:
- 问题2:为什么要设置left和top属性?
- 解答:因为后期要移动蛇是要通过控制left和top属性的改变来实现的,而要控制left和top属性,首先是需要有这个属性的初始值,如果无,则控制不了。
- 问题3:如何计算left和top属性?
- 如图
- 思路:每一个格子的大小是snakeBody,也就是说蛇头要在3,0的位置要将蛇头的x乘以snakeBoyd加一个”px“,y值不变,以此类推
- 如图
1.3绘制食物
- 问题:绘制食物需要有什么配置信息?
- 解答:
- 需要有x和y轴坐标(初始值无所谓)
- domContent(用于生成dom元素)
- 动手:
在config.js文件中写//食物相关的配置 var food = { //食物的初始位置 x: 0, y: 0, domContent: '', }
- 解答:
function initGame(){
//3.绘制食物
drawFood(food)
}
function drawFood(food){
}
-
问题1:绘制食物需要注意什么?
- 要求:
- 首先食物坐标是随机的
- 食物不能生成在蛇头或者蛇身上面
- 要求:
-
问题2:绘制食物的步骤是什么?
- 步骤:
- 随机生成食物坐标
- 判断食物坐标是否符合要求
- 如果符合要求判断食物是否存在dom元素
- 如果存在则生成一个div元素,添加width和height属性为snakeBody,position为absolute,background为食物图片,居中 铺满 不重复,在添加到contaniner容器里
- 设置食物dom元素的left和top值为生成的坐标
- 步骤:
-
问题3:怎么生成食物坐标?
- 思路:构成一个死循环,用random函数一直生成直到生成符合要求的食物坐标才能退出循环
-
问题4:x和y的范围是多少?
- 如图:tr和td有30种可能,Math.random()生成的范围是[0,1) 只需要将这个范围处理成(0,0)–>(29,29)即可
- 提示:可以使用floor函数,用于向下取整
- 如图:tr和td有30种可能,Math.random()生成的范围是[0,1) 只需要将这个范围处理成(0,0)–>(29,29)即可
-
问题5:如何判断食物是否在蛇头或者蛇身上?
- 解答:只需要遍历snake数组的x,y值即可。
-
问题6:食物dom的left和top值怎么设置?
-
解答:
和绘制蛇时类似用生成符合要求的食物坐标的x和y分别乘以snakeBody加上px -
绘制食物实现函数:
//绘制食物 function drawFood(food) { //构成一个死循环,直到生成符合要求的坐标 while (true) { var isRepeat = false //默认生成的食物坐标是符合要求 //随机生成一个坐标 food.x = Math.floor(Math.random() * td) food.y = Math.floor(Math.random() * tr) //查看坐标是否符合要求(遍历蛇) var len = snake.snakePos.length, snakePos = snake.snakePos for (var i = 0; i < len; i++) { if (snakePos[i].x === food.x && snakePos[i].y === food.y) { //进入此if说明生成的坐标和蛇身体冲突了 isRepeat = true break } } if (!isRepeat) { break } } //整个while循环退出之后,食物的坐标肯定是ok的 if (!food.domContent) { food.domContent = document.createElement('div') food.domContent.style.position = 'absolute' food.domContent.style.width = snakeBody + 'px' food.domContent.style.height = snakeBody + 'px' food.domContent.style.background = `url(./img/西瓜.png) center/contain no-repeat` document.getElementsByTagName('div')[0].appendChild(food.domContent) } food.domContent.style.left = food.x * snakeBody + 'px' food.domContent.style.top = food.y * snakeBody + 'px' }
-
完成图:
-
3.绑定事件
- 思考:绑定事件需要有哪些参数?
- 解答:
- 要明确新的蛇头和旧的蛇头之间的位置关系,即需要一个移动参数,用于存储上下左右变化时蛇头坐标的变化从而得出新的蛇头的坐标。
- 需要一个flag用于存储蛇是怎么移动的。
- 此时,蛇还需要目前移动的方向,由于我们的蛇是创建在左上角,那么默认一开始移动方向是向右,在snake对象中添加一个属性用于存放一开始移动的方向
- 参数实现:
在config.js种
补充绘制蛇时的蛇移动的方向//明确新的蛇头和旧的蛇头之间的位置关系 //我们在确定新的蛇头坐标的时候,会拿下面的对象和旧蛇头做一个计算 //从而得出新的蛇头的坐标 var directionNum={ left:{x:-1,y:0,flag:'left'}, top:{x:0,y:-1,flag:'top'}, right:{x:1,y:0,flag:'right'}, bottom:{x:0,y:1,flag:'bottom'} }
//蛇相关的配置 var snake = { //蛇的初始位置 这里不写省略代码 //添加: 蛇目前移动的方向 direction:directionNum.right }
- 解答:
第二步我们需要在main函数里绑定事件。
function main(){
//2.绑定事件
bindEvent()
}
//绑定事件函数
function bindEvent(){
}
-
问题1:绑定事件需要有哪些步骤?
- 步骤:
- 首先,是绑定键盘事件,用户按下上下左右,蛇会移动。
- 修改蛇目前的移动方向
- 执行让蛇移动的方法snakeMove()
- 在绑定事件函数外写一个让蛇移动的方法
- 绑定事件实现函数:
//绑定事件 function bindEvent() { //1.首先是键盘事件 document.onkeydown = function (e) { //keyCode:38是上 87 w 40是下 83 s 37是左 65 a 39是右 68 d if (e.keyCode == '37' || e.keyCode == '65') { snake.direction = directionNum.left } if (e.keyCode == '38' || e.keyCode == '87') { snake.direction = directionNum.top } if (e.keyCode == '39' || e.keyCode == '68') { snake.direction = directionNum.right } if (e.keyCode == '40' || e.keyCode == '83') { snake.direction = directionNum.bottom } snakeMove() } }
- 步骤:
-
问题2:绑定哪些键盘事件?
- 解答:wasd 和上下左右方向键
-
问题3:怎么触发这些事件?
- 解答:
- 一种是通过keypress触发一种是通过keydown触发。推荐使用keydown触发。
注意:keypress只能触发ASCII有的,即上下左右方向键不可以触发。
- 解答:
-
问题4:怎么确定方向键?
- 两种方法:
1. 如果采用的是keydowm绑定事件:那么一种是通过e.keyCode一种是通过e.key(e就是event事件,函数调用会默认有一个这个参数。)
2. 如果采用keypress绑定事件那么无法触发上下左右方向键,可以触发wasd通过e.charCode或者e.key来锁定.
我采用的是keydown触发:测试得keyCode:37是左38是上39是右40是下 87 w 83 s 65 a 68 d
- 两种方法:
-
问题5:如何修改蛇目前移动的方向?
- 解答:在判断完键盘事件之后,在对应的事件里将snake的direction修改为对应的键盘事件方向即可
-
问题6:蛇移动方法需要怎么实现?
-
实现步骤:
- 根据蛇目前的方向在下一个点添加蛇头,蛇头对象新增domContent对象为空,x值,y值,flag值
- 做碰撞检测:看计算出来新的蛇头是否碰上食物或者蛇身体或者墙壁封装一个函数isCollide(newHead)并且执行,在设移动的方法外书写此函数具体逻辑(具体分析看下面的碰撞检测分析)
- 接受碰撞检测的结果,判断isCollide的结果,如果是撞墙或者撞到身体则游戏结束(现在先简单的console.log一下后期在进行处理)
- 将旧的蛇头修改为身体 需要修改的是flag,background,borderRadius
- 接受碰撞检测返回的结果,判断isEatFood的结果。如果吃到食物则重新生成食物,如果没有吃到食物则调用container容器的removeChild方法删除子元素(snake的snakePos的第一个对象的domContent),还需要删除蛇数组的第一个元素(通过shift方法)
- 将新的蛇头push进snake.snakePos
- 重新绘制蛇(调用drawSnake方法) (此时有个蛇头方向BUG,看下面解答的蛇头方向问题BUG分析)
-
蛇移动的代码
//让蛇移动 function snakeMove() { var oldHead = snake.snakePos[snake.snakePos.length - 1] //获取旧的蛇头 //根据方向计算出新的蛇头的坐标 var newHead = { domContent: '', x: oldHead.x + snake.direction.x, //蛇头的x坐标 y: oldHead.y + snake.direction.y, //蛇头的y坐标 flag: 'head', } //碰撞检测 var collideCheckResult = isCollide(newHead) if (collideCheckResult.isCollide) { console.log('撞墙了') } //旧蛇头修改为蛇身 oldHead.flag = 'body' oldHead.domContent.style.background = '#9ddbb1' oldHead.domContent.style.borderRadius = '50%' if (collideCheckResult.isEatFood) { //重新生成食物 drawFood() } else { //没有吃到食物 移出最后一个元素 document.getElementsByTagName('div')[0].removeChild(snake.snakePos[0].domContent) snake.snakePos.shift() } //将新蛇头添加到蛇数组中 snake.snakePos.push(newHead) //重新绘制蛇 drawSnake(snake) }
-
如何判断蛇头的新坐标?
- 解答:新增x值为旧蛇头的x坐标加上蛇目前移动方向(snake.direction)的x值,y值以此类推。
-
蛇头方向问题BUG
- 已经可以让蛇移动了但是此时蛇头的位置没改变,如下图
- 判断蛇头的direction的flag属性,根据flag属性,添加绘制蛇里的绘制蛇头时根据方向旋转蛇头图片,由于蛇头默认朝向右边,那么如果向左则旋转180°,向上则旋转270°以此类推
- 蛇头转换代码:
function drawSnake(snake){ if (snakePos[i].flag === 'head') { //添加根据方向进行一个旋转的判断(已有代码不重复书写在此) switch (snake.direction.flag) { case 'left': snake.snakePos[i].domContent.style.transform = 'rotate(180deg)' break case 'top': snake.snakePos[i].domContent.style.transform = 'rotate(270deg)' break case 'right': snake.snakePos[i].domContent.style.transform = 'rotate(0deg)' break case 'bottom': snake.snakePos[i].domContent.style.transform = 'rotate(90deg)' break } } }
- 蛇头显示已正常图:
- 已经可以让蛇移动了但是此时蛇头的位置没改变,如下图
-
碰撞检测分析:
- 步骤:
- 首先要有一个碰撞对象信息collideCheckInfo
- 碰撞对象信息里面需要有是否撞墙属性:isCollide默认false
- 碰撞对象信息里面需要有是否吃到食物属性:isEatFood默认false
- 判断是否撞到墙新蛇头的x是否小于0 或者 大于等于td 或者 新蛇头的y是否小于0 或者 是否大于等于tr 如果是则修改collideCheckInfo.isCollide属性为true且返回collideCheckInfo
- 判断是否碰到自己的身体(遍历snake数组将每一个x与y与新蛇头的x,y对比)如果是则修改collideCheckInfo.isCollide为true且返回collideCheckInfo
- 判断是否吃到东西(将新蛇头的x与y与食物的坐标进行对比)如果是则修改collideCheckInfo.isEatFood = true
- 返回collideCheckInfo
- 碰撞检测代码:
//碰撞检测 function isCollide(newHead) { var collideCheckInfo = { //1.是否撞墙 isCollide: false, //2.是否吃到食物 isEatFood: false, } //1.撞墙 if (newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr) { collideCheckInfo.isCollide = true return collideCheckInfo } //2.检测是否碰到自己 for(var i=0;i<snake.snakePos.length;i++){ if(snake.snakePos[i].x==newHead.x&&snake.snakePos[i].y==newHead.y){ collideCheckInfo.isCollide = true return collideCheckInfo } } //3.检测是否吃到食物 if(newHead.x==food.x&&newHead.y==food.y){ collideCheckInfo.isEatFood = true } return collideCheckInfo }
- 步骤:
-
4.游戏分数
在config.js中添加一个分数变量
//游戏分数
var score = 0
- 问题:什么地方让游戏分数++?
- 解答:在碰撞检测函数吃到食物的时候让分数++
- 实现代码
//3.检测是否吃到食物 function isCollide(newHead){ if (newHead.x == food.x && newHead.y == food.y) { //之前的代码这里不做展示 score++ ; //分数自增 <----新增 } }
5.精化
config.js中创建一个计分变量
//游戏分数
var score = 0
在碰撞函数处理中的判断是否吃到食物添加分数++
//3.判断是否吃到食物
if (newHead.x == food.x && newHead.y == food.y) {
collideCheckInfo.isEat = true
score ++;//分数加一
}
修改碰到东西之后的处理
//判断是否撞到东西
if (collideCheckInfo.isCollide) {
//如果进入此if表示碰到东西了
var result = window.confirm(`游戏结束,你的得分为${score}分,是否重新开始?`)
if (result) {
//如果进来则说明重新开始
// window.location.reload() //刷新页面
//方法二:
document.getElementsByClassName('container')[0].innerHTML = ''
score = 0
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' },
],
//蛇一开始移动的方法
direction: directionNum.right,
}
food = {
//食物的初始位置
x: 0,
y: 0,
domContent: '',
}
initGame()
} else {
//结束游戏
document.onkeydown=false
clearInterval(timerStop)
}
return
}
优化移动:
往左的时候不能往右
往上的时候不能往下
以此类推
修改绑定事件函数
//绑定事件
function bindEvent() {
//1.监听用户的键盘事件 上下左右
document.onkeydown = function (e) {
//37 左 38上 39右 40下 ↑↓←→
//65 a 87 w 68d 83s
//上
if ((e.keyCode == '38' || e.keyCode == '87')&&snake.direction.flag!='bottom') {
snake.direction = directionNum.top
}
//下
if( (e.keyCode == '40' || e.keyCode == '83') && snake.direction.flag!='top'){
snake.direction = directionNum.bottom
}
//左
if( (e.keyCode == '37' || e.keyCode == '65') && snake.direction.flag!='right') {
snake.direction = directionNum.left
}
//右
if ((e.keyCode == '39' || e.keyCode == '68') && snake.direction.flag!='left') {
snake.direction = directionNum.right
}
snakeMove() //蛇的移动方法
}
}
写一个定时器让蛇自己移动
将绑定事件中的snakeMove()修改为startGame()
在config.js中创建三个变量
//停止计时器
var timerStop = null
//计时器事件
var speed =500
//防止重复执行setInterval
var flag=true
书写startGame函数
//定时器
function startGame() {
if (flag) {
timerStop = setInterval(function () {
snakeMove()
flag = false
}, speed)
}
}
6.暂停
在bindEvent函数中添加
//当点击container的时候暂停游戏
document.getElementsByClassName('container')[0].onclick=function(){
document.getElementsByClassName('pauseBtn')[0].style.display='block'
clearInterval(timerStop)
flag=true
}
//点击暂停按钮继续游戏
document.getElementsByClassName('pauseBtn')[0].onclick=function(e){
//阻止冒泡事件
e.stopPropagation()
document.getElementsByClassName('pauseBtn')[0].style.display='none'
startGame()
}
7.开始游戏
在main函数中修改代码
function main() {
//用户点击了开始游戏之后在左后续的工作
var startBtn = document.getElementsByClassName('startBtn')[0]
startBtn.onclick = function (e) {
//阻止冒泡
e.stopPropagation()
//1.初始化游戏
initGame()
//2.绑定事件
bindEvent()
//3.将按钮隐藏
startBtn.style.display='none'
}
}
但是有个bug 点击开始按钮之后无法暂停
为什么会出现这样子的事情?
因为在重新开始的时候我们将container的innerHTML置空了
本身是有开始和暂停两个按钮的
修复:
document.getElementsByClassName('container')[0].innerHTML =
` <!-- 开始游戏按钮 -->
<button class="startBtn" style="display: none"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn" style="display: none"></button>`
这么写还是有bug 就是开始和暂停按钮会同时出现
将container的暂停事件重新写
//当点击container的时候暂停游戏
document.getElementsByClassName('container')[0].onclick = function (e) {
var pauseBtn = document.getElementsByClassName('pauseBtn')[0]
if(e.target.className=='container'){
pauseBtn.style.display = 'block'
clearInterval(timerStop)
flag = true
}else{
//恢复游戏操作
pauseBtn.style.display = 'none'
startGame()
}
}
四、完整代码
index.js
//游戏的主方法
function main() {
//用户点击了开始游戏之后在左后续的工作
var startBtn = document.getElementsByClassName('startBtn')[0]
startBtn.onclick = function (e) {
//阻止冒泡
e.stopPropagation()
//1.初始化游戏
initGame()
//2.绑定事件
bindEvent()
//3.将按钮隐藏
startBtn.style.display='none'
}
}
//初始化游戏
function initGame() {
//1.初始化地图
drawMap()
//2.初始化蛇
drawSnake(snake)
//3.初始化食物
drawFood()
}
//绘制地图的方法
function drawMap() {
for (var i = 0; i < td; i++) {
for (var j = 0; j < td; j++) {
griData.push({ x: j, y: i }) //将所有的格子坐标存储到数组中
}
}
}
//绘制蛇的方法
function drawSnake(snake) {
for (var i = 0; i < snake.snakePos.length; i++) {
if (!snake.snakePos[i].domContent) {
//如果进入此if说明是第一次创建蛇
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("../img/蛇头.png") center/contain no-repeat`
//根据方向进行一个旋转
switch (snake.direction.flag) {
case 'left': {
snake.snakePos[i].domContent.style.transform = `rotate(180deg)`
break
}
case 'right': {
snake.snakePos[i].domContent.style.transform = `rotate(0deg)`
break
}
case 'top': {
snake.snakePos[i].domContent.style.transform = `rotate(270deg)`
break
}
case 'bottom': {
snake.snakePos[i].domContent.style.transform = `rotate(90deg)`
break
}
}
} else {
//说明是蛇身
snake.snakePos[i].domContent.style.background = '#9ddbb1'
snake.snakePos[i].domContent.style.borderRadius = '50%'
}
}
//需要将创建dom元素添加到container容器上面
var container = document.getElementsByClassName('container')[0]
container.appendChild(snake.snakePos[i].domContent)
}
}
//绘制食物的方法
function drawFood() {
//思考:
//1.食物的坐标是随机的
//2.食物不能生成在container的边界上及以外
//3.食物不能生成在蛇的身体上蛇的头上
//TODO:
while (true) {
//构成一个死循环 直到生成符合要求的食物坐标 才能退出循环
var isRepeat = false
food.x = Math.floor(Math.random() * tr) //最大值为tr-1 因为是从0开始的
food.y = Math.floor(Math.random() * td) //最大值为td-1 因为是从0开始的
//floor:总是返回小于等于一个给定数字的最大整数。
//random:返回0到1之间的一个随机数
for (var i = 0; i < snake.snakePos.length; i++) {
if (food.x == snake.snakePos[i].x && food.y == snake.snakePos[i].y) {
//进入此if说明食物生成在蛇的身体上
isRepeat = true
break
}
}
if (!isRepeat) {
//跳出while循环
break
}
}
//跳出while循环说明已经生成了符合要求的食物坐标
if (!food.domContent) {
food.domContent = document.createElement('div')
food.domContent.style.position = 'absolute'
food.domContent.style.height = snakeBody + 'px'
food.domContent.style.width = snakeBody + 'px'
food.domContent.style.background = `url("../img/西瓜.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'
}
//蛇的移动方法
function snakeMove() {
var oldHead = snake.snakePos[snake.snakePos.length - 1]
//1.根据方向计算出新的蛇头的坐标作为
var newHead = {
domContent: '',
x: oldHead.x + snake.direction.x,
y: oldHead.y + snake.direction.y,
flag: 'head',
}
//这之间要左个碰撞检测 看计算出来的蛇头有没有碰上食物或者蛇身体或者墙壁
var collideCheckInfo = isCollide(newHead)
//判断是否撞到东西
if (collideCheckInfo.isCollide) {
//如果进入此if表示碰到东西了
var result = window.confirm(`游戏结束,你的得分为${score}分,是否重新开始?`)
if (result) {
//如果进来则说明重新开始
// window.location.reload() //刷新页面
//方法二:
document.getElementsByClassName('container')[0].innerHTML =
` <!-- 开始游戏按钮 -->
<button class="startBtn" style="display: none"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn" style="display: none"></button>`
score = 0
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' },
],
//蛇一开始移动的方法
direction: directionNum.right,
}
food = {
//食物的初始位置
x: 0,
y: 0,
domContent: '',
}
initGame()
} else {
//结束游戏
document.onkeydown = false
}
clearInterval(timerStop)
return
}
//2.将旧的头修改为身体
oldHead.flag = 'body'
oldHead.domContent.style.background = '#9ddbb1'
oldHead.domContent.style.borderRadius = '50%'
//判断是否吃到东西
if (collideCheckInfo.isEat) {
//如果进入此if表示吃到食物了
//重新生成食物
drawFood()
} else {
//说明没有吃到食物
document.getElementsByClassName('container')[0].removeChild(snake.snakePos[0].domContent)
snake.snakePos.shift()
}
//3.将新的头添加到蛇的数组中
snake.snakePos.push(newHead)
//4.重新绘制蛇
drawSnake(snake)
}
//定时器
function startGame() {
if (flag) {
timerStop = setInterval(function () {
snakeMove()
flag = false
}, speed)
}
}
//绑定事件
function bindEvent() {
//1.监听用户的键盘事件 上下左右
document.onkeydown = function (e) {
//37 左 38上 39右 40下 ↑↓←→
//65 a 87 w 68d 83s
//上
if ((e.keyCode == '38' || e.keyCode == '87') && snake.direction.flag != 'bottom') {
snake.direction = directionNum.top
}
//下
if ((e.keyCode == '40' || e.keyCode == '83') && snake.direction.flag != 'top') {
snake.direction = directionNum.bottom
}
//左
if ((e.keyCode == '37' || e.keyCode == '65') && snake.direction.flag != 'right') {
snake.direction = directionNum.left
}
//右
if ((e.keyCode == '39' || e.keyCode == '68') && snake.direction.flag != 'left') {
snake.direction = directionNum.right
}
//计时器自动移动蛇
startGame()
//当点击container的时候暂停游戏
document.getElementsByClassName('container')[0].onclick = function (e) {
var pauseBtn = document.getElementsByClassName('pauseBtn')[0]
if(e.target.className=='container'){
pauseBtn.style.display = 'block'
clearInterval(timerStop)
flag = true
}else{
//恢复游戏操作
pauseBtn.style.display = 'none'
startGame()
}
}
// //点击暂停按钮继续游戏
// document.getElementsByClassName('pauseBtn')[0].onclick = function (e) {
// //阻止冒泡事件
// e.stopPropagation()
// document.getElementsByClassName('pauseBtn')[0].style.display = 'none'
// startGame()
// }
}
}
//碰撞检测
function isCollide(newHead) {
var collideCheckInfo = {
isCollide: false, //是否碰撞墙壁
isEat: false, //是否吃到食物
}
///1.判断是否碰撞墙壁
if (newHead.x < 0 || newHead.x > td - 1 || newHead.y < 0 || newHead.y > tr - 1) {
collideCheckInfo.isCollide = true
return collideCheckInfo
}
//2.检测是否碰到自己的身体
for (var i = 0; i < snake.snakePos.length; i++) {
if (newHead.x == snake.snakePos[i].x && newHead.y == snake.snakePos[i].y) {
collideCheckInfo.isCollide = true
return collideCheckInfo
}
}
//3.判断是否吃到食物
if (newHead.x == food.x && newHead.y == food.y) {
collideCheckInfo.isEat = true
score++ //分数加一
}
return collideCheckInfo
}
//调用主方法
main()
config.js
//存储地图对象 坐标x,y
var griData = []
//整个网格的行列
var tr = 30 //行 y
var td = 30 //列 x
//蛇的身体大小 一个格子的大小width/tr
// 1.先获取container元素
// 2.在通过container元素获取width 但是此时带有px单位
// 3.通过split方法去掉px
// 4.在通过parseInt转化为数字 就获取了width大小
var snakeBody = parseInt(window.getComputedStyle(document.getElementsByClassName('container')[0], null).width.split('px')) / tr
//明确新的蛇头和旧的蛇头之间的位置关系
//我们在确定新的蛇头坐标的时候,会拿下面的对象和旧蛇头做一个计算
var directionNum={
left:{x:-1,y:0,flag:'left'},
top:{x:0,y:-1,flag:'top'},
right:{x:1,y:0,flag:'right'},
bottom:{x:0,y:1,flag:'bottom'}
}
//蛇相关的配置
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' },
],
//蛇一开始移动的方法
direction:directionNum.right
}
//食物相关的配置
var food = {
//食物的初始位置
x: 0,
y: 0,
domContent: '',
}
//游戏分数
var score = 0
//停止计时器
var timerStop = null
//计时器事件
var speed =200
//防止重复执行setInterval
var flag=true
index.css
*{
/* 去除内外边距 */
margin: 0;
padding: 0;
}
/* 整体游戏容器样式 */
.container{
display: flex;
justify-content: center;
align-items: center;
width:600px;
height: 600px;
background: #225675;
border: 20px solid #7dd9ff;
margin: 20px auto;
position: relative;
}
/* 设置按钮公共样式 */
.container button{
border:none;
outline: none;
/* 边框和轮廓 */
}
/* 开始按钮 */
.startBtn{
display: block;
width: 200px;
height: 80px;
background:url(./img/开始游戏.png) center/contain no-repeat
}
.pauseBtn{
display: none;
width: 70px;
height: 70px;
background:url(./img/开始.png) center/contain no-repeat
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<!-- 整个游戏容器 -->
<div class="container">
<!-- 开始游戏按钮 -->
<button class="startBtn"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn"></button>
</div>
<script src="../js/config.js"></script>
<script src="../js/index.js"></script>
</body>
</html>