引言 📖
使用HTML、CSS和JavaScript创建一个有趣的小游戏——🐒猴子追月🌕。在这个游戏中,玩家需要操纵一只可拖动的猴子,躲避随机出现的星星障碍物,并尝试追到移动的月亮以获取分数。游戏的目标是获得尽可能高的分数,最高分为50分。
(😎我才不会告诉你,为了录gif,我把分数调到了22分,嘻嘻嘻~😀)
技术要点✴️
- CSS 动画: 游戏中的星星的闪烁效果是通过CSS动画实现的,这增加了游戏的视觉吸引力。
- 碰撞检测: 碰撞检测是游戏开发中一个常见但关键的部分。在这个游戏中,我们使用了矩形碰撞
getBoundingClientRect检测来判断猴子是否成功追到月亮。 - 动态元素生成: 障碍物和星星是动态生成的,它们的位置和移动是通过JavaScript代码控制的。这种动态生成元素的技术在游戏开发中很常见。
- 游戏难度递增: 游戏的难度逐渐增加,随着得分的提高,障碍物移动速度也会增加,这使得游戏更加具有挑战性。
核心功能点✔️
- 创建星空背景:我们使用
starBg函数创建了一个闪烁的星空背景,这个背景是通过添加多个星星元素并使用CSS动画来实现的。 - 障碍物生成:通过
createObstacle函数,我们在游戏容器中创建了障碍物元素。这些障碍物从顶部开始向底部移动,星星形状。 - 得分计算:我们使用
updateScore函数来计算得分,并且根据得分来调整游戏的难度。当得分达到一定阈值时,游戏难度会增加。 - 碰撞检测:游戏中最重要的部分之一是碰撞检测。我们使用
checkCollision函数来检测猴子是否成功追到月亮,如果是,就会增加得分。 - 游戏结束和重置:当游戏结束时,我们显示相应的游戏结束或成功弹窗,并提供重新开始游戏的选项。这是通过
endGame和resetGame函数实现的。 - 猴子的拖动:玩家可以拖动猴子来追月亮,这是通过在猴子元素上添加鼠标事件监听器来实现的。
- 游戏开始:最后,我们启动游戏的核心逻辑,包括开始障碍物生成、启用碰撞检测等功能。这是通过
startGame函数实现的。
具体实现👇
创建游戏界面 🎮
开始猴子追月之旅,首先需要建立一个吸引人的游戏界面。游戏界面的元素包括星空背景、可拖动的猴子、移动的月亮以及用于显示分数和游戏状态的区域。以下是关键部分的HTML代码:
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 省略部分代码 -->
</head>
<body>
<!-- 游戏容器 -->
<div id="game-container" class="star-group">
<!-- 添加开始游戏按钮 -->
<button id="start-button">开始游戏</button>
<!-- 月亮和圆坑 -->
<div class="moon" id="moon">
<!-- 省略部分代码 -->
</div>
<!-- 猴子和得分 -->
<div id="monkey"></div>
<div id="score">得分: <span id="current-score">0</span></div>
</div>
<!-- 游戏成功弹窗 -->
<div class="overlay" id="game-success-modal">
<!-- 省略部分代码 -->
</div>
<!-- 游戏结束弹窗 -->
<div class="overlay" id="game-over-modal">
<!-- 省略部分代码 -->
</div>
<!-- JavaScript 代码部分 -->
<script>
// JavaScript 代码将在后面详细介绍
</script>
</body>
</html>
以上HTML代码包括游戏容器、开始按钮、月亮、猴子、得分显示以及游戏结束和成功的弹窗。
功能实现 🛠️
在创建猴子追月游戏时,有几个关键的技术要点需要实现,包括月亮的随机移动、猴子的拖动、猴子追到月亮的互动以及障碍物的生成和移动。以下是这些技术要点的详细说明:
1. 月亮的随机移动🌕
月亮的随机移动使游戏更具挑战性和趣味性。实现月亮的随机移动可按以下步骤进行:
- 首先,在CSS中为月亮元素定义样式,包括其初始位置。
.moon {
position: absolute; /* 初始位置 */
top: 50%;
left: 50%;
/* 省略其他样式 */
}
- 接下来,使用JavaScript监听游戏循环,例如
requestAnimationFrame,以在每一帧更新月亮的位置。
function moveMoonRandomly() {
const moon = document.getElementById("moon");
const randomTop = Math.random() * 80; // 随机生成top位置
const randomLeft = Math.random() * 80; // 随机生成left位置
// 更新月亮的位置
moon.style.top = `${randomTop}%`;
moon.style.left = `${randomLeft}%`;
// 在下一帧继续移动
requestAnimationFrame(moveMoonRandomly);
}
// 启动月亮的随机移动
moveMoonRandomly();
这段代码中,我们使用Math.random()函数生成随机的top和left位置,然后将其应用到月亮元素上,从而实现月亮的随机移动。requestAnimationFrame确保了持续的动画效果。
2. 猴子的拖动 🐒
猴子的拖动是玩家与游戏互动的核心功能。要实现猴子的拖动,我们使用了鼠标事件,包括 mousedown、mousemove 和 mouseup 事件。
let isDragging = false; // 声明一个变量isDragging,用于表示当前是否正在拖动猴子
monkey.addEventListener("mousedown", () => { // 给猴子元素添加mousedown事件监听器,当鼠标按下时触发
if (isMonkeyDraggable) { // 检查猴子是否可以拖动(游戏中的标志位)
isDragging = true; // 如果可以拖动,将isDragging设置为true,表示正在拖动
}
});
window.addEventListener("mousemove", (e) => {// 给整个窗口添加mousemove事件监听器,用于监测鼠标移动
if (isDragging) {// 如果isDragging为true,表示正在拖动
// 根据鼠标位置计算猴子的新位置,使其跟随鼠标移动
monkey.style.left = e.clientX - monkey.clientWidth / 2 + "px";
monkey.style.top = e.clientY - monkey.clientHeight / 2 + "px";
}
});
window.addEventListener("mouseup", () => {// 给整个窗口添加mouseup事件监听器,当鼠标释放时触发
isDragging = false;// 将isDragging重新设置为false,表示停止拖动
});
当玩家点击猴子并拖动鼠标时,mousedown事件触发,将isDragging标志设置为true,允许猴子跟随鼠标移动。mousemove事件用于实时更新猴子的位置,而mouseup事件表示停止拖动。
3. 猴子追到月亮 🌕🐒
在游戏中,猴子的任务是追上不断移动的月亮以获得分数。这一目标的实现主要依赖于检测猴子和月亮之间的碰撞。主要使用 getBoundingClientRect方法。
getBoundingClientRect是用于获取网页元素位置和尺寸信息的方法。它返回一个包含元素位置、大小等属性的对象,这些信息可以用于进行元素的精确定位和碰撞检测等操作。
function checkCollision() {
// 获取猴子和月亮的矩形区域信息
const monkeyRect = monkey.getBoundingClientRect(); // 获取猴子的位置和尺寸信息
const moonRect = moon.getBoundingClientRect(); // 获取月亮的位置和尺寸信息
// 检查是否发生碰撞
if (
monkeyRect.left < moonRect.right && // 如果猴子的左边界小于月亮的右边界
monkeyRect.right > moonRect.left && // 且猴子的右边界大于月亮的左边界
monkeyRect.top < moonRect.bottom && // 且猴子的上边界小于月亮的下边界
monkeyRect.bottom > moonRect.top // 且猴子的下边界大于月亮的上边界
) {
// 处理猴子追到月亮的逻辑
updateScore(); // 调用updateScore函数来增加玩家的得分
// 随机更新月亮的位置,让月亮出现在新的随机位置
moon.style.top = `${Math.random() * 80}%`; // 随机设置月亮的上下位置
moon.style.left = `${Math.random() * 80}%`; // 随机设置月亮的左右位置
}
if (!gameIsOver) {
// 继续监听碰撞,通过requestAnimationFrame来实现持续的碰撞检测
requestAnimationFrame(checkCollision);
}
}
以上代码使用 getBoundingClientRect 方法获取猴子和月亮的边界框信息,然后检查它们是否相交。如果相交,触发追月的逻辑,同时更新玩家的分数。这个技术实现了游戏中关键的互动动态,使玩家可以通过控制猴子不断获得分数,增加游戏的乐趣。
4. 障碍物的生成和移动 🚧
游戏的难度可以通过生成和移动障碍物来增加。以下是实现障碍物的生成和移动的技术要点:
- 定义障碍物样式:首先,在CSS中定义了障碍物的样式,这决定了它们在游戏中的外观。在这个例子中,五角星形状的障碍物使用了线性渐变的背景颜色和多边形剪裁路径,以创建有趣的外观。
/* 设置障碍物样式 */
.obstacle {
width: 60px; /* 设置障碍物的宽度 */
height: 60px; /* 设置障碍物的高度 */
background: linear-gradient(#febd4b, #fed84a); /* 使用线性渐变背景颜色,创建障碍物的外观 */
clip-path: polygon( 50% 0, 65% 40%, 100% 40%, 72% 60%, 85% 100%, 50% 75%, 15% 100%, 28% 60%, 0 40%, 35% 40% ); /* 使用多边形剪裁路径创建障碍物的形状 */
position: absolute;
}
- 创建和移动障碍物:为了在游戏中生成和移动障碍物,我们执行以下步骤:
- 生成障碍物:使用名为
createObstacle的JavaScript函数,创建并添加障碍物元素到游戏容器。 - 随机水平位置:随机设置障碍物的水平位置,增加游戏的难度和变化性。
- 初始化垂直位置:将障碍物的垂直位置初始化为游戏容器的顶部,使它们从顶部向下移动。
- 碰撞检测和边界处理:在
createObstacle函数中进行碰撞检测,确保玩家与障碍物碰撞时有适当的响应。同时,处理障碍物是否越界游戏容器的情况,以保持游戏流畅。 - 使用定时器更新位置:使用
setInterval定时器模拟障碍物向下移动,以便它们逐帧向下滚动。在每一帧中,更新障碍物的位置,并执行碰撞检测和边界检测。
- 生成障碍物:使用名为
function createObstacle() {
// 创建一个障碍物元素
const obstacle = document.createElement("div");
obstacle.className = "obstacle";
// 随机设置障碍物的水平位置
const randomX = Math.random() * (gameContainer.clientWidth - 60);
obstacle.style.left = randomX + "px";
// 将障碍物初始位置设置在游戏容器的顶部
obstacle.style.top = "0";
// 将障碍物添加到游戏容器中
gameContainer.appendChild(obstacle);
// 创建定时器,用于移动障碍物
const moveInterval = setInterval(() => {
if (!gameIsOver) {
// 计算新的障碍物位置(向下移动)
const top = obstacle.offsetTop + gameSpeed;
// 更新障碍物的垂直位置
obstacle.style.top = top + "px";
// 在这里进行碰撞检测和边界检测
if (/* 碰撞检测 */ || /* 判断障碍物是否超出游戏容器 */) {
// 处理碰撞或障碍物超出边界的逻辑
// 停止移动障碍物,清除定时器
clearInterval(moveInterval);
}
}
}, 10);
}
// 启动障碍物生成,定期调用createObstacle函数来创建障碍物
obstacleInterval = setInterval(createObstacle, 1500);
5. 游戏计分与难度升级 🌟🚀
在这个游戏中,玩家需要不断地追逐月亮,并获得分数。这一功能由 updateScore() 函数来实现,它还负责监测游戏成功的条件和提高游戏难度。
updateScore()函数会检查游戏是否结束,以防止继续得分。- 每次猴子追到月亮,得分会增加,同时更新界面上的得分显示。
- 当得分达到50分时,游戏被标记为成功,然后调用
endGame(true)来结束游戏。
function updateScore() {
if (!gameIsOver) {
score++; // 增加得分
currentScoreElement.textContent = score; // 更新得分显示
modalScoreElement.textContent = score; // 更新弹窗中的得分
if (score >=50) {
// 当得分达到50分时,表示游戏成功
endGame(true);
} else {
if (score >= 10) {
// 当得分达到10分时,提高游戏速度,增加难度
gameSpeed = 7;
}
if (score >= 20) {
// 当得分达到20分时,进一步增加难度
gameSpeed = 10;
}
}
}
}
这种得分机制和游戏难度逐渐升级的设计增加了游戏的挑战性和吸引力。
6. 游戏结束与重新开始 🏁🔄
游戏结束时,玩家可以选择重新开始游戏。这个功能由 endGame() 函数和 resetGame() 函数来实现。
endGame(isSuccess)函数根据参数isSuccess来判断游戏是成功结束还是失败结束,然后展示相应的弹窗。- 当玩家点击重新开始按钮(游戏失败或游戏成功弹窗上的按钮)时,将调用
resetGame()函数来重置游戏状态,包括得分、游戏状态、游戏速度,并清除之前的障碍物。
- 最后,调用
startGame()函数重新开始游戏,为玩家提供新的挑战。
function endGame(isSuccess = false) {
gameIsOver = true; // 标记游戏结束
clearInterval(obstacleInterval); // 停止创建障碍物的定时器
if (isSuccess) {
gameSuccessModal.style.display = "flex"; // 显示游戏成功弹窗
} else {
gameOverModal.style.display = "flex"; // 显示游戏失败弹窗
}
}
// 点击重新开始按钮(游戏失败弹窗)
restartButton.addEventListener("click", () => {
resetGame(); // 重置游戏
gameOverModal.style.display = "none"; // 隐藏游戏失败弹窗
});
// 点击重新开始按钮(游戏成功弹窗)
restartButtonSuccess.addEventListener("click", () => {
resetGame(); // 重置游戏
gameSuccessModal.style.display = "none"; // 隐藏游戏成功弹窗
});
// 重置游戏
function resetGame() {
score = 0; // 重置得分
currentScoreElement.textContent = score; // 更新得分显示
modalScoreElement.textContent = score; // 更新弹窗中的得分
gameIsOver = false; // 游戏状态重置为未结束
gameSpeed = 5; // 重置游戏速度
// 清除障碍物
const obstacles = document.querySelectorAll(".obstacle");
obstacles.forEach((obstacle) => {
gameContainer.removeChild(obstacle);
});
startGame(); // 重新开始游戏
}
代码链接 🔗
结语 📜
在猴子追逐明亮的中秋圆月时,愿你的生活充满了冒险和乐趣,不断追逐梦想的光辉。愿你在这个美丽的中秋佳节里,像猴子一样充满活力,勇往直前,追求自己的幸福和成功。 中秋快乐!🐒🌕🏮