<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>恐龙快跑</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<h1>恐龙快跑</h1>
<div id="instruction">按空格键开始游戏和跳跃</div>
<div id="controls">
<button id="startBtn">开始游戏</button>
<button id="pauseBtn" disabled>暂停</button>
</div>
<div id="score">得分: <span id="scoreValue">0</span></div>
<div id="highScore">最高分: <span id="highScoreValue">0</span></div>
<div id="time">时间: <span id="timeValue">0</span>秒</div>
<div id="game">
<div id="dino"></div>
</div>
<script src="index.js"></script>
</body>
</html>
/* 页面整体布局样式 */
/*
CSS 属性 font-family 允许你通过给定一个有先后顺序的
由字体名或者字体族名组成的列表来为选定的元素设置字体。
*/
body {
display: flex;
flex-direction: column;
align-items: center;
background: #023b5098 0%;
font-family: system-ui;
color: #fff;
min-height: 100vh;
margin: 0;
padding: 20px;
}
/* 标题样式 */
h1 {
font-size: 48px;
margin-bottom: 30px;
/* 文字渐变效果 */
/*inear-gradient() CSS 函数
创建一个由两种或多种颜色沿一条直线进行线性过渡的图像,
其结果是 <gradient> 数据类型的对象,
此对象是一种特殊的 <image> 数据类型
*/
background: -webkit-linear-gradient(#fff, #694343af);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* 游戏主容器样式 */
#game {
width: 800px;
height: 300px;
border-radius: 15px;
position: relative;
overflow: hidden;
/*加个阴影有边界*/
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
/* 恐龙角色样式 */
#dino {
width: 50px;
height: 50px;
/* 绿色渐变背景 */
background-image: url("dino.png");
background-size: cover;
position: absolute;
bottom: 0;
left: 50px;
border-radius: 8px;
/* 平滑跳跃动画 */
transition: bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
/* 地面障碍物样式 */
.ground-obstacle {
width: 30px;
height: 50px;
/* 红色渐变背景 */
background-image:url("tree.png");
background-size:cover;
position: absolute;
bottom: 0;
right: 0;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
/* 空中障碍物样式 */
.air-obstacle {
width: 40px;
height: 40px;
background-image:url("brid.png");
background-size:cover ;
position: absolute;
bottom: 180px;
right: 0;
border-radius: 50%;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
/* 控制按钮容器 */
#controls {
margin: 30px 0;
}
/* 按钮样式 */
#controls button {
padding: 12px 30px;
font-size: 18px;
border: none;
border-radius: 25px;
margin: 0 15px;
cursor: pointer;
/* 渐变背景 */
background: linear-gradient(45deg, #000000, #ffffff);
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
font-weight: bold;
text-transform: uppercase;
letter-spacing: 1px;
}
/* 禁用按钮样式 */
#controls button:disabled {
background: linear-gradient(45deg, #9e9e9e, #bdbdbd);
cursor: not-allowed;
box-shadow: none;
}
/* 按钮悬停效果 */
#controls button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
/* 分数和时间显示样式 */
#score, #time, #highScore {
margin: 20px 0;
font-size: 32px;
font-weight: bold;
color: #fff;
background: rgba(255,255,255,0.1);
padding: 15px 40px;
border-radius: 25px;
backdrop-filter: blur(5px);
}
/* 游戏说明文字样式 */
#instruction {
font-size: 20px;
color: rgba(255,255,255,0.9);
margin-bottom: 25px;
/* 呼吸动画效果 */
animation: pulse 2s infinite;
background: rgba(255,255,255,0.1);
padding: 12px 30px;
border-radius: 20px;
backdrop-filter: blur(5px);
}
/* 呼吸动画关键帧 */
/* animation6
*/
@keyframes pulse {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(0.98); }
100% { opacity: 1; transform: scale(1); }
}
// 获取DOM元素,全写成const
const dino = document.getElementById('dino');
const game = document.getElementById('game');
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const scoreValue = document.getElementById('scoreValue');
const highScoreValue = document.getElementById('highScoreValue');
const timeValue = document.getElementById('timeValue');
const instruction = document.getElementById('instruction');
const images = [
'url(run_left.png)',
'url(run_right.png)',
];
const righrPostion=800; //游戏区域最右侧
// 初始化游戏状态变量
let isJumping = false; // 是否正在跳跃
let gameOver = false; // 游戏是否结束
let gamePaused = true; // 游戏是否暂停
let obstacleInterval; // 生成障碍物的定时器
let timeInterval; // 计时器
let score = 0; // 当前游戏得分
let highScore = 0; // 最高分,初始为0
let gameTime = 0; // 游戏时长(秒)
let startTime = 0; // 游戏开始时间
let pauseStartTime = 0; // 暂停开始时间
let totalPausedTime = 0; // 总暂停时间
let activeObstacles = []; // 存储当前活动的障碍物及其移动定时器
let currentImageid=0; // 当前显示的小恐龙图片id
// 初始化显示最高分为0
highScoreValue.textContent = '0';
/**
* 小恐龙跑步的图片切换
*/
function changeBackground() {
document.getElementById('dino').style.backgroundImage=images[currentImageid];
currentImageid== (currentImageid+1) % images.length;
}
/**
* 跳跃功能实现
* 通过CSS transition实现平滑的跳跃动画
* 跳跃高度为180px,持续时间0.5秒
* setInterval(): 间隔指定的毫秒数不停地执行指定的代码,定时器
clearInterval(): 用于停止 setInterval() 方法执行的函数代码
定时器可以当作unity的update来使用,定时的控制游戏场景
*/
function jump() {
// 检查是否可以跳跃(未在跳跃(在地面)中且游戏进行中)
if (!isJumping && !gameOver && !gamePaused) {
isJumping = true;
// 设置恐龙跳跃高度
dino.style.bottom = '180px'; // 上升阶段
// 跳跃后落地
setTimeout(() => {
dino.style.bottom = '0px'; // 下落阶段
// 落地后重置跳跃状态
setTimeout(() => {
isJumping = false; // 重置跳跃状态,允许下次跳跃
}, 500);
}, 500);
}
}
/**
* 创建并移动障碍物
* 随机生成三种不同类型的障碍物
*/
function createObstacle() {
// 检查游戏状态
if (!gameOver && !gamePaused) {
// 创建障碍物DOM元素
const obstacle = document.createElement('div');
// 随机选择障碍物类型
const obstacleType = Math.floor(Math.random() * 2);
switch(obstacleType) {
case 0: // 地面障碍物
obstacle.classList.add('ground-obstacle');
break;
case 1: // 空中障碍物
obstacle.classList.add('air-obstacle');
break;
}
game.appendChild(obstacle);
// 初始化障碍物位置和状态
let position =righrPostion; // 初始位置(游戏区域最右侧)
let scored = false; // 是否已计分标记
let lastTime = Date.now(); // 上次更新时间
// 设置障碍物移动定时器
let moveInterval = setInterval(() => {
// 暂停检查
if (gamePaused) {
lastTime = Date.now(); // 更新上次时间
//这里同文档里一样不能够直接+1,而是用上次的时间差来计算
return;
}
const currentTime = Date.now();
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// 移出屏幕检查
if (position < -30) {
clearInterval(moveInterval);
if (game.contains(obstacle)) {
game.removeChild(obstacle);
}
// 从活动障碍物数组中移除
const index = activeObstacles.findIndex(item => item.obstacle === obstacle);
if (index !== -1) {
activeObstacles.splice(index, 1);
}
} else {
// 碰撞检测
const dinoRect = dino.getBoundingClientRect();
const obstacleRect = obstacle.getBoundingClientRect();
// 检查矩形是否重叠
if (dinoRect.right > obstacleRect.left &&
dinoRect.left < obstacleRect.right &&
dinoRect.bottom > obstacleRect.top &&
dinoRect.top < obstacleRect.bottom) {
// 发生碰撞,游戏结束
clearInterval(moveInterval);
gameOver = true;
// 游戏结束时更新最高分
if (score > highScore) {
highScore = score;
highScoreValue.textContent = highScore;
}
alert(`游戏结束!\n最终得分: ${score}\n最高分: ${highScore}\n坚持时间: ${gameTime}秒`);
resetGame();
} else if (position < 50 && !scored) {
// 通过障碍物,得分加1
scored = true;
score++;
scoreValue.textContent = score;
}
}
// 更新障碍物位置
position -= (4 * deltaTime / 20); // 根据时间差调整移动距离
obstacle.style.right = (800 - position) + 'px';
}, 20);
// 将障碍物和其移动定时器添加到活动障碍物数组
activeObstacles.push({
obstacle: obstacle,
interval: moveInterval
});
}
}
/**
* 开始游戏
* 初始化游戏状态,开始生成障碍物和计时
*/
function startGame() {
if (gamePaused) {
if (pauseStartTime) {
totalPausedTime += Date.now() - pauseStartTime;
} else {
startTime = Date.now();
totalPausedTime = 0;
score = 0;
gameTime = 0;
scoreValue.textContent = '0';
timeValue.textContent = '0';
}
}
gamePaused = false;
gameOver = false;
startBtn.disabled = true;
pauseBtn.disabled = false;
instruction.style.display = 'none';
// 开始生成障碍物
obstacleInterval = setInterval(createObstacle, 2500);
// 开始计时
timeInterval = setInterval(() => {
if (!gamePaused) {
gameTime = Math.floor((Date.now() - startTime - totalPausedTime) / 1000);
timeValue.textContent = gameTime;
}
}, 100);
}
/**
* 暂停游戏
* 暂停所有游戏进程
*/
function pauseGame() {
gamePaused = true;
pauseStartTime = Date.now();
startBtn.disabled = false;
pauseBtn.disabled = true;
clearInterval(obstacleInterval);
instruction.style.display = 'block';
}
/**
* 重置游戏
* 清除所有游戏状态和元素
*/
function resetGame() {
gamePaused = true;
startBtn.disabled = false;
pauseBtn.disabled = true;
clearInterval(obstacleInterval);
clearInterval(timeInterval);
// 清除所有活动障碍物及其定时器
activeObstacles.forEach(item => {
clearInterval(item.interval);
if (game.contains(item.obstacle)) {
game.removeChild(item.obstacle);
}
});
activeObstacles = [];
score = 0;
gameTime = 0;
startTime = 0;
pauseStartTime = 0;
totalPausedTime = 0;
scoreValue.textContent = '0';
timeValue.textContent = '0';
instruction.style.display = 'block';
}
// 添加按钮点击事件监听
startBtn.addEventListener('click', startGame);
pauseBtn.addEventListener('click', pauseGame);
//切换恐龙跑步图片
setInterval(changeBackground, 100);
// 添加键盘事件监听
document.addEventListener('keydown', (event) => {
// 空格键跳跃和开始游戏
if (event.code === 'Space') {
event.preventDefault(); // 防止页面滚动
if (gamePaused) {
startGame();
}
jump();
}
// ESC键暂停
if (event.code === 'Escape') {
if (!gamePaused) {
pauseGame();
}
}
});
/*
游戏核心逻辑说明:
1. 游戏状态管理:
- 使用多个状态变量控制游戏流程
- gamePaused: 暂停状态
- gameOver: 结束状态
- isJumping: 跳跃状态
- score: 当前游戏得分
- highScore: 最高分(初始为0,仅在游戏结束时更新)
- gameTime: 游戏时长
2. 跳跃实现:
- 使用CSS transition实现平滑动画
- 跳跃高度180px,总持续时间1秒
- 上升和下落各0.5秒
- 防止重复跳跃的状态控制
3. 障碍物系统:
- 随机生成三种不同类型的障碍物
- 地面障碍物(红色)
- 空中障碍物(蓝色)
- 定时生成(每2.5秒)
- 匀速移动(4px/20ms)
- 超出屏幕自动清除
- 碰撞检测和计分
4. 计分规则:
- 成功避开障碍物得1分
- 使用scored标记防止重复计分
- 记录游戏持续时间
- 最高分仅在游戏结束时更新,不会实时更新
- 最高分初始显示为0,不从localStorage读取
5. 用户交互:
- 空格键: 开始/跳跃
- ESC键: 暂停
- 按钮控制
- 游戏状态提示
*/
游戏核心逻辑说明:
1. 游戏状态管理:
- 使用多个状态变量控制游戏流程
- gamePaused: 暂停状态
- gameOver: 结束状态
- isJumping: 跳跃状态
- score: 当前游戏得分
- highScore: 最高分(初始为0,仅在游戏结束时更新)
- gameTime: 游戏时长
2. 跳跃实现:
- 使用CSS transition实现平滑动画
- 跳跃高度180px,总持续时间1秒
- 上升和下落各0.5秒
- 防止重复跳跃的状态控制
3. 障碍物系统:
- 随机生成三种不同类型的障碍物
- 地面障碍物(红色)
- 空中障碍物(蓝色)
- 定时生成(每2.5秒)
- 匀速移动(4px/20ms)
- 超出屏幕自动清除
- 碰撞检测和计分
4. 计分规则:
- 成功避开障碍物得1分
- 使用scored标记防止重复计分
- 记录游戏持续时间
- 最高分仅在游戏结束时更新,不会实时更新
- 最高分初始显示为0,不从localStorage读取
5. 用户交互:
- 空格键: 开始/跳跃
- ESC键: 暂停
- 按钮控制
- 游戏状态提示