Enduro 克隆游戏 — 基于 HTML、CSS 与 JavaScript 的完整教程模板

目录


项目概述

Enduro 克隆游戏 是对经典街机赛车 Enduro 的前端复刻。游戏以纯 HTML、CSS 与原生 JavaScript 实现,模拟车辆在无尽道路上的行驶、超车与躲避障碍的体验。该项目既可作为教学示例,也便于扩展加入更多功能。


主要特性

  • 纯前端实现:仅使用 HTML、CSS 和 JavaScript,无需后端或数据库。
  • 复古 UI:模仿经典 Enduro 外观,屏幕上实时显示分数和进度。
  • 响应式布局:可适应不同屏幕尺寸。
  • 核心游戏玩法
    • 玩家控制一辆赛车在无尽赛道上行驶。
    • 使用键盘进行平滑的左右移动,并可控制速度(加速/减速)。
    • 随着游戏进程,速度和难度不断增加。
    • 模拟原版 Enduro 的昼夜过渡效果。
  • 游戏机制
    • 超越一定数量赛车可晋级下一阶段。
    • 碰撞系统:与障碍物(其他车辆)碰撞会导致速度减慢。
    • 跟踪距离和时间。
    • 交通密度逐渐增加,提升挑战性。
  • 视觉和音频增强
    • 像素艺术风格图形,营造怀旧感。
    • 模拟道路运动的动画背景。
    • 流畅的汽车运动和碰撞动画。
    • 引人入胜的引擎、碰撞和过渡音效(若有实现)。
  • 教育用途:该项目是练习和巩固基本编程概念,提升 JavaScript 游戏开发技能的宝贵资源。

技术栈与需求

  • 核心语言:JavaScript
  • 前端技术:HTML (用于结构), CSS (用于样式)
  • 编码工具:Notepad++ 或任何可以运行 HTML 文件的文本编辑器(如 Visual Studio Code, Sublime Text 等)。
  • 运行环境:任何现代 Web 浏览器(如 Chrome, Firefox, Edge, Safari)。
  • 数据库:无。
  • 项目类型:Web 应用程序。

环境准备

本游戏作为纯前端 Web 应用,无需复杂的后端或数据库配置。

  1. 文本编辑器:请确保您已安装并熟悉使用一款文本编辑器,例如 Visual Studio Code、Sublime Text 或 Notepad++。
  2. 现代浏览器:准备一个您常用的现代浏览器,用于打开和运行游戏。

获取并运行项目

  1. 下载源代码:通常,项目源代码会以 .zip 压缩包的形式提供。请从可靠来源(如 SourceCodester 网站)下载本游戏的源代码。
  2. 解压文件:将下载的 .zip 文件解压到您选择的任意目录中。解压后,您会得到一个包含游戏所有文件和文件夹的项目目录。
  3. 运行游戏
    • 在解压后的项目文件夹中,找到主 HTML 文件(通常命名为 index.html)。
    • 双击 index.html 文件,它将自动在您的默认浏览器中打开,游戏即可启动。
    • 或者,您也可以在浏览器中选择“文件” -> “打开文件”,然后导航到 index.html 并打开。
    • (可选)为了避免本地文件系统限制,您可以使用轻量级本地服务器(如 VS Code 的 Live Server 扩展或 Node.js 的 http-server)来运行项目。

项目结构示例

enduro-clone/
├─ index.html          # 游戏主页面
├─ css/                # 存放 CSS 样式文件
│  └─ style.css       # 游戏样式表
├─ js/                 # 存放 JavaScript 文件
│  └─ game.js         # 游戏核心逻辑
├─ assets/             # 存放游戏资源,如图片和音效(可选)
│  ├─ images/
│  │  ├─ car.png
│  │  └─ obstacle.png
│  └─ audio/
│     ├─ engine.mp3
│     └─ crash.mp3
└─ README.md           # 项目说明文件 (当前正在阅读的)

示例代码片段

以下代码为可直接复制到项目中的基础实现示例。这些片段旨在展示核心结构,实际项目可能包含更多细节和优化。

index.html 示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Enduro 克隆游戏</title>
  <link rel="stylesheet" href="css/style.css" />
