《MC跑酷冒险》游戏介绍
欢迎来到《MC跑酷冒险》!这是一款基于《我的世界》风格的横版跑酷游戏,融合了经典平台跳跃玩法与随机生成世界的无限乐趣。
游戏玩法
核心控制:使用WASD键操控角色------W跳跃/上梯子,S下蹲/下梯子,AD控制左右移动。按Q在起点重生,R重新生成世界。
冒险目标:穿越随机生成的地形,避开致命陷阱(红色岩浆和尖刺),利用棕色梯子攀爬,收集蓝色钻石获得高分,借助黄色弹簧实现超远跳跃,最终到达终点旗帜!
挑战要素:每次游戏都会生成全新的地图布局,包含移动平台、动态障碍、隐藏路径等多样机关。小心别掉出地图外,也别被陷阱终结你的冒险!
游戏特色
-
随机生成世界:每次开局都是全新地图,保证无限重玩价值
-
多样地形元素:包含草地、石砖、木材、岩浆、弹簧等10+种方块类型
-
动态平台系统:移动平台、升降梯子带来多变挑战
-
粒子特效:跳跃轨迹、收集特效、死亡爆炸等视觉反馈
-
多结局体验:既可追求快速通关,也可探索收集全钻石
适合玩家
喜欢《我的世界》风格、平台跳跃挑战和roguelike随机元素的玩家。游戏简单易上手但富有深度,考验你的反应速度、空间判断和策略规划能力。
准备好开始你的跑酷冒险了吗?每一次跳跃都是新的挑战,每一次通关都是新的成就!🎮✨
下面是源代码,希望点赞,关注,收藏,评论!
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>跑酷冒险</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: #1a1a1a;
color: white;
overflow: hidden;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#game-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: linear-gradient(to bottom, #87CEEB 60%, #5a9c5a 100%);
}
#game-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
#game-info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0,0,0,0.7);
color: white;
padding: 10px 15px;
border-radius: 5px;
font-family: 'Courier New', monospace;
z-index: 101;
display: flex;
gap: 15px;
}
.info-item {
display: flex;
flex-direction: column;
align-items: center;
}
.info-label {
font-size: 12px;
opacity: 0.8;
}
.info-value {
font-size: 18px;
font-weight: bold;
}
.control-btn {
position: absolute;
z-index: 101;
padding: 8px 16px;
background: rgba(0,0,0,0.7);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: 'Courier New', monospace;
font-size: 14px;
transition: all 0.2s;
pointer-events: auto;
}
.control-btn:hover {
background: rgba(50,50,50,0.8);
transform: translateY(-2px);
}
#help-btn {
top: 20px;
right: 20px;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 30px;
background: rgba(0,0,0,0.95);
color: white;
text-align: center;
z-index: 1000;
border-radius: 15px;
font-family: 'Courier New', monospace;
display: none;
border: 4px solid #3d85c6;
box-shadow: 0 0 20px rgba(0,0,0,0.8);
max-width: 80%;
max-height: 80%;
overflow-y: auto;
pointer-events: auto;
}
.modal h3 {
margin-bottom: 20px;
color: #f1c27d;
font-size: 24px;
text-shadow: 2px 2px 0 #000;
}
.modal ul {
list-style: none;
padding: 0;
max-height: 300px;
overflow-y: auto;
}
.modal li {
padding: 10px;
border-bottom: 1px solid #444;
text-align: left;
font-size: 14px;
display: flex;
justify-content: space-between;
}
.modal li span:first-child {
font-weight: bold;
color: #FFD700;
}
.modal button {
margin-top: 20px;
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: 'Courier New', monospace;
font-size: 16px;
transition: all 0.2s;
margin: 10px;
}
.modal button:hover {
background: #45a049;
transform: translateY(-2px);
}
#game-over-modal {
display: none;
}
#game-over-modal h3 {
color: #FF4500;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 20px;
}
.control-item {
background: rgba(50,50,50,0.5);
padding: 10px;
border-radius: 5px;
text-align: center;
}
.key {
display: inline-block;
background: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
margin: 0 5px;
font-weight: bold;
}
.jump-trail {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
z-index: 50;
pointer-events: none;
}
.particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
z-index: 50;
pointer-events: none;
}
.end-point {
position: absolute;
width: 60px;
height: 80px;
z-index: 90;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
color: white;
font-size: 16px;
}
.end-point-flag {
position: absolute;
width: 40px;
height: 60px;
background: repeating-linear-gradient(
45deg,
#000,
#000 10px,
#fff 10px,
#fff 20px
);
z-index: 91;
top: 10px;
left: 60px;
border: 2px solid #000;
}
.end-point-base {
position: absolute;
width: 60px;
height: 20px;
background: #FFD700;
z-index: 90;
bottom: 0;
border: 2px solid #B8860B;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="ui-overlay">
<!-- 游戏信息 -->
<div id="game-info">
<div class="info-item">
<div class="info-label">得分</div>
<div id="score-display" class="info-value">0</div>
</div>
</div>
<!-- 控制按钮 -->
<button id="help-btn" class="control-btn">❓ 玩法说明</button>
</div>
</div>
<!-- 玩法说明弹窗 -->
<div id="help-modal" class="modal">
<h3>🎮 跑酷冒险指南 🎮</h3>
<p>控制方式:</p>
<div class="controls">
<div class="control-item"><span class="key">W</span> 跳跃/上梯子</div>
<div class="control-item"><span class="key">S</span> 下蹲/下梯子</div>
<div class="control-item"><span class="key">A</span> 向左移动</div>
<div class="control-item"><span class="key">D</span> 向右移动</div>
<div class="control-item"><span class="key">Q</span> 起点重生</div>
<div class="control-item"><span class="key">R</span> 重新生成世界</div>
</div>
<p>生存目标:</p>
<p>✅ 避开红色岩浆和尖刺!</p>
<p>✅ 爬棕色梯子越障</p>
<p>✅ 收集钻石获得高分</p>
<p>✅ 使用弹簧跳得更高</p>
<p>❌ 别掉地图外!</p>
<p>通关条件:到达终点!</p>
<button id="close-help">开始冒险</button>
</div>
<!-- 游戏结束弹窗 -->
<div id="game-over-modal" class="modal">
<h3 id="game-over-title">游戏结束</h3>
<p id="game-over-reason">你掉出了地图</p>
<p>收集钻石: <span id="diamonds-collected">0</span></p>
<p>最终得分: <span id="final-score">0</span></p>
<button id="restart-btn">重新生成世界</button>
<button id="respawn-btn">起点重生</button>
</div>
<!-- 通关弹窗 -->
<div id="win-modal" class="modal">
<h3>恭喜通关!🎉</h3>
<p>你成功到达了终点!</p>
<p>收集钻石: <span id="win-diamonds">0</span></p>
<p>最终得分: <span id="win-score">0</span></p>
<button id="play-again-btn">再玩一次</button>
<button id="new-world-btn">开启全新地图</button>
</div>
<script>
// 防止WebAssembly错误
window.WebAssembly = undefined;
// ==================== 游戏核心配置 ====================
const canvas = document.getElementById('game-canvas');
const ctx = canvas.getContext('2d');
const gameContainer = document.getElementById('game-container');
// 设置画布尺寸
let CANVAS_WIDTH = 1200;
let CANVAS_HEIGHT = 700;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
// 游戏常量
const TILE_SIZE = 32;
const GROUND_HEIGHT = 40;
const GRAVITY = 0.3;
const JUMP_POWER = -8;
const PLAYER_SPEED = 5;
const PLAYER_CROUCH_SPEED = 2;
const DEATH_LINE = CANVAS_HEIGHT + 100;
const CAMERA_SMOOTHING = 0.1;
// 视角控制
let cameraOffset = { x: 0, y: 0 };
let targetCameraOffset = { x: 0, y: 0 };
// 玩家对象
let player = {
x: 100,
y: CANVAS_HEIGHT - GROUND_HEIGHT - 64,
width: 24,
height: 48,
velX: 0,
velY: 0,
isJumping: false,
isCrouching: false,
isForcedCrouching: false,
onGround: false,
onLadder: false,
onPlatform: false,
gravity: GRAVITY,
jumpPower: JUMP_POWER,
speed: PLAYER_SPEED,
facingRight: true,
originalHeight: 48,
crouchHeight: 24,
headRotation: 0,
limbRotation: 0,
mouseX: 0,
mouseY: 0,
lastPlatform: null,
platformVelX: 0,
platformVelY: 0
};
// 游戏状态
let gameStarted = false;
let gameOver = false;
let score = 0;
let diamondsCollected = 0;
// 游戏元素
let tiles = [];
let obstacles = [];
let diamonds = [];
let platforms = [];
let springs = [];
let levelGoal = 0;
let jumpTrails = [];
let particles = [];
let endPoints = [];
// 按键状态
let keys = {
'w': false,
'a': false,
's': false,
'd': false,
'q': false,
'r': false
};
// 障碍物类型
const TILE_TYPES = {
GRASS: { color: '#7CFC00', fatal: false, texture: 'grass', solid: true },
DIRT: { color: '#8B4513', fatal: false, texture: 'dirt', solid: true },
STONE: { color: '#808080', fatal: false, texture: 'stone', solid: true },
LAVA: { color: '#FF4500', fatal: true, texture: 'lava', solid: false },
LADDER: { color: '#D2B48C', fatal: false, climbable: true, texture: 'ladder', solid: false },
PILLAR: { color: '#A9A9A9', fatal: false, block: true, texture: 'stone_brick', solid: true },
SPIKE: { color: '#FF0000', fatal: true, texture: 'spike', solid: false },
SPRING: { color: '#FFFF00', bounce: true, texture: 'spring', solid: false },
WOOD: { color: '#8B4513', fatal: false, texture: 'wood', solid: true },
WATER: { color: '#1E90FF', fatal: false, slows: true, texture: 'water', solid: false },
BEDROCK: { color: '#2F2F2F', fatal: false, texture: 'bedrock', solid: true },
COBBLESTONE: { color: '#808080', fatal: false, texture: 'cobblestone', solid: true },
OBSIDIAN: { color: '#2F2F2F', fatal: false, texture: 'obsidian', solid: true },
SAND: { color: '#C2B280', fatal: false, texture: 'sand', solid: true },
GRAVEL: { color: '#8B8680', fatal: false, texture: 'gravel', solid: true },
NETHERRACK: { color: '#8B0000', fatal: false, texture: 'netherrack', solid: true },
END_STONE: { color: '#D9D9A3', fatal: false, texture: 'end_stone', solid: true },
TEMPLE_BRICK: { color: '#C19A6B', fatal: false, texture: 'temple_brick', solid: true },
PYRAMID_BRICK: { color: '#D7B98C', fatal: false, texture: 'pyramid_brick', solid: true },
MOUNTAIN_STONE: { color: '#A0A0A0', fatal: false, texture: 'mountain_stone', solid: true }
};
// 纹理资源
const TEXTURES = {
grass: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#5a9c5a';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#7CFC00';
for (let i = 0; i < w; i += 8) {
ctx.fillRect(x + i, y, 4, 2);
}
}
},
dirt: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#5a3a1e';
for (let i = 0; i < w; i += 6) {
for (let j = 0; j < h; j += 6) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
stone: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#808080';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#666';
for (let i = 0; i < w; i += 6) {
for (let j = 0; j < h; j += 6) {
ctx.fillRect(x + i, y + j, 3, 3);
}
}
}
},
cobblestone: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#808080';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
for (let i = 0; i < w; i += 8) {
for (let j = 0; j < h; j += 8) {
ctx.strokeRect(x + i, y + j, 8, 8);
}
}
}
},
stone_brick: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#808080';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = '#555';
ctx.lineWidth = 1;
for (let i = 0; i < w; i += 8) {
for (let j = 0; j < h; j += 8) {
ctx.strokeRect(x + i, y + j, 8, 8);
}
}
}
},
lava: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#FF4500';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#FF5500';
for (let i = 0; i < w; i += 15) {
for (let j = 0; j < h; j += 15) {
ctx.fillRect(x + i, y + j, 8, 8);
}
}
}
},
ladder: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#D2B48C';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#8B4513';
for (let i = 0; i < h; i += 8) {
ctx.fillRect(x, y + i, 4, 4);
ctx.fillRect(x + w - 4, y + i, 4, 4);
if (i % 16 === 0) {
ctx.fillRect(x + 4, y + i, w - 8, 4);
}
}
}
},
spike: {
draw: function(ctx, x, y, w, h) {
const spikeWidth = 10;
const spikeCount = Math.floor(w / spikeWidth);
const spikeHeight = 16;
ctx.fillStyle = '#FF0000';
for (let i = 0; i < spikeCount; i++) {
const spikeX = x + i * spikeWidth;
ctx.beginPath();
ctx.moveTo(spikeX, y + h);
ctx.lineTo(spikeX + spikeWidth/2, y + h - spikeHeight);
ctx.lineTo(spikeX + spikeWidth, y + h);
ctx.closePath();
ctx.fill();
}
}
},
spring: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#FFFF00';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.moveTo(x, y + h);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w, y);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x + w/4, y + h/3);
ctx.lineTo(x + w*3/4, y + h/3);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x + w/4, y + h*2/3);
ctx.lineTo(x + w*3/4, y + h*2/3);
ctx.stroke();
}
},
wood: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = '#5a3a1e';
ctx.lineWidth = 1;
for (let i = 0; i < w; i += 6) {
ctx.beginPath();
ctx.moveTo(x + i, y);
ctx.lineTo(x + i, y + h);
ctx.stroke();
}
}
},
water: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#1E90FF';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
for (let i = 0; i < w; i += 10) {
for (let j = 0; j < h; j += 10) {
ctx.fillRect(x + i, y + j, 6, 6);
}
}
}
},
bedrock: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#2F2F2F';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#1a1a1a';
for (let i = 0; i < w; i += 4) {
for (let j = 0; j < h; j += 4) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
obsidian: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#2F2F2F';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#1a1a1a';
for (let i = 0; i < w; i += 6) {
for (let j = 0; j < h; j += 6) {
ctx.fillRect(x + i, y + j, 3, 3);
}
}
}
},
sand: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#C2B280';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#a8996e';
for (let i = 0; i < w; i += 4) {
for (let j = 0; j < h; j += 4) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
gravel: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#8B8680';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#666';
for (let i = 0; i < w; i += 4) {
for (let j = 0; j < h; j += 4) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
netherrack: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#8B0000';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#660000';
for (let i = 0; i < w; i += 4) {
for (let j = 0; j < h; j += 4) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
end_stone: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#D9D9A3';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#b8b887';
for (let i = 0; i < w; i += 4) {
for (let j = 0; j < h; j += 4) {
ctx.fillRect(x + i, y + j, 2, 2);
}
}
}
},
temple_brick: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#C19A6B';
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = '#8B7355';
ctx.lineWidth = 1;
for (let i = 0; i < w; i += 10) {
for (let j = 0; j < h; j += 10) {
ctx.strokeRect(x + i, y + j, 10, 10);
}
}
}
},
pyramid_brick: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#D7B98C';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#B89C7C';
for (let i = 0; i < w; i += 8) {
for (let j = 0; j < h; j += 8) {
ctx.fillRect(x + i, y + j, 4, 4);
}
}
}
},
mountain_stone: {
draw: function(ctx, x, y, w, h) {
ctx.fillStyle = '#A0A0A0';
ctx.fillRect(x, y, w, h);
ctx.fillStyle = '#808080';
for (let i = 0; i < w; i += 6) {
for (let j = 0; j < h; j += 6) {
ctx.fillRect(x + i, y + j, 3, 3);
}
}
}
}
};
// 预定义的关卡结构模板
const LEVEL_TEMPLATES = [
{
name: "基础平台",
structures: [
{ type: 'PILLAR', x: 500, y: 0, width: 50, height: 3 },
{ type: 'PILLAR', x: 800, y: 0, width: 50, height: 5 },
{ type: 'LADDER', x: 500, y: 0, width: 32, height: 6 },
{ type: 'PLATFORM', x: 600, y: 300, width: 100, height: 20, platformType: 'WOOD', speed: 1, direction: 1 }
]
},
{
name: "高塔",
structures: [
{ type: 'PILLAR', x: 700, y: 0, width: 80, height: 8 },
{ type: 'LADDER', x: 700, y: 0, width: 32, height: 8 },
{ type: 'PLATFORM', x: 650, y: 200, width: 80, height: 20, platformType: 'COBBLESTONE' },
{ type: 'PLATFORM', x: 750, y: 350, width: 80, height: 20, platformType: 'COBBLESTONE' }
]
},
{
name: "移动平台",
structures: [
{ type: 'PLATFORM', x: 400, y: 200, width: 120, height: 20, platformType: 'WOOD', speed: 2, direction: 1, verticalSpeed: 1, verticalDirection: 1, minY: 150, maxY: 300 },
{ type: 'PLATFORM', x: 700, y: 250, width: 100, height: 20, platformType: 'COBBLESTONE', speed: 1.5, direction: -1 },
{ type: 'PLATFORM', x: 900, y: 180, width: 80, height: 20, platformType: 'WOOD', speed: 1, direction: 1 }
]
},
{
name: "岩浆陷阱",
structures: [
{ type: 'LAVA', x: 600, y: 0, width: 150, height: 1 },
{ type: 'PILLAR', x: 550, y: 0, width: 50, height: 4 },
{ type: 'PILLAR', x: 750, y: 0, width: 50, height: 4 },
{ type: 'PLATFORM', x: 650, y: 150, width: 80, height: 20, platformType: 'COBBLESTONE' },
{ type: 'SPRING', x: 550, y: 0, width: 32, height: 1 }
]
},
{
name: "复杂跳跃",
structures: [
{ type: 'PILLAR', x: 500, y: 0, width: 40, height: 3 },
{ type: 'PILLAR', x: 650, y: 0, width: 40, height: 5 },
{ type: 'PILLAR', x: 800, y: 0, width: 40, height: 4 },
{ type: 'PLATFORM', x: 550, y: 150, width: 70, height: 20, platformType: 'WOOD' },
{ type: 'PLATFORM', x: 700, y: 100, width: 70, height: 20, platformType: 'WOOD' },
{ type: 'SPRING', x: 600, y: 0, width: 32, height: 1 },
{ type: 'SPRING', x: 750, y: 0, width: 32, height: 1 }
]
}
];
// 随机种子生成器
function generateRandomSeed() {
return Math.floor(Math.random() * 1000000);
}
// 使用随机种子生成关卡
let currentSeed = generateRandomSeed();
function generateLevel(seed) {
// 重置游戏状态
tiles = [];
obstacles = [];
diamonds = [];
platforms = [];
springs = [];
endPoints = [];
player.x = 100;
player.y = CANVAS_HEIGHT - GROUND_HEIGHT - player.height;
player.velX = player.velY = 0;
player.isJumping = false;
player.isCrouching = false;
player.isForcedCrouching = false;
player.height = player.originalHeight;
player.speed = PLAYER_SPEED;
player.platformVelX = 0;
player.platformVelY = 0;
cameraOffset = { x: 0, y: 0 };
targetCameraOffset = { x: 0, y: 0 };
// 重置按键状态
resetKeys();
// 设置随机种子
const random = seededRandom(seed);
// 关卡长度
const levelLength = 2000 + Math.floor(random() * 1000);
// 生成地面
for (let x = 0; x < levelLength; x += TILE_SIZE) {
tiles.push({
x,
y: CANVAS_HEIGHT - GROUND_HEIGHT,
w: TILE_SIZE,
h: TILE_SIZE,
type: TILE_TYPES.GRASS
});
for (let y = CANVAS_HEIGHT - GROUND_HEIGHT + TILE_SIZE; y < CANVAS_HEIGHT - GROUND_HEIGHT + TILE_SIZE * 3; y += TILE_SIZE) {
tiles.push({
x,
y,
w: TILE_SIZE,
h: TILE_SIZE,
type: TILE_TYPES.DIRT
});
}
for (let y = CANVAS_HEIGHT - GROUND_HEIGHT + TILE_SIZE * 3; y < CANVAS_HEIGHT - GROUND_HEIGHT + TILE_SIZE * 6; y += TILE_SIZE) {
tiles.push({
x,
y,
w: TILE_SIZE,
h: TILE_SIZE,
type: TILE_TYPES.STONE
});
}
for (let y = CANVAS_HEIGHT - GROUND_HEIGHT + TILE_SIZE * 6; y < CANVAS_HEIGHT; y += TILE_SIZE) {
tiles.push({
x,
y,
w: TILE_SIZE,
h: TILE_SIZE,
type: TILE_TYPES.BEDROCK
});
}
}
// 随机选择模板
const templateCount = 5 + Math.floor(random() * 3);
const selectedTemplates = [];
for (let i = 0; i < templateCount; i++) {
const templateIndex = Math.floor(random() * LEVEL_TEMPLATES.length);
selectedTemplates.push(LEVEL_TEMPLATES[templateIndex]);
}
// 应用模板
selectedTemplates.forEach((template, index) => {
const offsetX = 300 + (index * (levelLength - 600) / templateCount) + (random() * 200 - 100);
template.structures.forEach(structure => {
const adjustedX = structure.x + offsetX;
switch(structure.type) {
case 'PILLAR':
obstacles.push({
x: adjustedX,
y: CANVAS_HEIGHT - GROUND_HEIGHT - structure.height * TILE_SIZE,
w: structure.width,
h: structure.height * TILE_SIZE,
type: TILE_TYPES[structure.type]
});
break;
case 'LADDER':
obstacles.push({
x: adjustedX,
y: CANVAS_HEIGHT - GROUND_HEIGHT - structure.height * TILE_SIZE,
w: structure.width,
h: structure.height * TILE_SIZE,
type: TILE_TYPES[structure.type]
});
break;
case 'LAVA':
obstacles.push({
x: adjustedX,
y: CANVAS_HEIGHT - GROUND_HEIGHT - structure.height * TILE_SIZE,
w: structure.width,
h: structure.height * TILE_SIZE,
type: TILE_TYPES[structure.type]
});
break;
case 'SPRING':
springs.push({
x: adjustedX,
y: CANVAS_HEIGHT - GROUND_HEIGHT - TILE_SIZE,
w: structure.width,
h: TILE_SIZE,
type: TILE_TYPES[structure.type]
});
break;
case 'PLATFORM':
platforms.push({
x: adjustedX,
y: structure.y,
w: structure.width,
h: structure.height,
speed: structure.speed || 0,
direction: structure.direction || 1,
type: TILE_TYPES[structure.platformType || 'WOOD'],
originalX: adjustedX,
verticalSpeed: structure.verticalSpeed || 0,
verticalDirection: structure.verticalDirection || 0,
minY: structure.minY || structure.y,
maxY: structure.maxY || structure.y,
prevX: adjustedX,
prevY: structure.y
});
break;
}
});
});
// 添加随机障碍物
const obstacleCount = 6 + Math.floor(random() * 8);
for (let i = 0; i < obstacleCount; i++) {
const x = 300 + (i * (levelLength - 600) / obstacleCount) + (random() * 200 - 100);
const width = 50 + random() * 100;
const height = 1 + Math.floor(random() * 3);
let type;
const rand = random();
if (rand < 0.3) {
type = 'LAVA';
} else if (rand < 0.5) {
type = 'SPIKE';
} else if (rand < 0.7) {
type = 'PILLAR';
} else {
type = 'LADDER';
}
obstacles.push({
type: TILE_TYPES[type],
x: x,
y: CANVAS_HEIGHT - GROUND_HEIGHT - height * TILE_SIZE,
w: type === 'LADDER' ? 32 : width,
h: height * TILE_SIZE
});
}
// 添加随机弹簧
const springCount = 2 + Math.floor(random() * 3);
for (let i = 0; i < springCount; i++) {
const x = 200 + (i * (levelLength - 400) / springCount) + (random() * 100 - 50);
springs.push({
x: x,
y: CANVAS_HEIGHT - GROUND_HEIGHT - TILE_SIZE,
w: 32,
h: TILE_SIZE,
type: TILE_TYPES.SPRING
});
}
// 添加随机平台
const platformCount = Math.min(4, 2 + Math.floor(random() * 2));
for (let i = 0; i < platformCount; i++) {
const x = 200 + (i * (levelLength - 400) / platformCount) + (random() * 150 - 75);
const y = 150 + random() * 300;
const width = 80 + random() * 120;
const platformTypes = ['WOOD', 'COBBLESTONE', 'STONE'];
const type = platformTypes[Math.floor(random() * platformTypes.length)];
const speed = random() * 2;
const direction = random() > 0.5 ? 1 : -1;
const platform = {
x: x,
y: y,
w: width,
h: 20,
speed: speed,
direction: direction,
type: TILE_TYPES[type],
originalX: x,
prevX: x,
prevY: y
};
if (random() > 0.7) {
platform.verticalSpeed = 0.5 + random() * 1.5;
platform.verticalDirection = random() > 0.5 ? 1 : -1;
platform.minY = platform.y - 80;
platform.maxY = platform.y + 80;
}
platforms.push(platform);
}
// 添加钻石
const diamondCount = 6 + Math.floor(random() * 8);
for (let i = 0; i < diamondCount; i++) {
const x = 100 + (i * (levelLength - 200) / diamondCount) + (random() * 100 - 50);
const y = 100 + random() * 400;
diamonds.push({
x: x,
y: y,
w: 16,
h: 16,
collected: false
});
}
// 添加终点
endPoints.push({
x: levelLength - 100,
y: CANVAS_HEIGHT - GROUND_HEIGHT - 80,
w: 60,
h: 80
});
// 设置关卡目标
levelGoal = levelLength - CANVAS_WIDTH;
}
// 随机数生成器
function seededRandom(seed) {
let value = seed;
return function() {
value = (value * 9301 + 49297) % 233280;
return value / 233280;
};
}
// ==================== 游戏初始化 ====================
function init() {
// 设置画布尺寸
resizeCanvas();
// 初始化事件监听器
setupEventListeners();
// 生成初始关卡
generateLevel(currentSeed);
// 显示玩法说明
showHelp();
}
function resizeCanvas() {
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
canvas.style.position = 'absolute';
canvas.style.left = '50%';
canvas.style.top = '50%';
canvas.style.transform = 'translate(-50%, -50%)';
if (gameStarted && !gameOver) {
targetCameraOffset.x = player.x - CANVAS_WIDTH / 2;
targetCameraOffset.y = player.y - CANVAS_HEIGHT / 2;
} else {
player.y = CANVAS_HEIGHT - GROUND_HEIGHT - player.height;
}
}
// ==================== 修复Bug 5: 事件监听器内存泄漏 ====================
let eventListenersBound = false;
function setupEventListeners() {
// 只绑定一次事件监听器
if (eventListenersBound) return;
eventListenersBound = true;
window.addEventListener('resize', resizeCanvas);
document.addEventListener('keydown', handleKeydown);
document.addEventListener('keyup', handleKeyup);
canvas.addEventListener('mousemove', handleMouseMove);
// UI按钮事件 - 使用事件委托
document.addEventListener('click', function(e) {
const target = e.target;
if (target.id === 'help-btn') {
showHelp();
} else if (target.id === 'close-help') {
document.getElementById('help-modal').style.display = 'none';
startGame();
} else if (target.id === 'restart-btn') {
restartGame();
} else if (target.id === 'respawn-btn') {
respawnAtStart();
} else if (target.id === 'play-again-btn') {
playAgain();
} else if (target.id === 'new-world-btn') {
newWorld();
}
});
}
function handleMouseMove(e) {
const rect = canvas.getBoundingClientRect();
player.mouseX = e.clientX - rect.left;
player.mouseY = e.clientY - rect.top;
}
// ==================== 游戏核心逻辑 ====================
function startGame() {
gameStarted = true;
gameOver = false;
score = 0;
diamondsCollected = 0;
document.getElementById('score-display').textContent = score;
gameLoop();
}
function handleKeydown(e) {
const key = e.key.toLowerCase();
if (key in keys) {
keys[key] = true;
}
if (key === 'q') {
respawnAtStart();
}
if (key === 'r') {
restartGame();
}
}
function handleKeyup(e) {
const key = e.key.toLowerCase();
if (key in keys) {
keys[key] = false;
}
}
function resetKeys() {
for (let key in keys) {
keys[key] = false;
}
}
function canJump() {
return player.onGround || player.onLadder || player.onPlatform ||
(player.velY >= 0 && checkGroundCollision());
}
function checkGroundCollision() {
const playerRect = {
x: player.x,
y: player.y + player.height,
width: player.width,
height: 5
};
const items = [...tiles, ...obstacles, ...platforms];
for (const item of items) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if (checkCollision(playerRect, item)) {
return true;
}
}
}
return false;
}
// ==================== 修复Bug 1: 玩家下蹲时碰撞检测问题 ====================
function checkAboveObstacle() {
const checkHeight = 30;
const checkRect = {
x: player.x,
y: player.y - checkHeight,
width: player.width,
height: checkHeight
};
const items = [...tiles, ...obstacles, ...platforms];
for (const item of items) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if (checkCollision(checkRect, item)) {
// 检查是否真的会碰撞,考虑玩家站起来后的高度
const playerStandingRect = {
x: player.x,
y: player.y - (player.originalHeight - player.height),
width: player.width,
height: player.originalHeight
};
if (checkCollision(playerStandingRect, item)) {
return true;
}
}
}
}
return false;
}
function updatePlayer() {
player.velX = 0;
let movingLeft = keys['a'];
let movingRight = keys['d'];
if (movingLeft && movingRight) {
player.velX = 0;
} else if (movingLeft) {
player.velX = -player.speed;
player.facingRight = false;
} else if (movingRight) {
player.velX = player.speed;
player.facingRight = true;
}
if (keys['s']) {
if (!player.isCrouching) {
player.isCrouching = true;
const originalBottom = player.y + player.height;
player.height = player.crouchHeight;
player.y = originalBottom - player.height;
player.speed = PLAYER_CROUCH_SPEED;
}
} else {
if (!checkAboveObstacle()) {
if (player.isCrouching) {
player.isCrouching = false;
const originalBottom = player.y + player.height;
player.height = player.originalHeight;
player.y = originalBottom - player.height;
player.speed = PLAYER_SPEED;
player.isForcedCrouching = false;
}
} else {
player.isCrouching = true;
player.isForcedCrouching = true;
player.speed = PLAYER_CROUCH_SPEED;
}
}
if (keys['w'] && canJump()) {
player.velY = player.jumpPower;
player.isJumping = true;
player.onLadder = false;
player.onPlatform = false;
createJumpTrail();
}
if (!player.onLadder) {
player.velY += player.gravity;
}
let newX = player.x;
let newY = player.y;
if (player.velX !== 0) {
newX = player.x + player.velX;
if (checkSolidCollision({x: newX, y: player.y, width: player.width, height: player.height})) {
const step = player.velX > 0 ? 1 : -1;
let testX = player.x;
for (let i = 0; i < Math.abs(player.velX); i++) {
testX += step;
if (!checkSolidCollision({x: testX, y: player.y, width: player.width, height: player.height})) {
newX = testX;
} else {
break;
}
}
}
}
if (player.velY !== 0) {
newY = player.y + player.velY;
if (checkSolidCollision({x: newX, y: newY, width: player.width, height: player.height})) {
const step = player.velY > 0 ? 1 : -1;
let testY = player.y;
for (let i = 0; i < Math.abs(player.velY); i++) {
testY += step;
if (!checkSolidCollision({x: newX, y: testY, width: player.width, height: player.height})) {
newY = testY;
} else {
if (step > 0) {
const allSolids = [...tiles, ...obstacles, ...platforms];
let closestBlock = null;
let closestDistance = Infinity;
for (const item of allSolids) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if (newX < item.x + item.w &&
newX + player.width > item.x &&
testY + player.height > item.y &&
testY < item.y + item.h) {
const distance = item.y - (testY + player.height);
if (distance < closestDistance) {
closestDistance = distance;
closestBlock = item;
}
}
}
}
if (closestBlock) {
newY = closestBlock.y - player.height;
player.onGround = true;
player.isJumping = false;
player.velY = 0;
if (platforms.includes(closestBlock)) {
player.onPlatform = true;
player.lastPlatform = closestBlock;
}
}
}
break;
}
}
}
}
player.x = newX;
player.y = newY;
if (player.x < 0) player.x = 0;
if (player.velX !== 0) {
player.limbRotation = Math.sin(Date.now() / 100) * 0.5;
} else {
player.limbRotation = 0;
}
const centerX = player.x + player.width / 2;
const centerY = player.y + player.height / 2;
const dx = (player.mouseX + cameraOffset.x) - centerX;
player.headRotation = Math.atan2(100, dx) * 0.5;
}
function createJumpTrail() {
for (let i = 0; i < 5; i++) {
jumpTrails.push({
x: player.x + player.width / 2,
y: player.y + player.height,
size: Math.random() * 4 + 2,
alpha: 1,
life: 20
});
}
}
function updateJumpTrails() {
for (let i = jumpTrails.length - 1; i >= 0; i--) {
const trail = jumpTrails[i];
trail.y += 2;
trail.alpha -= 0.05;
trail.life--;
if (trail.life <= 0) {
jumpTrails.splice(i, 1);
}
}
}
function update() {
if (!gameStarted || gameOver) return;
updatePlatforms();
updatePlayer();
player.onLadder = false;
player.onPlatform = false;
player.onGround = false;
checkCollisions();
updateCamera();
collectDiamonds();
updateJumpTrails();
updateParticles();
checkEndPoint();
}
// ==================== 修复Bug 2: 平台移动时的玩家跟随问题 ====================
function updatePlatforms() {
// 先检查玩家是否还在平台上
if (player.lastPlatform) {
const platform = player.lastPlatform;
const playerBottom = player.y + player.height;
const platformTop = platform.y;
// 如果玩家底部不在平台顶部5像素内,则离开平台
if (Math.abs(playerBottom - platformTop) > 5) {
player.onPlatform = false;
player.lastPlatform = null;
}
}
platforms.forEach(platform => {
platform.prevX = platform.x;
platform.prevY = platform.y;
// 移动平台
platform.x += platform.speed * platform.direction;
if (platform.verticalSpeed) {
platform.y += platform.verticalSpeed * platform.verticalDirection;
}
// 边界检测
if (platform.x <= platform.originalX - 100 || platform.x >= platform.originalX + 100) {
platform.direction *= -1;
}
if (platform.verticalSpeed && (platform.y <= platform.minY || platform.y >= platform.maxY)) {
platform.verticalDirection *= -1;
}
// 计算平台移动距离
const dx = platform.x - platform.prevX;
const dy = platform.y - platform.prevY;
// 如果玩家在平台上,让玩家随平台移动
if (player.onPlatform && player.lastPlatform === platform) {
player.x += dx;
player.y += dy;
}
});
}
function checkSolidCollision(rect) {
const items = [...tiles, ...obstacles, ...platforms];
for (const item of items) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if (
rect.x < item.x + item.w &&
rect.x + rect.width > item.x &&
rect.y < item.y + item.h &&
rect.y + rect.height > item.y
) {
return true;
}
}
}
return false;
}
function checkCollision(rect1, rect2) {
return (
rect1.x < rect2.x + rect2.w &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.h &&
rect1.y + rect1.height > rect2.y
);
}
function checkCollisions() {
// 检查致命障碍物
[...obstacles, ...platforms].forEach(item => {
if (item.type && item.type.fatal) {
// 修复Bug 3: 尖刺碰撞检测
if (item.type === TILE_TYPES.SPIKE) {
const spikeRect = {
x: item.x,
y: item.y + item.h - 24,
width: item.w,
height: 24
};
const playerBottom = player.y + player.height;
const spikeTop = spikeRect.y;
if (playerBottom > spikeTop - 5 && playerBottom < spikeTop + 10 &&
player.x + player.width > spikeRect.x &&
player.x < spikeRect.x + spikeRect.width) {
createDeathParticles();
endGame('撞上尖刺!');
return;
}
} else {
if (checkCollision(player, item)) {
createDeathParticles();
endGame(`撞上${item.type === TILE_TYPES.LAVA ? '岩浆' : '致命障碍物'}!`);
return;
}
}
}
});
// ==================== 修复Bug 6: 梯子交互逻辑问题 ====================
let onLadder = false;
obstacles.forEach(item => {
if (item.type === TILE_TYPES.LADDER) {
const ladderRect = {
x: item.x,
y: item.y,
w: item.w,
h: item.h
};
const playerCenterX = player.x + player.width / 2;
const playerCenterY = player.y + player.height / 2;
// 放宽梯子碰撞检测
if (playerCenterX > item.x - 16 &&
playerCenterX < item.x + item.w + 16 &&
playerCenterY > item.y &&
playerCenterY < item.y + item.h) {
// 如果按W键并且玩家接近梯子顶部,可以爬上去
if (keys['w'] && player.y + player.height < item.y + item.h + 10) {
onLadder = true;
player.onLadder = true;
player.y -= 3;
}
// 如果按S键并且玩家在梯子顶部,可以爬下来
else if (keys['s'] && player.y + player.height > item.y) {
onLadder = true;
player.onLadder = true;
player.y += 3;
}
// 如果玩家站在梯子范围内,可以爬上爬下
else if (Math.abs(playerCenterX - (item.x + item.w/2)) < 20) {
onLadder = true;
player.onLadder = true;
}
if (player.onLadder) {
player.velY = 0;
player.isJumping = false;
}
}
}
});
if (!onLadder) {
player.onLadder = false;
}
// 修复Bug 4: 玩家碰撞检测中的陷地问题
const allSolids = [...tiles, ...obstacles, ...platforms];
player.onGround = false;
player.onPlatform = false;
player.lastPlatform = null;
if (player.velY > 0) {
const nextY = player.y + player.velY;
const bottom = nextY + player.height;
for (const item of allSolids) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if (bottom > item.y && bottom < item.y + 20 &&
player.x + player.width > item.x &&
player.x < item.x + item.w) {
player.y = item.y - player.height;
player.velY = 0;
player.onGround = true;
player.isJumping = false;
if (platforms.includes(item)) {
player.onPlatform = true;
player.lastPlatform = item;
}
break;
}
}
}
}
if (player.velX !== 0) {
const nextX = player.x + player.velX;
for (const item of allSolids) {
if (item.type && (item.type.solid || item.type === TILE_TYPES.PILLAR)) {
if ((player.velX > 0 && nextX + player.width > item.x && player.x < item.x &&
player.y + player.height > item.y && player.y < item.y + item.h) ||
(player.velX < 0 && nextX < item.x + item.w && player.x > item.x &&
player.y + player.height > item.y && player.y < item.y + item.h)) {
if (player.velX > 0) {
player.x = item.x - player.width;
} else {
player.x = item.x + item.w;
}
player.velX = 0;
break;
}
}
}
}
// ==================== 修复Bug 3: 弹簧碰撞检测 ====================
springs.forEach(spring => {
const springRect = {
x: spring.x,
y: spring.y,
w: spring.w,
h: spring.h
};
// 只从顶部碰撞才触发弹簧
if (player.x + player.width > springRect.x &&
player.x < springRect.x + springRect.w &&
player.y + player.height > springRect.y &&
player.y + player.height < springRect.y + 15) { // 只检测顶部15像素
player.velY = -15;
player.isJumping = true;
player.onGround = false;
player.onPlatform = false;
createJumpTrail();
createSpringEffect(spring);
}
});
if (player.y > DEATH_LINE) {
createDeathParticles();
endGame('掉出地图啦!');
return;
}
}
// 修复Bug 6: 终点检测问题
function checkEndPoint() {
endPoints.forEach(endPoint => {
const playerRect = {
x: player.x,
y: player.y,
width: player.width,
height: player.height
};
const endRect = {
x: endPoint.x,
y: endPoint.y,
width: endPoint.w,
height: endPoint.h
};
if (playerRect.x < endRect.x + endRect.width &&
playerRect.x + playerRect.width > endRect.x &&
playerRect.y < endRect.y + endRect.height &&
playerRect.y + playerRect.height > endRect.y) {
if (!gameOver) {
winGame();
}
}
});
}
function createSpringEffect(spring) {
for (let i = 0; i < 15; i++) {
particles.push({
x: spring.x + spring.w / 2,
y: spring.y,
size: Math.random() * 6 + 3,
color: '#FFFF00',
alpha: 1,
life: 30,
velX: (Math.random() - 0.5) * 4,
velY: -Math.random() * 6
});
}
}
// ==================== 性能优化: 限制粒子数量 ====================
function createDeathParticles() {
// 限制最多100个粒子
if (particles.length > 100) return;
for (let i = 0; i < 15; i++) { // 减少粒子数量
particles.push({
x: player.x + player.width / 2,
y: player.y + player.height / 2,
size: Math.random() * 8 + 3, // 减小大小
color: '#FF4500',
alpha: 1,
life: 40, // 减少生命周期
velX: (Math.random() - 0.5) * 8,
velY: (Math.random() - 0.5) * 8
});
}
}
function updateParticles() {
for (let i = particles.length - 1; i >= 0; i--) {
const particle = particles[i];
particle.x += particle.velX;
particle.y += particle.velY;
particle.alpha -= 0.04; // 加快消失
particle.life--;
if (particle.life <= 0 || particle.alpha <= 0) {
particles.splice(i, 1);
}
}
}
function updateCamera() {
targetCameraOffset.x = player.x - CANVAS_WIDTH / 2;
targetCameraOffset.y = player.y - CANVAS_HEIGHT / 2;
cameraOffset.x += (targetCameraOffset.x - cameraOffset.x) * CAMERA_SMOOTHING;
cameraOffset.y += (targetCameraOffset.y - cameraOffset.y) * CAMERA_SMOOTHING;
cameraOffset.x = Math.max(0, Math.min(cameraOffset.x, levelGoal));
cameraOffset.y = Math.max(0, Math.min(cameraOffset.y, CANVAS_HEIGHT - GROUND_HEIGHT));
}
function collectDiamonds() {
diamonds.forEach(diamond => {
if (!diamond.collected && checkCollision(player, diamond)) {
diamond.collected = true;
diamondsCollected++;
score += 100;
document.getElementById('score-display').textContent = score;
for (let i = 0; i < 10; i++) {
particles.push({
x: diamond.x + diamond.w / 2,
y: diamond.y + diamond.h / 2,
size: Math.random() * 4 + 2,
color: '#00FFFF',
alpha: 1,
life: 30,
velX: (Math.random() - 0.5) * 3,
velY: (Math.random() - 0.5) * 3
});
}
}
});
}
function respawnAtStart() {
resetKeys();
player.x = 100;
player.y = CANVAS_HEIGHT - GROUND_HEIGHT - player.height;
player.velX = 0;
player.velY = 0;
player.isJumping = false;
player.isCrouching = false;
player.isForcedCrouching = false;
player.height = player.originalHeight;
player.speed = PLAYER_SPEED;
player.platformVelX = 0;
player.platformVelY = 0;
cameraOffset = { x: 0, y: 0 };
targetCameraOffset = { x: 0, y: 0 };
document.getElementById('game-over-modal').style.display = 'none';
gameStarted = true;
gameOver = false;
gameLoop();
}
// ==================== 渲染系统 ====================
function draw() {
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT - GROUND_HEIGHT);
drawClouds();
drawTiles();
drawObstacles();
drawPlatforms();
drawSprings();
drawDiamonds();
drawEndPoints();
drawPlayer();
drawJumpTrails();
drawParticles();
}
function drawClouds() {
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.beginPath();
ctx.arc(200 - cameraOffset.x * 0.2, 80 - cameraOffset.y * 0.1, 20, 0, Math.PI * 2);
ctx.arc(230 - cameraOffset.x * 0.2, 70 - cameraOffset.y * 0.1, 25, 0, Math.PI * 2);
ctx.arc(260 - cameraOffset.x * 0.2, 80 - cameraOffset.y * 0.1, 20, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(500 - cameraOffset.x * 0.2, 120 - cameraOffset.y * 0.1, 25, 0, Math.PI * 2);
ctx.arc(540 - cameraOffset.x * 0.2, 110 - cameraOffset.y * 0.1, 30, 0, Math.PI * 2);
ctx.arc(580 - cameraOffset.x * 0.2, 120 - cameraOffset.y * 0.1, 25, 0, Math.PI * 2);
ctx.fill();
}
// ==================== 性能优化: 视锥体剔除 ====================
function drawTiles() {
tiles.forEach(item => {
const x = item.x - cameraOffset.x;
const y = item.y - cameraOffset.y;
// 只在视口内绘制
if (x > -TILE_SIZE * 2 && x < CANVAS_WIDTH + TILE_SIZE * 2 &&
y > -TILE_SIZE * 2 && y < CANVAS_HEIGHT + TILE_SIZE * 2) {
TEXTURES[item.type.texture].draw(ctx, x, y, item.w, item.h);
}
});
}
function drawObstacles() {
obstacles.forEach(item => {
const x = item.x - cameraOffset.x;
const y = item.y - cameraOffset.y;
if (x > -item.w - 50 && x < CANVAS_WIDTH + item.w + 50) {
if (item.type.texture) {
TEXTURES[item.type.texture].draw(ctx, x, y, item.w, item.h);
} else {
ctx.fillStyle = item.type.color;
ctx.fillRect(x, y, item.w, item.h);
}
}
});
}
function drawPlatforms() {
platforms.forEach(platform => {
const x = platform.x - cameraOffset.x;
const y = platform.y - cameraOffset.y;
if (x > -platform.w - 50 && x < CANVAS_WIDTH + platform.w + 50) {
if (platform.type.texture) {
TEXTURES[platform.type.texture].draw(ctx, x, y, platform.w, platform.h);
} else {
ctx.fillStyle = platform.type.color;
ctx.fillRect(x, y, platform.w, platform.h);
}
}
});
}
function drawSprings() {
springs.forEach(spring => {
const x = spring.x - cameraOffset.x;
const y = spring.y - cameraOffset.y;
if (x > -spring.w - 50 && x < CANVAS_WIDTH + spring.w + 50) {
if (spring.type.texture) {
TEXTURES[spring.type.texture].draw(ctx, x, y, spring.w, spring.h);
} else {
ctx.fillStyle = spring.type.color;
ctx.fillRect(x, y, spring.w, spring.h);
}
}
});
}
function drawDiamonds() {
diamonds.forEach(diamond => {
if (!diamond.collected) {
const x = diamond.x - cameraOffset.x;
const y = diamond.y - cameraOffset.y;
if (x > -diamond.w - 50 && x < CANVAS_WIDTH + diamond.w + 50) {
ctx.fillStyle = '#00FFFF';
ctx.beginPath();
ctx.moveTo(x + diamond.w/2, y);
ctx.lineTo(x + diamond.w, y + diamond.h/2);
ctx.lineTo(x + diamond.w/2, y + diamond.h);
ctx.lineTo(x, y + diamond.h/2);
ctx.closePath();
ctx.fill();
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.beginPath();
ctx.arc(x + diamond.w/2, y + diamond.h/2, 5, 0, Math.PI * 2);
ctx.fill();
}
}
});
}
function drawEndPoints() {
endPoints.forEach(endPoint => {
const x = endPoint.x - cameraOffset.x;
const y = endPoint.y - cameraOffset.y;
if (x > -60 - 50 && x < CANVAS_WIDTH + 60 + 50) {
ctx.fillStyle = '#FFD700';
ctx.fillRect(x, y + 60, 60, 20);
ctx.strokeStyle = '#B8860B';
ctx.lineWidth = 2;
ctx.strokeRect(x, y + 60, 60, 20);
ctx.fillStyle = '#000';
ctx.fillRect(x + 60, y, 5, 80);
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 4; j++) {
ctx.fillStyle = (i + j) % 2 === 0 ? '#000' : '#fff';
ctx.fillRect(x + 65 + j * 10, y + i * 10, 10, 10);
}
}
ctx.fillStyle = '#000';
ctx.font = '14px Courier New';
ctx.fillText('终点', x + 10, y + 75);
}
});
}
// ==================== 游戏体验改进: 添加简单动画 ====================
function drawPlayer() {
const x = player.x - cameraOffset.x;
const y = player.y - cameraOffset.y;
// 添加跳跃动画
let bobOffset = 0;
if (player.isJumping) {
bobOffset = Math.sin(Date.now() / 50) * 2;
}
ctx.fillStyle = '#3d85c6';
ctx.fillRect(x + 4, y + 10 + bobOffset, player.width - 8, player.height - 10);
// 头部旋转动画
const headBob = Math.sin(Date.now() / 100) * 1;
ctx.fillStyle = '#f1c27d';
ctx.fillRect(x + 6, y + headBob, player.width - 12, 10);
// 眼睛
ctx.fillStyle = '#000';
if (player.facingRight) {
ctx.fillRect(x + player.width - 8, y + 3 + headBob, 2, 2);
} else {
ctx.fillRect(x + 6, y + 3 + headBob, 2, 2);
}
}
function drawJumpTrails() {
jumpTrails.forEach(trail => {
const x = trail.x - cameraOffset.x;
const y = trail.y - cameraOffset.y;
ctx.fillStyle = `rgba(255, 255, 255, ${trail.alpha})`;
ctx.beginPath();
ctx.arc(x, y, trail.size, 0, Math.PI * 2);
ctx.fill();
});
}
function drawParticles() {
particles.forEach(particle => {
const x = particle.x - cameraOffset.x;
const y = particle.y - cameraOffset.y;
ctx.fillStyle = `${particle.color}${Math.floor(particle.alpha * 255).toString(16).padStart(2, '0')}`;
ctx.beginPath();
ctx.arc(x, y, particle.size, 0, Math.PI * 2);
ctx.fill();
});
}
// 游戏循环控制
let gameLoopId = null;
function gameLoop() {
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
}
if (!gameStarted || gameOver) return;
update();
draw();
gameLoopId = requestAnimationFrame(gameLoop);
}
// ==================== 游戏流程控制 ====================
function showHelp() {
document.getElementById('help-modal').style.display = 'block';
}
function endGame(reason) {
gameOver = true;
gameStarted = false;
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
gameLoopId = null;
}
function continueParticles() {
if (particles.length > 0) {
updateParticles();
draw();
requestAnimationFrame(continueParticles);
}
}
createDeathParticles();
document.getElementById('game-over-title').textContent = "游戏结束";
document.getElementById('game-over-reason').textContent = reason;
document.getElementById('diamonds-collected').textContent = diamondsCollected;
document.getElementById('final-score').textContent = score;
document.getElementById('game-over-modal').style.display = 'block';
continueParticles();
}
function winGame() {
gameOver = true;
gameStarted = false;
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
gameLoopId = null;
}
document.getElementById('win-diamonds').textContent = diamondsCollected;
document.getElementById('win-score').textContent = score;
document.getElementById('win-modal').style.display = 'block';
}
// ==================== 修复Bug 4: 游戏重启时的状态问题 ====================
function restartGame() {
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
gameLoopId = null;
}
// 清理所有粒子效果
particles = [];
jumpTrails = [];
document.getElementById('game-over-modal').style.display = 'none';
document.getElementById('win-modal').style.display = 'none';
currentSeed = generateRandomSeed();
generateLevel(currentSeed);
gameStarted = true;
gameOver = false;
score = 0;
diamondsCollected = 0;
document.getElementById('score-display').textContent = score;
gameLoop();
}
function playAgain() {
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
gameLoopId = null;
}
// 清理所有粒子效果
particles = [];
jumpTrails = [];
document.getElementById('win-modal').style.display = 'none';
generateLevel(currentSeed);
gameStarted = true;
gameOver = false;
score = 0;
diamondsCollected = 0;
document.getElementById('score-display').textContent = score;
gameLoop();
}
function newWorld() {
if (gameLoopId) {
cancelAnimationFrame(gameLoopId);
gameLoopId = null;
}
// 清理所有粒子效果
particles = [];
jumpTrails = [];
document.getElementById('win-modal').style.display = 'none';
currentSeed = generateRandomSeed();
generateLevel(currentSeed);
gameStarted = true;
gameOver = false;
score = 0;
diamondsCollected = 0;
document.getElementById('score-display').textContent = score;
gameLoop();
}
// 启动游戏
init();
</script>
</body>
</html>


可以直接在ourcraft.xin上游玩,这是我的网站谢谢