MC
2026-10-19 16:20:22
发布于:浙江
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>方块世界</title>
<style>
body { margin: 0; overflow: hidden; font-family: sans-serif; background:#87ceeb; }
canvas { display:block; }
#help {
position:fixed; top:12px; left:12px; color:white; background:#0009;
padding:10px 12px; line-height:1.7; border-radius:6px; user-select:none;
}
#crosshair {
position:fixed; left:50%; top:50%; width:16px; height:16px;
transform:translate(-50%,-50%); pointer-events:none;
}
#crosshair:before, #crosshair:after {
content:""; position:absolute; background:white; box-shadow:0 0 2px #000;
}
#crosshair:before { width:2px; height:16px; left:7px; }
#crosshair:after { height:2px; width:16px; top:7px; }
#hotbar {
position:fixed; bottom:22px; left:50%; transform:translateX(-50%);
display:flex; gap:5px; user-select:none;
}
.slot {
width:48px; height:48px; border:2px solid #bbb; background:#222b;
color:white; display:grid; place-items:center; font-size:12px;
}
.slot.active { border-color:white; transform:translateY(-3px); }
#start {
position:fixed; inset:0; display:grid; place-items:center;
background:#0006; color:white; font-size:20px; text-align:center;
cursor:pointer;
}
</style>
</head>
<body>
<div id="help">点击开始<br>WASD 移动 · 空格跳跃<br>左键挖掘 · 右键放置<br>1–5 切换方块 · Esc 解锁鼠标</div>
<div id="crosshair"></div>
<div id="hotbar"></div>
<div id="start">点击进入方块世界</div>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js";
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 20, 65);
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 5, 8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x607050, 2));
const sun = new THREE.DirectionalLight(0xffffff, 2);
sun.position.set(-10, 20, 10);
sun.castShadow = true;
scene.add(sun);
const materials = {
grass: new THREE.MeshLambertMaterial({ color: 0x58a838 }),
dirt: new THREE.MeshLambertMaterial({ color: 0x79502d }),
stone: new THREE.MeshLambertMaterial({ color: 0x888888 }),
wood: new THREE.MeshLambertMaterial({ color: 0x9a6638 }),
sand: new THREE.MeshLambertMaterial({ color: 0xd9c277 })
};
const blockNames = ["草方块", "泥土", "石头", "木头", "沙子"];
const blockTypes = ["grass", "dirt", "stone", "wood", "sand"];
const cube = new THREE.BoxGeometry(1, 1, 1);
const blocks = new Map();
const meshes = [];
const key = (x,y,z) => `${x},${y},${z}`;
function addBlock(x, y, z, type) {
const k = key(x,y,z);
if (blocks.has(k)) return;
const mesh = new THREE.Mesh(cube, materials[type] || materials.dirt);
mesh.position.set(x,y,z);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.userData = { x,y,z,type };
scene.add(mesh);
blocks.set(k, mesh);
meshes.push(mesh);
}
function removeBlock(mesh) {
const {x,y,z} = mesh.userData;
blocks.delete(key(x,y,z));
scene.remove(mesh);
const i = meshes.indexOf(mesh);
if (i >= 0) meshes.splice(i,1);
}
// 生成起伏地形
for (let x=-14; x<=14; x++) {
for (let z=-14; z<=14; z++) {
const h = Math.floor(
2 + Math.sin(x * 0.35) * 1.2 + Math.cos(z * 0.28) * 1.1
);
for (let y=0; y<=h; y++) {
addBlock(x,y,z, y === h ? "grass" : (y < h-2 ? "stone" : "dirt"));
}
}
}
// 几棵方块树
function tree(x,z) {
let ground = 2;
for (let y=0; y<8; y++) {
if (blocks.has(key(x,y,z))) ground = y + 1;
}
for (let y=ground; y<ground+4; y++) addBlock(x,y,z,"wood");
for (let dx=-2; dx<=2; dx++) for (let dz=-2; dz<=2; dz++) {
for (let dy=2; dy<=4; dy++) {
if (Math.abs(dx)+Math.abs(dz)+Math.abs(dy-3) < 5) {
addBlock(x+dx,ground+dy,z+dz,"grass");
}
}
}
}
tree(-6,-4); tree(7,5); tree(4,-8);
let yaw = 0, pitch = 0;
let selected = 0;
let velocityY = 0;
let grounded = false;
const keys = {};
const playerHeight = 1.7;
const start = document.querySelector("#start");
function lockPointer() { renderer.domElement.requestPointerLock(); }
start.addEventListener("click", lockPointer);
document.addEventListener("pointerlockchange", () => {
start.style.display = document.pointerLockElement ? "none" : "grid";
});
document.addEventListener("mousemove", e => {
if (document.pointerLockElement !== renderer.domElement) return;
yaw -= e.movementX * 0.002;
pitch -= e.movementY * 0.002;
pitch = Math.max(-1.5, Math.min(1.5, pitch));
});
document.addEventListener("keydown", e => {
keys[e.code] = true;
if (e.code.startsWith("Digit")) {
const n = Number(e.code.slice(5));
if (n >= 1 && n <= 5) { selected = n - 1; updateHotbar(); }
}
});
document.addEventListener("keyup", e => keys[e.code] = false);
const hotbar = document.querySelector("#hotbar");
function updateHotbar() {
hotbar.innerHTML = "";
blockNames.forEach((name,i) => {
const slot = document.createElement("div");
slot.className = "slot" + (i === selected ? " active" : "");
slot.textContent = `${i+1} ${name}`;
hotbar.appendChild(slot);
});
}
updateHotbar();
const raycaster = new THREE.Raycaster();
raycaster.far = 6;
function getTarget() {
raycaster.setFromCamera(new THREE.Vector2(0,0), camera);
return raycaster.intersectObjects(meshes, false)[0];
}
document.addEventListener("contextmenu", e => e.preventDefault());
document.addEventListener("mousedown", e => {
if (document.pointerLockElement !== renderer.domElement) return;
const hit = getTarget();
if (!hit) return;
if (e.button === 0) {
// 不允许挖掉脚下最底层
if (hit.object.position.y > 0) removeBlock(hit.object);
} else if (e.button === 2) {
const p = hit.object.position.clone().add(hit.face.normal);
const px = Math.round(p.x), py = Math.round(p.y), pz = Math.round(p.z);
const dx = px-camera.position.x, dy = py-(camera.position.y-0.8), dz = pz-camera.position.z;
if (Math.hypot(dx,dz) > 0.8 || Math.abs(dy) > 1.8) {
addBlock(px,py,pz,blockTypes[selected]);
}
}
});
function groundHeight(x,z) {
const bx = Math.round(x), bz = Math.round(z);
let highest = -Infinity;
for (const mesh of meshes) {
const p = mesh.position;
if (p.x === bx && p.z === bz && p.y > highest) highest = p.y;
}
return highest === -Infinity ? 0 : highest + 0.5;
}
function animate() {
requestAnimationFrame(animate);
camera.rotation.order = "YXZ";
camera.rotation.y = yaw;
camera.rotation.x = pitch;
const forward = new THREE.Vector3(-Math.sin(yaw),0,-Math.cos(yaw));
const right = new THREE.Vector3(Math.cos(yaw),0,-Math.sin(yaw));
const move = new THREE.Vector3();
if (keys.KeyW) move.add(forward);
if (keys.KeyS) move.sub(forward);
if (keys.KeyD) move.add(right);
if (keys.KeyA) move.sub(right);
if (move.lengthSq()) {
move.normalize().multiplyScalar(0.09);
camera.position.x += move.x;
camera.position.z += move.z;
}
const floor = groundHeight(camera.position.x, camera.position.z) + playerHeight;
if (keys.Space && grounded) { velocityY = 0.18; grounded = false; }
velocityY -= 0.009;
camera.position.y += velocityY;
if (camera.position.y <= floor) {
camera.position.y = floor;
velocityY = 0;
grounded = true;
} else grounded = false;
renderer.render(scene,camera);
}
animate();
addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>
这里空空如也


















有帮助,赞一个