</head>
<body>
  <div id="game-root">
    <header id="hud">
      <div id="score">分数: <span id="score-value">0</span></div>
      <div id="stage">关卡: <span id="stage-value">1</span></div>
    </header>

    <main id="game-area">
      <!-- 游戏元素(由 JS 动态插入) -->
      <div id="player" class="car"></div>
    </main>

    <div id="overlay" class="hidden">
      <div id="game-over">
        <h1>游戏结束</h1>
        <p>最终得分:<span id="final-score">0</span></p>
        <button id="btn-restart">重新开始</button>
      </div>
    </div>
  </div>

  <script src="js/game.js"></script>
</body>
</html>

style.css 示例

/* css/style.css */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  background: #222; /* 主背景色 */
  color: #fff;
  font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden; /* 防止滚动条 */
}

#game-root {
  width: 800px;
  max-width: 95vw; /* 适应小屏幕 */
  border: 2px solid #fff;
  box-shadow: 0 0 15px rgba(0,0,0,0.5);
  background-color: #333;
}

#hud {
  display: flex;
  justify-content: space-between;
  padding: 10px;
  font-size: 1.2em;
  background-color: #444;
  border-bottom: 1px solid #666;
}
#score, #stage {
  padding: 5px 10px;
  background-color: #555;
  border-radius: 4px;
}

#game-area {
  position: relative;
  width: 100%; /* 继承父元素宽度 */
  height: 600px; /* 游戏区域固定高度 */
  background: linear-gradient(#444, #222); /* 简单道路背景 */
  overflow: hidden; /* 隐藏超出边界的元素 */
}

/* 玩家车辆 */
.car {
  position: absolute;
  width: 50px; /* 车辆宽度 */
  height: 90px; /* 车辆高度 */
  background: red; /* 车辆颜色 */
  bottom: 30px; /* 距离底部 */
  left: calc(50% - 25px); /* 水平居中 */
  border-radius: 6px;
  transition: left 0.1s linear; /* 平滑左右移动 */
  box-shadow: 0 0 10px rgba(255,255,255,0.3);
}

/* 障碍物(其他车辆) */
.obstacle {
  position: absolute;
  width: 48px;
  height: 48px;
  background: gray; /* 障碍物颜色 */
  border-radius: 4px;
  box-shadow: 0 0 8px rgba(0,0,0,0.5);
}

/* 覆盖层(游戏结束) */
#overlay {
  position: absolute;
  inset: 0; /* 覆盖整个游戏区域 */
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0,0,0,0.7); /* 半透明黑色背景 */
  z-index: 100; /* 确保在最上层 */
}
#overlay.hidden { display: none; } /* 默认隐藏 */

#game-over {
  background: #2a2a2a;
  padding: 30px 40px;
  border-radius: 10px;
  text-align: center;
  box-shadow: 0 0 20px rgba(0,255,255,0.5);
  transform: scale(0.9);
  animation: popIn 0.3s ease-out forwards;
}

@keyframes popIn {
  from { transform: scale(0.7); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

#game-over h1 {
  margin-bottom: 15px;
  color: #ffcc00;
  font-size: 2.5em;
  text-shadow: 2px 2px 4px rgba(0,0,0,0.7);
}
#game-over p {
  margin-bottom: 25px;
  font-size: 1.5em;
}
#game-over #final-score {
  font-weight: bold;
  color: #00ff00;
}
#btn-restart {
  padding: 12px 25px;
  font-size: 1.2em;
  cursor: pointer;
  border: none;
  border-radius: 5px;
  background: #007bff;
  color: #fff;
  transition: background 0.3s ease, transform 0.1s ease;
  box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
#btn-restart:hover {
  background: #0056b3;
  transform: translateY(-2px);
}
#btn-restart:active {
  transform: translateY(0);
  box-shadow: 0 2px 3px rgba(0,0,0,0.3);
}

game.js 示例

