JS配合canvas实现贪吃蛇小游戏

简介: 本文通过详细的代码示例介绍了如何使用JavaScript和HTML5的Canvas API实现一个贪吃蛇游戏,包括蛇的移动、食物的生成、游戏的开始与结束逻辑,以及如何响应键盘事件来控制蛇的方向。
  • @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>

里面的设计思路肯定还有很多种,还有其他思路的大佬欢迎评论,一起探讨,一起加油。

目录
相关文章
|
10天前
|
JavaScript
JS实现简单的打地鼠小游戏源码
这是一款基于JS实现简单的打地鼠小游戏源码。画面中的九宫格中随机出现一个地鼠,玩家移动并点击鼠标控制画面中的锤子打地鼠。打中地鼠会出现卡通爆破效果。同时左上角统计打地鼠获得的分数
25 1
|
8天前
|
JavaScript
JS趣味打字金鱼小游戏特效源码
hi fish是一款打字趣味小游戏,捞出海里的鱼,捞的越多越好。这款游戏用于电脑初学者练习打字。初学者可以根据自己的水平设置游戏难度。本段代码可以在各个网页使用,有需要的朋友可以直接下载使用,本段代码兼容目前最新的各类主流浏览器,是一款非常优秀的特效源码!
15 3
|
9天前
|
移动开发 HTML5
html5+three.js公路开车小游戏源码
html5公路开车小游戏是一款html5基于three.js制作的汽车开车小游戏源代码,在公路上开车网页小游戏源代码。
27 0
html5+three.js公路开车小游戏源码
|
9天前
|
JavaScript
JS趣味打字金鱼小游戏特效源码
hi fish是一款打字趣味小游戏,捞出海里的鱼,捞的越多越好。这款游戏用于电脑初学者练习打字。初学者可以根据自己的水平设置游戏难度。本段代码可以在各个网页使用,有需要的朋友可以直接下载使用,本段代码兼容目前最新的各类主流浏览器,是一款非常优秀的特效源码!
18 0
JS趣味打字金鱼小游戏特效源码
|
2月前
|
JavaScript 前端开发 开发工具
五子棋小游戏(JS+Node+Websocket)可分房间对战
本文介绍了通过JS、Node和WebSocket实现的五子棋游戏,支持多人在线对战和观战功能。
45 1
五子棋小游戏(JS+Node+Websocket)可分房间对战
|
2月前
|
移动开发 前端开发 JavaScript
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
本文介绍了一个使用JavaScript和HTML5 Canvas API实现的贪吃蛇游戏的升级版本,该版本支持PC端和移动端,提供了丝滑的转向效果,并允许玩家通过键盘或触摸屏控制蛇的移动。代码中包含了详细的注释,解释了游戏逻辑、食物生成、得分机制以及如何响应不同的输入设备。
58 1
JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端
|
2月前
|
移动开发 前端开发 JavaScript
js之Canvas|2-1
js之Canvas|2-1
|
2月前
|
移动开发 前端开发 JavaScript
原生JavaScript+canvas实现五子棋游戏_值得一看
本文介绍了如何使用原生JavaScript和HTML5的Canvas API实现五子棋游戏,包括棋盘的绘制、棋子的生成和落子、以及判断胜负的逻辑,提供了详细的代码和注释。
34 0
原生JavaScript+canvas实现五子棋游戏_值得一看
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
95 2
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
118 4