js制作贪吃蛇小游戏

130 阅读5分钟

大致布局

image.png

main
    -play
        snake
        food
    -info
        score
        level

1.把外层容器设计出来

可以修改,最好是10的倍数,因为蛇的大小打算设计成10px一节

2.内层的游戏框、分数和等级信息

游戏框要注意,设计图中的大小是包含了边框后的大小,而不是未含边框的大小,因为我们的游戏框也需要是10的倍数

3.设置食物(可以使用方块,也可以设计成其它样式,10px)和蛇

如何让食物的样式改变


<div class="food">
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</div>
<style>
    .play .food {
        display:flex; /* 横向 */
        flex-wrap: wrap; /* 换行 */
    }
    .food > div {
        width:5px;
        height:5px;  /* 25*4充满food */
        transform: rotate(45deg); 
    }
</style>

4.按下方向键时让蛇移动:

获取蛇头和判断按下的是哪个方向键,按下方向键时让对应的left、top等加10(减10)
蛇的整个身体(整体,就是装每一节的容器)和每一节都需要获取以及第一节身体(脑袋)
移动:例如向右,按下右键,就把脑袋离左侧的距离加上10px,所以需要得到--->距离左侧/上方的距离(offsetLeft/offsetRight)

5.设置定时器,在定时器中调用移动函数,就能让蛇一直移动

setTimeout(function move() {
            switch (e.key) {
                case "ArrowUp":
                    snake_head.style.top = snake_head.offsetTop - 10 + 'px';
                    break;
                case "ArrowDown":
                    snake_head.style.top = snake_head.offsetTop + 10 + 'px';
                    break;
                case "ArrowLeft":
                    snake_head.style.left = snake_head.offsetLeft - 10 + 'px';
                    break;
                case "ArrowRight":
                    snake_head.style.left = snake_head.offsetLeft + 10 + 'px';
                    break
            }
            setTimeout(move,300)
        },300)

请一定不要像上面这样写,不然你会发现你的蛇就在那抽搐的乱跑QAQ
需要定义一个变量来存储蛇移动的方向(dir),然后就能把定时器放在keydown外边了

let dir
    document.addEventListener('keydown', (e) => {
        dir = e.key
    })
    setTimeout(function move() {
        switch (dir) {
            case "ArrowUp":
                snake_head.style.top = snake_head.offsetTop - 10 + 'px';
                break;
            case "ArrowDown":
                snake_head.style.top = snake_head.offsetTop + 10 + 'px';
                break;
            case "ArrowLeft":
                snake_head.style.left = snake_head.offsetLeft - 10 + 'px';
                break;
            case "ArrowRight":
                snake_head.style.left = snake_head.offsetLeft + 10 + 'px';
                break
        }
        setTimeout(move,300)
    },300)

6.蛇和食物的碰撞检测:即判断蛇的坐标和食物的坐标是否相同

snake_head.offsetLeft === food.offsetLeft && snake_head.offsetTop === food.offsetTop

7.吃到食物后改变食物的位置,增加蛇的身体

食物的坐标应该在0-290之间,此处也是10的倍数 OVO,也就是说我们生成的随机坐标应该是0-29的随机数*10
用到math.random和math.floor,insertAdjacentHTML()

function changeFood() {
        const x = Math.floor(Math.random() * 30) * 10
        const y = Math.floor(Math.random() * 30) * 10
        food.style.left = x + 'px'
        food.style.top = y + 'px'
    }
    
    //增加身体
    snake.insertAdjacentHTML('beforeend', '<div/>')

8.让新增的身体和头一起移动

容易想到的是:当前一节身体移动后,后一节身体就移动到它的位置后,后面依次,就能完成移动的功能
但是这个方法的问题也很明显,我们有多少节身体就要操作多少次的dom元素
一个聪明人说:这当然非常简单了,只要把最后一节身体放到最前面就OK了~~~
需要获取:当前蛇脑袋的坐标,当前尾巴的坐标【在4中也使用了脑袋的坐标,记得修改哦】
修改尾巴坐标时请把获取尾巴的方法放在修改尾巴的方法上,不要和获取脑袋的放在一起,不然你点尾巴可能会卡顿然后再回到蛇的身体上哦

image.png

image.png

9.禁止掉头

身体大于等于两节的时候按下的方向不能相反(保持原来的方向不变)
判断:按键按下时我们记录了状态dir,再次按下时就需要和这个dir进行比对,看看是否是相反的

if (dir === "ArrowUp" && e.key !== "ArrowDown") {
    dir = e.key
}else if (dir === "ArrowDown" && e.key !== "ArrowUp") {
    dir = e.key
     ...

很明显,重复这样做是可以做到禁止掉头的,但是这样的结构太笨重了,也有点呆
创建一个对象:属性名是按下的key,属性值是与它相反的key

const reObj = {
    "ArrowUp": 'ArrowDown',
    "ArrowDown": 'ArrowUp',
    "ArrowLeft": 'ArrowRight',
    "ArrowRight":'ArrowLeft'
}

if (reObj[dir] !== e.key) {
    dir = e.key
}

10.游戏终止

撞墙:四堵墙都不能超过,使用return来结束整个函数
撞自己:检查蛇头的位置(尾巴移动后的新位置)有没有和身体重合【注意:蛇的尾巴和头的坐标时可以相同的】

//是否撞墙
if (x < 0 || x > 290 || y < 0 || y > 290) {
    alert('撞到墙了,游戏结束!')
    return
}
//是否撞到自己
for (let i = 1; i < snake_body.length-1; i++) {
    if (snake_body[i].offsetLeft === x && snake_body[i].offsetTop === y) {
        alert('撞到自己了,游戏结束!')
        return
    }     
}

11.分数和等级的对应增加

分别创建变量存储分数和等级
用到textContent 和 %

if (x === food.offsetLeft && y === food.offsetTop) {
            changeFood()
            snake.insertAdjacentHTML('beforeend', '<div/>')
            score++
            scoreSpan.textContent = score
            if (score % 5 === 0 && level < 14) {
                level++
                levelSpan.textContent = level
            }
        }

过程中可能出现的问题

当按下某个方向键不松开的时候,第一次和第二次的移动之间总是会有一点卡顿,这是电脑本来的设计,但是这会导致蛇不能流畅的移动
解决:定义一个变量用来存储蛇的移动方向,设置一个定时器(因为在让蛇持续移动的时候就要设置定时器,所以这个问题也就同时被解决了)

因为我们是通过判断是否按下了某个方向键来让蛇移动的,这也导致了我们在按非方向键的时候蛇会停止移动
解决:需要增加一个判断,判断按下的按键是否在有效范围内
可以把合法按键存放在数组内

const keyArr = ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'] //存放合法按键
document.addEventListener('keydown', (e) => {
        if (keyArr.includes(e.key)) {
            dir = e.key
        }
    })

获取尾巴并放到最前面的位置后会发生停止移动的问题
这是因为结构上并没有改变(尾巴还是尾巴,是最后一个元素),只是坐标变了(坐标在最前边了),这样的话我们在下次移动的时候获取的蛇头坐标还是原来那个蛇头的坐标
解决:需要调整结构insertAdjacentHTML()
snake.insertAdjacentElement('afterbegin', tail)

在判断是否掉头时,通过快速的按下其它键再掉头会发现还是可以掉头,因为是在定时器中的,按下了两个按键
解决:禁止按下两个按键
创建一个变量来记录按键的状态

详情视频请点击【JavaScript核心基础_讲师(李立超)_JS教程】 www.bilibili.com/video/BV1mG…

直接get代码请点击 gitee.com/icyh1228/js…