键码经典游戏:简易版贪吃蛇

序言

🐍 欢迎来到贪吃蛇的世界!

在这篇博客中,我们将一起探索如何使用HTML、CSS和JavaScript创建一个经典的贪吃蛇游戏。

贪吃蛇,作为一款极具代表性的经典游戏,以其其简单而引人入胜的游戏机制老少咸宜~

作为编程练手而言,通过实现贪吃蛇游戏,可以学习和巩固许多前端开发的基础知识,如 DOM 操作、事件处理、画布绘制等。

废话少说,直接开冲!!

实现思路

首先,游戏的主要元素是:蛇、食物、障碍物;

通常来说:其中障碍物是游戏的边界;

本次,我想新增设置一个机制:即根据贪吃蛇不断增长的长度,在墙内也不断随机新增障碍物,以增加游戏的难度~

因为是基础本 HTML 代码练手,就是老三样:

  • HTML结构:负责HTML元素的组织。

  • CSS样式:简易的像素点及颜色。

  • JavaScript逻辑:描述如何初始化游戏元素、游戏循环和 Canvas 绘图函数、分析碰撞检测的逻辑等

实现关键在于:

  • 处理用户输入,控制蛇的移动
  • 检测碰撞,以及碰撞后如何处理
  • 态添加食物和障碍物

另外还要加一个按钮,用于撞墙后,重新开始游戏~ 避免手动刷新

代码实现

ini 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇游戏</title>
    <style>
        #game-board {
            width: 400px;
            height: 400px;
            border: 1px solid black;
        }

        #restart-button {
            margin-top: 20px;
            padding: 10px 20px;
            font-size: 16px;
        }
    </style>
</head>
<body>

<canvas id="game-board" width="400" height="400"></canvas>
<button id="restart-button">重新开始</button>

<script>
    document.getElementById('restart-button').addEventListener('click', function() {
        location.reload();
    });
    // 400x400 像素的画布
    const canvas = document.getElementById('game-board');
    const context = canvas.getContext('2d');
    const box = 20;
    canvas.width = 20 * box;
    canvas.height = 20 * box;

    let snake = [];
    snake[0] = {x: 10 * box, y: 10 * box};

    let food = {
        x: Math.floor(Math.random() * 19 + 1) * box,
        y: Math.floor(Math.random() * 19 + 1) * box
    };

    let d;
    let obstacles = [];

    document.addEventListener("keydown", direction);
    
    // 用于根据玩家的键盘输入更改蛇的移动方向。它会监听键盘的箭头键,并相应地更新方向变量 `d`
    function direction(event) {
        if(event.keyCode == 37 && d != "RIGHT"){
            d = "LEFT";
        } else if(event.keyCode == 38 && d != "DOWN"){
            d = "UP";
        } else if(event.keyCode == 39 && d != "LEFT"){
            d = "RIGHT";
        } else if(event.keyCode == 40 && d != "UP"){
            d = "DOWN";
        }
    }
    
    // 负责绘制和更新游戏的所有元素(蛇、食物和障碍物)
    function draw() {
        // 清除画布,准备绘制新的一帧
        context.clearRect(0, 0, canvas.width, canvas.height);
        
        // 遍历蛇的每个部分并绘制它们
        for(let i = 0; i < snake.length; i++){
            context.fillStyle = (i == 0)? "green" : "white";
            context.fillRect(snake[i].x, snake[i].y, box, box);
            context.strokeStyle = "black";
            context.strokeRect(snake[i].x,snake[i].y,box,box);
        }
        
        // 绘制食物和障碍物
        for(let i = 0; i < obstacles.length; i++) {
            context.fillStyle = "black";
            context.fillRect(obstacles[i].x, obstacles[i].y, box, box);
        }

        context.fillStyle = "red";
        context.fillRect(food.x, food.y, box, box);

        let snakeX = snake[0].x;
        let snakeY = snake[0].y;
        
        // 更新蛇的位置。如果蛇吃到食物,它会变长,同时生成新的食物和障碍物
        if(d == "LEFT") snakeX -= box;
        if(d == "UP") snakeY -= box;
        if(d == "RIGHT") snakeX += box;
        if(d == "DOWN") snakeY += box;
        
        // 如果蛇碰到边界、自身或障碍物,游戏会结束
        if(snakeX == food.x && snakeY == food.y){
            food = {
                x: Math.floor(Math.random()*19+1) * box,
                y: Math.floor(Math.random()*19+1) * box
            };
            let obstacle = {
                x: Math.floor(Math.random()*19+1) * box,
                y: Math.floor(Math.random()*19+1) * box
            };
            obstacles.push(obstacle);
        } else {
            snake.pop();
        }

        let newHead = {
            x: snakeX,
            y: snakeY
        };

        if(snakeX < 0 || snakeY < 0 || snakeX >= canvas.width || snakeY >= canvas.height 
            || collision(newHead,snake) || collision(newHead, obstacles)){
            clearInterval(game);
        }

        snake.unshift(newHead);
    }
    
    // 检查蛇的头部是否与数组中的任何元素发生碰撞。它用于检测蛇是否碰到了自身、边界或障碍物
    function collision(head, array) {
        for(let i = 0; i < array.length; i++){
            if(head.x == array[i].x && head.y == array[i].y){
                return true;
            }
        }
        return false;
    }

    let game = setInterval(draw, 200);
</script>

</body>
</html>

运行效果

本地网页运行:

码上掘金:

code.juejin.cn/pen/7293778...

上下左右键操作:黑色以及黑框边界为障碍物;绿色为蛇头;红色为食物;

优化思考

通常以我们探究(尤其面试中)的尿性,实现之后都要想想优化的可能性:

对于一个基本的贪吃蛇游戏来说,以上代码性能应该是可以接受的,存在一些可以优化的地方比如有:

1、以考虑将相关的变量和函数组织成对象;

2、其中,每个游戏循环都会清除整个画布然后重新绘制。可以考虑只清除和绘制发生变化的部分;

3、碰撞检测可以更加高效:例如,检查蛇头是否碰到蛇身,可以从蛇的第四个部分开始检查,因为前三个部分不可能与蛇头碰撞;

4、还有当生成新的食物或障碍物时,应该确保它们不会出现在蛇的身体上,可以添加一个检查来确保新生成的食物或障碍物的位置是空闲的

scss 复制代码
function generatePosition() {
    let position;
    do {
        position = {
            x: Math.floor(Math.random() * 19 + 1) * box,
            y: Math.floor(Math.random() * 19 + 1) * box
        };
    } while (collision(position, snake) || collision(position, obstacles));
    return position;
}

food = generatePosition();

5、考虑使用 requestAnimationFrame 替代 setInterval 来创建游戏循环。requestAnimationFrame 有更好的性能(这个很多文章都有讲了,就不作展开了)

scss 复制代码
function gameLoop() {
    draw();
    requestAnimationFrame(gameLoop);
}

gameLoop();

OK,以上就是本次分享~~

有兴趣的掘友们可基于此版自行动手试试~

相关推荐
mldong30 分钟前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
Interview Aid1123 小时前
Walmart Global Tech SDE 三轮面经|基础、并发、压力面
面试·职场和发展
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马6 小时前
AI源码分析:拆解模型底层实现逻辑
前端