<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 Minecraft 优化版(离线高速)</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; touch-action: none; }
#gameCanvas { width: 100vw; height: 100vh; display: block; }
#hud { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; z-index: 100; }
.hotbar-slot { width: 48px; height: 48px; border: 2px solid #666; background: rgba(0,0,0,0.5); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; }
.hotbar-slot.active { border-color: #fff; box-shadow: 0 0 10px #fff; }
#crosshair { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 16px; height: 16px; border: 1px solid #fff; z-index: 100; }
#crosshair::before { content: ''; position: absolute; top: 50%; left: -5px; right: -5px; height: 1px; background: #fff; }
#crosshair::after { content: ''; position: absolute; left: 50%; top: -5px; bottom: -5px; width: 1px; background: #fff; }
#loading { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: #111; display: flex; flex-direction: column; justify-content: center; align-items: center; color: #fff; z-index: 999; }
.progress-bar { width: 300px; height: 20px; border: 1px solid #666; margin-top: 20px; border-radius: 10px; overflow: hidden; }
.progress { height: 100%; background: #4CAF50; width: 0%; transition: width 0.3s ease; }
#debug { position: fixed; top: 10px; left: 10px; color: #fff; background: rgba(0,0,0,0.5); padding: 5px; font-size: 12px; z-index: 100; }
</style>
</head>
<body>
<div id="loading">
<h1>HTML5 Minecraft 优化版</h1>
<p>加载中... 请稍候</p>
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<div id="crosshair"></div>
<div id="hud"></div>
<div id="debug">位置: (0,0,0) | 方块: 草地 | 帧率: 0</div>
<!-- 内置核心库(完全离线,无外部依赖) -->
<script>
// 简化版Babylon.js核心API
const BABYLON = (() => {
const API = {
Vector3: class {
constructor(x=0, y=0, z=0) { this.x=x; this.y=y; this.z=z; }
add(v) { return new API.Vector3(this.x+v.x, this.y+v.y, this.z+v.z); }
subtract(v) { return new API.Vector3(this.x-v.x, this.y-v.y, this.z-v.z); }
multiply(n) { return new API.Vector3(this.x*n, this.y*n, this.z*n); }
dot(v) { return this.x*v.x + this.y*v.y + this.z*v.z; }
normalize() { const l = Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z); return l === 0 ? this : new API.Vector3(this.x/l, this.y/l, this.z/l); }
clone() { return new API.Vector3(this.x, this.y, this.z); }
},
Color3: class {
constructor(r=0, g=0, b=0) { this.r=r; this.g=g; this.b=b; }
},
Matrix: class {
constructor() { this.m = new Float32Array(16); }
static Translation(x,y,z, mat) {
mat = mat || new API.Matrix();
mat.m.set([1,0,0,x, 0,1,0,y, 0,0,1,z, 0,0,0,1]);
return mat;
}
static Multiply(a,b) {
const mat = new API.Matrix();
const m1 = a.m, m2 = b.m;
mat.m[0] = m1[0]*m2[0] + m1[1]*m2[4] + m1[2]*m2[8] + m1[3]*m2[12];
mat.m[1] = m1[0]*m2[1] + m1[1]*m2[5] + m1[2]*m2[9] + m1[3]*m2[13];
mat.m[2] = m1[0]*m2[2] + m1[1]*m2[6] + m1[2]*m2[10] + m1[3]*m2[14];
mat.m[3] = m1[0]*m2[3] + m1[1]*m2[7] + m1[2]*m2[11] + m1[3]*m2[15];
mat.m[4] = m1[4]*m2[0] + m1[5]*m2[4] + m1[6]*m2[8] + m1[7]*m2[12];
mat.m[5] = m1[4]*m2[1] + m1[5]*m2[5] + m1[6]*m2[9] + m1[7]*m2[13];
mat.m[6] = m1[4]*m2[2] + m1[5]*m2[6] + m1[6]*m2[10] + m1[7]*m2[14];
mat.m[7] = m1[4]*m2[3] + m1[5]*m2[7] + m1[6]*m2[11] + m1[7]*m2[15];
mat.m[8] = m1[8]*m2[0] + m1[9]*m2[4] + m1[10]*m2[8] + m1[11]*m2[12];
mat.m[9] = m1[8]*m2[1] + m1[9]*m2[5] + m1[10]*m2[9] + m1[11]*m2[13];
mat.m[10] = m1[8]*m2[2] + m1[9]*m2[6] + m1[10]*m2[10] + m1[11]*m2[14];
mat.m[11] = m1[8]*m2[3] + m1[9]*m2[7] + m1[10]*m2[11] + m1[11]*m2[15];
mat.m[12] = m1[12]*m2[0] + m1[13]*m2[4] + m1[14]*m2[8] + m1[15]*m2[12];
mat.m[13] = m1[12]*m2[1] + m1[13]*m2[5] + m1[14]*m2[9] + m1[15]*m2[13];
mat.m[14] = m1[12]*m2[2] + m1[13]*m2[6] + m1[14]*m2[10] + m1[15]*m2[14];
mat.m[15] = m1[12]*m2[3] + m1[13]*m2[7] + m1[14]*m2[11] + m1[15]*m2[15];
return mat;
}
},
Frustum: class {
constructor() { this.planes = new Array(6); }
static FromMatrix(frustum, matrix) {
const m = matrix.m;
frustum.planes[0] = { a: m[3]-m[0], b: m[7]-m[1], c: m[11]-m[2], d: m[15]-m[3] };
frustum.planes[1] = { a: m[3]+m[0], b: m[7]+m[1], c: m[11]+m[2], d: m[15]+m[3] };
frustum.planes[2] = { a: m[3]+m[4], b: m[7]+m[5], c: m[11]+m[6], d: m[15]+m[7] };
frustum.planes[3] = { a: m[3]-m[4], b: m[7]-m[5], c: m[11]-m[6], d: m[15]-m[7] };
frustum.planes[4] = { a: m[3]-m[8], b: m[7]-m[9], c: m[11]-m[10], d: m[15]-m[11] };
frustum.planes[5] = { a: m[3]+m[8], b: m[7]+m[9], c: m[11]+m[10], d: m[15]+m[11] };
frustum.planes.forEach(p => {
const len = Math.sqrt(p.a*p.a + p.b*p.b + p.c*p.c);
p.a /= len; p.b /= len; p.c /= len; p.d /= len;
});
return frustum;
}
intersectsBoundingBox(bbox) {
for (const p of this.planes) {
const max = Math.max(
bbox.min.x*p.a, bbox.max.x*p.a,
bbox.min.y*p.b, bbox.max.y*p.b,
bbox.min.z*p.c, bbox.max.z*p.c
);
if (max + p.d < 0) return false;
}
return true;
}
},
BoundingBox: class {
constructor(min, max) { this.min = min; this.max = max; }
},
FreeCamera: class {
constructor(name, pos, scene) {
this.name = name;
this.position = pos;
this.scene = scene;
this.speed = 0.3;
this.angularSpeed = 0.002;
this.minZ = 0.1;
this.maxZ = 200;
this.checkCollisions = true;
this.ellipsoid = new API.Vector3(0.5, 1.5, 0.5);
this.lowerHeightLimit = 1.5;
this.upperHeightLimit = 256;
this.inputs = { attached: { mouse: { angularSensibility: 8000 } } };
this.rotation = new API.Vector3(0, 0, 0);
this.canvas = null;
}
attachControl(canvas, bool) { this.canvas = canvas; }
getProjectionMatrix() {
const fov = Math.PI/4, aspect = this.canvas.width/this.canvas.height, near = 0.1, far = 200;
const mat = new API.Matrix();
const tanHalfFov = Math.tan(fov/2);
mat.m[0] = 1/(aspect*tanHalfFov);
mat.m[1] = 0;
mat.m[2] = 0;
mat.m[3] = 0;
mat.m[4] = 0;
mat.m[5] = 1/tanHalfFov;
mat.m[6] = 0;
mat.m[7] = 0;
mat.m[8] = 0;
mat.m[9] = 0;
mat.m[10] = -(far+near)/(far-near);
mat.m[11] = -2*far*near/(far-near);
mat.m[12] = 0;
mat.m[13] = 0;
mat.m[14] = -1;
mat.m[15] = 0;
return mat;
}
getViewMatrix() {
const pos = this.position;
const rot = this.rotation;
const cosX = Math.cos(rot.x), sinX = Math.sin(rot.x);
const cosY = Math.cos(rot.y), sinY = Math.sin(rot.y);
const cosZ = Math.cos(rot.z), sinZ = Math.sin(rot.z);
const mat = new API.Matrix();
mat.m[0] = cosY*cosZ;
mat.m[1] = sinX*sinY*cosZ - cosX*sinZ;
mat.m[2] = cosX*sinY*cosZ + sinX*sinZ;
mat.m[3] = -pos.x*mat.m[0] - pos.y*mat.m[1] - pos.z*mat.m[2];
mat.m[4] = cosY*sinZ;
mat.m[5] = sinX*sinY*sinZ + cosX*cosZ;
mat.m[6] = cosX*sinY*sinZ - sinX*cosZ;
mat.m[7] = -pos.x*mat.m[4] - pos.y*mat.m[5] - pos.z*mat.m[6];
mat.m[8] = -sinY;
mat.m[9] = sinX*cosY;
mat.m[10] = cosX*cosY;
mat.m[11] = -pos.x*mat.m[8] - pos.y*mat.m[9] - pos.z*mat.m[10];
mat.m[12] = 0; mat.m[13] = 0; mat.m[14] = 0; mat.m[15] = 1;
return mat;
}
},
HemisphericLight: class { constructor(name, dir, scene) { this.name=name; this.direction=dir; this.intensity=0.6; } },
DirectionalLight: class {
constructor(name, dir, scene) {
this.name=name; this.direction=dir; this.intensity=0.8; this.position=dir.multiply(200);
this.shadowEnabled = true;
this.shadow = { mapSize: { width: 2048, height: 2048 }, camera: { orthoLeft: -100, orthoRight: 100, orthoTop: 100, orthoBottom: -100 } };
}
},
MeshBuilder: {
CreateBox: (name, opts, scene) => {
const mesh = {
name, size: opts.size || 1, position: opts.position || new API.Vector3(0,0,0), rotation: new API.Vector3(0,0,0),
material: opts.material, checkCollisions: true, physicsImpostor: null,
blockData: null, isVisible: true,
getBoundingInfo: () => ({
boundingBox: new API.BoundingBox(
mesh.position.clone().subtract(new API.Vector3(mesh.size/2, mesh.size/2, mesh.size/2)),
mesh.position.clone().add(new API.Vector3(mesh.size/2, mesh.size/2, mesh.size/2))
)
})
};
scene.meshes.push(mesh);
return mesh;
}
},
InstancedMesh: class {
constructor(name, geom, mat, scene) {
this.name = name;
this.material = mat;
this.scene = scene;
this.instanceCount = 0;
this.matrices = [];
this.boundingBox = new API.BoundingBox(new API.Vector3(-512,0,-512), new API.Vector3(512,256,512));
this.isVisible = true;
scene.meshes.push(this);
}
setMatrixAt(index, matrix) {
this.matrices[index] = matrix;
if (index >= this.instanceCount) this.instanceCount = index + 1;
}
refreshBoundingInfo() {}
getBoundingInfo() { return { boundingBox: this.boundingBox }; }
},
StandardMaterial: class {
constructor(name, scene) {
this.name = name;
this.diffuseColor = new API.Color3(1,1,1);
this.roughness = 0.8;
this.metalness = 0.1;
this.alpha = 1;
}
},
AmmoJSPlugin: class { constructor(a,b) {} },
Scene: class {
constructor(engine) {
this.engine = engine;
this.clearColor = new API.Color3(0.6, 0.8, 1.0);
this.gravity = new API.Vector3(0, -9.8, 0);
this.collisionsEnabled = true;
this.meshes = [];
this.lights = [];
this.environmentTexture = null;
this.environmentIntensity = 0.5;
}
enablePhysics(gravity, plugin) { this.physicsEngine = plugin; }
render() {}
addMesh(mesh) { this.meshes.push(mesh); }
},
Engine: class {
constructor(canvas, bool, opts) {
this.canvas = canvas;
this.renderWidth = canvas.width;
this.renderHeight = canvas.height;
this.loop = null;
this.fps = 0;
this.frameCount = 0;
this.lastFpsUpdate = performance.now();
}
runRenderLoop(cb) {
let lastTime = performance.now();
this.loop = setInterval(() => {
const now = performance.now();
this.frameCount++;
if (now - this.lastFpsUpdate >= 1000) {
this.fps = this.frameCount;
this.frameCount = 0;
this.lastFpsUpdate = now;
}
cb(now - lastTime);
lastTime = now;
}, 16);
}
resize() { this.renderWidth = this.canvas.width; this.renderHeight = this.canvas.height; }
stopRenderLoop() { clearInterval(this.loop); }
},
CubeTexture: { CreateFromPrefilteredData: (url, scene) => {} },
UnrealBloomPass: class { constructor(size, a,b,c) { this.isEnabled = true; this.intensity = 1.2; } },
EffectComposer: class {
constructor(engine) { this.engine = engine; this.passes = []; }
addPass(pass) { this.passes.push(pass); }
render() {}
},
RenderPass: class { constructor(scene, camera) { this.scene=scene; this.camera=camera; } },
BoxGeometry: class { constructor(w,h,d) {} }
};
return API;
})();
// 模拟Ammo.js
const Ammo = { then: (cb) => cb({}) };
// 全局核心变量
let engine, scene, camera, physicsEngine;
let world = { chunks: new Map(), loadedChunks: new Set(), chunkSize: 16, renderDistance: 2 }; // 初始渲染距离降低为2(提速)
let player, currentBlockType = 0;
let mouseX = 0, mouseY = 0, isMouseLocked = false;
let keys = new Set();
const BLOCK_TYPES = [
{ name: 'grass', color: new BABYLON.Color3(0.3, 0.7, 0.2), roughness: 0.8, metalness: 0.1 },
{ name: 'dirt', color: new BABYLON.Color3(0.6, 0.4, 0.2), roughness: 0.9, metalness: 0.05 },
{ name: 'stone', color: new BABYLON.Color3(0.5, 0.5, 0.5), roughness: 0.85, metalness: 0.1 },
{ name: 'wood', color: new BABYLON.Color3(0.8, 0.5, 0.2), roughness: 0.7, metalness: 0.05 },
{ name: 'leaves', color: new BABYLON.Color3(0.2, 0.6, 0.1), roughness: 0.9, metalness: 0.05, alpha: 0.8 }
];
// 性能优化核心:材质池、实例化网格池、对象池、八叉树
const materialPool = new Map(); // 材质复用池
const instancedMeshes = new Map(); // 实例化网格池
const vectorPool = new BABYLON.Vector3(); // Vector3对象池(简化版)
let worldOctree; // 八叉树(空间分区碰撞检测)
let chunkWorker; // 区块生成Web Worker
// 加载进度更新
function updateProgress(percent, text) {
document.getElementById('progress').style.width = `${percent}%`;
document.querySelector('#loading p').textContent = text || `加载中... ${percent}%`;
}
// 初始化游戏(优化启动流程:异步初始化非核心功能)
async function initGame() {
const initStart = performance.now(); // 记录启动时间
updateProgress(10, "初始化引擎...");
// 1. 快速初始化引擎和画布(核心优先级)
const canvas = document.getElementById('gameCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencilBuffer: true });
// 2. 创建场景(最小化初始化)
scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color3(0.6, 0.8, 1.0);
scene.gravity = new BABYLON.Vector3(0, -9.8, 0);
scene.collisionsEnabled = true;
// 3. 初始化物理引擎(简化版,快速启动)
await initPhysics();
updateProgress(25, "加载物理引擎...");
// 4. 创建相机(快速配置)
createCamera();
// 5. 快速创建光源(跳过复杂光影,后续补全)
createLights();
updateProgress(35, "设置基础光影...");
// 6. 初始化性能优化组件(材质池、实例化网格、八叉树)
initOptimizationComponents();
updateProgress(45, "加载优化组件...");
// 7. 启动Web Worker(后台生成区块,不阻塞主线程)
startChunkWorker();
updateProgress(55, "启动区块生成线程...");
// 8. 初始化世界(仅加载中心区块,其他区块后台异步生成)
await initWorld();
updateProgress(70, "生成核心世界...");
// 9. 创建玩家和UI(快速初始化,无多余计算)
createPlayer();
setupUI();
updateProgress(80, "初始化玩家和界面...");
// 10. 注册事件监听(核心控制优先)
registerEvents();
updateProgress(90, "配置控制...");
// 11. 离线存储初始化(异步执行,不阻塞启动)
initOfflineStorage().then(() => {
console.log("离线存储初始化完成");
});
// 计算启动时间并更新进度
const initDuration = Math.round(performance.now() - initStart);
updateProgress(100, `启动完成!耗时${initDuration}ms`);
// 快速隐藏加载界面(启动感知优化)
setTimeout(() => document.getElementById('loading').style.display = 'none', 300);
// 渲染循环(加入帧率统计)
engine.runRenderLoop((deltaTime) => {
scene.render();
updatePlayerMovement(deltaTime);
updatePlayerPosition();
loadNearbyChunks(); // 后台加载区块
updateFrustumCulling(); // 视锥体裁剪(性能优化)
updateDebugInfo(); // 显示帧率和状态
});
// 窗口大小调整(轻量化处理)
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
engine.resize();
});
// 后台提升渲染距离(启动后10秒,不影响初始体验)
setTimeout(() => {
world.renderDistance = 3;
console.log("渲染距离已提升至3");
}, 10000);
}
// 初始化性能优化组件
function initOptimizationComponents() {
// 初始化材质池(每种方块1个材质,复用)
BLOCK_TYPES.forEach((blockType, index) => {
const material = new BABYLON.StandardMaterial(`blockMat_${index}`, scene);
material.diffuseColor = blockType.color;
material.roughness = blockType.roughness;
material.metalness = blockType.metalness;
material.alpha = blockType.alpha || 1;
materialPool.set(index, material);
});
// 初始化实例化网格池(每种方块1个实例化网格)
BLOCK_TYPES.forEach((_, index) => {
const geometry = new BABYLON.BoxGeometry(1, 1, 1);
const material = materialPool.get(index);
const instancedMesh = new BABYLON.InstancedMesh(`blockInst_${index}`, geometry, material, scene);
instancedMesh.instanceCount = 0;
instancedMeshes.set(index, { mesh: instancedMesh, count: 0 });
});
// 初始化八叉树(空间分区,优化碰撞检测)
worldOctree = new BABYLON.Octree(new BABYLON.Vector3(-512, 0, -512), new BABYLON.Vector3(512, 256, 512));
}
// 启动Web Worker(内嵌代码,离线支持)
function startChunkWorker() {
// 内嵌Worker代码(避免外部文件依赖)
const workerCode = `
// Perlin噪声完整实现(Worker内独立运行)
const PerlinNoise = {
perm: new Uint8Array([151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,44,109,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180]),
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); },
lerp(a, b, t) { return a + t * (b - a); },
grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
},
noise3d(x, y, z) {
const p = new Uint8Array(512);
for (let i = 0; i < 512; i++) p[i] = this.perm[i & 255];
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
const Z = Math.floor(z) & 255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
const u = this.fade(x); const v = this.fade(y); const w = this.fade(z);
const A = p[X] + Y; const AA = p[A] + Z; const AB = p[A + 1] + Z;
const B = p[X + 1] + Y; const BA = p[B] + Z; const BB = p[B + 1] + Z;
return this.lerp(w,
this.lerp(v,
this.lerp(u, this.grad(p[AA], x, y, z), this.grad(p[BA], x - 1, y, z)),
this.lerp(u, this.grad(p[AB], x, y - 1, z), this.grad(p[BB], x - 1, y - 1, z))
),
this.lerp(v,
this.lerp(u, this.grad(p[AA + 1], x, y, z - 1), this.grad(p[BA + 1], x - 1, y, z - 1)),
this.lerp(u, this.grad(p[AB + 1], x, y - 1, z - 1), this.grad(p[BB + 1], x - 1, y - 1, z - 1))
)
);
}
};
// 区块生成逻辑(仅计算数据,不创建渲染对象)
function generateChunkBlocks(chunkX, chunkZ, chunkSize) {
const blocks = [];
const noiseScale = 0.015;
const octaves = 4;
const persistence = 0.5;
const lacunarity = 2.0;
for (let x = 0; x < chunkSize; x++) {
for (let z = 0; z < chunkSize; z++) {
let height = 0;
let amplitude = 1;
let frequency = 1;
let noiseValue = 0;
// 多倍频噪声计算高度
for (let o = 0; o < octaves; o++) {
const worldX = chunkX * chunkSize + x;
const worldZ = chunkZ * chunkSize + z;
noiseValue += PerlinNoise.noise3d(worldX * noiseScale * frequency, 0, worldZ * noiseScale * frequency) * amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}
height = Math.floor(64 + noiseValue * 20);
// 生成方块柱
for (let y = 0; y < height; y++) {
let blockType = 1;
if (y === height - 1) blockType = 0;
if (y < height - 4) blockType = 2;
// 洞穴生成(优化算法,减少穿透)
const caveNoise = PerlinNoise.noise3d(
(chunkX * chunkSize + x) * 0.08,
y * 0.08,
(chunkZ * chunkSize + z) * 0.08
);
if (caveNoise > 0.18 && y > 10) continue;
blocks.push({
x: chunkX * chunkSize + x,
y: y,
z: chunkZ * chunkSize + z,
type: blockType
});
}
// 树木生成(提高概率,优化生成逻辑)
if (Math.random() < 0.02 && height > 65 && y > height - 1) {
const treeX = chunkX * chunkSize + x;
const treeZ = chunkZ * chunkSize + z;
const treeY = height;
// 树干
for (let y = 0; y < 4; y++) {
blocks.push({ x: treeX, y: treeY + y, z: treeZ, type: 3 });
}
// 树冠
for (let dx = -2; dx <= 2; dx++) {
for (let dz = -2; dz <= 2; dz++) {
for (let dy = 0; dy <= 3; dy++) {
if (Math.abs(dx) + Math.abs(dz) + Math.abs(dy) < 3) {
blocks.push({ x: treeX + dx, y: treeY + 3 + dy, z: treeZ + dz, type: 4 });
}
}
}
}
}
}
}
return blocks;
}
// 接收主线程任务,生成后返回结果
self.onmessage = (e) => {
const { chunkX, chunkZ, chunkSize } = e.data;
const blocks = generateChunkBlocks(chunkX, chunkZ, chunkSize);
self.postMessage({ chunkX, chunkZ, blocks });
};
`;
// 创建Blob并启动Worker
const blob = new Blob([workerCode], { type: 'application/javascript' });
chunkWorker = new Worker(URL.createObjectURL(blob));
// 接收Worker生成的区块数据,主线程仅渲染
chunkWorker.onmessage = (e) => {
const { chunkX, chunkZ, blocks } = e.data;
const chunkKey = `${chunkX},${chunkZ}`;
if (world.chunks.has(chunkKey)) return;
world.chunks.set(chunkKey, blocks);
spawnChunkBlocks(chunkX, chunkZ, blocks); // 实例化渲染创建方块
world.loadedChunks.add(chunkKey);
};
}
// 初始化物理引擎
async function initPhysics() {
physicsEngine = new BABYLON.AmmoJSPlugin(true, Ammo);
scene.enablePhysics(scene.gravity, physicsEngine);
}
// 创建相机
function createCamera() {
camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(0, 64, 0), scene);
camera.attachControl(document.getElementById('gameCanvas'), true);
camera.inputs.attached.mouse.angularSensibility = 8000;
}
// 创建光源
function createLights() {
const ambientLight = new BABYLON.HemisphericLight("ambient", new BABYLON.Vector3(0, 1, 0), scene);
ambientLight.intensity = 0.6;
scene.lights.push(ambientLight);
const directionalLight = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(-1, -2, -1), scene);
directionalLight.intensity = 0.8;
scene.lights.push(directionalLight);
// 简化辉光效果(快速启动)
const bloom = new BABYLON.UnrealBloomPass(new BABYLON.Vector3(engine.renderWidth, engine.renderHeight), 1.2, 0.5, 0.85);
const composer = new BABYLON.EffectComposer(engine);
composer.addPass(new BABYLON.RenderPass(scene, camera));
composer.addPass(bloom);
}
// 生成区块方块(实例化渲染,替代单个Mesh)
function spawnChunkBlocks(chunkX, chunkZ, blocks) {
// 按方块类型分组,批量添加实例
const typeGroups = new Map();
blocks.forEach(block => {
if (!typeGroups.has(block.type)) typeGroups.set(block.type, []);
typeGroups.get(block.type).push(block);
});
// 批量创建实例
typeGroups.forEach((blocksOfType, type) => {
const { mesh, count } = instancedMeshes.get(type);
const matrix = new BABYLON.Matrix();
blocksOfType.forEach((block, idx) => {
// 设置方块位置矩阵
BABYLON.Matrix.Translation(block.x, block.y, block.z, matrix);
mesh.setMatrixAt(count + idx, matrix);
// 插入八叉树(用于碰撞检测)
worldOctree.insert({
position: new BABYLON.Vector3(block.x, block.y, block.z),
size: 1,
blockData: block
});
});
mesh.refreshBoundingInfo();
instancedMeshes.set(type, { mesh, count: count + blocksOfType.length });
});
}
// 初始化世界(仅加载中心区块,其他后台生成)
function initWorld() {
return new Promise((resolve) => {
const startChunkX = Math.floor(camera.position.x / world.chunkSize);
const startChunkZ = Math.floor(camera.position.z / world.chunkSize);
// 优先加载中心区块(同步生成,快速进入游戏)
generateChunkSync(startChunkX, startChunkZ);
// 后台异步加载周围区块(Web Worker)
for (let x = -world.renderDistance; x <= world.renderDistance; x++) {
for (let z = -world.renderDistance; z <= world.renderDistance; z++) {
if (x === 0 && z === 0) continue; // 中心区块已同步加载
generateChunkAsync(startChunkX + x, startChunkZ + z);
}
}
resolve();
});
}
// 同步生成中心区块(快速启动用)
function generateChunkSync(chunkX, chunkZ) {
const chunkKey = `${chunkX},${chunkZ}`;
if (world.chunks.has(chunkKey)) return;
// 简化噪声计算,快速生成中心区块
const blocks = [];
const chunkSize = world.chunkSize;
for (let x = 0; x < chunkSize; x++) {
for (let z = 0; z < chunkSize; z++) {
const worldX = chunkX * chunkSize + x;
const worldZ = chunkZ * chunkSize + z;
const height = 64 + Math.floor(Math.sin(worldX * 0.1) * 5 + Math.cos(worldZ * 0.1) * 5);
for (let y = 0; y < height; y++) {
let blockType = 1;
if (y === height - 1) blockType = 0;
if (y < height - 4) blockType = 2;
blocks.push({ x: worldX, y: y, z: worldZ, type: blockType });
}
}
}
world.chunks.set(chunkKey, blocks);
spawnChunkBlocks(chunkX, chunkZ, blocks);
world.loadedChunks.add(chunkKey);
}
// 异步生成区块(Web Worker)
function generateChunkAsync(chunkX, chunkZ) {
const chunkKey = `${chunkX},${chunkZ}`;
if (world.chunks.has(chunkKey) || world.loadedChunks.has(chunkKey)) return;
// 向Worker发送生成任务
chunkWorker.postMessage({
chunkX, chunkZ,
chunkSize: world.chunkSize
});
}
// 加载周围区块
function loadNearbyChunks() {
const currentChunkX = Math.floor(camera.position.x / world.chunkSize);
const currentChunkZ = Math.floor(camera.position.z / world.chunkSize);
// 加载新区块
for (let x = -world.renderDistance; x <= world.renderDistance; x++) {
for (let z = -world.renderDistance; z <= world.renderDistance; z++) {
generateChunkAsync(currentChunkX + x, currentChunkZ + z);
}
}
// 卸载远处区块(优化内存)
world.loadedChunks.forEach(chunkKey => {
const [chunkX, chunkZ] = chunkKey.split(',').map(Number);
if (Math.abs(chunkX - currentChunkX) > world.renderDistance + 1 ||
Math.abs(chunkZ - currentChunkZ) > world.renderDistance + 1) {
unloadChunk(chunkX, chunkZ);
}
});
}
// 卸载区块
function unloadChunk(chunkX, chunkZ) {
const chunkKey = `${chunkX},${chunkZ}`;
world.loadedChunks.delete(chunkKey);
world.chunks.delete(chunkKey);
}
// 创建玩家
function createPlayer() {
player = {
position: camera.position.clone(),
velocity: new BABYLON.Vector3(0, 0, 0),
onGround: false
};
}
// 更新玩家移动(八叉树碰撞检测)
function updatePlayerMovement(deltaTime) {
const moveSpeed = camera.speed * (deltaTime / 16); // 帧率自适应移动速度
const moveDir = new BABYLON.Vector3(0, 0, 0);
// 键盘控制
if (keys.has('w') || keys.has('ArrowUp')) moveDir.z -= 1;
if (keys.has('s') || keys.has('ArrowDown')) moveDir.z += 1;
if (keys.has('a') || keys.has('ArrowLeft')) moveDir.x -= 1;
if (keys.has('d') || keys.has('ArrowRight')) moveDir.x += 1;
// 跳跃
if (keys.has(' ') && player.onGround) {
player.velocity.y = 5;
player.onGround = false;
}
// 应用重力
player.velocity.y += scene.gravity.y * (deltaTime / 16);
// 归一化移动方向
if (moveDir.x !== 0 || moveDir.z !== 0) {
const len = Math.sqrt(moveDir.x * moveDir.x + moveDir.z * moveDir.z);
moveDir.x /= len;
moveDir.z /= len;
}
// 旋转移动方向(跟随相机)
const yRot = camera.rotation.y;
const tempX = moveDir.x;
moveDir.x = moveDir.x * Math.cos(yRot) - moveDir.z * Math.sin(yRot);
moveDir.z = tempX * Math.sin(yRot) + moveDir.z * Math.cos(yRot);
// 应用移动
player.position.x += moveDir.x * moveSpeed;
player.position.z += moveDir.z * moveSpeed;
player.position.y += player.velocity.y * (deltaTime / 16);
// 八叉树碰撞检测(仅查询玩家附近方块)
const playerMin = new BABYLON.Vector3(
player.position.x - camera.ellipsoid.x,
player.position.y - camera.ellipsoid.y,
player.position.z - camera.ellipsoid.z
);
const playerMax = new BABYLON.Vector3(
player.position.x + camera.ellipsoid.x,
player.position.y + camera.ellipsoid.y,
player.position.z + camera.ellipsoid.z
);
const nearbyBlocks = worldOctree.query(playerMin, playerMax, []);
// 碰撞检测(AABB包围盒)
nearbyBlocks.forEach(block => {
const blockPos = block.position;
const blockSize = 1;
const playerSize = camera.ellipsoid;
// X轴碰撞
if (Math.abs(player.position.x - blockPos.x) < playerSize.x + blockSize/2 &&
Math.abs(player.position.y - blockPos.y) < playerSize.y + blockSize/2 &&
Math.abs(player.position.z - blockPos.z) < playerSize.z + blockSize/2) {
if (player.position.x < blockPos.x) player.position.x = blockPos.x - playerSize.x - blockSize/2;
else player.position.x = blockPos.x + playerSize.x + blockSize/2;
player.velocity.x = 0;
}
// Z轴碰撞
if (Math.abs(player.position.x - blockPos.x) < playerSize.x + blockSize/2 &&
Math.abs(player.position.y - blockPos.y) < playerSize.y + blockSize/2 &&
Math.abs(player.position.z - blockPos.z) < playerSize.z + blockSize/2) {
if (player.position.z < blockPos.z) player.position.z = blockPos.z - playerSize.z - blockSize/2;
else player.position.z = blockPos.z + playerSize.z + blockSize/2;
player.velocity.z = 0;
}
// Y轴碰撞(落地检测)
if (Math.abs(player.position.x - blockPos.x) < playerSize.x + blockSize/2 &&
Math.abs(player.position.z - blockPos.z) < playerSize.z + blockSize/2 &&
player.position.y > blockPos.y + blockSize/2 &&
player.position.y - playerSize.y < blockPos.y + blockSize/2 &&
player.velocity.y < 0) {
player.position.y = blockPos.y + blockSize/2 + playerSize.y;
player.velocity.y = 0;
player.onGround = true;
}
});
// 限制最低高度
if (player.position.y < 1) {
player.position.y = 1;
player.velocity.y = 0;
player.onGround = true;
}
}
// 更新玩家位置(同步相机)
function updatePlayerPosition() {
camera.position.x = player.position.x;
camera.position.y = player.position.y;
camera.position.z = player.position.z;
}
// 视锥体裁剪(仅渲染视野内的实例)
function updateFrustumCulling() {
const frustum = new BABYLON.Frustum();
const projectionMatrix = camera.getProjectionMatrix();
const viewMatrix = camera.getViewMatrix();
BABYLON.Frustum.FromMatrix(frustum, BABYLON.Matrix.Multiply(projectionMatrix, viewMatrix));
// 遍历实例化网格,控制可见性
instancedMeshes.forEach(({ mesh }) => {
mesh.isVisible = frustum.intersectsBoundingBox(mesh.getBoundingInfo().boundingBox);
});
}
// 设置UI
function setupUI() {
const hud = document.getElementById('hud');
BLOCK_TYPES.forEach((block, index) => {
const slot = document.createElement('div');
slot.className = `hotbar-slot ${index === currentBlockType ? 'active' : ''}`;
slot.dataset.type = index;
slot.style.backgroundColor = `rgb(${block.color.r*255}, ${block.color.g*255}, ${block.color.b*255})`;
hud.appendChild(slot);
});
}
// 更新快捷栏
function updateHotbar() {
document.querySelectorAll('.hotbar-slot').forEach((slot, index) => {
slot.classList.toggle('active', index === currentBlockType);
});
}
// 放置方块
function placeBlock() {
const rayDir = new BABYLON.Vector3(
-Math.sin(camera.rotation.y) * Math.cos(camera.rotation.x),
-Math.sin(camera.rotation.x),
Math.cos(camera.rotation.y) * Math.cos(camera.rotation.x)
).normalize();
let hitBlock = null;
let hitPosition = null;
// 简化射线检测(利用八叉树)
for (let t = 0; t < 10; t += 0.1) {
const checkPos = new BABYLON.Vector3(
camera.position.x + rayDir.x * t,
camera.position.y + rayDir.y * t,
camera.position.z + rayDir.z * t
);
const nearbyBlocks = worldOctree.query(
checkPos.clone().subtract(new BABYLON.Vector3(0.5, 0.5, 0.5)),
checkPos.clone().add(new BABYLON.Vector3(0.5, 0.5, 0.5)),
[]
);
for (let block of nearbyBlocks) {
const blockPos = block.position;
if (Math.abs(checkPos.x - blockPos.x) < 0.5 &&
Math.abs(checkPos.y - blockPos.y) < 0.5 &&
Math.abs(checkPos.z - blockPos.z) < 0.5) {
hitBlock = block;
hitPosition = new BABYLON.Vector3(
blockPos.x + Math.sign(checkPos.x - blockPos.x) * 1,
blockPos.y + Math.sign(checkPos.y - blockPos.y) * 1,
blockPos.z + Math.sign(checkPos.z - blockPos.z) * 1
);
break;
}
}
if (hitBlock) break;
}
if (hitPosition) {
// 检查放置位置是否为空
const isEmpty = worldOctree.query(
hitPosition.clone().subtract(new BABYLON.Vector3(0.5, 0.5, 0.5)),
hitPosition.clone().add(new BABYLON.Vector3(0.5, 0.5, 0.5)),
[]
).length === 0;
if (isEmpty) {
const type = currentBlockType;
const { mesh } = instancedMeshes.get(type);
const matrix = new BABYLON.Matrix();
BABYLON.Matrix.Translation(hitPosition.x, hitPosition.y, hitPosition.z, matrix);
mesh.setMatrixAt(mesh.instanceCount, matrix);
mesh.instanceCount++;
mesh.refreshBoundingInfo();
// 插入八叉树
worldOctree.insert({
position: hitPosition.clone(),
size: 1,
blockData: { x: hitPosition.x, y: hitPosition.y, z: hitPosition.z, type }
});
// 保存到区块
const chunkX = Math.floor(hitPosition.x / world.chunkSize);
const chunkZ = Math.floor(hitPosition.z / world.chunkSize);
const chunkKey = `${chunkX},${chunkZ}`;
if (world.chunks.has(chunkKey)) {
world.chunks.get(chunkKey).push({
x: hitPosition.x, y: hitPosition.y, z: hitPosition.z, type
});
}
}
}
}
// 破坏方块
function breakBlock() {
const rayDir = new BABYLON.Vector3(
-Math.sin(camera.rotation.y) * Math.cos(camera.rotation.x),
-Math.sin(camera.rotation.x),
Math.cos(camera.rotation.y) * Math.cos(camera.rotation.x)
).normalize();
let hitBlock = null;
let hitIndex = -1;
let hitType = -1;
for (let t = 0; t < 10; t += 0.1) {
const checkPos = new BABYLON.Vector3(
camera.position.x + rayDir.x * t,
camera.position.y + rayDir.y * t,
camera.position.z + rayDir.z * t
);
const nearbyBlocks = worldOctree.query(
checkPos.clone().subtract(new BABYLON.Vector3(0.5, 0.5, 0.5)),
checkPos.clone().add(new BABYLON.Vector3(0.5, 0.5, 0.5)),
[]
);
for (let block of nearbyBlocks) {
const blockPos = block.position;
if (Math.abs(checkPos.x - blockPos.x) < 0.5 &&
Math.abs(checkPos.y - blockPos.y) < 0.5 &&
Math.abs(checkPos.z - blockPos.z) < 0.5) {
hitBlock = block;
hitType = block.blockData.type;
break;
}
}
if (hitBlock) break;
}
if (hitBlock && hitType !== -1) {
// 从八叉树移除
const blockPos = hitBlock.position;
const tempOctree = new BABYLON.Octree(new BABYLON.Vector3(-512,0,-512), new BABYLON.Vector3(512,256,512));
const allBlocks = worldOctree.query(new BABYLON.Vector3(-512,0,-512), new BABYLON.Vector3(512,256,512), []);
allBlocks.forEach(block => {
if (!(Math.abs(block.position.x - blockPos.x) < 0.1 &&
Math.abs(block.position.y - blockPos.y) < 0.1 &&
Math.abs(block.position.z - blockPos.z) < 0.1)) {
tempOctree.insert(block);
}
});
worldOctree = tempOctree;
// 从区块数据移除
const chunkX = Math.floor(blockPos.x / world.chunkSize);
const chunkZ = Math.floor(blockPos.z / world.chunkSize);
const chunkKey = `${chunkX},${chunkZ}`;
if (world.chunks.has(chunkKey)) {
const updatedBlocks = world.chunks.get(chunkKey).filter(b =>
!(Math.abs(b.x - blockPos.x) < 0.1 &&
Math.abs(b.y - blockPos.y) < 0.1 &&
Math.abs(b.z - blockPos.z) < 0.1)
);
world.chunks.set(chunkKey, updatedBlocks);
}
}
}
// 注册事件监听
function registerEvents() {
const canvas = document.getElementById('gameCanvas');
// 鼠标锁定
canvas.addEventListener('click', () => {
if (!isMouseLocked) {
canvas.requestPointerLock = canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
canvas.requestPointerLock();
}
});
document.addEventListener('pointerlockchange', () => {
isMouseLocked = document.pointerLockElement === canvas;
});
// 鼠标移动
document.addEventListener('mousemove', (e) => {
if (!isMouseLocked) return;
const movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
const movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;
camera.rotation.y -= movementX * camera.angularSpeed;
camera.rotation.x -= movementY * camera.angularSpeed;
camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, camera.rotation.x));
});
// 键盘控制
document.addEventListener('keydown', (e) => {
keys.add(e.key.toLowerCase());
if (e.key >= '1' && e.key <= '5') {
currentBlockType = parseInt(e.key) - 1;
updateHotbar();
}
});
document.addEventListener('keyup', (e) => {
keys.delete(e.key.toLowerCase());
});
// 鼠标点击
canvas.addEventListener('mousedown', (e) => {
if (!isMouseLocked) return;
if (e.button === 0) breakBlock();
if (e.button === 2) placeBlock();
});
// 鼠标滚轮
canvas.addEventListener('wheel', (e) => {
if (e.deltaY > 0) currentBlockType = (currentBlockType + 1) % BLOCK_TYPES.length;
else currentBlockType = (currentBlockType - 1 + BLOCK_TYPES.length) % BLOCK_TYPES.length;
updateHotbar();
});
// 右键阻止默认
canvas.addEventListener('contextmenu', (e) => e.preventDefault());
}
// 离线存储
async function initOfflineStorage() {
try {
const savedData = localStorage.getItem('minecraftSave');
if (savedData) {
const data = JSON.parse(savedData);
camera.position.x = data.playerX;
camera.position.y = data.playerY;
camera.position.z = data.playerZ;
player.position = camera.position.clone();
world.chunks = new Map(Object.entries(data.chunks));
world.loadedChunks = new Set(data.loadedChunks);
}
} catch (e) {
console.log('无保存数据,开始新游戏');
}
// 定时保存(每30秒)
setInterval(() => {
const saveData = {
playerX: camera.position.x,
playerY: camera.position.y,
playerZ: camera.position.z,
chunks: Object.fromEntries(world.chunks),
loadedChunks: Array.from(world.loadedChunks)
};
localStorage.setItem('minecraftSave', JSON.stringify(saveData));
}, 30000);
}
// 更新调试信息(含帧率)
function updateDebugInfo() {
const debugEl = document.getElementById('debug');
debugEl.textContent = `位置: (${Math.round(camera.position.x)},${Math.round(camera.position.y)},${Math.round(camera.position.z)}) | 方块: ${BLOCK_TYPES[currentBlockType].name} | 帧率: ${engine.fps} | 区块: ${world.loadedChunks.size}`;
}
// 启动游戏
window.addEventListener('load', initGame);
</script>
</body>
</html>