html跑酷游戏(副本难多了)
2026-08-11 09:04:19
发布于:浙江
以下代码是困难版本的,这里不出html使用教程,可以自行搜索,最后复制代码运行即可
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>平台跳跃 · 平台带尖刺 · 高密度风险</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; user-select:none; }
body { background:#0b1a2f; min-height:100vh; display:flex; justify-content:center; align-items:center; font-family:'Courier New', monospace; }
.game-container { background:#1f3a4b; padding:20px 20px 25px; border-radius:28px; box-shadow:0 20px 30px rgba(0,0,0,0.7); display:flex; flex-direction:column; align-items:center; }
canvas { display:block; margin:0 auto; width:800px; height:400px; border-radius:18px; background:#8ecae6; box-shadow:inset 0 -6px 0 #3a6b7c, 0 10px 16px rgba(0,0,0,0.5); cursor:pointer; }
.status-bar { width:100%; display:flex; justify-content:space-between; margin-top:16px; color:#d4edf8; text-shadow:0 2px 0 #0f2a38; font-weight:700; font-size:1.2rem; letter-spacing:1px; padding:0 10px; }
.status-bar span { background:#12303e; padding:6px 18px; border-radius:40px; box-shadow:inset 0 3px 0 #3f6f82, 0 4px 0 #0a1e28; }
.key-hint { margin-top:12px; display:flex; gap:32px; color:#b4d9e9; font-weight:600; font-size:1rem; background:#1d3342; padding:8px 28px; border-radius:60px; letter-spacing:1px; box-shadow:inset 0 2px 5px #3f6b7e; flex-wrap:wrap; justify-content:center; }
.key-hint kbd { background:#0b1f2b; color:#ebf9ff; padding:2px 12px; border-radius:30px; margin:0 6px; font-weight:700; box-shadow:0 2px 0 #3d6b7e; }
button { background:#f7c948; border:none; font-weight:800; font-size:1.1rem; padding:6px 22px; border-radius:40px; color:#1b3849; box-shadow:0 6px 0 #a57d2c, 0 4px 12px black; transition:0.07s ease; cursor:pointer; font-family:inherit; letter-spacing:1px; }
button:active { transform:translateY(6px); box-shadow:0 1px 0 #a57d2c; }
</style>
</head>
<body>
<div class="game-container">
<canvas id="gameCanvas" width="800" height="400"></canvas>
<div class="status-bar">
<span>🏆 关卡 <span id="levelDisplay">1</span></span>
</div>
<div class="key-hint">
<span><kbd>W</kbd><kbd>A</kbd><kbd>D</kbd> 移动 · <kbd>空格</kbd> 跳跃</span>
<button id="resetButton">↺ 重来</button>
</div>
</div>
<script>
(function() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const levelSpan = document.getElementById('levelDisplay');
const W = 800, H = 400;
const GRAVITY = 0.5;
const JUMP_FORCE = -9.5;
const PLAYER_SIZE = 24;
const MOVE_SPEED = 4.5;
let player = {
x: 60, y: 300,
w: PLAYER_SIZE, h: PLAYER_SIZE,
vy: 0, onGround: false,
startX: 60, startY: 300
};
let cameraX = 0, cameraY = 0;
let currentLevel = 0;
let winFlag = false, levelComplete = false;
let deathTimer = 0;
let mapWidth = 0;
let platforms = [], spikes = [], movingPlatforms = [], stars = [];
let movingSpikes = []; // 移动平台上的尖刺(动态计算)
let flag = null;
const keys = { w: false, a: false, d: false, space: false };
// ========================== 关卡定义 ==========================
const LEVELS = [];
// ---- 关卡1 ----
LEVELS.push({
name: '第一关 · 初试身手',
playerStart: { x: 60, y: 300 },
mapWidth: 1000,
build: function() {
platforms = [
{ x: -50, y: 370, w: 1100, h: 30, color: '#5b8c5a' },
{ x: 150, y: 310, w: 80, h: 16, color: '#8B6B4A' },
{ x: 320, y: 310, w: 80, h: 16, color: '#8B6B4A' },
{ x: 500, y: 310, w: 80, h: 16, color: '#8B6B4A' },
{ x: 680, y: 310, w: 80, h: 16, color: '#8B6B4A' },
{ x: 860, y: 310, w: 80, h: 16, color: '#8B6B4A' },
];
// 地面尖刺(密集)
spikes = [];
for (let i = 0; i < 10; i++) spikes.push({ x: 100 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 8; i++) spikes.push({ x: 500 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 6; i++) spikes.push({ x: 750 + i * 50, y: 360, w: 30, h: 12 });
// 平台顶部尖刺(每个平台1个)
for (let plat of platforms) {
if (plat.y < 370) { // 非地面
const cx = plat.x + plat.w / 2;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
movingPlatforms = [];
stars = [
{ x: 190, y: 285, collected: false },
{ x: 360, y: 285, collected: false },
{ x: 540, y: 285, collected: false },
{ x: 720, y: 285, collected: false },
{ x: 900, y: 285, collected: false },
];
flag = { x: 960, y: 340 };
}
});
// ---- 关卡2 ----
LEVELS.push({
name: '第二关 · 高墙与移动',
playerStart: { x: 60, y: 300 },
mapWidth: 1300,
build: function() {
platforms = [
{ x: -50, y: 370, w: 1400, h: 30, color: '#5b8c5a' },
{ x: 150, y: 300, w: 60, h: 16, color: '#8B6B4A' },
{ x: 300, y: 240, w: 60, h: 16, color: '#8B6B4A' },
{ x: 480, y: 300, w: 120, h: 16, color: '#8B6B4A' },
{ x: 700, y: 240, w: 100, h: 16, color: '#8B6B4A' },
{ x: 920, y: 300, w: 80, h: 16, color: '#8B6B4A' },
{ x: 1100, y: 240, w: 80, h: 16, color: '#8B6B4A' },
];
// 地面尖刺
spikes = [];
for (let i = 0; i < 12; i++) spikes.push({ x: 80 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 10; i++) spikes.push({ x: 400 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 8; i++) spikes.push({ x: 700 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 6; i++) spikes.push({ x: 1000 + i * 50, y: 360, w: 30, h: 12 });
// 平台尖刺
for (let plat of platforms) {
if (plat.y < 370) {
const count = plat.w > 80 ? 2 : 1;
for (let i = 0; i < count; i++) {
const offset = (i + 1) / (count + 1);
const cx = plat.x + plat.w * offset;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
}
movingPlatforms = [
{ x: 200, y: 180, w: 100, h: 14, color: '#b89a6a', startX: 150, endX: 400, speed: 1.8, dir: 1, spikeOffsets: [0.5] },
{ x: 600, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 550, endX: 800, speed: 1.5, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 1000, y: 180, w: 100, h: 14, color: '#b89a6a', startX: 950, endX: 1150, speed: 1.8, dir: 1, spikeOffsets: [0.5] },
];
stars = [
{ x: 180, y: 275, collected: false },
{ x: 330, y: 215, collected: false },
{ x: 540, y: 275, collected: false },
{ x: 750, y: 215, collected: false },
{ x: 960, y: 275, collected: false },
{ x: 1140, y: 215, collected: false },
{ x: 280, y: 155, collected: false },
{ x: 680, y: 155, collected: false },
{ x: 1050, y: 155, collected: false },
];
flag = { x: 1240, y: 340 };
}
});
// ---- 关卡3 ----
LEVELS.push({
name: '第三关 · 险象环生',
playerStart: { x: 60, y: 300 },
mapWidth: 1500,
build: function() {
platforms = [
{ x: -50, y: 370, w: 1600, h: 30, color: '#5b8c5a' },
{ x: 120, y: 300, w: 70, h: 16, color: '#8B6B4A' },
{ x: 280, y: 240, w: 70, h: 16, color: '#8B6B4A' },
{ x: 460, y: 300, w: 90, h: 16, color: '#8B6B4A' },
{ x: 650, y: 240, w: 80, h: 16, color: '#8B6B4A' },
{ x: 840, y: 300, w: 70, h: 16, color: '#8B6B4A' },
{ x: 1020, y: 240, w: 90, h: 16, color: '#8B6B4A' },
{ x: 1210, y: 300, w: 80, h: 16, color: '#8B6B4A' },
{ x: 1390, y: 240, w: 70, h: 16, color: '#8B6B4A' },
];
spikes = [];
for (let i = 0; i < 15; i++) spikes.push({ x: 80 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 12; i++) spikes.push({ x: 400 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 10; i++) spikes.push({ x: 700 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 12; i++) spikes.push({ x: 1000 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 8; i++) spikes.push({ x: 1300 + i * 50, y: 360, w: 30, h: 12 });
// 平台尖刺
for (let plat of platforms) {
if (plat.y < 370) {
const count = plat.w > 80 ? 2 : 1;
for (let i = 0; i < count; i++) {
const offset = (i + 1) / (count + 1);
const cx = plat.x + plat.w * offset;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
}
movingPlatforms = [
{ x: 400, y: 160, w: 80, h: 14, color: '#b89a6a', startX: 350, endX: 550, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 750, y: 160, w: 80, h: 14, color: '#b89a6a', startX: 700, endX: 900, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 1100, y: 160, w: 80, h: 14, color: '#b89a6a', startX: 1050, endX: 1250, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
];
stars = [
{ x: 155, y: 275, collected: false },
{ x: 315, y: 215, collected: false },
{ x: 505, y: 275, collected: false },
{ x: 690, y: 215, collected: false },
{ x: 875, y: 275, collected: false },
{ x: 1065, y: 215, collected: false },
{ x: 1250, y: 275, collected: false },
{ x: 1430, y: 215, collected: false },
{ x: 450, y: 135, collected: false },
{ x: 800, y: 135, collected: false },
{ x: 1150, y: 135, collected: false },
];
flag = { x: 1480, y: 340 };
}
});
// ---- 关卡4 ----
LEVELS.push({
name: '第四关 · 飞跃巅峰',
playerStart: { x: 60, y: 300 },
mapWidth: 1600,
build: function() {
platforms = [
{ x: -50, y: 370, w: 1700, h: 30, color: '#5b8c5a' },
{ x: 120, y: 300, w: 60, h: 16, color: '#8B6B4A' },
{ x: 280, y: 240, w: 60, h: 16, color: '#8B6B4A' },
{ x: 460, y: 300, w: 100, h: 16, color: '#8B6B4A' },
{ x: 660, y: 240, w: 80, h: 16, color: '#8B6B4A' },
{ x: 860, y: 300, w: 100, h: 16, color: '#8B6B4A' },
{ x: 1060, y: 240, w: 80, h: 16, color: '#8B6B4A' },
{ x: 1260, y: 300, w: 60, h: 16, color: '#8B6B4A' },
{ x: 1420, y: 240, w: 60, h: 16, color: '#8B6B4A' },
];
spikes = [];
for (let i = 0; i < 18; i++) spikes.push({ x: 80 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 14; i++) spikes.push({ x: 400 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 12; i++) spikes.push({ x: 700 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 14; i++) spikes.push({ x: 1000 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 10; i++) spikes.push({ x: 1300 + i * 50, y: 360, w: 30, h: 12 });
// 平台尖刺
for (let plat of platforms) {
if (plat.y < 370) {
const count = plat.w > 80 ? 2 : 1;
for (let i = 0; i < count; i++) {
const offset = (i + 1) / (count + 1);
const cx = plat.x + plat.w * offset;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
}
movingPlatforms = [
{ x: 200, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 150, endX: 400, speed: 1.5, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 500, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 450, endX: 700, speed: 1.5, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 800, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 750, endX: 1000, speed: 1.5, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 1100, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 1050, endX: 1300, speed: 1.5, dir: 1, spikeOffsets: [0.3, 0.7] },
];
stars = [
{ x: 150, y: 275, collected: false },
{ x: 310, y: 215, collected: false },
{ x: 510, y: 275, collected: false },
{ x: 700, y: 215, collected: false },
{ x: 910, y: 275, collected: false },
{ x: 1100, y: 215, collected: false },
{ x: 1300, y: 275, collected: false },
{ x: 1460, y: 215, collected: false },
{ x: 300, y: 155, collected: false },
{ x: 600, y: 155, collected: false },
{ x: 900, y: 155, collected: false },
{ x: 1200, y: 155, collected: false },
];
flag = { x: 1560, y: 340 };
}
});
// ---- 关卡5 ----
LEVELS.push({
name: '第五关 · 极限跳跃',
playerStart: { x: 60, y: 300 },
mapWidth: 1800,
build: function() {
platforms = [
{ x: -50, y: 370, w: 1900, h: 30, color: '#5b8c5a' },
];
const platPositions = [
{ x: 100, y: 310, w: 50 }, { x: 220, y: 260, w: 50 },
{ x: 340, y: 310, w: 50 }, { x: 460, y: 260, w: 50 },
{ x: 580, y: 310, w: 50 }, { x: 700, y: 260, w: 50 },
{ x: 820, y: 310, w: 50 }, { x: 940, y: 260, w: 50 },
{ x: 1060, y: 310, w: 50 }, { x: 1180, y: 260, w: 50 },
{ x: 1300, y: 310, w: 50 }, { x: 1420, y: 260, w: 50 },
{ x: 1540, y: 310, w: 50 }, { x: 1660, y: 260, w: 50 },
];
for (let p of platPositions) {
platforms.push({ x: p.x, y: p.y, w: p.w, h: 16, color: '#8B6B4A' });
}
spikes = [];
for (let i = 0; i < 20; i++) spikes.push({ x: 80 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 18; i++) spikes.push({ x: 300 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 20; i++) spikes.push({ x: 600 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 18; i++) spikes.push({ x: 900 + i * 45, y: 360, w: 30, h: 12 });
for (let i = 0; i < 20; i++) spikes.push({ x: 1200 + i * 40, y: 360, w: 30, h: 12 });
for (let i = 0; i < 18; i++) spikes.push({ x: 1500 + i * 45, y: 360, w: 30, h: 12 });
// 平台尖刺
for (let plat of platforms) {
if (plat.y < 370) {
const cx = plat.x + plat.w / 2;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
movingPlatforms = [
{ x: 250, y: 200, w: 80, h: 14, color: '#b89a6a', startX: 200, endX: 400, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 550, y: 200, w: 80, h: 14, color: '#b89a6a', startX: 500, endX: 700, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 850, y: 200, w: 80, h: 14, color: '#b89a6a', startX: 800, endX: 1000, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 1150, y: 200, w: 80, h: 14, color: '#b89a6a', startX: 1100, endX: 1300, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
{ x: 1450, y: 200, w: 80, h: 14, color: '#b89a6a', startX: 1400, endX: 1600, speed: 2.0, dir: 1, spikeOffsets: [0.5] },
];
stars = [];
for (let p of platPositions) {
stars.push({ x: p.x + p.w / 2, y: p.y - 25, collected: false });
}
for (let mp of movingPlatforms) {
stars.push({ x: mp.startX + 40, y: mp.y - 25, collected: false });
}
flag = { x: 1780, y: 340 };
}
});
// ---- 关卡6 ----
LEVELS.push({
name: '第六关 · 终极试炼',
playerStart: { x: 60, y: 300 },
mapWidth: 2000,
build: function() {
platforms = [
{ x: -50, y: 370, w: 2100, h: 30, color: '#5b8c5a' },
{ x: 100, y: 310, w: 60, h: 16, color: '#8B6B4A' },
{ x: 230, y: 260, w: 80, h: 16, color: '#8B6B4A' },
{ x: 390, y: 310, w: 50, h: 16, color: '#8B6B4A' },
{ x: 520, y: 240, w: 100, h: 16, color: '#8B6B4A' },
{ x: 700, y: 310, w: 60, h: 16, color: '#8B6B4A' },
{ x: 840, y: 260, w: 80, h: 16, color: '#8B6B4A' },
{ x: 1000, y: 310, w: 50, h: 16, color: '#8B6B4A' },
{ x: 1130, y: 240, w: 100, h: 16, color: '#8B6B4A' },
{ x: 1310, y: 310, w: 60, h: 16, color: '#8B6B4A' },
{ x: 1450, y: 260, w: 80, h: 16, color: '#8B6B4A' },
{ x: 1610, y: 310, w: 50, h: 16, color: '#8B6B4A' },
{ x: 1740, y: 240, w: 100, h: 16, color: '#8B6B4A' },
{ x: 1920, y: 310, w: 60, h: 16, color: '#8B6B4A' },
];
spikes = [];
for (let i = 0; i < 25; i++) spikes.push({ x: 80 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 22; i++) spikes.push({ x: 350 + i * 38, y: 360, w: 30, h: 12 });
for (let i = 0; i < 25; i++) spikes.push({ x: 650 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 22; i++) spikes.push({ x: 950 + i * 38, y: 360, w: 30, h: 12 });
for (let i = 0; i < 25; i++) spikes.push({ x: 1250 + i * 35, y: 360, w: 30, h: 12 });
for (let i = 0; i < 22; i++) spikes.push({ x: 1550 + i * 38, y: 360, w: 30, h: 12 });
for (let i = 0; i < 15; i++) spikes.push({ x: 1850 + i * 45, y: 360, w: 30, h: 12 });
// 平台尖刺
for (let plat of platforms) {
if (plat.y < 370) {
const count = plat.w > 80 ? 2 : 1;
for (let i = 0; i < count; i++) {
const offset = (i + 1) / (count + 1);
const cx = plat.x + plat.w * offset;
spikes.push({ x: cx - 15, y: plat.y - 12, w: 30, h: 12 });
}
}
}
movingPlatforms = [
{ x: 300, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 250, endX: 500, speed: 1.8, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 600, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 550, endX: 800, speed: 1.8, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 900, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 850, endX: 1100, speed: 1.8, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 1200, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 1150, endX: 1400, speed: 1.8, dir: 1, spikeOffsets: [0.3, 0.7] },
{ x: 1500, y: 180, w: 120, h: 14, color: '#b89a6a', startX: 1450, endX: 1700, speed: 1.8, dir: 1, spikeOffsets: [0.3, 0.7] },
];
stars = [
{ x: 130, y: 285, collected: false },
{ x: 270, y: 235, collected: false },
{ x: 415, y: 285, collected: false },
{ x: 570, y: 215, collected: false },
{ x: 730, y: 285, collected: false },
{ x: 880, y: 235, collected: false },
{ x: 1025, y: 285, collected: false },
{ x: 1180, y: 215, collected: false },
{ x: 1340, y: 285, collected: false },
{ x: 1490, y: 235, collected: false },
{ x: 1635, y: 285, collected: false },
{ x: 1790, y: 215, collected: false },
{ x: 1950, y: 285, collected: false },
{ x: 380, y: 155, collected: false },
{ x: 680, y: 155, collected: false },
{ x: 980, y: 155, collected: false },
{ x: 1280, y: 155, collected: false },
{ x: 1580, y: 155, collected: false },
];
flag = { x: 1980, y: 340 };
}
});
// ========================== 工具函数 ==========================
function rectCollide(r1, r2) {
return r1.x < r2.x + r2.w && r1.x + r1.w > r2.x &&
r1.y < r2.y + r2.h && r1.y + r1.h > r2.y;
}
// ========================== 更新移动平台尖刺 ==========================
function updateMovingSpikes() {
movingSpikes = [];
for (let mp of movingPlatforms) {
if (mp.spikeOffsets) {
for (let offset of mp.spikeOffsets) {
const cx = mp.x + mp.w * offset;
movingSpikes.push({
x: cx - 15,
y: mp.y - 12,
w: 30,
h: 12
});
}
}
}
}
// ========================== 关卡加载 ==========================
function loadLevel(index) {
const level = LEVELS[index];
if (!level) { winFlag = true; return; }
level.build();
mapWidth = level.mapWidth || 1200;
const start = level.playerStart;
player.x = start.x;
player.y = start.y;
player.vy = 0;
player.onGround = false;
for (let mp of movingPlatforms) {
mp.x = mp.startX;
mp.dir = 1;
}
updateMovingSpikes();
cameraX = 0;
cameraY = 0;
levelComplete = false;
levelSpan.textContent = index + 1;
}
function resetGame() {
currentLevel = 0;
winFlag = false;
levelComplete = false;
loadLevel(currentLevel);
}
// ========================== 更新 ==========================
function update() {
if (winFlag) return;
if (levelComplete) {
deathTimer++;
if (deathTimer > 30) {
deathTimer = 0;
currentLevel++;
if (currentLevel >= LEVELS.length) { winFlag = true; }
else loadLevel(currentLevel);
}
return;
}
if (keys.a) player.x -= MOVE_SPEED;
if (keys.d) player.x += MOVE_SPEED;
if (player.x < 0) player.x = 0;
if (player.x + player.w > mapWidth) player.x = mapWidth - player.w;
if ((keys.w || keys.space) && player.onGround) {
player.vy = JUMP_FORCE;
player.onGround = false;
}
player.vy += GRAVITY;
player.y += player.vy;
// 移动平台更新
for (let mp of movingPlatforms) {
mp.x += mp.speed * mp.dir;
if (mp.x >= mp.endX) { mp.x = mp.endX; mp.dir = -1; }
if (mp.x <= mp.startX) { mp.x = mp.startX; mp.dir = 1; }
}
updateMovingSpikes();
// 平台碰撞
player.onGround = false;
const allPlatforms = [...platforms];
for (let mp of movingPlatforms) {
allPlatforms.push({ x: mp.x, y: mp.y, w: mp.w, h: mp.h });
}
for (let plat of allPlatforms) {
if (player.vy >= 0 &&
rectCollide(player, plat) &&
player.y + player.h - player.vy <= plat.y + 4) {
player.y = plat.y - player.h;
player.vy = 0;
player.onGround = true;
}
}
// 所有尖刺碰撞(地面 + 固定平台 + 移动平台)
const allSpikes = [...spikes, ...movingSpikes];
for (let spike of allSpikes) {
if (rectCollide(player, spike)) {
const start = LEVELS[currentLevel].playerStart;
player.x = start.x;
player.y = start.y;
player.vy = 0;
player.onGround = false;
for (let mp of movingPlatforms) { mp.x = mp.startX; mp.dir = 1; }
updateMovingSpikes();
return;
}
}
// 掉落
if (player.y > H + 80) {
const start = LEVELS[currentLevel].playerStart;
player.x = start.x;
player.y = start.y;
player.vy = 0;
player.onGround = false;
for (let mp of movingPlatforms) { mp.x = mp.startX; mp.dir = 1; }
updateMovingSpikes();
return;
}
// 收集星星
for (let star of stars) {
if (star.collected) continue;
const starBox = { x: star.x - 10, y: star.y - 10, w: 20, h: 20 };
if (rectCollide(player, starBox)) {
star.collected = true;
}
}
// 旗子
if (flag) {
const flagRect = { x: flag.x - 10, y: flag.y - 40, w: 20, h: 50 };
if (rectCollide(player, flagRect)) {
const allCollected = stars.every(s => s.collected);
if (allCollected && stars.length > 0) {
levelComplete = true;
deathTimer = 0;
}
}
}
cameraX = player.x - W/2 + player.w/2;
cameraY = player.y - H/2 + player.h/2;
}
// ========================== 绘制 ==========================
function draw() {
ctx.clearRect(0, 0, W, H);
// 天空
const sky = ctx.createLinearGradient(0, 0, 0, H);
sky.addColorStop(0, '#b3dff0');
sky.addColorStop(0.7, '#d7ecf5');
ctx.fillStyle = sky;
ctx.fillRect(0, 0, W, H);
// 云
ctx.fillStyle = 'rgba(255,255,245,0.3)';
const clouds = [
{ x: 80, y: 50, s: 50 }, { x: 350, y: 30, s: 60 },
{ x: 650, y: 60, s: 45 }, { x: 950, y: 35, s: 55 },
{ x: 1250, y: 50, s: 50 }
];
for (let cloud of clouds) {
const sx = cloud.x - cameraX * 0.2;
const sy = cloud.y - cameraY * 0.1;
ctx.beginPath();
ctx.ellipse(sx, sy, cloud.s * 0.6, cloud.s * 0.25, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(sx - cloud.s * 0.3, sy + 6, cloud.s * 0.4, cloud.s * 0.2, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(sx + cloud.s * 0.3, sy + 4, cloud.s * 0.35, cloud.s * 0.2, 0, 0, Math.PI * 2);
ctx.fill();
}
// 平台
for (let plat of platforms) {
const sx = plat.x - cameraX;
const sy = plat.y - cameraY;
if (sx > -plat.w && sx < W + plat.w && sy > -30 && sy < H + 30) {
ctx.shadowColor = 'rgba(0,0,0,0.15)';
ctx.shadowBlur = 8;
ctx.fillStyle = plat.color;
ctx.fillRect(sx, sy, plat.w, plat.h);
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,220,0.12)';
ctx.fillRect(sx, sy, plat.w, 3);
ctx.fillStyle = 'rgba(0,0,0,0.08)';
ctx.fillRect(sx, sy + plat.h - 3, plat.w, 3);
}
}
// 移动平台
for (let mp of movingPlatforms) {
const sx = mp.x - cameraX;
const sy = mp.y - cameraY;
if (sx > -mp.w && sx < W + mp.w && sy > -30 && sy < H + 30) {
ctx.shadowColor = 'rgba(0,0,0,0.2)';
ctx.shadowBlur = 10;
const grad = ctx.createLinearGradient(sx, sy, sx, sy + mp.h);
grad.addColorStop(0, '#d4b48a');
grad.addColorStop(1, '#a8865a');
ctx.fillStyle = grad;
ctx.fillRect(sx, sy, mp.w, mp.h);
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,220,0.2)';
ctx.fillRect(sx + 4, sy + 2, mp.w - 8, 3);
ctx.fillStyle = 'rgba(255,255,255,0.3)';
if (mp.dir > 0) {
ctx.beginPath();
ctx.moveTo(sx + mp.w - 8, sy + mp.h / 2);
ctx.lineTo(sx + mp.w - 2, sy + mp.h / 2 - 5);
ctx.lineTo(sx + mp.w - 2, sy + mp.h / 2 + 5);
ctx.closePath();
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(sx + 8, sy + mp.h / 2);
ctx.lineTo(sx + 2, sy + mp.h / 2 - 5);
ctx.lineTo(sx + 2, sy + mp.h / 2 + 5);
ctx.closePath();
ctx.fill();
}
}
}
// 所有尖刺(地面 + 固定平台)
const allSpikes = [...spikes, ...movingSpikes];
for (let spike of allSpikes) {
const sx = spike.x - cameraX;
const sy = spike.y - cameraY;
if (sx > -spike.w && sx < W + spike.w && sy > -spike.h && sy < H + spike.h) {
ctx.shadowColor = 'rgba(180,0,0,0.3)';
ctx.shadowBlur = 12;
ctx.fillStyle = '#cc3333';
const count = 3;
const segW = spike.w / count;
for (let i = 0; i < count; i++) {
ctx.beginPath();
ctx.moveTo(sx + i * segW, sy + spike.h);
ctx.lineTo(sx + i * segW + segW / 2, sy);
ctx.lineTo(sx + (i + 1) * segW, sy + spike.h);
ctx.closePath();
ctx.fill();
}
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,200,200,0.25)';
for (let i = 0; i < count; i++) {
ctx.beginPath();
ctx.moveTo(sx + i * segW + segW * 0.2, sy + spike.h * 0.6);
ctx.lineTo(sx + i * segW + segW / 2, sy + spike.h * 0.2);
ctx.lineTo(sx + i * segW + segW * 0.8, sy + spike.h * 0.6);
ctx.closePath();
ctx.fill();
}
}
}
// 星星
for (let star of stars) {
if (star.collected) continue;
const sx = star.x - cameraX;
const sy = star.y - cameraY;
if (sx > -30 && sx < W + 30 && sy > -30 && sy < H + 30) {
ctx.shadowColor = '#f7d44a';
ctx.shadowBlur = 25;
ctx.fillStyle = '#f7d44a';
const spikes = 5;
const outerR = 12;
const innerR = 5;
let rot = -Math.PI / 2 + Date.now() / 800;
const step = Math.PI / spikes;
ctx.beginPath();
for (let i = 0; i < spikes * 2; i++) {
const r = i % 2 === 0 ? outerR : innerR;
const x = sx + Math.cos(rot) * r;
const y = sy + Math.sin(rot) * r;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
rot += step;
}
ctx.closePath();
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = '#fff8d0';
ctx.beginPath();
ctx.arc(sx - 3, sy - 4, 3, 0, Math.PI * 2);
ctx.fill();
}
}
// 旗子
if (flag) {
const fx = flag.x - cameraX;
const fy = flag.y - cameraY;
ctx.shadowBlur = 0;
ctx.fillStyle = '#5a5a5a';
ctx.fillRect(fx + 8, fy - 40, 4, 50);
const wave = Math.sin(Date.now() / 300) * 4;
ctx.fillStyle = '#ff4444';
ctx.beginPath();
ctx.moveTo(fx + 12, fy - 38);
ctx.quadraticCurveTo(fx + 28, fy - 38 + wave, fx + 12, fy - 18 + wave * 0.5);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#ff6666';
ctx.beginPath();
ctx.moveTo(fx + 12, fy - 38);
ctx.quadraticCurveTo(fx + 24, fy - 38 + wave * 0.5, fx + 12, fy - 28 + wave * 0.3);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#6d4c2a';
ctx.fillRect(fx + 2, fy + 6, 16, 6);
ctx.fillStyle = '#8B6B4A';
ctx.fillRect(fx + 4, fy + 4, 12, 4);
const allCollected = stars.every(s => s.collected);
if (!allCollected && stars.length > 0) {
ctx.fillStyle = 'rgba(255,200,50,0.8)';
ctx.font = '14px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillText('⭐ 收集所有星星!', fx + 14, fy - 50);
} else if (allCollected && stars.length > 0) {
ctx.fillStyle = 'rgba(50,255,100,0.9)';
ctx.font = '16px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillText('🚩 到达旗子过关!', fx + 14, fy - 52);
}
}
// 玩家
const psx = player.x - cameraX;
const psy = player.y - cameraY;
ctx.shadowColor = '#1f3a3f';
ctx.shadowBlur = 16;
ctx.fillStyle = '#3b7e9e';
ctx.fillRect(psx, psy + 4, player.w, player.h - 8);
ctx.fillStyle = '#2c5f7a';
ctx.fillRect(psx + 2, psy + player.h - 8, 8, 8);
ctx.fillRect(psx + player.w - 10, psy + player.h - 8, 8, 8);
ctx.fillStyle = '#f7d9aa';
ctx.beginPath();
ctx.ellipse(psx + player.w / 2, psy + 6, 11, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#2f6b82';
ctx.beginPath();
ctx.ellipse(psx + player.w / 2 - 1, psy + 2, 14, 9, 0.1, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#1c1c1c';
ctx.beginPath();
ctx.arc(psx + player.w / 2 - 5, psy + 4, 2.5, 0, Math.PI * 2);
ctx.arc(psx + player.w / 2 + 5, psy + 4, 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(psx + player.w / 2 - 6, psy + 2, 1, 0, Math.PI * 2);
ctx.arc(psx + player.w / 2 + 4, psy + 2, 1, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#3a281c';
ctx.fillRect(psx - 1, psy + player.h - 2, 8, 5);
ctx.fillRect(psx + player.w - 7, psy + player.h - 2, 8, 5);
ctx.shadowBlur = 0;
// 关卡名与星星进度
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.font = '16px "Courier New", monospace';
ctx.textAlign = 'left';
const levelName = LEVELS[currentLevel] ? LEVELS[currentLevel].name : '🎉 通关!';
ctx.fillText(levelName, 20, 30);
const collected = stars.filter(s => s.collected).length;
const total = stars.length;
if (total > 0) {
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.font = '14px "Courier New", monospace';
ctx.textAlign = 'right';
ctx.fillText(`⭐ ${collected}/${total}`, W - 20, 30);
}
if (levelComplete) {
ctx.fillStyle = 'rgba(40, 80, 60, 0.5)';
ctx.fillRect(0, 0, W, H);
ctx.font = 'bold 40px "Courier New", monospace';
ctx.fillStyle = '#7fdb8a';
ctx.shadowBlur = 30;
ctx.shadowColor = '#3aa84a';
ctx.textAlign = 'center';
ctx.fillText('✨ 过关! ✨', W / 2, 180);
ctx.shadowBlur = 0;
ctx.font = '18px "Courier New", monospace';
ctx.fillStyle = '#b0e0b0';
ctx.fillText('进入下一关...', W / 2, 230);
}
if (winFlag) {
ctx.fillStyle = 'rgba(40, 60, 80, 0.7)';
ctx.fillRect(0, 0, W, H);
ctx.font = 'bold 50px "Courier New", monospace';
ctx.fillStyle = '#f7e05e';
ctx.shadowBlur = 40;
ctx.shadowColor = '#f0b34b';
ctx.textAlign = 'center';
ctx.fillText('🎉 通关! 🎉', W / 2, 170);
ctx.shadowBlur = 0;
ctx.font = '22px "Courier New", monospace';
ctx.fillStyle = '#d4edf8';
ctx.fillText('恭喜你完成了所有关卡!', W / 2, 220);
ctx.fillStyle = '#b0d0e0';
ctx.font = '18px "Courier New", monospace';
ctx.fillText('点击 "重来" 重新挑战', W / 2, 260);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
function handleKeyDown(e) {
const key = e.key;
if (key === ' ' || key === 'Space') { e.preventDefault(); keys.space = true; }
else if (key === 'w' || key === 'W') { e.preventDefault(); keys.w = true; }
else if (key === 'a' || key === 'A') { e.preventDefault(); keys.a = true; }
else if (key === 'd' || key === 'D') { e.preventDefault(); keys.d = true; }
else if (key === 'r' || key === 'R') { e.preventDefault(); resetGame(); }
}
function handleKeyUp(e) {
const key = e.key;
if (key === ' ' || key === 'Space') { e.preventDefault(); keys.space = false; }
else if (key === 'w' || key === 'W') { e.preventDefault(); keys.w = false; }
else if (key === 'a' || key === 'A') { e.preventDefault(); keys.a = false; }
else if (key === 'd' || key === 'D') { e.preventDefault(); keys.d = false; }
}
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
window.addEventListener('blur', () => { keys.w = keys.a = keys.d = keys.space = false; });
document.getElementById('resetButton').addEventListener('click', resetGame);
resetGame();
gameLoop();
canvas.addEventListener('click', () => canvas.focus());
canvas.setAttribute('tabindex', '0');
canvas.focus();
})();
</script>
</body>
</html>
这里空空如也




















有帮助,赞一个