一、css 部分
1. 居中
想要开始和暂停两个按钮居中,可以将盒子设置为弹性盒
也可以使用其他方法
【代码】
2. 将父元素设置为相对定位,偏于之后贪吃蛇长长的身子,是以父元素为基点的绝对定位,通过 left 和 top 来控制位置
二、 JS 部分
(一) 初始化地图
用一个数组,来储存地图上的每一个点
注意:上图红框位置是(0,0)
1. 设置标签
标签不一样,到时候渲染的东西也就不一样
2. 用绝对定位设置初始位置
3. 【代码】
// 蛇相关的配置
let 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:i,
y:j
})
}
}
(二)绘制蛇
1. 设置蛇头蛇身
注意:CSS中样式转变到JS中 - 短横线删除变为大写字母
2. 用 append 将创建的元素加到容器上
3. 【代码】
// 绘制蛇的方法
function drawSnake(snake){
for(let 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/snake.jpg) center/contain no-repeat'
snake.snakePos[i].domContent.style.borderRadius = '50%'
snake.snakePos[i].domContent.style.width = snakeBody +5 + "px"
snake.snakePos[i].domContent.style.height = snakeBody +5 + "px"
}else{
//说明是蛇身
snake.snakePos[i].domContent.style.background = "rgb(249, 203, 210)";
snake.snakePos[i].domContent.style.borderRadius = '50%'
snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody +3 + "px"
}
}
// 需要将添加的元素加到 container 容器上去
document.querySelector(".container").append(snake.snakePos[i].domContent)
}
}
(三)绘制食物
1. 随机生成一个坐标
2.【代码】
// 绘制食物
function drawFood(){
// 1. 食物的坐标是随机的
// 2. 食物不能生成在蛇头或者蛇身上
while(true){
//构成一个死循环,直到生成符合要求的实物坐标才能退出该循环
let isRepeat = false //默认生成的坐标是符合要求的
// 随机生成一个坐标(几格)
food.x = Math.floor(Math.random() * tr )
food.y = Math.floor(Math.random() * tr )
// 查看坐标是否符合要求(遍历蛇)
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 //没必要重复了,直接跳出for循环
}
}
if(!isRepeat){
//跳出 while 循环
break
}
}
// 若跳出了整个 while 循环,那么食物坐标一定是 OK 的
// 给他添加食物
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.borderRadius = '50%'
food.domContent.style.background = 'url(/img/食物.jpg) 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"
}
(四)绑定事件
1. 用户按键盘上的上下左右,对应的字符串
2. 通过计算得到新蛇头的坐标
新的蛇头坐标
注意:把所有snake 都改为 oldHead
按上下左右可以实现移动
————>
根据方向转蛇头
是否吃到食物
// 碰撞检测 function isCollide(newHead){ let collideCheckInfo = { isCollide : false, //是否碰到身体或者墙壁 isEat : false // 是否吃到食物 } // 1. 检测是否碰到墙壁 // td 是列,tr 是行 if(newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr ){ collideCheckInfo.isCollide = true return collideCheckInfo } // 2. 检测是否碰到身体 // 遍历整个蛇身,检查有没有碰到身体 for(let 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.isEat = true } return collideCheckInfo }
控制用户不能左移动的时候按右,上移动的时候按下
添加定时器,让蛇自己动
(五)bug 修复
点击重新开始之后,暂停键不起作用
可以将两个按钮添加上去
但是,点击按钮不会有相应的反应(它上面没有绑定事件)
可以通过事件委托,将之前添加的事件给到后面的新 dom 元素中
三、总代码
1. HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
<!-- 整个游戏容器 -->
<div class="container">
<!-- 开始游戏按钮 -->
<button class="startBtn"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn"></button>
<script src="/js/index.js"></script>
<script src="/js/config.js"></script>
</div>
</body>
</html>
2. CSS
*{
margin: 0;
padding: 0;
}
.container{
width: 600px;
height: 600px;
background-color: lightblue;
margin: 20px auto;
border: 20px solid lightskyblue;
/* 想要开始和暂停两个按钮居中,可以将盒子设置为弹性盒 */
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
/* 按钮公共样式 */
.container button{
border: none;
outline: none;
}
/* 开始按钮 */
/* contain 是铺满整个 */
.startBtn{
width: 200px;
height: 80px;
background: url(/img/开始.png) center/contain no-repeat;
display: block;
}
/* 暂停按钮 */
.pauseBtn{
width: 70px;
height: 70px;
background: url(/img/暂停.png) center/contain no-repeat;
display: none;
}
/* 食物 */
/* snake */
3. JS
// 游戏的相关配置
const gridData = []; //储存地图数组
// 整个网格的行与列
const tr = 30 //行
const td = 30 //列
// 蛇的身体大小
// 大盒子宽高600 30行 30列 所以每一小格就是 20 ,所以蛇的身体就是 20
const snakeBody = 20
// 要明确
// 我们在确定新的蛇头坐标的时候,会拿下面的对象和旧蛇头做一个计算
// 从而得到新蛇头的坐标
let directionNum = {
left:{ x:-1, y: 0 , flag: 'left'},
right:{ x:1, y: 0 , flag: 'right'},
top:{ x:0, y: -1 , flag: 'top'},
bottom:{ x:0, y: 1, flag: 'bottom'}
}
// 蛇相关的配置
let snake = {
// 蛇一开始移动的方向
direction : directionNum.right, // 一开始向右边移动
// 蛇的初始位置
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'}
]
}
// 游戏分数
let score = 0
// 停止计时器
let timerStop = null
const time = 200
// 食物相关的配置信息
let food= {
x:0, y:0, domContent:"" //domContent刚开始是空
}
// 配置信息结束
// 绘制蛇的方法
function drawSnake(snake){
for(let 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/snake.jpg) center/contain no-repeat'
snake.snakePos[i].domContent.style.borderRadius = '50%'
snake.snakePos[i].domContent.style.width = snakeBody +5 + "px"
snake.snakePos[i].domContent.style.height = snakeBody +5 + "px"
// 根据方向旋转蛇头
switch(snake.direction.flag){
case 'top':{
snake.snakePos[i].domContent.style.transform = 'rotate(-90deg)'
break
}
case 'bottom':{
snake.snakePos[i].domContent.style.transform = 'rotate(90deg)'
break
}
case 'left':{
snake.snakePos[i].domContent.style.transform = 'rotate(180deg)'
break
}
case 'right':{
snake.snakePos[i].domContent.style.transform = 'rotate(0deg)'
break
}
}
}else{
//说明是蛇身
snake.snakePos[i].domContent.style.background = "rgb(249, 203, 210)";
snake.snakePos[i].domContent.style.borderRadius = '50%'
snake.snakePos[i].domContent.style.top = snake.snakePos[i].y * snakeBody +3 + "px"
}
}
// 需要将添加的元素加到 container 容器上去
document.querySelector(".container").append(snake.snakePos[i].domContent)
}
}
// 绘制食物
function drawFood(){
// 1. 食物的坐标是随机的
// 2. 食物不能生成在蛇头或者蛇身上
while(true){
//构成一个死循环,直到生成符合要求的实物坐标才能退出该循环
let isRepeat = false //默认生成的坐标是符合要求的
// 随机生成一个坐标(几格)
food.x = Math.floor(Math.random() * tr )
food.y = Math.floor(Math.random() * tr )
// 查看坐标是否符合要求(遍历蛇)
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 //没必要重复了,直接跳出for循环
}
}
if(!isRepeat){
//跳出 while 循环
break
}
}
// 若跳出了整个 while 循环,那么食物坐标一定是 OK 的
// 给他添加食物
if(!food.domContent){
food.domContent = document.createElement("div")
food.domContent.style.width = snakeBody +5 + "px"
food.domContent.style.height = snakeBody +5 + "px"
food.domContent.style.position = "absolute"
food.domContent.style.borderRadius = '50%'
food.domContent.style.background = 'url(/img/食物.jpg) 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 initGame(){
// 1. 初始化地图
for(let i = 0;i < tr; i++){
for(let j = 0 ; j < td ; j++){
gridData.push({
// 给数组里面加对象
x:i,
y:j
})
}
}
// 2. 绘制蛇
drawSnake(snake); //将snake传进去,调用 drawSnake函数
// 3. 绘制食物
drawFood(snake);
}
// 绑定事件
function bindEvent(){
// 1. 首先键盘事件,用户按上下左右,蛇能移动
document.onkeydown = function(e){
if((e.key === "ArrowUp" || e.key.toLocaleLowerCase() === "w") && snake.direction.flag !== "bottom"){
// 用户按的是上
snake.direction = directionNum.top
}
if((e.key === "ArrowDown" || e.key.toLocaleLowerCase() === "s") && snake.direction.flag !== "top"){
// 用户按的是下
snake.direction = directionNum.bottom
}
if((e.key === "ArrowLeft" || e.key.toLocaleLowerCase() === "a") && snake.direction.flag !== "right"){
// 用户按的是左
snake.direction = directionNum.left
}
if((e.key === "ArrowRight" || e.key.toLocaleLowerCase() === "d") && snake.direction.flag !== "left"){
// 用户按的是右
snake.direction = directionNum.right
}
}
// 2. 计时器自动调用,蛇移动的方法
startGame()
// 3. 点击整个容器的时候,可以暂停游戏和重新开始游戏
document.querySelector('.container').onclick = function(e){
// 这边通过事件委托的形式,判断用户酒精点击的是 container 容器,还是暂停按钮或者开始按钮
// 从而做出不同的处理
// 用一个类名判断,点击的是暂停按钮,还是重新开始按钮
if(e.target.className === 'container'){
// 那么要做的是暂停操作
document.querySelector('.pauseBtn').style.display = 'block'
clearInterval(timerStop)
}else{
// 那么要做的是恢复游戏操作
document.querySelector('.pauseBtn').style.display = 'none'
startGame()
}
}
}
// 自动调用蛇移动
function startGame(){
timerStop = setInterval(function(){
snakeMove()
},time)
}
// 蛇的移动方法
function snakeMove(){
let oldHead = snake.snakePos[snake.snakePos.length - 1]
// 根据方向计算出新的蛇头坐标
let newHead = {
domContent:"",
x : oldHead.x + snake.direction.x,
y : oldHead.y + snake.direction.y,
flag : 'head'
}
// 接下来我们首先要做碰撞检测
// 看计算出来的新的蛇头有没有碰上食物,蛇的身体,墙壁
let collideCheckResult = isCollide(newHead)
if(collideCheckResult.isCollide){
// 进入这个 if 说明碰撞了
if( window.confirm(`哦呦~ gameover~ 笨蛋凯凯当前的分数是${score}分,啵我一口,允许你再来一次~ 嘿嘿`)){
// 重新开始游戏
//重置内容
document.querySelector(".container").innerHTML = `
<!-- 开始游戏按钮 -->
<button class="startBtn" style="display:none"></button>
<!-- 暂停游戏按钮 -->
<button class="pauseBtn" style="display:none"></button>
`
score = 0
// 蛇的初始位置
snake = {
// 蛇一开始移动的方向
direction : directionNum.right, // 一开始向右边移动
// 蛇的初始位置
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'}
]
}
food = {
x:0,y:0,domContent:""
}
// 再次调用initGame
initGame()
}else{
// 结束游戏
// 把绑定的事件删除
document.onkeydown = null;
// 关闭定时器
clearInterval(timerStop)
}
return //结束 snakeMove 函数
}
// 将旧的头修改为身体
oldHead.flag = 'body'
oldHead.domContent.style.background = "rgb(249, 203, 210)"
oldHead.domContent.style.borderRadius = '50%'
// 将蛇头大小改为蛇身大小,并且垂直居中
oldHead.domContent.style.width = snakeBody + "px"
oldHead.domContent.style.height = snakeBody + "px"
oldHead.domContent.style.top = oldHead.y * snakeBody +3 + "px"
// 判断是否吃到食物
if(collideCheckResult.isEat){
// 吃到食物了,重新生成新食物
drawFood()
}else{
// 进入这里,说明没有吃到食物
// 那么移除最后一个元素
document.querySelector(".container").removeChild(snake.snakePos[0].domContent)
// 把最后一个元素从数组中删除,即删除第一个数组
snake.snakePos.shift()
}
// 把新蛇头加进去
snake.snakePos.push(newHead)
// 重新绘制蛇
drawSnake(snake)
}
// 碰撞检测
function isCollide(newHead){
let collideCheckInfo = {
isCollide : false, //是否碰到身体或者墙壁
isEat : false // 是否吃到食物
}
// 1. 检测是否碰到墙壁
// td 是列,tr 是行
if(newHead.x < 0 || newHead.x >= td || newHead.y < 0 || newHead.y >= tr ){
collideCheckInfo.isCollide = true
return collideCheckInfo
}
// 2. 检测是否碰到身体
// 遍历整个蛇身,检查有没有碰到身体
for(let 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.isEat = true
score++ //分数自增
}
return collideCheckInfo
}
// 游戏的主方法
function main() {
// 用户点击了开始游戏之后,再做后续的工作
document.querySelector('.startBtn').onclick = function(e){
// 取消冒泡
e.stopPropagation()
document.querySelector('.startBtn').style.display = 'none'
// 1. 第一步 初始化游戏
initGame(); //调用初始化游戏方法
// 2. 绑定事件
bindEvent();
}
}
main(); //调用游戏的主方法
好啦!本次的分享到这里就结束啦!大家快去练练这个小游戏吧~
我们下次不见不散哦 !!!