网页版贪吃蛇小游戏开发HTML实现附源码!

发布于:2025-02-16 ⋅ 阅读:(41) ⋅ 点赞:(0)

项目背景

贪吃蛇是一款经典的休闲小游戏,因其简单易玩的机制和丰富的变形而深受玩家喜爱。本次开发目标是实现一款网页版贪吃蛇小游戏,并通过前端与后端结合的方式,提供一个流畅的在线体验。

实现过程

游戏逻辑设计

  1. 蛇的移动:贪吃蛇每次只能向上下左右四个方向移动。通过用户输入的方向键触发移动。
  2. 转向机制:前后控制蛇头的方向变化,确保蛇不会自己移动或倒转。
  3. 食物生成:随机在游戏区域中生成食物,并记录其位置。
  4. 吃掉食物:当蛇吃掉食物时,增加长度(或得分),并生成新的食物位置。

前端实现

  1. 初始化界面:使用 React 组件构建贪吃蛇游戏的初始界面,包括蛇头、身体和背景网格。
  2. 动态缩放效果:利用 CSS 变位动画实现蛇在增长时的视觉效果。
  3. 事件处理:绑定用户方向键的变化事件,触发贪吃蛇移动。

后端实现

  1. API 接口:创建控制贪吃蛇的游戏接口,接收用户的按键输入并返回游戏状态。
  2. 状态更新:使用 Django REST框架接收前端发送的方向数据,并更新游戏状态。
  3. 数据存储:将玩家的操作和游戏结果保存到数据库中。

代码展示

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>贪吃蛇游戏</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            font-family: Arial, sans-serif;
        }

        .game-container {
            text-align: center;
            display: flex;
            align-items: center;
            justify-content: space-between;
            max-width: 100%;
            flex-wrap: wrap;
        }

        #gameCanvas {
            border: 2px solid #333;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            margin: 0 auto;
        }

        #gameControls {
            text-align: center;
            margin: 0 auto;
            padding: 0 8px;
        }

        #gameScore {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        #score {
            font-size: 24px;
            margin: 10px 0;
        }

        #startBtn {
            font-size: 18px;
            padding: 12px 24px;
            background: linear-gradient(145deg, #f0f0f0, #cacaca);
            color: #333;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: 5px 5px 10px #bebebe,
                -5px -5px 10px #ffffff;
            position: relative;
            overflow: hidden;
            font-weight: bold;
            text-transform: uppercase;
            letter-spacing: 1px;
        }

        #startBtn::before {
            content: '';
            position: absolute;
            top: 2px;
            left: 2px;
            right: 2px;
            bottom: 50%;
            background: linear-gradient(180deg, rgba(255, 255, 255, 0.3), transparent);
            border-radius: 8px 8px 0 0;
            pointer-events: none;
        }

        #startBtn:hover {
            transform: translateY(-2px);
            box-shadow: 6px 6px 12px #bebebe,
                -6px -6px 12px #ffffff;
            background: linear-gradient(145deg, #f5f5f5, #d0d0d0);
        }

        #startBtn:active {
            transform: translateY(1px);
            box-shadow: inset 4px 4px 8px #bebebe,
                inset -4px -4px 8px #ffffff;
            background: linear-gradient(145deg, #e6e6e6, #c0c0c0);
        }

        .controls {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
            width: 180px;
            margin: 20px auto;
        }

        .control-btn {
            width: 60px;
            height: 60px;
            font-size: 24px;
            background: linear-gradient(145deg, #f0f0f0, #cacaca);
            color: #333;
            border: none;
            border-radius: 50%;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            justify-content: center;
            align-items: center;
            box-shadow: 5px 5px 10px #bebebe,
                -5px -5px 10px #ffffff;
            position: relative;
            overflow: hidden;
        }

        .control-btn::before {
            content: '';
            position: absolute;
            top: 5%;
            left: 5%;
            right: 5%;
            bottom: 5%;
            border-radius: 50%;
            z-index: -1;
        }

        .control-btn:hover {
            transform: translateY(-2px);
            box-shadow: 6px 6px 12px #bebebe,
                -6px -6px 12px #ffffff;
        }

        .control-btn:active {
            transform: translateY(1px);
            box-shadow: inset 4px 4px 8px #bebebe,
                inset -4px -4px 8px #ffffff;
        }

        #up {
            grid-column: 2;
        }

        #left {
            grid-column: 1;
            grid-row: 2;
        }

        #right {
            grid-column: 3;
            grid-row: 2;
        }

        #down {
            grid-column: 2;
            grid-row: 3;
        }
    </style>
</head>

