<!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; }
#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; }
.hotbar-slot.active { border-color: #fff; }
#crosshair { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 20px; height: 20px; border: 1px solid #fff; z-index: 100; }
#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; }
</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>
<!-- 引入核心库 -->
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
<script src="https://cdn.babylonjs.com/ammo.js"></script>
<script src="https://cdn.babylonjs.com/postProcesses/babylonjs.postProcess.min.js"></script>
<script>
// 全局变量
let engine, scene, camera, physicsEngine;
let world = { chunks: new Map(), loadedChunks: new Set() };
let player, currentBlockType = 0;
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 }
];
// 初始化游戏
async function initGame() {
updateProgress(10, "初始化引擎...");
// 1. 创建引擎
const canvas = document.getElementById('gameCanvas');
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(30, "加载物理引擎...");
// 4. 创建相机(第一人称)
createCamera();
// 5. 创建光源
createLights();
updateProgress(40, "设置光影效果...");
// 6. 初始化世界
await initWorld();
updateProgress(60, "生成世界...");
// 7. 创建玩家
createPlayer();
updateProgress(70, "创建玩家...");
// 8. 设置UI
setupUI();
updateProgress(80, "初始化界面...");
// 9. 注册事件监听
registerEvents();
updateProgress(90, "配置控制...");
// 10. 初始化离线存储
await initOfflineStorage();
updateProgress(100, "准备就绪!");
// 隐藏加载界面
setTimeout(() => document.getElementById('loading').style.display = 'none', 500);
// 渲染循环
engine.runRenderLoop(() => {
scene.render();
updatePlayerPosition();
loadNearbyChunks();
});
// 窗口大小调整
window.addEventListener('resize', () => engine.resize());
}
// 初始化物理引擎
async function initPhysics() {
await Ammo().then((AmmoLib) => {
Ammo = AmmoLib;
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(canvas, true);
camera.inputs.attached.mouse.angularSensibility = 8000;
camera.speed = 0.2;
camera.angularSpeed = 0.01;
camera.minZ = 0.1;
camera.maxZ = 200;
// 限制相机俯仰角度
camera.lowerRadiusLimit = null;
camera.upperRadiusLimit = null;
camera.lowerHeightLimit = 1.5;
camera.upperHeightLimit = 256;
// 启用碰撞检测
camera.checkCollisions = true;
camera.ellipsoid = new BABYLON.Vector3(0.5, 1.5, 0.5);
}
// 创建光源和光影效果
function createLights() {
// 环境光
const ambientLight = new BABYLON.HemisphericLight("ambient", new BABYLON.Vector3(0, 1, 0), scene);
ambientLight.intensity = 0.6;
// 方向光(太阳)
const directionalLight = new BABYLON.DirectionalLight("sun", new BABYLON.Vector3(-1, -2, -1), scene);
directionalLight.intensity = 0.8;
directionalLight.position = new BABYLON.Vector3(100, 200, 100);
// 启用阴影
directionalLight.shadowEnabled = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.orthoLeft = -100;
directionalLight.shadow.camera.orthoRight = 100;
directionalLight.shadow.camera.orthoTop = 100;
directionalLight.shadow.camera.orthoBottom = -100;
// PBR环境贴图
const hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("https://assets.babylonjs.com/environments/environmentSpecular.env", scene);
scene.environmentTexture = hdrTexture;
scene.environmentIntensity = 0.5;
// 后期处理(辉光效果)
const bloom = new BABYLON.UnrealBloomPass(new BABYLON.Vector2(engine.getRenderWidth(), engine.getRenderHeight()), 1.2, 0.5, 0.85);
const composer = new BABYLON.EffectComposer(engine);
composer.addPass(new BABYLON.RenderPass(scene, camera));
composer.addPass(bloom);
engine.runRenderLoop(() => {
composer.render();
});
}
// 初始化世界(区块生成)
function initWorld() {
// 简单的Perlin噪声实现(简化版)
const noise = {
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