vue贪吃蛇小游戏
偶然间跟一个前端大佬学习了一个用vue写的贪吃蛇小游戏,一个小demo 感觉挺好玩,就纪录下来了

直接全代码
<template>
<div>
<div class="title" :class="isShowTitle ? 'titlebg' : ''">
<span>分数:{{ score }}</span>
</div>
<div class="game-box">
<ul>
<!-- 每一个小格子 -->
<li
v-for="(item, index) in gameMap"
:key="index"
:class="{
tou: item.isTou,
shou: item.isShou,
jiao: item.isJiao,
shen: item.isShen,
r90: item.isR90,
r180: item.isR180,
rm90: item.isRM90,
cate: item.isCate
}"
></li>
</ul>
</div>
<div class="gameover" v-show="isGameOver">
<ul>
<!-- li有多少个是根据分数来的分数有可能是 0 10 100 1000 '150' -->
<li
v-for="(item, index) in score.toString()"
:key="index"
:style="{
background:
'url(' + scoreNumArr[item] + ') center top/auto 100% no-repeat'
}"
></li>
</ul>
<p>
历史最高 <span>{{ historyMax }}</span>
</p>
<div class="restart" @click="restart"></div>
</div>
<div class="gameaction" v-show="!isShowTitle">
<div class="btn" @click="action"></div>
</div>
</div>
</template>
<script>
import num0 from '../assets/images/numbers/0.png';
import num1 from '../assets/images/numbers/1.png';
import num2 from '../assets/images/numbers/2.png';
import num3 from '../assets/images/numbers/3.png';
import num4 from '../assets/images/numbers/4.png';
import num5 from '../assets/images/numbers/5.png';
import num6 from '../assets/images/numbers/6.png';
import num7 from '../assets/images/numbers/7.png';
import num8 from '../assets/images/numbers/8.png';
import num9 from '../assets/images/numbers/9.png';
export default {
data() {
return {
// 生成地图
gameMap: [],
// 生成蛇身数组,记录了蛇的位置在地图上的下标(格子下标的记录)
snakeArr: [50, 51, 52, 53, 54],
// 蛇的移动方向
direction: 'right',
// 定时器
timer: null,
// 月饼的下标
foodIndexs: [],
// 分数
score: 0,
// 是否游戏结束
isGameOver: false,
// 分数数组
scoreNumArr: [num0, num1, num2, num3, num4, num5, num6, num7, num8, num9],
// 历史最高
historyMax: 3000,
// 是否显示title
isShowTitle: false
};
},
created() {
this.gameInit();
},
methods: {
gameInit() {
// 初始化
this.gameMap = [];
this.snakeArr = [50, 51, 52, 53, 54];
this.score = 0;
this.direction = 'right';
this.isGameOver = false;
// 生成游戏地图,600个小格子
for (let i = 0; i < 600; i++) {
this.gameMap.push({});
}
// 生成蛇
this.generSnake();
// 生成食物
// this.数组名[parseInt(Math.random()*this.数组名.length)]
// this.gameMap[parseInt(Math.random()*this.gameMap.length)].isCate = true;
this.generCates();
},
action() {
this.isShowTitle = true;
this.toplay();
},
toplay() {
// 蛇的移动
this.timer = setInterval(this.play, 100);
},
restart() {
this.gameInit();
this.toplay();
},
gameover() {
this.isGameOver = true;
clearInterval(this.timer);
//历史最高分的处理 (历史最高分保存在本地存储中)
// 1、获取本地历史最高分
let localMax = localStorage.getItem('historyMax');
// 2、和this.score进行对比
// localMax? (真正和score进行对比):this.score 第一次游戏结束的时候拿不到localMax,拿到了才进行对比,拿不到,历史最高分就是这次游戏的分数this.score
// this.historyMax = localMax? (真正和this.score进行对比):this.score
this.historyMax = localMax
? Number(localMax) > this.score
? localMax
: this.score
: this.score;
// 3、把historyMax更新到本地存储
localStorage.setItem('historyMax', this.historyMax);
},
play() {
// 边界判断
// 判断是不是达到边界了
// 如果达到边界了,游戏结束 就要return (!!! 还要在return前面去关闭定时器)
// 每一个方向的边界条件不一样,例如: (都是那蛇头的位置来进行分析)
// 向右this.snakeArr的最后一个元素+1 能被40整除
// 向下this.snakeArr的最后一个元素 >=600
// 向上this.snakeArr的最后一个元素 <0
// 向左this.snakeArr的最后一个元素 能被40整除
let touIndex = this.snakeArr[this.snakeArr.length - 1];
let boundaryArr = [
this.direction == 'right' && (touIndex + 1) % 40 == 0,
this.direction == 'down' && touIndex >= 600 - 40,
this.direction == 'up' && touIndex < 40,
this.direction == 'left' && touIndex % 40 == 0
];
if (boundaryArr.includes(true)) {
// 游戏结束
this.gameover();
return;
}
// -------------以下代码就是处理蛇的移动-------------
// 先清除所有格子的isXxx为false
this.gameMap.forEach((item) => {
item.isTou = false;
item.isShou = false;
item.isShen = false;
item.isJiao = false;
item.isR90 = false;
item.isRM90 = false;
item.isR180 = false;
});
// 1,处理snakeArr数组(删除第一个元素,追加一个元素进去)
this.snakeArr.splice(0, 1);
let steps = 0;
switch (this.direction) {
case 'right':
steps = 1;
break;
case 'down':
steps = 40;
break;
case 'up':
steps = -40;
break;
case 'left':
steps = -1;
break;
}
this.snakeArr.push(this.snakeArr[this.snakeArr.length - 1] + steps);
// 渲染蛇和界面之前需要判断吃到了没有。吃到了的话,要完成吃食物的逻辑
// [吃食物的逻辑](多个月饼)思路:蛇的头部的下标在食物数组foodIndexs里面
let headIndex = this.snakeArr[this.snakeArr.length - 1];
if (this.foodIndexs.includes(headIndex)) {
// 1、蛇身要边长(把食物的下标添加到蛇数组中)
this.snakeArr.push(headIndex);
// 2、旧的月饼要消失(月饼数组的这个元素要删除)
this.foodIndexs.splice(this.foodIndexs.indexOf(headIndex), 1);
this.gameMap[headIndex].isCate = false;
// 3、随机生成新的月饼
this.generOneCate();
// 4、分数+10分
this.score += 10;
}
// 2,调用generSnake(),为了当前的新的snakeArr数组的每一个格子的 isXxx为true
this.generSnake();
this.$forceUpdate(); //界面更新
},
generOneCate() {
while (true) {
let foodIndex = parseInt(Math.random() * this.gameMap.length);
// 判断在不在蛇身数组上 或者 在月饼数组上
// if(foodIndex在snakeArr数组中 || 在foodIndexs数组中){
if (
this.snakeArr.includes(foodIndex) ||
this.foodIndexs.includes(foodIndex)
) {
continue;
}
// 展示食物
this.foodIndexs.push(foodIndex);
this.gameMap[foodIndex].isCate = true;
break;
}
},
generCates() {
// 生成食物
for (let i = 0; i < 20; i++) {
this.generOneCate();
}
},
generSnake() {
// 第一件事情: 生成蛇数组
this.gameMap[this.snakeArr[this.snakeArr.length - 1]].isTou = true;
this.gameMap[this.snakeArr[this.snakeArr.length - 2]].isShou = true;
// snakeArr数组中,除了第一个表示蛇脚,最后一个表示蛇头,倒数第二个表示蛇手
// 其他都是蛇身
for (let i = 1; i < this.snakeArr.length - 2; i++) {
this.gameMap[this.snakeArr[i]].isShen = true;
}
this.gameMap[this.snakeArr[0]].isJiao = true;
// 第二件事情: 处理角度
// 思路: 检查蛇数组的每一个格子是否需要旋转,要旋转多少度【难点】
// 蛇数组 snakeArr:[50,51,52,53,54]
// 这格子向右,当前的元素的下标减去下一个元素的下标 为 -1, 要转90度
// 这格子向左,当前的元素的下标减去下一个元素的下标 为 1 , 要转-90度
// 这格子向下,当前的元素的下标减去下一个元素的下标 为 -40, 要转180度
// 这格子向上,当前的元素的下标减去下一个元素的下标 为 40
// 遍历snakeArr这个数组(最后一个元素没有下一个元素,所以先忽略最后一个元素),用当前元素减去下一个元素,看看值是多少从而决定转多少度
for (let i = 0; i < this.snakeArr.length - 1; i++) {
// 用当前元素减去下一个元素
let item = this.snakeArr[i];
let next = this.snakeArr[i + 1];
switch (item - next) {
case -1:
this.gameMap[item].isR90 = true;
break;
case 1:
this.gameMap[item].isRM90 = true;
break;
case -40:
this.gameMap[item].isR180 = true;
break;
}
}
// 处理最后一个格子,蛇头的旋转角度
let lastIndex = this.snakeArr[this.snakeArr.length - 1];
switch (this.direction) {
case 'right':
this.gameMap[lastIndex].isR90 = true;
break;
case 'left':
this.gameMap[lastIndex].isRM90 = true;
break;
case 'down':
this.gameMap[lastIndex].isR180 = true;
break;
}
// this.gameMap[this.snakeArr[this.snakeArr.length-1]].isR90=true
}
},
mounted() {
document.addEventListener('keydown', (e) => {
// console.log(e.keyCode);
// 按下按键执行这里的代码
// 判断对比 e.keyCode 和对应的方向键的键码是否一致 37,38,39,40
switch (e.keyCode) {
case 40:
if (this.direction === 'up') break;
this.direction = 'down';
break;
case 37:
// 先判断当前方向是不是正在向右,如果是,就得break
if (this.direction === 'right') break;
this.direction = 'left';
break;
case 39:
// 先判断当前方向是不是正在向左,如果是,就得break
if (this.direction === 'left') break;
this.direction = 'right';
break;
case 38:
if (this.direction === 'down') break;
this.direction = 'up';
break;
}
});
}
};
</script>
<style lang = "less" scoped>
.titlebg {
background: url('../assets/images/title.png') no-repeat center 20px;
}
.title {
width: 1200px;
height: 200px;
/* background-color: skyblue; */
background-size: auto 160px;
margin: 0 auto;
position: relative;
span {
color: #fff;
font-size: 28px;
position: absolute;
right: 0;
bottom: 10px;
}
}
.game-box {
width: 1200px;
height: 450px;
margin: 0 auto;
border: 1px solid rgba(255, 255, 255, 0.15);
ul {
display: grid; /*网格布局*/
/* 设置列数, 每一个小格子的像素*/
grid-template-columns: repeat(40, 30px);
/* 设置行数, 每一个小格子的像素*/
grid-template-rows: repeat(15, 30px);
li {
width: 30px;
height: 30px;
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.15);
}
}
}
.shou::before {
background: url(../assets/images/wolf/shou.png);
}
.tou::before {
background: url(../assets/images/wolf/tou.png);
}
.jiao::before {
background: url(../assets/images/wolf/jiao.png) no-repeat;
}
.shen::before {
background: url(../assets/images/wolf/shen.png) no-repeat;
}
.cate::before {
background: url(../assets/images/mooncates/1.png) no-repeat;
}
.tou::before,
.shou::before,
.jiao::before,
.shen::before,
.cate::before {
content: '';
width: 30px;
height: 30px;
display: block;
background-size: 30px 30px;
/* transform: rotate(-90deg); */
}
.jiao::before {
background-size: 30px 18px;
}
/*向右移动,要转90度*/
.r90::before {
transform: rotate(90deg);
}
/*向下移动,要转180度*/
.r180::before {
transform: rotate(180deg);
}
/*向左移动,要转-90度*/
.rm90::before {
transform: rotate(-90deg);
}
/*向上移动,不用转*/
.gameover {
width: 1779px;
height: 875px;
background: url(../assets/images/end.png) no-repeat;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0.55);
ul {
height: 224px;
margin-top: 300px;
text-align: center;
li {
width: 164px;
height: 224px;
/* background: url(../assets/images/numbers/0.png) no-repeat;*/
/* background-size: auto 224px; */
display: inline-block;
margin: 0 10px;
}
}
.restart {
width: 400px;
height: 126px;
background: url(../assets/images/restart.png) no-repeat;
margin: 70px auto 0;
cursor: pointer;
}
p {
font-size: 40px;
text-align: center;
color: #fff;
margin-top: 50px;
span {
color: gold;
}
}
}
.gameaction {
width: 900px;
height: 809px;
background: url(../assets/images/title.png) no-repeat;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0.7);
.btn {
width: 400px;
height: 126px;
background: url(../assets/images/action.png) no-repeat;
margin: 519px auto 0;
cursor: pointer;
}
}
</style>
``

浙公网安备 33010602011771号