- @Descripttion:
- @Author: zhangJunQing
- @Date: 2021-04-25
- @LastEditors: zhangJunQing
机会是留给有准备的人的!
这是五一前一周周六,吃了午饭,突然想做个贪吃蛇,没有为啥,也不知道为啥想做这个,马上氪:
首先 我们看一下效果图
我们先将简单的样式搞出来:
<!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>canvas贪吃蛇游戏</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
width: 100vw;
height: 100vh;
}
#canvas {
background: black;
}
button {
position: absolute;
border: none;
background: #ccc;
color: black;
font-weight: bold;
font-size: 30px;
width: 350px;
height: 80px;
line-height: 80px;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: all 1s;
}
.but {
top: 20%;
width: 800px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<button>开始游戏</button>
<button class="but">因为打开的窗口大小不是小方块的倍数,所以可能存在误差</button>
</body>
</html>
页面内上只有canvas和两个按钮
然后我们需要考虑贪吃蛇这条蛇是怎么运动的,特点是
控制舌头,蛇身会走舌头走过的路线。
1 我们需要给canvas定制宽高
2 两个button显示隐藏
3 获取canvas画笔
4 贪吃蛇分为舌头和蛇身
let canvas = document.getElementById('canvas')
let btn = document.getElementsByTagName('button')[0]
let btn1 = document.getElementsByTagName('button')[1]
canvas.width = document.getElementsByTagName('body')[0].clientWidth
canvas.height = document.getElementsByTagName('body')[0].clientHeight
let ctx = canvas.getContext('2d')
//蛇头
let SnackTop = []
//蛇身体
let SnackList = []
//蛇整体
let SnackFonlyList = [...SnackTop, ...SnackList]
创建蛇这个构造函数
//X向右 y向下 -x 向左 -y 上
//随机颜色
let colorList = ["#33B5E5", "#0099CC", "#AA66CC", "#9933CC", "#99CC00", "#669900", "#FFBB33", "#FF8800", "#FF4444", "#CC0000"]
function Snack(x1 = 30, y1 = 30, x2 = 30, y2 = 30, color) {
//这几个属性在使用canvas画方块的时候会使用
this.x1 = x1; //x轴坐标
this.x2 = x2; //x坐标长度
this.y1 = y1; //y轴左边
this.y2 = y2; //y坐标长度
this.color = color || colorList[Math.floor(Math.random() * 10)]
this.direction = 'x' //X向右 y向下 -x 向左 -y 上
this.paceSoon = 30
}
创建两个个蛇身一个舌头对象数组,并且让他从30,30这个坐标开始创建
let x1 = 30, y1 = 30, x2 = 30, y2 = 30;
//生成三个
function getSanck() {
for (let i = 0; i < 3; i++) {
x1 += 30;
SnackList.length != 2 ? SnackList.push(new Snack(x1, y1, x2, y2)) : SnackTop.push(new Snack(x1, y1, x2, y2))
}
}
SnackTop 里面一个Snack实例化对象,SnackList里面两个
我们是用 +=30 生成的,所以我们需要将整个数组颠倒,才能是我们整个蛇身的顺序,这里好好思考一下
SnackList = SnackList.reverse()
下面要做的就是让他渲染到页面上,属性都有了直接加方法
//创建蛇身和舌头方法
Snack.prototype.RectFun = function () {
ctx.beginPath();
ctx.fillStyle = this.color
ctx.fillRect(this.x1, this.y1, this.x2, this.y2);
ctx.stroke();
}
让他动起来的方法,就是一直改变他的x1,y1
Snack.prototype.Update = function () {
if (this.direction == 'x') {
this.x1 += this.paceSoon
} else if (this.direction == 'y') {
this.y1 += this.paceSoon
} else if (this.direction == '-x') {
this.x1 -= this.paceSoon
} else if (this.direction == '-y') {
this.y1 -= this.paceSoon
}
}
通过键盘事件改变方向并控制不能直接掉头
//添加键盘按下时间 并且不能直接掉头
window.addEventListener('keydown', function (e) {
if (e.keyCode == '39') {
SnackTop[0].direction == '-x' ? '' : SnackTop[0].direction = 'x'
// console.log('右')
} else if (e.keyCode == '40') {
//下 40
SnackTop[0].direction == '-y' ? '' : SnackTop[0].direction = 'y'
// console.log('下')
} else if (e.keyCode == '37') {
//左 37
SnackTop[0].direction == 'x' ? '' : SnackTop[0].direction = '-x'
// console.log('-x')
} else if (e.keyCode == '38') {
//右 39
SnackTop[0].direction == 'y' ? '' : SnackTop[0].direction = '-y'
// console.log('-y')
}
})
当然现在还不能实现蛇身移动,再搞一个方法,让第一个蛇身跟着蛇头,其他蛇身跟着上一个蛇身,让他们的状态一直改变(也就是属性值改变)
Snack.prototype.directionFun = function (index) {
if (index == 0) {
if (SnackTop[0].direction == 'x') {
this.x1 = SnackTop[0].x1 - 30
this.direction = 'x'
this.y1 = SnackTop[0].y1
} else if (SnackTop[0].direction == 'y') {
this.direction = 'y'
this.x1 = SnackTop[0].x1
this.y1 = SnackTop[0].y1 - 30
} else if (SnackTop[0].direction == '-x') {
this.direction = '-x'
this.x1 = SnackTop[0].x1 + 30
this.y1 = SnackTop[0].y1
} else if (SnackTop[0].direction == '-y') {
this.direction = '-y'
this.y1 = SnackTop[0].y1 + 30
this.x1 = SnackTop[0].x1
}
} else {
if (list[index - 1].direction == 'x') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == 'y') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == '-x') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == '-y') {
this.y1 = list[index - 1].y1
this.x1 = list[index - 1].x1
}
}
}
这里面的list就是上一次每一个蛇身的状态
我们使用深拷贝进行取状态
list = JSON.parse(JSON.stringify(SnackList))
这个时候我们的蛇头和蛇身就可以跟随运动了
下一步就是食物
我们还是需要给他一个构造函数和一个生成食物的方法
//食物
function Eat(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.color = colorList[Math.floor(Math.random() * 10)]
}
Eat.prototype.RectFunE = function () {
ctx.beginPath();
ctx.fillStyle = this.color
ctx.fillRect(this.x1, this.y1, this.x2, this.y2);
ctx.stroke();
}
我们需要判断一下当前页面有没有食物,没有得话生成
//食物
if (EatList.length == 0)
EatList.push(new Eat(MathRandomFun(120, canvas.width - 120), MathRandomFun(150, canvas.height - 90), x2, y2))
EatList.map(item => {
item.RectFunE()
})
MathRandomFun方法是生成一个随机数,并且使蛇方块的大小倍数
//生成食物随机数
function MathRandomFun(min, max) {
let num = Math.floor((max - min) * Math.random() + min)
if (num % 30 == 0) {
return num
} else {
return MathRandomFun(min, max)
}
}
判断有没有碰到食物,碰到蛇身加一,添加到数组,颜色为吃的食物颜色
//判断舌头和食物的位置信息
//SnackTop 蛇头
//EatList 食物
if (SnackTop[0].x1 == EatList[0].x1 && SnackTop[0].y1 == EatList[0].y1) {
//添加蛇身体
SnackList.push(new Snack(list[list.length - 1].x1, list[list.length - 1].y1, x2, y2, EatList[0].color))
EatList.length = 0
}
设置边界
//任意一遍超出界限就是游戏失败
if (SnackTop[0].x1 < -30 || SnackTop[0].x1 > canvas.width || SnackTop[0].y1 < -30 || SnackTop[0].y1 > canvas.height) {
clearInterval(timeID)
// alert('游戏结束')
ctx.clearRect(0, 0, canvas.width, canvas.height)
console.log("=========")
btn.style.opacity = 1
btn1.style.opacity = 1
return false
}
整体代码如下:
<!--
* @Descripttion:
* @Author: zhangJunQing
* @Date: 2021-04-25
* @LastEditors: zhangJunQing 1
-->
<!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>canvas贪吃蛇游戏</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
width: 100vw;
height: 100vh;
}
#canvas {
background: black;
}
button {
position: absolute;
border: none;
background: #ccc;
color: black;
font-weight: bold;
font-size: 30px;
width: 350px;
height: 80px;
line-height: 80px;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: all 1s;
}
.but {
top: 20%;
width: 800px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<button>开始游戏</button>
<button class="but">因为打开的窗口大小不是小方块的倍数,所以可能存在误差</button>
<script>
let canvas = document.getElementById('canvas')
let btn = document.getElementsByTagName('button')[0]
let btn1 = document.getElementsByTagName('button')[1]
canvas.width = document.getElementsByTagName('body')[0].clientWidth
canvas.height = document.getElementsByTagName('body')[0].clientHeight
let ctx = canvas.getContext('2d')
//蛇头
let SnackTop = []
//蛇身体
let SnackList = []
//蛇整体
let SnackFonlyList = [...SnackTop, ...SnackList]
//食物数组
let EatList = []
//存放上次状态的数组
let list = []
//定时器id
let timeID = null;
let colorList = ["#33B5E5", "#0099CC", "#AA66CC", "#9933CC", "#99CC00", "#669900", "#FFBB33", "#FF8800", "#FF4444", "#CC0000"]
let x1 = 30, y1 = 30, x2 = 30, y2 = 30;
btn.addEventListener('click', function () {
if (btn.innerHTML == '开始游戏') {
btn.style.opacity = 0
btn1.style.opacity = 0
setTimeout(function () {
requestAnimation()
btn.innerHTML = '触碰边界,游戏结束'
timeID = setInterval(requestAnimation, 100)
}, 1000)
}
})
function Snack(x1 = 30, y1 = 30, x2 = 30, y2 = 30, color) {
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
this.color = color || colorList[Math.floor(Math.random() * 10)]
this.direction = 'x' //X向右 y向下 -x 向左 -y 上
this.paceSoon = 30
}
Snack.prototype.RectFun = function () {
ctx.beginPath();
ctx.fillStyle = this.color
ctx.fillRect(this.x1, this.y1, this.x2, this.y2);
ctx.stroke();
}
Snack.prototype.Update = function () {
if (this.direction == 'x') {
this.x1 += this.paceSoon
} else if (this.direction == 'y') {
this.y1 += this.paceSoon
} else if (this.direction == '-x') {
this.x1 -= this.paceSoon
} else if (this.direction == '-y') {
this.y1 -= this.paceSoon
}
}
Snack.prototype.directionFun = function (index) {
if (index == 0) {
if (SnackTop[0].direction == 'x') {
this.x1 = SnackTop[0].x1 - 30
this.direction = 'x'
this.y1 = SnackTop[0].y1
} else if (SnackTop[0].direction == 'y') {
this.direction = 'y'
this.x1 = SnackTop[0].x1
this.y1 = SnackTop[0].y1 - 30
} else if (SnackTop[0].direction == '-x') {
this.direction = '-x'
this.x1 = SnackTop[0].x1 + 30
this.y1 = SnackTop[0].y1
} else if (SnackTop[0].direction == '-y') {
this.direction = '-y'
this.y1 = SnackTop[0].y1 + 30
this.x1 = SnackTop[0].x1
}
} else {
if (list[index - 1].direction == 'x') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == 'y') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == '-x') {
this.x1 = list[index - 1].x1
this.y1 = list[index - 1].y1
} else if (list[index - 1].direction == '-y') {
this.y1 = list[index - 1].y1
this.x1 = list[index - 1].x1
}
}
}
//食物
function Eat(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.color = colorList[Math.floor(Math.random() * 10)]
}
Eat.prototype.RectFunE = function () {
ctx.beginPath();
ctx.fillStyle = this.color
ctx.fillRect(this.x1, this.y1, this.x2, this.y2);
ctx.stroke();
}
//生成八个
function getSanck() {
for (let i = 0; i < 3; i++) {
x1 += 30;
SnackList.length != 2 ? SnackList.push(new Snack(x1, y1, x2, y2)) : SnackTop.push(new Snack(x1, y1, x2, y2))
}
}
getSanck()
//生成食物随机数
function MathRandomFun(min, max) {
let num = Math.floor((max - min) * Math.random() + min)
if (num % 30 == 0) {
return num
} else {
return MathRandomFun(min, max)
}
}
SnackList = SnackList.reverse()
// 生成n个正方形 和一个食物
function requestAnimation() {
list = JSON.parse(JSON.stringify(SnackList))
ctx.clearRect(0, 0, canvas.width, canvas.height)
//食物
if (EatList.length == 0)
EatList.push(new Eat(MathRandomFun(120, canvas.width - 120), MathRandomFun(150, canvas.height - 90), x2, y2))
EatList.map(item => {
item.RectFunE()
})
//判断舌头和食物的位置信息
//SnackTop 舌头
//EatList 食物
if (SnackTop[0].x1 == EatList[0].x1 && SnackTop[0].y1 == EatList[0].y1) {
//添加蛇身体
SnackList.push(new Snack(list[list.length - 1].x1, list[list.length - 1].y1, x2, y2, EatList[0].color))
EatList.length = 0
}
SnackTop.map(item => {
item.RectFun()
item.Update()
})
SnackList.map((item, index) => {
item.RectFun()
item.directionFun(index)
})
if (SnackTop[0].x1 < -30 || SnackTop[0].x1 > canvas.width || SnackTop[0].y1 < -30 || SnackTop[0].y1 > canvas.height) {
console.log(SnackTop[0], 'SnackTop[0]')
clearInterval(timeID)
// alert('游戏结束')
ctx.clearRect(0, 0, canvas.width, canvas.height)
console.log("=========")
btn.style.opacity = 1
btn1.style.opacity = 1
return false
}
}
//添加键盘按下时间 并且不能直接掉头
window.addEventListener('keydown', function (e) {
if (e.keyCode == '39') {
SnackTop[0].direction == '-x' ? '' : SnackTop[0].direction = 'x'
// console.log('右')
} else if (e.keyCode == '40') {
//下 40
SnackTop[0].direction == '-y' ? '' : SnackTop[0].direction = 'y'
// console.log('下')
} else if (e.keyCode == '37') {
//左 37
SnackTop[0].direction == 'x' ? '' : SnackTop[0].direction = '-x'
// console.log('-x')
} else if (e.keyCode == '38') {
//右 39
SnackTop[0].direction == 'y' ? '' : SnackTop[0].direction = '-y'
// console.log('-y')
}
})
</script>
</body>
</html>
里面的设计思路肯定还有很多种,还有其他思路的大佬欢迎评论,一起探讨,一起加油。