JS配合canvas实现贪吃蛇小游戏_升级_丝滑版本_支持PC端和移动端

简介: 本文介绍了一个使用JavaScript和HTML5 Canvas API实现的贪吃蛇游戏的升级版本,该版本支持PC端和移动端,提供了丝滑的转向效果,并允许玩家通过键盘或触摸屏控制蛇的移动。代码中包含了详细的注释,解释了游戏逻辑、食物生成、得分机制以及如何响应不同的输入设备。

在这里插入图片描述

对上一个文章JS配合canvas实现贪吃蛇小游戏做了一个大更新,可以手指控制可以键盘控制,APP端控制方向极为丝滑,代码可以直接copy使用。
请忽略上面样式,哈哈哈哈哈,主要看功能
代码里面我做了注释,有什么不清楚的大家一起探讨。
直接上代码

<!--
 * @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 {
   
            overflow: hidden;
            width: 100%;
            height: 100%;
        }

        #canvas {
   
            position: relative;
            /* background: black; */
            background-size: 100vw 100vh;
            background-image: url('https://tse1-mm.cn.bing.net/th/id/OIP.pIeqT6_ue0kmKhnO9gR5egHaEW?w=rectSize5&h=180&c=7&o=5&dpr=1.25&pid=1.7');
        }

        .divBox {
   
            border: none;
            position: absolute;
            display: flex;
            flex-direction: column;
            top: 0;
            left: 0;
            z-index: 888;
            width: 100%;
            height: 100%;
        }

        span {
   
            font-size: 20%;
            position: absolute;
        }

        .samllBig {
   
            width: 100vw;
            margin-top: 20%;
            height: 50%;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
        }

        .samllBigChild {
   
            width: 50%;
            margin-top: 12%;
            display: flex;
            justify-content: space-between;
        }

        .samllBigChild button {
   
            display: inline-block;
            border: none;
            background: rgb(143, 139, 179);
            color: black;
            font-weight: bold;
            line-height: 100%;
            padding: 6%;
            width: 30%;
            text-align: center;
            transition: all 1s;
        }

        input {
   
            width: 30%;
            height: 20%;
            line-height: 20%;
            border: none;
            background: rgb(143, 139, 179);
            color: black;
            font-weight: bold;
            text-align: center;
            transition: all 1s;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
    <div class="divBox">
        <span></span>
        <div class="samllBig">
            <input type="text" placeholder="请输入刚开始小方块个数">
            <div class="samllBigChild">
                <button class="button1">APP模式</button>
                <button class="button2">PC模式</button>
            </div>
        </div>

    </div>

    <!-- <button class="but">因为打开的窗口大小不是小方块的倍数,所以可能存在误差</button> -->
    <script>

        let canvas = document.getElementById('canvas')
        let btn = document.getElementsByTagName('button')[0]
        let btn1 = document.getElementsByTagName('button')[1]
        let input = document.getElementsByTagName('input')[0]
        let span = document.getElementsByTagName('span')[0]
        let smSetInterValTime = 100
        let SnackNum = null
        let EatNum = 0
        span.innerHTML = EatNum * 10 + '分;当前速度' + (100 - smSetInterValTime)
        window.onload = () => {
   
            canvas.width = document.documentElement.clientWidth
            canvas.height = document.documentElement.clientHeight
        }
        window.addEventListener('resize', () => {
   
            window.onload()
        })
        //每个蛇身体的大小
        let rectSize = 15
        //迟到的食物个数
        let ctx = canvas.getContext('2d')
        //蛇头
        let SnackTop = null
        //蛇身体
        let SnackList = null
        //蛇整体
        let SnackFonlyList = null
        //食物数组
        let EatList = null
        //存放上次状态的数组
        let list = null
        //定时器id
        let timeID = null;
        //坐标
        var x1, y1, x2, y2;
        //颜色数组
        let colorList = null
        //初始化
        let init = function () {
   
            // 吃掉的食物个数
            EatNum = 0
            //定时器毫秒数
            smSetInterValTime = 100
            if (input.value && input.value != undefined && input.value != '' && input.value != null) {
   
                if (input.value <= 3 || input.value > 20) {
   
                    alert('请输入大于3小于等于20')
                    return
                }
                SnackNum = input.value
                //吃掉一个食物 更改一次分数 速度   默认为 0 
                span.innerHTML = EatNum * 10 + '分;当前速度' + (100 - smSetInterValTime)
            } else {
   
                alert('请输入数量')
                return
            }
            x1 = rectSize; y1 = rectSize; x2 = rectSize; y2 = rectSize;
            list = []
            SnackTop = []
            SnackList = []
            SnackFonlyList = [...SnackTop, ...SnackList]
            EatList = []
            colorList = ["#33B5E5", "#0099CC", "#AA66CC", "#9933CC", "#99CC00", "#669900", "#FFBB33", "#FF8800", "#FF4444", "#CC0000"]
            getSanck()
            //将整个数组颠倒,才能是我们整个蛇身的顺序
            SnackList = SnackList.reverse()
            btn.style.opacity = 0
            btn1.style.opacity = 0
            input.style.opacity = 0
            //输入框制空
            input.value = ''
            // btn1.style.opacity = 0
            setTimeout(function () {
   
                requestAnimation()
                btn.innerHTML = 'APP重开'
                btn1.innerHTML = 'PC重开'
                timeID = setInterval(requestAnimation, smSetInterValTime)
            }, 1000)
        }

        btn1.addEventListener('click', function () {
   
            if (btn1.innerHTML == 'PC模式') {
   
                rectSize = 30
                init()
            } else {
   
                rectSize = 30
                init()
            }
        })
        btn.addEventListener('click', function () {
   
            if (btn.innerHTML == 'APP模式') {
   
                rectSize = 15
                init()
            } else {
   
                rectSize = 15
                init()
            }
        })
        function Snack(x1 = rectSize, y1 = rectSize, x2 = rectSize, y2 = rectSize, 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 = rectSize
        }
        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
            }
        }
        //让第一个蛇身跟着蛇头,其他蛇身跟着上一个蛇身,让他们的状态一直改变(也就是属性值改变)
        Snack.prototype.directionFun = function (index) {
   
            if (index == 0) {
   
                if (SnackTop[0].direction == 'x') {
   
                    this.x1 = SnackTop[0].x1 - rectSize
                    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 - rectSize
                } else if (SnackTop[0].direction == '-x') {
   
                    this.direction = '-x'
                    this.x1 = SnackTop[0].x1 + rectSize
                    this.y1 = SnackTop[0].y1
                } else if (SnackTop[0].direction == '-y') {
   
                    this.direction = '-y'
                    this.y1 = SnackTop[0].y1 + rectSize
                    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 < SnackNum; i++) {
   
                x1 += rectSize;
                SnackList.length != SnackNum - 1 ? SnackList.push(new Snack(x1, y1, x2, y2)) : SnackTop.push(new Snack(x1, y1, x2, y2))
            }
        }

        //生成食物随机数 
        //利用递归生成一个随机数,并且使蛇方块的大小倍数
        function MathRandomFun(min, max) {
   
            let num = Math.floor((max - min) * Math.random() + min)
            if (num % rectSize == 0) {
   
                return num
            } else {
   
                return MathRandomFun(min, max)
            }
        }

        // 生成n个正方形  和一个食物
        function requestAnimation() {
   
            //上一次每一个蛇身的状态
            list = JSON.parse(JSON.stringify(SnackList))
            // ctx.fillStyle ="rgba(0,0,0,.05)"
            // ctx.fillRect(0, 0, canvas.width, canvas.height)
            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) {
   
                //添加蛇身体
                EatNum++
                //没吃掉是个食物  定时器毫秒-10   页面显示速度+10
                switch (EatNum) {
   
                    case 10: smSetInterValTime = 80; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                    case 20: smSetInterValTime = 70; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                    case 30: smSetInterValTime = 60; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                    case 40: smSetInterValTime = 50; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                    case 50: smSetInterValTime = 40; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                    case 60: smSetInterValTime = 30; clearInterval(timeID); timeID = setInterval(requestAnimation, smSetInterValTime); break;
                }
                //吃掉一个食物 更改一次分数 速度
                span.innerHTML = EatNum * 10 + '分;当前速度' + (100 - smSetInterValTime)
                //定时器速度小于30 闯关成功
                if (smSetInterValTime <= 30) {
   
                    alert('恭喜您闯关成功')
                    //取消定时器
                    clearInterval(timeID)
                    //重置画布
                    ctx.clearRect(0, 0, canvas.width, canvas.height)
                    //按钮 inp显示
                    btn.style.opacity = 1
                    btn1.style.opacity = 1
                    input.style.opacity = 1
                    return
                }
                //吃掉食物 身体加1 颜色为吃的食物颜色
                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 <= -60 || SnackTop[0].x1 > canvas.width || SnackTop[0].y1 <= -60 || SnackTop[0].y1 > canvas.height) {
   
                clearInterval(timeID)
                ctx.clearRect(0, 0, canvas.width, canvas.height)
                btn.style.opacity = 1
                btn1.style.opacity = 1
                input.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')
            }
        })
        var touchSX
        var touchSY
        var touchMX
        var touchMY
        //手机端  控制方向  
        window.addEventListener('touchstart', function (e) {
   
            //记录除此触摸的X
            touchSX = e.changedTouches[0].clientX
             //记录除此触摸的Y
            touchSY = e.changedTouches[0].clientY
        })
        window.addEventListener('touchmove', function (e) {
   
            //记录滑动中的X
            touchMX = e.changedTouches[0].clientX
            //记录滑动中的Y
            touchMY = e.changedTouches[0].clientY
            //计算x轴正方向 x和y的cos值
            let xyorx = (touchMX - touchSX) / (Math.sqrt(Math.pow(touchMX - touchSX, 2) + Math.pow(touchSY - touchMY, 2)))
            //计算x轴负方向 x和y的cos值
            let xyor_x = -(touchMX - touchSX) / (Math.sqrt(Math.pow(touchMX - touchSX, 2) + Math.pow(touchSY - touchMY, 2)))
            if (touchMX - touchSX > 0) {
   //向右  
                if (touchMY - touchSY > 0) {
    // y 下
                    if (xyorx >= 0.707 && xyorx <= 1) {
   
                        SnackTop[0].direction == '-x' ? '' : SnackTop[0].direction = 'x'
                    } else {
   
                        SnackTop[0].direction == '-y' ? '' : SnackTop[0].direction = 'y'
                    }
                } else {
    //-y  向上  touchMY < touchSY
                    if (xyorx >= 0.707 && xyorx <= 1) {
   
                        SnackTop[0].direction == '-x' ? '' : SnackTop[0].direction = 'x'
                    } else {
   
                        SnackTop[0].direction == 'y' ? '' : SnackTop[0].direction = '-y'
                    }
                }
            } else {
   // //-x 
                if (touchMY - touchSY > 0) {
    // y 下
                    if (xyor_x >= 0.707 && xyor_x <= 1) {
   
                        SnackTop[0].direction == 'x' ? '' : SnackTop[0].direction = '-x'
                    } else {
   
                        SnackTop[0].direction == '-y' ? '' : SnackTop[0].direction = 'y'
                    }
                } else {
    //-y  向上  touchMY < touchSY
                    if (xyor_x >= 0.707 && xyor_x <= 1) {
   
                        SnackTop[0].direction == 'x' ? '' : SnackTop[0].direction = '-x'
                    } else {
   
                        SnackTop[0].direction == 'y' ? '' : SnackTop[0].direction = '-y'
                    }
                }
            }
        })
        window.addEventListener('touchend', function (e) {
   
            touchSX = 0
            touchSY = 0
            touchMX = 0
            touchMY = 0
        })
    </script>