// js/game.js
document.addEventListener('DOMContentLoaded', () => {
  // --- DOM 元素获取 ---
  const player = document.getElementById('player');
  const gameArea = document.getElementById('game-area');
  const scoreValue = document.getElementById('score-value');
  const stageValue = document.getElementById('stage-value'); // 关卡显示
  const overlay = document.getElementById('overlay');
  const finalScore = document.getElementById('final-score');
  const restartBtn = document.getElementById('btn-restart');

  // --- 游戏状态变量 ---
  let score = 0;
  let currentStage = 1;
  let gameRunning = false; // 游戏是否正在进行
  let animationFrameId; // 用于 requestAnimationFrame
  let playerX = gameArea.clientWidth / 2 - player.clientWidth / 2; // 玩家X坐标
  const playerSpeed = 8; // 玩家左右移动速度
  let gameSpeed = 5; // 游戏整体下落速度 (像素/帧)
  const obstacleGenerationInterval = 60; // 障碍物生成间隔帧数
  let frameCount = 0; // 帧计数器

  // --- 初始化游戏 ---
  function initGame() {
    // 重置状态
    score = 0;
    currentStage = 1;
    gameSpeed = 5;
    gameRunning = true;
    
    scoreValue.textContent = score;
    stageValue.textContent = currentStage;
    overlay.classList.add('hidden'); // 隐藏游戏结束界面
    
    // 重置玩家位置
    playerX = gameArea.clientWidth / 2 - player.clientWidth / 2;
    player.style.left = `${playerX}px`;
    player.style.bottom = '30px'; // 确保在底部

    // 清除所有现有障碍物
    document.querySelectorAll('.obstacle').forEach(obs => obs.remove());
    
    // 启动游戏循环
    if (animationFrameId) cancelAnimationFrame(animationFrameId); // 取消之前的循环
    gameLoop();
  }

  // --- 游戏主循环 ---
  function gameLoop() {
    if (!gameRunning) return;

    frameCount++;

    // 障碍物生成逻辑
    if (frameCount % obstacleGenerationInterval === 0) {
      createObstacle();
    }
    
    // 移动障碍物并进行碰撞检测
    document.querySelectorAll('.obstacle').forEach(obstacle => {
      let currentTop = parseFloat(obstacle.style.top);
      obstacle.style.top = `${currentTop + gameSpeed}px`;

      // 障碍物超出底部,移除并加分
      if (currentTop > gameArea.clientHeight) {
        obstacle.remove();
        score++;
        scoreValue.textContent = score;
        
        // 简单关卡晋升逻辑 (例如每得20分升一级)
        if (score % 20 === 0 && score !== 0) {
          currentStage++;
          stageValue.textContent = currentStage;
          gameSpeed += 0.5; // 增加游戏速度,提升难度
        }

      } else {
        // 碰撞检测
        if (isColliding(player, obstacle)) {
          gameOver();
        }
      }
    });

    animationFrameId = requestAnimationFrame(gameLoop);
  }

  // --- 创建障碍物 ---
  function createObstacle() {
    const obstacle = document.createElement('div');
    obstacle.className = 'obstacle';
    obstacle.style.top = '-50px'; // 从顶部之外开始生成
    
    // 随机左右位置,确保不会超出边界
    const randomX = Math.random() * (gameArea.clientWidth - 48); // 48为障碍物宽度
    obstacle.style.left = `${randomX}px`;
    
    gameArea.appendChild(obstacle);
  }

  // --- 碰撞检测函数 ---
  function isColliding(element1, element2) {
    const rect1 = element1.getBoundingClientRect();
    const rect2 = element2.getBoundingClientRect();

    // 检查是否有任何重叠
    return !(
      rect1.bottom < rect2.top ||
      rect1.top > rect2.bottom ||
      rect1.right < rect2.left ||
      rect1.left > rect2.right
    );
  }

  // --- 游戏结束逻辑 ---
  function gameOver() {
    gameRunning = false;
    cancelAnimationFrame(animationFrameId); // 停止动画循环
    finalScore.textContent = score;
    overlay.classList.remove('hidden'); // 显示游戏结束界面
  }

  // --- 事件监听器 ---
  document.addEventListener('keydown', (e) => {
    if (!gameRunning) return; // 游戏未进行时,不响应按键

    // 玩家左右移动
    if (e.key === 'ArrowLeft') {
      playerX -= playerSpeed;
    } else if (e.key === 'ArrowRight') {
      playerX += playerSpeed;
    }

    // 限制玩家在游戏区域内
    playerX = Math.max(0, Math.min(playerX, gameArea.clientWidth - player.clientWidth));
    player.style.left = `${playerX}px`;
  });

  restartBtn.addEventListener('click', initGame);

  // --- 首次加载时启动游戏 ---
  initGame();
});

