首页 > web前端 > js教程 > 正文

怎样用JavaScript实现一个简单的贪吃蛇游戏?

畫卷琴夢
发布: 2025-07-04 23:55:02
原创
641人浏览过

游戏的核心循环通过setinterval驱动,分为更新和绘制两个阶段。1. 更新阶段处理蛇的移动、碰撞检测和食物逻辑;2. 绘制阶段将最新状态渲染到canvas上。蛇的移动通过计算新头部位置并更新数组实现,使用unshift添加头部和pop移除尾部模拟移动效果。碰撞检测包含三种情况:撞墙(超出画布边界)、撞自己(头部与身体坐标重合)和撞食物(得分并增长蛇身)。生成食物时通过随机坐标并检查是否与蛇身重叠,若重叠则递归重新生成,确保食物出现在空闲位置。

怎样用JavaScript实现一个简单的贪吃蛇游戏?

用JavaScript实现一个简单的贪吃蛇游戏,核心在于构建一个游戏循环,在这个循环里不断更新蛇的位置、绘制游戏状态,并响应玩家的键盘输入。它是一个非常经典的入门项目,能很好地帮助你理解游戏开发中的基本概念,比如游戏循环、碰撞检测和状态管理。

怎样用JavaScript实现一个简单的贪吃蛇游戏?

解决方案

怎样用JavaScript实现一个简单的贪吃蛇游戏?

要构建一个贪吃蛇游戏,我们需要HTML来提供一个画布(canvas),CSS来简单美化一下,然后用JavaScript来处理所有的游戏逻辑。

立即学习Java免费学习笔记(深入)”;

首先,在HTML中创建一个canvas元素:

