3

这个中秋可是悲催,别人放假,我还得在家办公写项目,带娃的时间都没有,这不,娃要缠着陪她玩积木游戏,哎,心中有事,陪娃都陪不好,咋整,灵机一动,先搞个小游戏让娃耍个把小时,毕竟孩子长时间对着电脑不好,写个贪吃蛇吧,能玩很久。

先看一下简单的界面是这样的:

网页游戏,比较简单,一个页面就搞定了,代码是这样的,直接生成一个 html 就能玩了。

创建一个简单的贪吃蛇网页游戏,使用 HTML、CSS 和 JavaScript 三件套,简单高效。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Snake Game</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #333;
            margin: 0;
        }

        canvas {
            border: 1px solid #fff;
            background-color: #000;
        }
    </style>
</head>
<body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>

    <script>
        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 = { x: 5, y: 5 };
        let direction = { x: 0, y: 0 };
        let nextDirection = { x: 0, y: 0 };
        let speed = 200;

        function gameLoop() {
            // Update snake direction
            direction = nextDirection;

            // Move snake
            const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
            snake.unshift(head);

            // Check for food collision
            if (head.x === food.x && head.y === food.y) {
                placeFood();
            } else {
                snake.pop();
            }

            // Check for wall collision
            if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
                gameOver();
                return;
            }

            // Check for self collision
            for (let i = 1; i < snake.length; i++) {
                if (snake[i].x === head.x && snake[i].y === head.y) {
                    gameOver();
                    return;
                }
            }

            drawGame();
        }

        function drawGame() {
            // Clear canvas
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // Draw snake
            ctx.fillStyle = 'lime';
            snake.forEach(part => {
                ctx.fillRect(part.x * gridSize, part.y * gridSize, gridSize, gridSize);
            });

            // Draw food
            ctx.fillStyle = 'red';
            ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
        }

        function placeFood() {
            food.x = Math.floor(Math.random() * tileCount);
            food.y = Math.floor(Math.random() * tileCount);

            // Make sure food doesn't appear on the snake
            for (let i = 0; i < snake.length; i++) {
                if (snake[i].x === food.x && snake[i].y === food.y) {
                    placeFood();
                }
            }
        }

        function changeDirection(event) {
            switch (event.keyCode) {
                case 37: // Left arrow
                    if (direction.x === 0) {
                        nextDirection = { x: -1, y: 0 };
                    }
                    break;
                case 38: // Up arrow
                    if (direction.y === 0) {
                        nextDirection = { x: 0, y: -1 };
                    }
                    break;
                case 39: // Right arrow
                    if (direction.x === 0) {
                        nextDirection = { x: 1, y: 0 };
                    }
                    break;
                case 40: // Down arrow
                    if (direction.y === 0) {
                        nextDirection = { x: 0, y: 1 };
                    }
                    break;
            }
        }

        function gameOver() {
            alert('Game Over!');
            snake = [{ x: 10, y: 10 }];
            direction = { x: 0, y: 0 };
            nextDirection = { x: 0, y: 0 };
            placeFood();
        }

        document.addEventListener('keydown', changeDirection);
        setInterval(gameLoop, speed);
        placeFood();
    </script>
</body>
</html>

前端三件套:

HTML:包含一个 <canvas> 元素,贪吃蛇游戏将会在这个画布上渲染。

CSS:简单地设置了画布的外观。

JavaScript:游戏逻辑,包括:

  • gameLoop 函数:负责蛇的移动、碰撞检测、食物生成等。
  • drawGame 函数:绘制蛇和食物。
  • placeFood 函数:随机放置食物在画布上。
  • changeDirection 函数:监听键盘事件,改变蛇的移动方向。
  • gameOver 函数:处理游戏结束的逻辑。

下面再详细解释一下这个小游戏的实现逻辑:

这个贪吃蛇游戏使用 HTML、CSS 和 JavaScript 编写,代码实现的逻辑和流程如下:

1. HTML 部分

<canvas id="gameCanvas" width="400" height="400"></canvas>
  • 使用 <canvas> 元素作为游戏画布,游戏会在这个区域绘制蛇、食物等内容。
  • 设置 id="gameCanvas" 以便在 JavaScript 中通过 document.getElementById 获取它。
  • widthheight 属性定义了画布的尺寸为 400x400 像素。

2. CSS 部分

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #333;
    margin: 0;
}

canvas {
    border: 1px solid #fff;
    background-color: #000;
}
  • 使用 CSS 将画布居中,背景设为黑色。
  • 设置画布的边框和背景颜色,增强视觉效果。

3. JavaScript 部分

JavaScript 部分包含了游戏的核心逻辑,包括蛇的移动、食物的生成、碰撞检测、绘制和用户输入的处理。

3.1. 初始化变量

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 = { x: 5, y: 5 };
let direction = { x: 0, y: 0 };
let nextDirection = { x: 0, y: 0 };
let speed = 200;
  • canvas:获取画布元素。
  • ctx:获取画布的 2D 渲染上下文,用于绘制图形。
  • gridSize:网格大小,每个网格 20x20 像素。
  • tileCount:计算画布中网格的数量,canvas.width / gridSize(这里为 20x20 个网格)。
  • snake:蛇的初始位置,初始为一个对象数组,蛇头位置在 {x: 10, y: 10}
  • food:食物的初始位置 {x: 5, y: 5}
  • directionnextDirection:蛇的当前移动方向和下一个移动方向。
  • speed:控制游戏循环的速度,单位是毫秒。

