先看效果图:
游戏要求:
1.方向键可以控制蛇头的运动方向。
2.食物随机出现在画布中。
3.蛇身跟随蛇头运动。
4.蛇头不能撞墙或撞到蛇身。
5.蛇吃了食物后蛇身增长。
细节注意事项:
其实贪吃蛇游戏有几个细节需要注意:
1.当蛇向上时向下键无效,同理当蛇向左时向右键无效等等。
2.蛇身的每一次移动相对前一段有延后性。
3.当蛇身长度小于等于三个时是无论如何也不会装上自己的。(可以减少一些计算量)
实现思路:
一,准备画布和按钮(主要逻辑不在此,所以不需要深入)
css用到了flex布局,在此不再展示
<div class="contain"><canvas width="360" height="360" id="canvas"></canvas><div class="direction"><div class="up" onclick="toUp()">上</div><div class="down" onclick="toDown()">下</div><div class="left" onclick="toLeft()">左</div><div class="right" onclick="toRight()">右</div></div></div>
还有一些初始化内容
const canvas = document.querySelector("#canvas")const ctx = canvas.getContext("2d")const w = 360//画布宽const h = 360//画布高const per_length = 40
二,准备两个类和两个数组:
1.蛇的类
//蛇的类class ball {/*** @description: * @param {*} x:圆心x轴坐标* @param {*} y:圆心y轴坐标* @param {*} color:颜色* @return {*}*/constructor(x, y, color) {this.dijige = she_body.lengththis.x = x || 20this.y = y || 20this.r = 20this.color = color || "#" + parseInt(Math.random() * 0xffffff).toString(16)this.xspeed = per_lengththis.yspeed = 0}/*** @description: 蛇的运动* @param {string} direct:方向* @return {*}*/move(direct) {//控制运动she_position[0].x = this.xshe_position[0].y = this.yswitch (direct) {case "x":this.x += this.xspeedcontrolMove()break;case "y":this.y += this.yspeedcontrolMove()break;}}make() {//根据每个实例制造蛇头或蛇身ctx.beginPath()ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)ctx.fillStyle = this.colorctx.fill()}check() {//边境检测if (this.x < this.r || w - this.x < this.r) {clearInterval(timer)return false}if (this.y < this.r || h - this.y < this.r) {clearInterval(timer)return false}return true}isEatFood() {//是否吃到了食物if (Math.abs(food.x - this.x + 20) <= this.r && Math.abs(food.y - this.y + 20) <= this.r) {food = new Food()she_body.push(new ball(she_position[she_position.length - 1].x, she_position[she_position.length - 1].y, "red"))she_position.push({ x: she_position[she_position.length - 1].x, y: she_position[she_position.length - 1].y })console.log("吃到了");}}}
2.食物的类
//食物的类class Food {constructor() {this.x = Math.floor(Math.random() * 9) * 40 //[1,10)this.y = Math.floor(Math.random() * 9) * 40 //[1,10)this.widthRect = per_lengththis.heigthRect = per_lengththis.color = "#" + parseInt(Math.random() * 0xffffff).toString(16)}make() {ctx.beginPath()ctx.fillStyle = this.colorctx.fillRect(this.x, this.y, this.widthRect, this.heigthRect)}}
3.存放蛇身的数组(注意,不包括蛇头哈)
const she_body = []//蛇身
4.记录蛇身每一段在未来一个单位时间运动的坐标
该数组是为了实现蛇身跟随运动,例如:到了某一位置会依次转弯
const she_position = [{ x: 20, y: 20 }]//蛇头走过的坐标
数组中有一个原始数据,这是蛇头最初的位置;数组的长度对应蛇身的长度。
例如:数组中第一个数据对应蛇头前一时间单位的位置。
该数组的数据是在不断更新的,每一项对应下一步的位置
判断游戏是否结束:
1.撞墙
这里使用边界检测:放到了蛇的类中
check() {//边境检测if (this.x < this.r || w - this.x < this.r) {clearInterval(timer)return false}if (this.y < this.r || h - this.y < this.r) {clearInterval(timer)return false}return true}
2.撞到自己
这个思路很简单,没走一步遍历蛇身数组(she_body),是否与蛇头相撞。
注意当蛇身的长度<=3无需判断,因为永远不会相撞
控制蛇头的转向
其实可以继续封装一下,但是这个游戏已经花了我一整天时间了,是在是懒了。
function toDown() {//下if (she.check() && she.yspeed === 0) {direct = "y"she.xspeed = 0she.yspeed = per_length}}function toRight() {//右if (she.check() && she.xspeed === 0) {direct = "x"she.yspeed = 0she.xspeed = per_length}}function toLeft() {//左if (she.check() && she.xspeed === 0) {direct = "x"she.yspeed = 0she.xspeed = -per_length}}function toUp() {//上if (she.check() && she.yspeed === 0) {direct = "y"she.xspeed = 0she.yspeed = -per_length}}
源码展示:
啊~哈哈哈哈哈~鸡汤来喽
<!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><style>.contain {width: 100vw;display: flex;flex-direction: column;justify-content: center;align-items: center;}#canvas {display: block;border: 2px solid black;}.contain>.direction {width: 200px;height: 200px;position: relative;}.up,.down,.left,.right {width: 60px;height: 60px;background-color: rgb(245, 183, 103);position: absolute;cursor: pointer;text-align: center;line-height: 60px;}.contain>.direction>.up {top: 0;left: 50%;transform: translateX(-50%);}.contain>.direction>.down {bottom: 0;left: 50%;transform: translateX(-50%);}.contain>.direction>.left {top: 50%;left: 0;transform: translateY(-50%);}.contain>.direction>.right {top: 50%;right: 0;transform: translateY(-50%);}</style></head><body><div class="contain"><canvas width="360" height="360" id="canvas"></canvas><div class="direction"><div class="up" onclick="toUp()">上</div><div class="down" onclick="toDown()">下</div><div class="left" onclick="toLeft()">左</div><div class="right" onclick="toRight()">右</div></div></div><script type="text/javascript">const canvas = document.querySelector("#canvas")const ctx = canvas.getContext("2d")const w = 360//画布宽const h = 360//画布高const per_length = 40const she_body = []//蛇身const she_position = [{ x: 20, y: 20 }]//蛇头走过的坐标function controlMove() {//move函数的公共部分if (she.check()) {ctx.clearRect(0, 0, w, h)} else {alert("撞墙了")}she.isEatFood()food.make()she.make()she_body.forEach((item, index) => {she_position[index + 1].x = item.xshe_position[index + 1].y = item.yitem.x = she_position[index].xitem.y = she_position[index].yitem.make()})}//蛇的类class ball {/*** @description: * @param {*} x:圆心x轴坐标* @param {*} y:圆心y轴坐标* @param {*} color:颜色* @return {*}*/constructor(x, y, color) {this.dijige = she_body.lengththis.x = x || 20this.y = y || 20this.r = 20this.color = color || "#" + parseInt(Math.random() * 0xffffff).toString(16)this.xspeed = per_lengththis.yspeed = 0}/*** @description: 蛇的运动* @param {string} direct:方向* @return {*}*/move(direct) {//控制运动she_position[0].x = this.xshe_position[0].y = this.yswitch (direct) {case "x":this.x += this.xspeedcontrolMove()break;case "y":this.y += this.yspeedcontrolMove()break;}}make() {//根据每个实例制造蛇头或蛇身ctx.beginPath()ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2)ctx.fillStyle = this.colorctx.fill()}check() {//边境检测if (this.x < this.r || w - this.x < this.r) {clearInterval(timer)return false}if (this.y < this.r || h - this.y < this.r) {clearInterval(timer)return false}return true}isEatFood() {//是否吃到了食物if (Math.abs(food.x - this.x + 20) <= this.r && Math.abs(food.y - this.y + 20) <= this.r) {food = new Food()she_body.push(new ball(she_position[she_position.length - 1].x, she_position[she_position.length - 1].y, "red"))she_position.push({ x: she_position[she_position.length - 1].x, y: she_position[she_position.length - 1].y })console.log("吃到了");}}}//食物的类class Food {constructor() {this.x = Math.floor(Math.random() * 9) * 40 //[1,10)this.y = Math.floor(Math.random() * 9) * 40 //[1,10)this.widthRect = per_lengththis.heigthRect = per_lengththis.color = "#" + parseInt(Math.random() * 0xffffff).toString(16)}make() {ctx.beginPath()ctx.fillStyle = this.colorctx.fillRect(this.x, this.y, this.widthRect, this.heigthRect)}}let she = new ball()let food = new Food()she.make()let direct = "x"//定时器const timer = setInterval(() => {she.move(direct)}, 300);function toDown() {//下if (she.check() && she.yspeed === 0) {direct = "y"she.xspeed = 0she.yspeed = per_length}}function toRight() {//右if (she.check() && she.xspeed === 0) {direct = "x"she.yspeed = 0she.xspeed = per_length}}function toLeft() {//左if (she.check() && she.xspeed === 0) {direct = "x"she.yspeed = 0she.xspeed = -per_length}}function toUp() {//上if (she.check() && she.yspeed === 0) {direct = "y"she.xspeed = 0she.yspeed = -per_length}}</script></body></html>