<body>
    <div class="game-container">
        <canvas id="gameCanvas" width="300" height="300"></canvas>
        <div id="gameControls">
            <div id="gameScore">
                <div id="score">得分: 0</div>
                <button id="startBtn">开始游戏</button>
            </div>
            <div class="controls">
                <button id="up" class="control-btn" onclick="changeDirectionByButton('up')">↑</button>
                <button id="left" class="control-btn" onclick="changeDirectionByButton('left')">←</button>
                <button id="right" class="control-btn" onclick="changeDirectionByButton('right')">→</button>
                <button id="down" class="control-btn" onclick="changeDirectionByButton('down')">↓</button>
            </div>
        </div>
    </div>
    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        const scoreElement = document.getElementById('score');
        const startBtn = document.getElementById('startBtn');

        const gridSize = 15;
        const tileCount = canvas.width / gridSize;

        let snake = [{ x: 10, y: 10 }];
        let food = { x: 15, y: 15 };
        let dx = 0;
        let dy = 0;
        let score = 0;
        let gameRunning = false;

        function drawGame() {
            if (!gameRunning) return;

            clearCanvas();
            moveSnake();
            drawSnake();
            drawFood();
            checkCollision();
            updateScore();
            setTimeout(drawGame, 200);
        }

        function clearCanvas() {
            ctx.fillStyle = '#f0f0f0';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }

        function moveSnake() {
            const head = { x: snake[0].x + dx, y: snake[0].y + dy };
            snake.unshift(head);

            if (head.x === food.x && head.y === food.y) {
                generateFood();
                score += 10;
            } else {
                snake.pop();
            }
        }

        function drawSnake() {
            snake.forEach((segment, index) => {
                const gradient = ctx.createLinearGradient(
                    segment.x * gridSize,
                    segment.y * gridSize,
                    (segment.x + 1) * gridSize,
                    (segment.y + 1) * gridSize
                );
                gradient.addColorStop(0, '#4CAF50');
                gradient.addColorStop(1, '#45a049');
                ctx.fillStyle = gradient;
                ctx.fillRect(segment.x * gridSize, segment.y * gridSize, gridSize - 2, gridSize - 2);

                if (index === 0) {
                    // Draw eyes
                    ctx.fillStyle = 'white';
                    ctx.beginPath();
                    ctx.arc(segment.x * gridSize + 5, segment.y * gridSize + 5, 2, 0, 2 * Math.PI);
                    ctx.arc(segment.x * gridSize + 10, segment.y * gridSize + 5, 2, 0, 2 * Math.PI);
                    ctx.fill();
                }
            });
        }

        function drawFood() {
            const gradient = ctx.createRadialGradient(
                food.x * gridSize + gridSize / 2,
                food.y * gridSize + gridSize / 2,
                2,
                food.x * gridSize + gridSize / 2,
                food.y * gridSize + gridSize / 2,
                gridSize / 2
            );
            gradient.addColorStop(0, '#ff6b6b');
            gradient.addColorStop(1, '#ee5253');
            ctx.fillStyle = gradient;
            ctx.beginPath();
            ctx.arc(food.x * gridSize + gridSize / 2, food.y * gridSize + gridSize / 2, gridSize / 2 - 1, 0, 2 * Math.PI);
            ctx.fill();
        }

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

        function checkCollision() {
            const head = snake[0];

            if (head.x < 0 || head.x >= tileCount || head.y < 0 || head.y >= tileCount) {
                gameOver();
            }

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

        function gameOver() {
            gameRunning = false;
            startBtn.textContent = '重新开始';
            startBtn.style.display = 'inline-block';
            alert(`游戏结束!你的得分是: ${score}`);
        }

        function updateScore() {
            scoreElement.textContent = `得分: ${score}`;
        }

        function resetGame() {
            snake = [{ x: 10, y: 10 }];
            food = { x: 15, y: 15 };
            dx = 0;
            dy = 0;
            score = 0;
            updateScore();
        }

        document.addEventListener('keydown', changeDirection);

        function changeDirection(event) {
            const LEFT_KEY = 37;
            const RIGHT_KEY = 39;
            const UP_KEY = 38;
            const DOWN_KEY = 40;

            const keyPressed = event.keyCode;

            const goingUp = dy === -1;
            const goingDown = dy === 1;
            const goingRight = dx === 1;
            const goingLeft = 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 changeDirectionByButton(direction) {
            if (direction === 'left' && dx !== 1) {
                dx = -1;
                dy = 0;
            } else if (direction === 'up' && dy !== 1) {
                dx = 0;
                dy = -1;
            } else if (direction === 'down' && dy !== -1) {
                dx = 0;
                dy = 1;
            } else if (direction === 'right' && dx !== -1) {
                dx = 1;
                dy = 0;
            }
        }

        startBtn.addEventListener('click', () => {
            resetGame();
            gameRunning = true;
            startBtn.style.display = 'none';
            drawGame();
        });

        clearCanvas();
    </script>
</body>

</html>

 运行结果

测试与优化

  1. 性能测试:确保前端与后端之间的通信流畅,避免因延迟导致的游戏卡顿。
  2. 用户体验测试:通过用户反馈不断优化界面和动画效果。

测试部分

在测试过程中,我们主要关注以下几个方面:

  1. 游戏是否能正常响应方向键输入。
  2. 食物生成位置是否随机且合理。
  3. 蛇的移动是否符合预期(如增长后的转向)。
  4. 前后端数据通信是否稳定。

结论与展望

本次贪吃蛇小游戏开发成功实现了贪吃蛇的经典玩法,并通过前端和后端技术结合,确保了游戏的流畅性和稳定性。未来可以进一步优化以下几个方面:

  1. 加入难度控制(如加速模式)。
  2. 支持多种语言的响应式设计。
  3. 增加游戏的多样性(如不同类型的吃物)。