本节教程我会带大家使用 HTML 、CSS和 JS 来制作一个HTML5拉杆子过关小游戏
✨ 项目基本结构
大致目录结构如下(共3个子文件):
├── js
│ └──script.js 13KB
├── css
│ └── style.css 1KB
└── index.html 2KB
场景展示
HTML源码
<div class="container">
<div id="score"></div>
<canvas id="game" width="375" height="375"></canvas>
<div id="introduction">按住鼠标伸出一根棍子</div>
<div id="perfect">双倍积分</div>
<button id="restart">重新开始</button>
</div>
CSS 源码
html,body
html,
body {
height: 100%;
margin: 0;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
cursor: pointer;
}
container
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
score
#score {
position: absolute;
top: 30px;
right: 30px;
font-size: 2em;
font-weight: 900;
}
introduction
#introduction {
width: 200px;
height: 150px;
position: absolute;
font-weight: 600;
font-size: 0.8em;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
text-align: center;
transition: opacity 2s;
}
restart
#restart {
width: 120px;
height: 120px;
position: absolute;
border-radius: 50%;
color: white;
background-color: red;
border: none;
font-weight: 700;
font-size: 1.2em;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
display: none;
cursor: pointer;
}
perfect
#perfect {
position: absolute;
opacity: 0;
transition: opacity 2s;
}
JS 源码
js 代码较多,这里提供部分,完整源码可以在文末下载
配置
const canvasWidth = 375;
const canvasHeight = 375;
const platformHeight = 100;
const heroDistanceFromEdge = 10; // 等待时
const paddingX = 100; // 从原始画布尺寸开始,英雄的等待位置
const perfectAreaSize = 10;
重置游戏变量和布局,但不启动游戏(游戏在按键时开始)
function resetGame() {
// 重置游戏进度
phase = "waiting";
lastTimestamp = undefined;
sceneOffset = 0;
score = 0;
introductionElement.style.opacity = 1;
perfectElement.style.opacity = 0;
restartButton.style.display = "none";
scoreElement.innerText = score;
// 第一个平台总是相同的x+w必须匹配paddingX
platforms = [{ x: 50, w: 50 }];
generatePlatform();
generatePlatform();
generatePlatform();
generatePlatform();
sticks = [{ x: platforms[0].x + platforms[0].w, length: 0, rotation: 0 }];
trees = [];
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
heroX = platforms[0].x + platforms[0].w - heroDistanceFromEdge;
heroY = 0;
draw();
}
最远树右边缘的X坐标
const lastTree = trees[trees.length - 1];
let furthestX = lastTree ? lastTree.x : 0;
如果按下空格键,则重新启动游戏
window.addEventListener("keydown", function (event) {
if (event.key == " ") {
event.preventDefault();
resetGame();
return;
}
});
返回棍子击中的平台(如果没有击中任何棍子,则返回未定义)
function thePlatformTheStickHits() {
if (sticks.last().rotation != 90)
throw Error(`Stick is ${sticks.last().rotation}°`);
const stickFarX = sticks.last().x + sticks.last().length;
const platformTheStickHits = platforms.find(
(platform) => platform.x < stickFarX && stickFarX < platform.x + platform.w
);
// 如果棍子击中完美区域
if (
platformTheStickHits &&
platformTheStickHits.x + platformTheStickHits.w / 2 - perfectAreaSize / 2 <
stickFarX &&
stickFarX <
platformTheStickHits.x + platformTheStickHits.w / 2 + perfectAreaSize / 2
)
return [platformTheStickHits, true];
return [platformTheStickHits, false];
}
将主画布区域居中到屏幕中间
ctx.translate(
(window.innerWidth - canvasWidth) / 2 - sceneOffset,
(window.innerHeight - canvasHeight) / 2
);
绘制场景
drawPlatforms();
drawHero();
drawSticks();
山丘是伸展的正弦波下的形状
function drawHill(baseHeight, amplitude, stretch, color) {
ctx.beginPath();
ctx.moveTo(0, window.innerHeight);
ctx.lineTo(0, getHillY(0, baseHeight, amplitude, stretch));
for (let i = 0; i < window.innerWidth; i++) {
ctx.lineTo(i, getHillY(i, baseHeight, amplitude, stretch));
}
ctx.lineTo(window.innerWidth, window.innerHeight);
ctx.fillStyle = color;
ctx.fill();
}