</body>

</html>

欢迎大佬一起探讨~

目录
相关文章
|
1月前
|
JavaScript
JS实现简单的打地鼠小游戏源码
这是一款基于JS实现简单的打地鼠小游戏源码。画面中的九宫格中随机出现一个地鼠,玩家移动并点击鼠标控制画面中的锤子打地鼠。打中地鼠会出现卡通爆破效果。同时左上角统计打地鼠获得的分数
61 1
|
20天前
|
JavaScript 前端开发
js+jquery实现贪吃蛇经典小游戏
本项目采用HTML、CSS、JavaScript和jQuery技术,无需游戏框架支持。通过下载项目文件至本地,双击index.html即可启动贪吃蛇游戏。游戏界面简洁,支持方向键控制蛇移动,空格键实现游戏暂停与恢复功能。
51 14
|
26天前
|
JavaScript
原生JS实现斗地主小游戏
这是一个原生的JS网页版斗地主小游戏,代码注释全。带有斗地主游戏基本的地主、选牌、提示、出牌、倒计时等功能。简单好玩,欢迎下载
29 7
|
1月前
|
JavaScript
JS趣味打字金鱼小游戏特效源码
hi fish是一款打字趣味小游戏,捞出海里的鱼,捞的越多越好。这款游戏用于电脑初学者练习打字。初学者可以根据自己的水平设置游戏难度。本段代码可以在各个网页使用,有需要的朋友可以直接下载使用,本段代码兼容目前最新的各类主流浏览器,是一款非常优秀的特效源码!
29 3
|
1月前
|
移动开发 HTML5
html5+three.js公路开车小游戏源码
html5公路开车小游戏是一款html5基于three.js制作的汽车开车小游戏源代码,在公路上开车网页小游戏源代码。
56 0
html5+three.js公路开车小游戏源码
|
1月前
|
JavaScript
JS趣味打字金鱼小游戏特效源码
hi fish是一款打字趣味小游戏,捞出海里的鱼,捞的越多越好。这款游戏用于电脑初学者练习打字。初学者可以根据自己的水平设置游戏难度。本段代码可以在各个网页使用,有需要的朋友可以直接下载使用,本段代码兼容目前最新的各类主流浏览器,是一款非常优秀的特效源码!
36 0
JS趣味打字金鱼小游戏特效源码
|
1月前
|
JSON 移动开发 数据格式
html5+css3+js移动端带歌词音乐播放器代码
音乐播放器特效是一款html5+css3+js制作的手机移动端音乐播放器代码,带歌词显示。包括支持单曲循环,歌词显示,歌曲搜索,音量控制,列表循环等功能。利用json获取音乐歌单和歌词,基于html5 audio属性手机音乐播放器代码。
106 6
|
2月前
|
JavaScript 测试技术 API
跟随通义灵码一步步升级vue2(js)项目到vue3版本
Vue 3 相较于 Vue 2 在性能、特性和开发体验上都有显著提升。本文介绍了如何利用通义灵码逐步将 Vue 2 项目升级到 Vue 3,包括备份项目、了解新特性、选择升级方式、升级依赖、迁移组件和全局 API、调整测试代码等步骤,并提供了注意事项和常见问题的解决方案。
|
2月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
56 4
|
1月前
|
JavaScript Linux iOS开发
详解如何实现自由切换Node.js版本
不同的项目中需要使用不同版本的 Node.js,有时旧项目需要旧版本,而新项目则可能依赖最新的 Node.js 版本
37 0