Enduro 克隆游戏 — 基于 HTML、CSS 与 JavaScript 的完整教程模板
Enduro 克隆游戏 — 基于 HTML、CSS 与 JavaScript 的完整教程模板
目录
- 项目概述
- 主要特性
- 技术栈与需求
- 环境准备
- 获取并运行项目
- 项目结构示例
- 示例代码片段
index.html
示例style.css
示例game.js
示例
- 操作与控制
- 常见问题与调试提示
- 扩展与改进建议
- 截图说明示例
- 许可证与来源说明
项目概述
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 应用,无需复杂的后端或数据库配置。
- 文本编辑器:请确保您已安装并熟悉使用一款文本编辑器,例如 Visual Studio Code、Sublime Text 或 Notepad++。
- 现代浏览器:准备一个您常用的现代浏览器,用于打开和运行游戏。
获取并运行项目
- 下载源代码:通常,项目源代码会以
.zip
压缩包的形式提供。请从可靠来源(如 SourceCodester 网站)下载本游戏的源代码。 - 解压文件:将下载的
.zip
文件解压到您选择的任意目录中。解压后,您会得到一个包含游戏所有文件和文件夹的项目目录。 - 运行游戏:
- 在解压后的项目文件夹中,找到主 HTML 文件(通常命名为
index.html
)。 - 双击
index.html
文件,它将自动在您的默认浏览器中打开,游戏即可启动。 - 或者,您也可以在浏览器中选择“文件” -> “打开文件”,然后导航到
index.html
并打开。 - (可选)为了避免本地文件系统限制,您可以使用轻量级本地服务器(如 VS Code 的 Live Server 扩展或 Node.js 的
http-server
)来运行项目。
- 在解压后的项目文件夹中,找到主 HTML 文件(通常命名为
项目结构示例
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; // 用于 requestAnimationFramelet 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.html
中link
和script
标签的href
或src
属性指向的 CSS 和 JavaScript 文件路径正确(例如css/style.css
,js/game.js
)。 - 浏览器控制台:打开浏览器开发者工具(通常按
F12
),切换到“控制台 (Console)”选项卡。如果代码有错误,这里会显示详细的错误信息,帮助您定位问题。
- 文件路径:请确保
- 图片或资源未加载:
- 如果您的项目使用了图片或音频文件,请检查其在 CSS 或 JavaScript 中引用的路径是否正确,并确认文件确实存在于相应位置。
- 本地服务器:直接从文件系统(
file:///
协议)打开 HTML 文件时,浏览器可能会实施 CORS(跨域资源共享)安全策略,导致某些资源(尤其是从外部加载的)无法加载。强烈建议使用本地静态服务器来运行项目(如 VS Code 的 Live Server 扩展,或通过 Node.js 安装http-server
:npm install -g http-server
,然后在项目根目录运行http-server
)。
- 移动卡顿或不流畅:
- 游戏循环:确保游戏的主循环使用
requestAnimationFrame
而不是setInterval
。requestAnimationFrame
会与浏览器刷新率同步,提供更流畅的动画。 - DOM 操作:频繁的 DOM 操作会影响性能。尽量减少在游戏循环中直接操作 DOM 的次数。例如,可以通过改变元素的
transform
属性来移动,而不是直接改变top
或left
。 - 性能分析:在浏览器开发者工具中,使用“性能 (Performance)”选项卡录制一段游戏运行过程,可以帮助您找出性能瓶颈。
- 游戏循环:确保游戏的主循环使用
扩展与改进建议
这个 Enduro 克隆项目是一个优秀的起点,您可以尝试以下方式来进一步探索和提升:
- 视觉增强:
- 滚动道路背景:实现背景图片或 CSS 渐变的平滑垂直滚动,模拟道路在移动。
- 白天/夜晚循环:更精细地控制背景颜色和亮度的变化,模拟时间流逝。
- 车辆贴图:替换单色车辆为真实的像素艺术贴图。
- 粒子效果:在车辆加速时添加尘土或尾气效果。
- 游戏性增强:
- 不同障碍物:引入不同大小、形状或行为模式的障碍物(例如,卡车、摩托车)。
- 道具系统:添加拾取道具(如加速、护盾、分数加成)的功能。
- 生命值/碰撞惩罚:除了减速,可以引入生命值系统,碰撞时扣除生命值,生命值为零时游戏结束。
- 更复杂的关卡设计:不仅仅是速度提升,还可以改变障碍物的生成模式或密度。
- 音效与背景音乐:为碰撞、得分、加速、游戏开始/结束等事件添加音效,并播放循环背景音乐。
- 用户体验:
- 开始菜单:添加一个更完善的开始界面,包含游戏说明、音量控制等。
- 高分榜:使用
localStorage
在本地存储玩家的高分记录。 - 键盘配置:允许玩家自定义控制按键。
- 代码优化:
- 模块化:将游戏的不同部分(如游戏逻辑、渲染、输入处理)拆分为独立的函数或模块,提高可维护性。
- 面向对象:可以尝试使用面向对象编程的思想,为玩家、障碍物等创建类。
截图说明示例
描述 | 截图示例 |
---|---|
游戏主界面:玩家在赛道起点准备开始。 | ![]() |
游戏进行中:玩家在躲避障碍物。 | ![]() |
游戏结束画面:显示最终得分与重玩选项。 | ![]() |
许可证与来源说明
- 源代码来源:本教程模板改编自 SourceCodester 网站上名为“使用 HTML、CSS 和 JavaScript 的 Enduro 克隆游戏”的项目。
- 教育用途:根据原始项目说明,此应用程序仅供教育用途。
- 许可证:请根据原始项目提供方的具体说明,遵守其源代码的使用和分发条款。