操作与控制

  • 向左移动:按下键盘上的 左箭头 (←) 键。
  • 向右移动:按下键盘上的 右箭头 (→) 键。
  • 加速(若实现):原始描述中提到“上箭头键加速”,但在提供的示例代码中未直接实现加速功能,而是通过得分增加 gameSpeed 来提升难度。您可以在 game.js 中添加对 ArrowUp 键的监听来控制玩家车辆的垂直移动速度或游戏全局速度。
  • 减速(若实现):原始描述中提到“下箭头键减速”,同样未在示例代码中直接实现。您可以在 game.js 中添加对 ArrowDown 键的监听。
  • 重新开始:游戏结束后,点击屏幕中央的“重新开始”按钮。

常见问题与调试提示

  • 游戏无法运行
    • 文件路径:请确保 index.htmllinkscript 标签的 hrefsrc 属性指向的 CSS 和 JavaScript 文件路径正确(例如 css/style.css, js/game.js)。
    • 浏览器控制台:打开浏览器开发者工具(通常按 F12),切换到“控制台 (Console)”选项卡。如果代码有错误,这里会显示详细的错误信息,帮助您定位问题。
  • 图片或资源未加载
    • 如果您的项目使用了图片或音频文件,请检查其在 CSS 或 JavaScript 中引用的路径是否正确,并确认文件确实存在于相应位置。
    • 本地服务器:直接从文件系统(file:/// 协议)打开 HTML 文件时,浏览器可能会实施 CORS(跨域资源共享)安全策略,导致某些资源(尤其是从外部加载的)无法加载。强烈建议使用本地静态服务器来运行项目(如 VS Code 的 Live Server 扩展,或通过 Node.js 安装 http-servernpm install -g http-server,然后在项目根目录运行 http-server)。
  • 移动卡顿或不流畅
    • 游戏循环:确保游戏的主循环使用 requestAnimationFrame 而不是 setIntervalrequestAnimationFrame 会与浏览器刷新率同步,提供更流畅的动画。
    • DOM 操作:频繁的 DOM 操作会影响性能。尽量减少在游戏循环中直接操作 DOM 的次数。例如,可以通过改变元素的 transform 属性来移动,而不是直接改变 topleft
    • 性能分析:在浏览器开发者工具中,使用“性能 (Performance)”选项卡录制一段游戏运行过程,可以帮助您找出性能瓶颈。

扩展与改进建议

这个 Enduro 克隆项目是一个优秀的起点,您可以尝试以下方式来进一步探索和提升:

  • 视觉增强
    • 滚动道路背景:实现背景图片或 CSS 渐变的平滑垂直滚动,模拟道路在移动。
    • 白天/夜晚循环:更精细地控制背景颜色和亮度的变化,模拟时间流逝。
    • 车辆贴图:替换单色车辆为真实的像素艺术贴图。
    • 粒子效果:在车辆加速时添加尘土或尾气效果。
  • 游戏性增强
    • 不同障碍物:引入不同大小、形状或行为模式的障碍物(例如,卡车、摩托车)。
    • 道具系统:添加拾取道具(如加速、护盾、分数加成)的功能。
    • 生命值/碰撞惩罚:除了减速,可以引入生命值系统,碰撞时扣除生命值,生命值为零时游戏结束。
    • 更复杂的关卡设计:不仅仅是速度提升,还可以改变障碍物的生成模式或密度。
    • 音效与背景音乐:为碰撞、得分、加速、游戏开始/结束等事件添加音效,并播放循环背景音乐。
  • 用户体验
    • 开始菜单:添加一个更完善的开始界面,包含游戏说明、音量控制等。
    • 高分榜:使用 localStorage 在本地存储玩家的高分记录。
    • 键盘配置:允许玩家自定义控制按键。
  • 代码优化
    • 模块化:将游戏的不同部分(如游戏逻辑、渲染、输入处理)拆分为独立的函数或模块,提高可维护性。
    • 面向对象:可以尝试使用面向对象编程的思想,为玩家、障碍物等创建类。

截图说明示例

描述 截图示例
游戏主界面:玩家在赛道起点准备开始。 游戏主界面
游戏进行中:玩家在躲避障碍物。 游戏过程截图
游戏结束画面:显示最终得分与重玩选项。 游戏结束界面截图

许可证与来源说明

  • 源代码来源:本教程模板改编自 SourceCodester 网站上名为“使用 HTML、CSS 和 JavaScript 的 Enduro 克隆游戏”的项目。
  • 教育用途:根据原始项目说明,此应用程序仅供教育用途
  • 许可证:请根据原始项目提供方的具体说明,遵守其源代码的使用和分发条款。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