怎样用JavaScript实现一个简单的贪吃蛇游戏?
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简单的贪吃蛇</title>
    <style>
        body { margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #222; }
        canvas { background-color: #000; border: 2px solid #555; display: block; }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>
    <script src="snake.js"></script>
</body>
</html>
登录后复制

接着,是snake.js的核心逻辑:

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');

const gridSize = 20; // 每个方块的大小
const tileCount = canvas.width / gridSize; // 一行/一列有多少个方块

let snake = [{ x: 10, y: 10 }]; // 蛇的初始位置
let food = {}; // 食物的位置
let dx = 0; // x方向的速度
let dy = 0; // y方向的速度
let score = 0;
let changingDirection = false; // 防止快速按键导致方向冲突

// 游戏主循环,我个人偏爱用 setInterval,在简单的游戏中它足够直观
let gameInterval;

function generateFood() {
    food = {
        x: Math.floor(Math.random() * tileCount),
        y: Math.floor(Math.random() * tileCount)
    };

    // 确保食物不生成在蛇身上
    for (let i = 0; i < snake.length; i++) {
        if (food.x === snake[i].x && food.y === snake[i].y) {
            generateFood(); // 递归调用直到找到一个空位
            return;
        }
    }
}

function draw() {
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 绘制食物
    ctx.fillStyle = 'red';
    ctx.strokeStyle = 'darkred';
    ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
    ctx.strokeRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);

    // 绘制蛇
    ctx.fillStyle = 'lime';
    ctx.strokeStyle = 'darkgreen';
    snake.forEach(segment => {
        ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
        ctx.strokeRect(segment.x * gridSize, segment.y * gridSize, gridSize, gridSize);
    });
}

function update() {
    changingDirection = false; // 允许再次改变方向

    const head = { x: snake[0].x + dx, y: snake[0].y + dy };

    // 碰撞检测
    if (head.x < 0 || head.x >= tileCount ||
        head.y < 0 || head.y >= tileCount ||
        checkCollision(head)) {
        clearInterval(gameInterval); // 游戏结束
        alert(`游戏结束!得分:${score}`);
        return;
    }

    snake.unshift(head); // 将新头部添加到蛇的数组开头

    const didEatFood = head.x === food.x && head.y === food.y;
    if (didEatFood) {
        score += 10;
        generateFood(); // 生成新食物
    } else {
        snake.pop(); // 如果没吃到食物,移除尾巴
    }
}

function checkCollision(head) {
    // 检查头部是否与身体其他部分碰撞
    for (let i = 1; i < snake.length; i++) {
        if (head.x === snake[i].x && head.y === snake[i].y) {
            return true;
        }
    }
    return false;
}

function changeDirection(event) {
    if (changingDirection) return;
    changingDirection = true;

    const keyPressed = event.keyCode;
    const LEFT_KEY = 37;
    const RIGHT_KEY = 39;
    const UP_KEY = 38;
    const DOWN_KEY = 40;

    const goingUp = dy === -1;
    const goingDown = dy === 1;
    const goingLeft = dx === -1;
    const goingRight = dx === 1;

    // 避免蛇立即掉头
    if (keyPressed === LEFT_KEY && !goingRight) {
        dx = -1;
        dy = 0;
    }
    if (keyPressed === UP_KEY && !goingDown) {
        dx = 0;
        dy = -1;
    }
    if (keyPressed === RIGHT_KEY && !goingLeft) {
        dx = 1;
        dy = 0;
    }
    if (keyPressed === DOWN_KEY && !goingUp) {
        dx = 0;
        dy = 1;
    }
}

// 初始化游戏
function startGame() {
    generateFood();
    document.addEventListener('keydown', changeDirection);
    // 初始方向,让蛇开始移动
    dx = 1;
    dy = 0;
    gameInterval = setInterval(() => {
        update();
        draw();
    }, 100); // 100毫秒更新一次,可以调整速度
}

startGame();
登录后复制

这个方案涵盖了游戏的基本要素:画布设置、蛇和食物的表示、绘制函数、更新游戏状态的函数以及键盘事件监听。它是一个相当基础但完整的实现。

游戏的核心循环是如何运作的?

在我看来,游戏的核心循环就像是游戏的心脏,它跳动着,驱动着整个世界的运转。在贪吃蛇这种简单的2D游戏中,这个“跳动”通常通过一个定时器来实现。你可能会看到两种主要的方式:setInterval 和 requestAnimationFrame。

对于贪吃蛇这种基于网格、状态更新相对离散的游戏,我个人更倾向于使用setInterval。它简单直观,你设置一个固定的时间间隔(比如100毫秒),然后告诉浏览器每隔这么久就执行一次我的游戏逻辑。它的好处是,你对游戏的速度有非常精确的控制,每100毫秒蛇就移动一步,不会因为帧率波动导致蛇忽快忽慢。

这个循环里通常会包含两个主要阶段:

  1. 更新(Update):这个阶段是纯粹的逻辑处理。蛇的位置变了没?吃到食物了没?撞墙了没?这些都在这里计算。它不涉及任何的视觉呈现,只是修改游戏内部的数据状态。比如,蛇的头部坐标会根据当前的方向进行调整,如果吃了食物,蛇的身体数组会增长,否则就移除尾部。
  2. 绘制(Draw):在更新完所有游戏状态后,就需要把这些新的状态“画”到屏幕上。清除旧的画面,然后根据最新的蛇和食物坐标,在画布上重新绘制它们。

所以,整个流程就是:设定一个时间间隔 -> 在每个间隔里,先更新所有游戏数据 -> 然后根据新数据重新绘制画面 -> 重复。这个循环不断进行,直到游戏结束。虽然requestAnimationFrame在动画平滑度和资源优化上更有优势,因为它会与浏览器绘制周期同步,但对于这种步进式的游戏,setInterval的固定步长反而让逻辑更清晰。当然,如果未来想做更复杂的动画效果,比如蛇的平滑过渡,那requestAnimationFrame就是更好的选择。

如何处理蛇的移动和碰撞检测?

蛇的移动和碰撞检测,是贪吃蛇游戏里最核心也最容易出错的部分。我通常会把它们看作一个紧密相连的舞蹈:蛇每一步的移动都伴随着对周围环境的“审视”,看有没有撞到什么。

蛇的移动:

蛇的移动,从逻辑上讲,并不是让整个蛇身一起平移。它更像是一个“头部先行,身体跟随”的过程。

  1. 确定新头部位置: 根据当前的方向(上、下、左、右),计算出蛇头即将到达的新坐标。比如,如果当前是向右移动(dx = 1, dy = 0),那么新头部x坐标就是当前头部x坐标加1。
  2. 添加新头部: 将这个新计算出的头部坐标添加到蛇身体数组的最前面。现在,蛇的数组长度暂时增加了1。
  3. 移除旧尾部(如果没吃到食物): 如果蛇头没有碰到食物,这意味着蛇只是单纯地向前移动了一格,所以需要从蛇身体数组的末尾移除最后一个元素,从而保持蛇的长度不变。如果蛇头碰到了食物,那么就移除尾部,这样蛇的身体长度就自然增加了一格,模拟了“吃”和“长大”的效果。

这种处理方式非常优雅,避免了复杂的循环来移动每个蛇节,而是通过数组的unshift和pop方法巧妙地实现了蛇的移动和增长。

碰撞检测:

碰撞检测是游戏逻辑中判断“发生了什么”的关键。在贪吃蛇中,主要有三种碰撞需要处理:

  1. 撞墙: 这是最直接的。只需要检查新计算出的蛇头坐标是否超出了画布的边界。例如,如果蛇头的x坐标小于0(超出左边界),或者大于等于tileCount(超出右边界),那就说明撞墙了。y坐标同理。一旦撞墙,游戏就应该结束。
  2. 撞自己: 这是比较有趣的一种。蛇头不能碰到自己身体的任何一部分。实现方法是,在计算出新蛇头的位置后,遍历蛇身体数组(从第二个元素开始,因为第一个元素就是当前蛇头,自己不会撞自己),检查新蛇头的坐标是否与任何一个身体节的坐标重合。如果重合了,同样游戏结束。这里有个小细节,如果蛇刚开始只有一两个节,是不会发生自撞的,所以循环从i = 1开始很关键。
  3. 撞食物: 这是玩家希望发生的碰撞!同样,检查新蛇头的坐标是否与食物的坐标重合。如果重合了,那么蛇就“吃”到了食物。这时候,除了增加分数,更重要的是要执行“蛇增长”的逻辑(即上面提到的,不移除蛇尾),并且在画布上生成一个新的食物。

这些碰撞检测都需要在蛇的头部移动之后绘制之前进行,这样才能在视觉上及时反映出游戏状态的变化,并决定游戏是否继续。

如何生成随机食物并确保其不出现在蛇身上?

生成随机食物听起来简单,但要确保它不出现在蛇身上,就多了一层考量。我通常会采用一个“先生成,再检查,不合格就重来”的策略。

  1. 随机位置生成: 首先,利用 Math.random() 和 Math.floor() 在游戏网格的范围内生成一对随机的 (x, y) 坐标。因为我们的游戏是基于网格的,所以生成的坐标应该是0到tileCount - 1之间的整数。

    food = {
        x: Math.floor(Math.random() * tileCount),
        y: Math.floor(Math.random() * tileCount)
    };
    登录后复制

    这确保了食物会落在画布内的某个网格单元上。

  2. 检查与蛇身重叠: 生成一个潜在的食物位置后,我们需要检查这个位置是否已经被蛇占据了。我会遍历蛇的每一个身体节(包括蛇头),比较食物的 (x, y) 坐标是否与任何一个蛇节的 (x, y) 坐标相同。

    for (let i = 0; i < snake.length; i++) {
        if (food.x === snake[i].x && food.y === snake[i].y) {
            // 重叠了!
        }
    }
    登录后复制
  3. 不合格则重新生成: 如果发现新生成的食物位置与蛇身重叠,那么这个位置就是无效的。这时候,最直接的办法就是重新调用生成食物的函数。这形成了一个递归或者循环,直到找到一个完全空闲的位置为止。

    function generateFood() {
        food = {
            x: Math.floor(Math.random() * tileCount),
            y: Math.floor(Math.random() * tileCount)
        };
    
        // 确保食物不生成在蛇身上
        for (let i = 0; i < snake.length; i++) {
            if (food.x === snake[i].x && food.y === snake[i].y) {
                generateFood(); // 递归调用直到找到一个空位
                return; // 找到重叠后,本次生成无效,直接返回等待下一次递归
            }
        }
        // 如果循环结束都没有重叠,说明这个位置是合格的
    }
    登录后复制

    这种递归方式在蛇比较短的时候效率很高。但理论上,如果蛇非常长,几乎占据了整个屏幕,那么找到一个空闲位置可能会需要多次尝试,甚至在极端情况下(比如屏幕全被蛇占满)会陷入无限循环。不过对于简单的贪吃蛇游戏,这种情况通常不会发生,所以这种简单直接的递归方法是完全可行的。

通过这种“生成-检查-重试”的模式,我们就能确保食物总是出现在一个对玩家来说可达且有效的空闲位置上。

以上就是怎样用JavaScript实现一个简单的贪吃蛇游戏?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号