3.2. 游戏主循环

function gameLoop() {
    // Update snake direction
    direction = nextDirection;

    // Move snake
    const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y };
    snake.unshift(head);

    // Check for food collision
    if (head.x === food.x && head.y === food.y) {
        placeFood();
    } else {
        snake.pop();
    }

    // Check for wall collision
    if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
        gameOver();
        return;
    }

    // Check for self collision
    for (let i = 1; i < snake.length; i++) {
        if (snake[i].x === head.x && snake[i].y === head.y) {
            gameOver();
            return;
        }
    }

    drawGame();
}
  • gameLoop 是游戏的主循环函数,使用 setInterval 在一定时间间隔内重复调用,实现游戏的实时更新。
  • 移动方向更新:更新蛇的移动方向。
  • 移动蛇:创建蛇头的新位置,根据方向调整 xy 坐标,将新头部添加到 snake 数组的开头。
  • 检查食物碰撞:如果蛇头和食物位置相同,调用 placeFood 生成新的食物位置;否则,移除蛇尾(snake.pop()),维持蛇的长度。
  • 检测碰撞

    • 墙壁碰撞:如果蛇头的位置超出画布范围,调用 gameOver 函数结束游戏。
    • 自身碰撞:如果蛇头与身体的任何部分重叠,调用 gameOver 函数结束游戏。
  • 绘制游戏:调用 drawGame 函数更新画布。

3.3. 绘制游戏

function drawGame() {
    // Clear canvas
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Draw snake
    ctx.fillStyle = 'lime';
    snake.forEach(part => {
        ctx.fillRect(part.x * gridSize, part.y * gridSize, gridSize, gridSize);
    });

    // Draw food
    ctx.fillStyle = 'red';
    ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
}
  • drawGame 函数负责绘制游戏的每一帧。
  • 清空画布:用黑色填充整个画布,清除之前的绘制内容。
  • 绘制蛇:遍历 snake 数组,在蛇每个部分的位置上绘制一个绿色矩形。
  • 绘制食物:在 food 的位置绘制一个红色矩形。

3.4. 生成新食物

function placeFood() {
    food.x = Math.floor(Math.random() * tileCount);
    food.y = Math.floor(Math.random() * tileCount);

    // Make sure food doesn't appear on the snake
    for (let i = 0; i < snake.length; i++) {
        if (snake[i].x === food.x && snake[i].y === food.y) {
            placeFood();
        }
    }
}
  • placeFood 随机在画布上放置食物。
  • 防止食物出现在蛇身上:如果生成的食物位置在蛇身上,则递归调用 placeFood 重新生成位置。

3.5. 处理方向改变

function changeDirection(event) {
    switch (event.keyCode) {
        case 37: // Left arrow
            if (direction.x === 0) {
                nextDirection = { x: -1, y: 0 };
            }
            break;
        case 38: // Up arrow
            if (direction.y === 0) {
                nextDirection = { x: 0, y: -1 };
            }
            break;
        case 39: // Right arrow
            if (direction.x === 0) {
                nextDirection = { x: 1, y: 0 };
            }
            break;
        case 40: // Down arrow
            if (direction.y === 0) {
                nextDirection = { x: 0, y: 1 };
            }
            break;
    }
}
  • changeDirection 函数监听键盘事件,更新蛇的移动方向。
  • 确保蛇不能直接反方向移动:例如,蛇正在向右移动时,不能直接按左键,否则会导致游戏结束。

3.6. 游戏结束

function gameOver() {
    alert('Game Over!');
    snake = [{ x: 10, y: 10 }];
    direction = { x: 0, y: 0 };
    nextDirection = { x: 0, y: 0 };
    placeFood();
}
  • gameOver 函数在蛇撞墙或自身时调用,显示“游戏结束”信息,并重置游戏状态。

3.7. 启动游戏

document.addEventListener('keydown', changeDirection);
setInterval(gameLoop, speed);
placeFood();
  • 事件监听:监听键盘事件,调用 changeDirection 函数。
  • 启动游戏循环:使用 setInterval 定期调用 gameLoop,使游戏持续运行。
  • 初始食物生成:调用 placeFood 函数生成初始食物位置。

最后小结一下

  • 这个贪吃蛇游戏使用了 JavaScript 进行画布绘制、碰撞检测和用户输入处理。
  • 游戏循环 (gameLoop) 不断更新蛇的位置、检查碰撞、绘制游戏元素,形成动画效果。
  • 使用键盘事件来改变蛇的方向,实现玩家的控制。

好了,现在可以静静的写项目了,专心致志,效率翻倍。


威哥爱编程
183 声望15 粉丝

华为开发者专家(HDE)、TiDB开发者官方认证讲师、Java畅销书作者、HarmonyOS应用开发高级讲师