Files
nagi-bench/outputs/mistral-medium-3-5/vibe-thinking/mythos-craft.html
T

1141 lines
48 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>神话幻想世界 - Minecraft 克隆版</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #000;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas { display: block; }
#info {
position: absolute;
top: 10px;
left: 10px;
color: #fff;
background: rgba(0,0,0,0.6);
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
z-index: 100;
font-weight: bold;
text-shadow: 1px 1px 2px #000;
}
#info span { color: #ffcc00; }
#loading {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
color: #fff;
z-index: 1000;
transition: opacity 0.5s;
}
#loading.hidden { opacity: 0; pointer-events: none; }
.loader {
width: 60px; height: 60px;
border: 4px solid rgba(255,255,255,0.3);
border-top-color: #ffcc00;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin { to { transform: rotate(360deg); } }
#fps { position: absolute; bottom: 10px; left: 10px; color: #fff; background: rgba(0,0,0,0.6); padding: 5px 10px; border-radius: 5px; font-size: 12px; }
</style>
</head>
<body>
<div id="loading">
<div class="loader"></div>
<div>正在生成神话世界...</div>
</div>
<div id="info">
<div>神话幻想世界 | WASD 移动 | 鼠标环视</div>
<div>左键破坏 | 右键放置 | 1/2/3 选择方块</div>
<div>当前方块: <span id="current-block">草地</span> | 位置: <span id="position">(0, 0, 0)</span></div>
</div>
<div id="fps">FPS: 60</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
<script>
// ==================== 配置 ====================
const WORLD_SIZE = 128;
const CHUNK_SIZE = 16;
const BLOCK_SIZE = 1;
const RENDER_DISTANCE = 1;
const GRAVITY = -0.08;
const JUMP_FORCE = 0.12;
const MOVEMENT_SPEED = 0.1;
// 方块类型
const BLOCK_TYPES = {
AIR: 0,
GRASS: 1,
DIRT: 2,
STONE: 3,
WOOD: 4,
LEAVES: 5,
SAND: 6,
WATER: 7,
GOLD: 8,
MYTH_STONE: 9,
ANCIENT_BRICK: 10,
MAGIC_GLOW: 11,
OBSIDIAN: 12,
CRYSTAL: 13
};
const BLOCK_COLORS = {
[BLOCK_TYPES.GRASS]: { top: 0x2e8b57, side: 0x3cb371, bottom: 0x1a5930 },
[BLOCK_TYPES.DIRT]: { all: 0x8b4513 },
[BLOCK_TYPES.STONE]: { all: 0x808080 },
[BLOCK_TYPES.WOOD]: { all: 0x8B4513 },
[BLOCK_TYPES.LEAVES]: { all: 0x228B22 },
[BLOCK_TYPES.SAND]: { all: 0xF4A460 },
[BLOCK_TYPES.WATER]: { all: 0x1E90FF },
[BLOCK_TYPES.GOLD]: { all: 0xFFD700 },
[BLOCK_TYPES.MYTH_STONE]: { all: 0x4B0082 },
[BLOCK_TYPES.ANCIENT_BRICK]: { all: 0x696969 },
[BLOCK_TYPES.MAGIC_GLOW]: { all: 0x9400D3, emissive: 0x9400D3 },
[BLOCK_TYPES.OBSIDIAN]: { all: 0x000000 },
[BLOCK_TYPES.CRYSTAL]: { all: 0x00FFFF, transparent: true, opacity: 0.7 }
};
const BLOCK_NAMES = {
[BLOCK_TYPES.GRASS]: '草地',
[BLOCK_TYPES.DIRT]: '泥土',
[BLOCK_TYPES.STONE]: '石头',
[BLOCK_TYPES.WOOD]: '木头',
[BLOCK_TYPES.LEAVES]: '树叶',
[BLOCK_TYPES.SAND]: '沙子',
[BLOCK_TYPES.WATER]: '水',
[BLOCK_TYPES.GOLD]: '黄金',
[BLOCK_TYPES.MYTH_STONE]: '神话石',
[BLOCK_TYPES.ANCIENT_BRICK]: '古代砖',
[BLOCK_TYPES.MAGIC_GLOW]: '魔法光',
[BLOCK_TYPES.OBSIDIAN]: '黑曜石',
[BLOCK_TYPES.CRYSTAL]: '水晶'
};
// ==================== 场景设置 ====================
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // 天空蓝
scene.fog = new THREE.FogExp2(0x87CEEB, 0.002);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 10, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// ==================== 光照 ====================
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(100, 200, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.left = -200;
directionalLight.shadow.camera.right = 200;
directionalLight.shadow.camera.top = 200;
directionalLight.shadow.camera.bottom = -200;
scene.add(directionalLight);
// 太阳
const sunGeometry = new THREE.SphereGeometry(30, 32, 32);
const sunMaterial = new THREE.MeshBasicMaterial({
color: 0xFFD700,
emissive: 0xFFD700,
emissiveIntensity: 0.8
});
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
sun.position.set(300, 300, -200);
scene.add(sun);
// 月亮
const moonGeometry = new THREE.SphereGeometry(15, 32, 32);
const moonMaterial = new THREE.MeshBasicMaterial({
color: 0xE6E6FA,
emissive: 0xADD8E6,
emissiveIntensity: 0.5
});
const moon = new THREE.Mesh(moonGeometry, moonMaterial);
moon.position.set(-300, 200, -100);
scene.add(moon);
// 星星
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({
color: 0xFFFFFF,
size: 0.5,
transparent: true,
opacity: 0.8
});
const starsVertices = [];
for (let i = 0; i < 5000; i++) {
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const z = (Math.random() - 0.5) * 2000;
starsVertices.push(x, y, z);
}
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3));
const stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
// ==================== 控制器 ====================
const controls = new THREE.PointerLockControls(camera, document.body);
// 鼠标锁定
const blocker = document.createElement('div');
blocker.style.position = 'absolute';
blocker.style.top = '0';
blocker.style.left = '0';
blocker.style.width = '100%';
blocker.style.height = '100%';
blocker.style.background = 'rgba(0,0,0,0.5)';
blocker.style.display = 'none';
document.body.appendChild(blocker);
const instructions = document.createElement('div');
instructions.style.position = 'absolute';
instructions.style.top = '50%';
instructions.style.left = '50%';
instructions.style.transform = 'translate(-50%, -50%)';
instructions.style.color = '#fff';
instructions.style.fontSize = '24px';
instructions.style.textAlign = 'center';
instructions.style.background = 'rgba(0,0,0,0.8)';
instructions.style.padding = '20px';
instructions.style.borderRadius = '10px';
instructions.innerHTML = '点击屏幕开始游戏<br><small>WASD 移动 | 鼠标环视 | 左键破坏 | 右键放置</small>';
document.body.appendChild(instructions);
instructions.addEventListener('click', () => {
controls.lock();
instructions.style.display = 'none';
blocker.style.display = 'none';
});
controls.addEventListener('lock', () => {
instructions.style.display = 'none';
blocker.style.display = 'none';
});
controls.addEventListener('unlock', () => {
blocker.style.display = 'block';
instructions.style.display = '';
});
// ==================== 玩家状态 ====================
const player = {
velocity: new THREE.Vector3(),
isOnGround: false,
canJump: false,
selectedBlock: BLOCK_TYPES.GRASS
};
// ==================== 地形生成 ====================
// 改进的柏林噪声
class ImprovedPerlinNoise {
constructor(seed = Math.random()) {
this.seed = seed;
this.permutation = this.generatePermutation();
this.gradients = this.generateGradients();
}
generatePermutation() {
const p = [];
for (let i = 0; i < 256; i++) p[i] = i;
// 使用种子进行洗牌
for (let i = 255; i > 0; i--) {
const j = Math.floor(this.random(i + 1));
[p[i], p[j]] = [p[j], p[i]];
}
return [...p, ...p];
}
generateGradients() {
const grad = [];
for (let i = 0; i < 256; i++) {
const theta = Math.random() * 2 * Math.PI;
grad[i] = { x: Math.cos(theta), y: Math.sin(theta) };
}
return grad;
}
random(max) {
let x = this.seed;
x = (x * 9301 + 49297) % 233280;
this.seed = x;
return x / 233280 * max;
}
fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
lerp(a, b, t) {
return a + t * (b - a);
}
grad(hash, x, y) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : h === 12 || h === 14 ? x : 0;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
noise2D(x, y) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
x -= Math.floor(x);
y -= Math.floor(y);
const u = this.fade(x);
const v = this.fade(y);
const p = this.permutation;
const A = p[X] + Y;
const AA = p[A];
const AB = p[A + 1];
const B = p[X + 1] + Y;
const BA = p[B];
const BB = p[B + 1];
return this.lerp(
this.lerp(this.grad(p[AA], x, y), this.grad(p[BA], x - 1, y), u),
this.lerp(this.grad(p[AB], x, y - 1), this.grad(p[BB], x - 1, y - 1), u),
v
);
}
octaveNoise(x, y, octaves = 4, persistence = 0.5) {
let total = 0;
let frequency = 1;
let amplitude = 1;
let maxValue = 0;
for (let i = 0; i < octaves; i++) {
total += this.noise2D(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= 2;
}
return total / maxValue;
}
}
const noise = new ImprovedPerlinNoise(12345);
// 3D 噪声
function noise3D(x, y, z) {
return (noise.octaveNoise(x, y, 4) + noise.octaveNoise(x, z, 4) + noise.octaveNoise(y, z, 4)) / 3;
}
// 生成高度
function getHeight(x, z) {
// 基础地形
let height = noise3D(x * 0.05, z * 0.05, 0) * 20;
// 添加山脉
height += noise3D(x * 0.1, z * 0.1, 100) * 15;
// 添加细节
height += noise3D(x * 0.5, z * 0.5, 200) * 3;
// 确保最低高度
height = Math.max(height, 5);
return Math.floor(height);
}
// 生成温度(用于生物群系)
function getTemperature(x, z) {
return noise.octaveNoise(x * 0.001, z * 0.001, 2) * 0.5 + 0.5;
}
// 生成湿度
function getMoisture(x, z) {
return noise.octaveNoise(x * 0.001, z * 0.001, 2, 0.3) * 0.5 + 0.5;
}
// 根据生物群系确定方块
function getBlockType(x, y, z) {
const height = getHeight(x, z);
const temp = getTemperature(x, z);
const moisture = getMoisture(x, z);
// 地下层
if (y < height - 4) {
if (y < height - 8) {
return BLOCK_TYPES.STONE;
} else {
return BLOCK_TYPES.DIRT;
}
}
// 地表
if (y === height) {
// 神话区域 - 中心区域
const centerX = 0, centerZ = 0;
const distToCenter = Math.sqrt(x * x + z * z);
if (distToCenter < 30) {
// 神话建筑区域
if (y >= 15 && y <= 25 && distToCenter < 20) {
if (Math.random() > 0.7) return BLOCK_TYPES.MYTH_STONE;
if (Math.random() > 0.5) return BLOCK_TYPES.ANCIENT_BRICK;
return BLOCK_TYPES.MAGIC_GLOW;
}
if (distToCenter < 10 && y >= 10) {
if (Math.random() > 0.8) return BLOCK_TYPES.CRYSTAL;
if (Math.random() > 0.6) return BLOCK_TYPES.OBSIDIAN;
return BLOCK_TYPES.MYTH_STONE;
}
}
// 沙漠
if (temp > 0.7 && moisture < 0.3) {
return BLOCK_TYPES.SAND;
}
// 森林
if (temp > 0.3 && moisture > 0.6) {
return BLOCK_TYPES.GRASS;
}
// 普通草地
return BLOCK_TYPES.GRASS;
}
// 地表下方
if (y > height - 4 && y < height) {
return BLOCK_TYPES.DIRT;
}
// 水
if (y <= 8 && height <= 8) {
return BLOCK_TYPES.WATER;
}
// 空气
return BLOCK_TYPES.AIR;
}
// 生成神话建筑
function generateMythicalStructure(centerX, centerZ) {
const structures = [];
// 神殿
if (Math.random() > 0.3) {
const templeSize = 10 + Math.floor(Math.random() * 10);
for (let x = -templeSize; x <= templeSize; x++) {
for (let z = -templeSize; z <= templeSize; z++) {
for (let y = 0; y <= 15; y++) {
const dist = Math.sqrt(x * x + z * z);
if (dist <= templeSize && (y === 0 || dist <= templeSize - 2 || y >= 10)) {
if (y === 0) {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.MYTH_STONE});
} else if (y < 10) {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.ANCIENT_BRICK});
} else {
if (Math.random() > 0.7) {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.MAGIC_GLOW});
} else {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.CRYSTAL});
}
}
}
}
}
}
}
// 方尖碑
if (Math.random() > 0.5) {
const obeliskHeight = 20 + Math.floor(Math.random() * 15);
for (let y = 0; y < obeliskHeight; y++) {
const size = Math.max(2, obeliskHeight - y - 10);
for (let x = -size; x <= size; x++) {
for (let z = -size; z <= size; z++) {
if (Math.abs(x) <= 1 && Math.abs(z) <= 1) {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.OBSIDIAN});
}
}
}
}
// 顶部水晶
structures.push({x: centerX, y: getHeight(centerX, centerZ) + obeliskHeight, z: centerZ, type: BLOCK_TYPES.CRYSTAL});
}
// 神树
if (Math.random() > 0.4) {
const treeHeight = 15 + Math.floor(Math.random() * 10);
// 树干
for (let y = 0; y < treeHeight; y++) {
structures.push({x: centerX, y: getHeight(centerX, centerZ) + y, z: centerZ, type: BLOCK_TYPES.WOOD});
if (y > 2 && y < treeHeight - 3) {
structures.push({x: centerX + 1, y: getHeight(centerX + 1, centerZ) + y, z: centerZ, type: BLOCK_TYPES.WOOD});
structures.push({x: centerX - 1, y: getHeight(centerX - 1, centerZ) + y, z: centerZ, type: BLOCK_TYPES.WOOD});
structures.push({x: centerX, y: getHeight(centerX, centerZ + 1) + y, z: centerZ + 1, type: BLOCK_TYPES.WOOD});
structures.push({x: centerX, y: getHeight(centerX, centerZ - 1) + y, z: centerZ - 1, type: BLOCK_TYPES.WOOD});
}
}
// 树冠
for (let y = treeHeight - 3; y < treeHeight + 3; y++) {
for (let x = -3; x <= 3; x++) {
for (let z = -3; z <= 3; z++) {
const dist = Math.sqrt(x * x + z * z);
if (dist <= 3 && Math.random() > 0.3) {
structures.push({x: centerX + x, y: getHeight(centerX + x, centerZ + z) + y, z: centerZ + z, type: BLOCK_TYPES.LEAVES});
}
}
}
}
// 顶部水晶
structures.push({x: centerX, y: getHeight(centerX, centerZ) + treeHeight + 1, z: centerZ, type: BLOCK_TYPES.MAGIC_GLOW});
}
return structures;
}
// ==================== 区块系统 ====================
class Chunk {
constructor(x, z) {
this.x = x;
this.z = z;
this.mesh = new THREE.Group();
this.blocks = new Map(); // key: "x,y,z", value: blockType
this.needsUpdate = true;
}
generate() {
this.mesh.clear();
this.blocks.clear();
const startX = this.x * CHUNK_SIZE;
const startZ = this.z * CHUNK_SIZE;
// 生成神话建筑
const mythStructures = generateMythicalStructure(
startX + CHUNK_SIZE / 2,
startZ + CHUNK_SIZE / 2
);
// 创建方块几何体
const geometry = new THREE.BufferGeometry();
const materials = [];
const position = [];
const normal = [];
const color = [];
const uv = [];
const index = [];
let vertexIndex = 0;
// 方块的6个面
const faces = [
{ dir: [0, 0, -1], corners: [0, 1, 2, 3] }, // 前
{ dir: [0, 0, 1], corners: [4, 5, 6, 7] }, // 后
{ dir: [-1, 0, 0], corners: [0, 4, 7, 3] }, // 左
{ dir: [1, 0, 0], corners: [1, 2, 6, 5] }, // 右
{ dir: [0, 1, 0], corners: [0, 4, 5, 1] }, // 上
{ dir: [0, -1, 0], corners: [3, 7, 6, 2] } // 下
];
// 方块顶点(相对坐标)
const vertices = [
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], // 前面
[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1] // 后面
];
// 处理每个方块
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
for (let y = 0; y < WORLD_SIZE; y++) {
const worldX = startX + x;
const worldZ = startZ + z;
const worldY = y;
// 检查是否有神话建筑
let blockType = BLOCK_TYPES.AIR;
const mythBlock = mythStructures.find(b =>
b.x === worldX && b.y === worldY && b.z === worldZ
);
if (mythBlock) {
blockType = mythBlock.type;
} else {
blockType = getBlockType(worldX, worldY, worldZ);
}
// 空气方块不渲染
if (blockType === BLOCK_TYPES.AIR) continue;
// 存储方块
this.blocks.set(`${worldX},${worldY},${worldZ}`, blockType);
// 检查每个面是否可见
for (const face of faces) {
const [dx, dy, dz] = face.dir;
const neighborX = worldX + dx;
const neighborY = worldY + dy;
const neighborZ = worldZ + dz;
// 检查邻居是否存在或是否为空气
const neighborType = mythStructures.find(b =>
b.x === neighborX && b.y === neighborY && b.z === neighborZ
)?.type || getBlockType(neighborX, neighborY, neighborZ);
if (neighborType === BLOCK_TYPES.AIR) {
// 这个面可见
for (const cornerIdx of face.corners) {
const [cx, cy, cz] = vertices[cornerIdx];
position.push(
(startX + x + cx) * BLOCK_SIZE,
worldY + cy * BLOCK_SIZE,
(startZ + z + cz) * BLOCK_SIZE
);
// 法线
normal.push(...face.dir);
// UV
const u = cornerIdx % 2 === 0 ? 0 : 1;
const v = Math.floor(cornerIdx / 2) % 2 === 0 ? 0 : 1;
uv.push(u, v);
}
// 颜色
const blockInfo = BLOCK_COLORS[blockType];
if (blockInfo) {
for (let i = 0; i < 4; i++) {
if (face.dir[1] === 1 && blockInfo.top) {
color.push(blockInfo.top >> 16 & 0xFF, blockInfo.top >> 8 & 0xFF, blockInfo.top & 0xFF);
} else if (face.dir[1] === -1 && blockInfo.bottom) {
color.push(blockInfo.bottom >> 16 & 0xFF, blockInfo.bottom >> 8 & 0xFF, blockInfo.bottom & 0xFF);
} else if (blockInfo.all) {
color.push(blockInfo.all >> 16 & 0xFF, blockInfo.all >> 8 & 0xFF, blockInfo.all & 0xFF);
} else if (blockInfo.side) {
color.push(blockInfo.side >> 16 & 0xFF, blockInfo.side >> 8 & 0xFF, blockInfo.side & 0xFF);
} else {
color.push(255, 255, 255);
}
}
} else {
color.push(255, 255, 255, 255, 255, 255, 255, 255, 255);
}
// 索引
const baseIdx = vertexIndex;
index.push(baseIdx, baseIdx + 1, baseIdx + 2);
index.push(baseIdx, baseIdx + 2, baseIdx + 3);
vertexIndex += 4;
}
}
}
}
}
// 创建几何体
geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normal, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(color, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uv, 2));
geometry.setIndex(index);
// 材质
const material = new THREE.MeshStandardMaterial({
vertexColors: true,
roughness: 0.8,
metalness: 0.2
});
// 处理发光材质
const glowGeometry = new THREE.BufferGeometry();
const glowMaterials = [];
const glowPosition = [];
const glowColor = [];
const glowIndex = [];
let glowVertexIndex = 0;
// 重新处理发光方块
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
for (let y = 0; y < WORLD_SIZE; y++) {
const worldX = startX + x;
const worldZ = startZ + z;
const worldY = y;
const blockType = mythStructures.find(b =>
b.x === worldX && b.y === worldY && b.z === worldZ
)?.type || getBlockType(worldX, worldY, worldZ);
if (blockType === BLOCK_TYPES.AIR) continue;
if (!BLOCK_COLORS[blockType]?.emissive) continue;
// 处理每个面
for (const face of faces) {
const [dx, dy, dz] = face.dir;
const neighborX = worldX + dx;
const neighborY = worldY + dy;
const neighborZ = worldZ + dz;
const neighborType = mythStructures.find(b =>
b.x === neighborX && b.y === neighborY && b.z === neighborZ
)?.type || getBlockType(neighborX, neighborY, neighborZ);
if (neighborType === BLOCK_TYPES.AIR) {
for (const cornerIdx of face.corners) {
const [cx, cy, cz] = vertices[cornerIdx];
glowPosition.push(
(startX + x + cx) * BLOCK_SIZE,
worldY + cy * BLOCK_SIZE,
(startZ + z + cz) * BLOCK_SIZE
);
const blockInfo = BLOCK_COLORS[blockType];
if (blockInfo.emissive) {
glowColor.push(
(blockInfo.emissive >> 16 & 0xFF) / 255,
(blockInfo.emissive >> 8 & 0xFF) / 255,
(blockInfo.emissive & 0xFF) / 255
);
} else {
glowColor.push(1, 1, 1);
}
}
const baseIdx = glowVertexIndex;
glowIndex.push(baseIdx, baseIdx + 1, baseIdx + 2);
glowIndex.push(baseIdx, baseIdx + 2, baseIdx + 3);
glowVertexIndex += 4;
}
}
}
}
}
glowGeometry.setAttribute('position', new THREE.Float32BufferAttribute(glowPosition, 3));
glowGeometry.setAttribute('color', new THREE.Float32BufferAttribute(glowColor, 3));
glowGeometry.setIndex(glowIndex);
const glowMaterial = new THREE.MeshBasicMaterial({
vertexColors: true,
transparent: true,
opacity: 0.8,
side: THREE.FrontSide
});
const mainMesh = new THREE.Mesh(geometry, material);
mainMesh.castShadow = true;
mainMesh.receiveShadow = true;
this.mesh.add(mainMesh);
if (glowPosition.length > 0) {
const glowMesh = new THREE.Mesh(glowGeometry, glowMaterial);
this.mesh.add(glowMesh);
}
this.mesh.position.set(
startX * BLOCK_SIZE,
0,
startZ * BLOCK_SIZE
);
this.needsUpdate = false;
}
}
// ==================== 区块管理器 ====================
class ChunkManager {
constructor() {
this.chunks = new Map(); // key: "x,z"
this.loadedChunks = new Set();
}
getChunk(x, z) {
const key = `${Math.floor(x / CHUNK_SIZE)},${Math.floor(z / CHUNK_SIZE)}`;
return this.chunks.get(key);
}
createChunk(x, z) {
const key = `${x},${z}`;
if (this.chunks.has(key)) return this.chunks.get(key);
const chunk = new Chunk(x, z);
this.chunks.set(key, chunk);
scene.add(chunk.mesh);
return chunk;
}
update(playerX, playerZ) {
const playerChunkX = Math.floor(playerX / CHUNK_SIZE);
const playerChunkZ = Math.floor(playerZ / CHUNK_SIZE);
// 卸载远距离区块
for (const [key, chunk] of this.chunks) {
const [x, z] = key.split(',').map(Number);
const dist = Math.sqrt(
Math.pow(x - playerChunkX, 2) +
Math.pow(z - playerChunkZ, 2)
);
if (dist > RENDER_DISTANCE) {
scene.remove(chunk.mesh);
this.chunks.delete(key);
this.loadedChunks.delete(key);
}
}
// 加载附近区块
for (let x = playerChunkX - RENDER_DISTANCE; x <= playerChunkX + RENDER_DISTANCE; x++) {
for (let z = playerChunkZ - RENDER_DISTANCE; z <= playerChunkZ + RENDER_DISTANCE; z++) {
const key = `${x},${z}`;
if (!this.chunks.has(key)) {
const chunk = this.createChunk(x, z);
chunk.generate();
this.loadedChunks.add(key);
}
}
}
}
}
const chunkManager = new ChunkManager();
// ==================== 方块交互 ====================
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function getIntersectedBlock() {
raycaster.setFromCamera(mouse, camera);
const intersects = [];
// 检查所有加载的区块
for (const chunk of chunkManager.chunks.values()) {
const chunkIntersects = raycaster.intersectObject(chunk.mesh, true);
intersects.push(...chunkIntersects);
}
if (intersects.length === 0) return null;
// 找到最近的交点
intersects.sort((a, b) => a.distance - b.distance);
const intersect = intersects[0];
// 计算方块位置
const blockPos = new THREE.Vector3().copy(intersect.point).add(intersect.face.normal);
blockPos.x = Math.floor(blockPos.x / BLOCK_SIZE) * BLOCK_SIZE;
blockPos.y = Math.floor(blockPos.y / BLOCK_SIZE) * BLOCK_SIZE;
blockPos.z = Math.floor(blockPos.z / BLOCK_SIZE) * BLOCK_SIZE;
// 检查是否在区块中
const chunkX = Math.floor(blockPos.x / (CHUNK_SIZE * BLOCK_SIZE)) * CHUNK_SIZE * BLOCK_SIZE;
const chunkZ = Math.floor(blockPos.z / (CHUNK_SIZE * BLOCK_SIZE)) * CHUNK_SIZE * BLOCK_SIZE;
const chunk = chunkManager.getChunk(chunkX / BLOCK_SIZE, chunkZ / BLOCK_SIZE);
if (!chunk) return null;
return {
position: new THREE.Vector3(blockPos.x, blockPos.y, blockPos.z),
chunk: chunk,
face: intersect.face
};
}
function destroyBlock(position) {
const x = Math.floor(position.x / BLOCK_SIZE);
const y = Math.floor(position.y / BLOCK_SIZE);
const z = Math.floor(position.z / BLOCK_SIZE);
const chunk = chunkManager.getChunk(x, z);
if (!chunk) return false;
const key = `${x},${y},${z}`;
if (chunk.blocks.has(key)) {
chunk.blocks.delete(key);
chunk.needsUpdate = true;
return true;
}
return false;
}
function placeBlock(position, blockType) {
const x = Math.floor(position.x / BLOCK_SIZE);
const y = Math.floor(position.y / BLOCK_SIZE);
const z = Math.floor(position.z / BLOCK_SIZE);
const chunk = chunkManager.getChunk(x, z);
if (!chunk) return false;
const key = `${x},${y},${z}`;
chunk.blocks.set(key, blockType);
chunk.needsUpdate = true;
return true;
}
// ==================== 输入处理 ====================
const keys = { w: false, a: false, s: false, d: false, space: false, shift: false };
window.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW': keys.w = true; break;
case 'KeyA': keys.a = true; break;
case 'KeyS': keys.s = true; break;
case 'KeyD': keys.d = true; break;
case 'Space': keys.space = true; break;
case 'ShiftLeft': keys.shift = true; break;
case 'Digit1': player.selectedBlock = BLOCK_TYPES.GRASS; updateInfo(); break;
case 'Digit2': player.selectedBlock = BLOCK_TYPES.DIRT; updateInfo(); break;
case 'Digit3': player.selectedBlock = BLOCK_TYPES.STONE; updateInfo(); break;
case 'Digit4': player.selectedBlock = BLOCK_TYPES.WOOD; updateInfo(); break;
case 'Digit5': player.selectedBlock = BLOCK_TYPES.MYTH_STONE; updateInfo(); break;
case 'Digit6': player.selectedBlock = BLOCK_TYPES.ANCIENT_BRICK; updateInfo(); break;
case 'Digit7': player.selectedBlock = BLOCK_TYPES.MAGIC_GLOW; updateInfo(); break;
case 'Digit8': player.selectedBlock = BLOCK_TYPES.CRYSTAL; updateInfo(); break;
}
});
window.addEventListener('keyup', (e) => {
switch (e.code) {
case 'KeyW': keys.w = false; break;
case 'KeyA': keys.a = false; break;
case 'KeyS': keys.s = false; break;
case 'KeyD': keys.d = false; break;
case 'Space': keys.space = false; break;
case 'ShiftLeft': keys.shift = false; break;
}
});
window.addEventListener('mousemove', (e) => {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});
window.addEventListener('mousedown', (e) => {
if (!controls.isLocked) return;
const intersected = getIntersectedBlock();
if (!intersected) return;
if (e.button === 0) { // 左键破坏
destroyBlock(intersected.position);
} else if (e.button === 2) { // 右键放置
const placePos = new THREE.Vector3().copy(intersected.position).add(intersected.face.normal);
placeBlock(placePos, player.selectedBlock);
}
});
window.addEventListener('contextmenu', (e) => e.preventDefault());
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
function updateInfo() {
document.getElementById('current-block').textContent = BLOCK_NAMES[player.selectedBlock] || '未知';
document.getElementById('position').textContent =
`(${Math.floor(camera.position.x)}, ${Math.floor(camera.position.y)}, ${Math.floor(camera.position.z)})`;
}
// ==================== 玩家移动 ====================
function updatePlayer(deltaTime) {
const direction = new THREE.Vector3();
if (keys.w) direction.z -= 1;
if (keys.s) direction.z += 1;
if (keys.a) direction.x -= 1;
if (keys.d) direction.x += 1;
if (direction.length() > 0) {
direction.normalize();
// 根据相机方向调整移动方向
direction.applyQuaternion(camera.quaternion);
direction.y = 0;
direction.normalize();
player.velocity.x = direction.x * MOVEMENT_SPEED;
player.velocity.z = direction.z * MOVEMENT_SPEED;
} else {
player.velocity.x *= 0.8;
player.velocity.z *= 0.8;
}
// 跳跃
if (keys.space && player.isOnGround) {
player.velocity.y = JUMP_FORCE;
player.isOnGround = false;
}
// 下蹲
if (keys.shift) {
player.velocity.y = -MOVEMENT_SPEED / 2;
}
// 重力
player.velocity.y += GRAVITY;
// 检查地面碰撞
const playerPos = new THREE.Vector3(
camera.position.x,
camera.position.y - 1.8,
camera.position.z
);
const blockBelow = getBlockType(
Math.floor(playerPos.x),
Math.floor(playerPos.y - 0.1),
Math.floor(playerPos.z)
);
if (blockBelow !== BLOCK_TYPES.AIR && player.velocity.y < 0) {
player.velocity.y = 0;
player.isOnGround = true;
} else {
player.isOnGround = false;
}
// 移动玩家
camera.position.x += player.velocity.x;
camera.position.y += player.velocity.y;
camera.position.z += player.velocity.z;
// 碰撞检测
const playerBox = new THREE.Box3().setFromCenterAndSize(
new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z),
new THREE.Vector3(0.6, 1.8, 0.6)
);
// 简单的碰撞检测
const blockSize = new THREE.Vector3(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
for (let dx = -1; dx <= 1; dx++) {
for (let dy = -1; dy <= 1; dy++) {
for (let dz = -1; dz <= 1; dz++) {
const blockX = Math.floor(camera.position.x + dx * 0.5);
const blockY = Math.floor(camera.position.y + dy * 0.5);
const blockZ = Math.floor(camera.position.z + dz * 0.5);
const blockType = getBlockType(blockX, blockY, blockZ);
if (blockType !== BLOCK_TYPES.AIR && blockType !== BLOCK_TYPES.WATER) {
const blockBox = new THREE.Box3().setFromCenterAndSize(
new THREE.Vector3(blockX + 0.5, blockY + 0.5, blockZ + 0.5),
blockSize
);
if (playerBox.intersectsBox(blockBox)) {
// 简单的碰撞响应
if (dx !== 0) {
camera.position.x -= player.velocity.x * 0.5;
player.velocity.x *= 0.5;
}
if (dy !== 0) {
camera.position.y -= player.velocity.y * 0.5;
player.velocity.y *= 0.5;
}
if (dz !== 0) {
camera.position.z -= player.velocity.z * 0.5;
player.velocity.z *= 0.5;
}
}
}
}
}
}
}
// ==================== 渲染循环 ====================
let lastTime = 0;
let frameCount = 0;
let fps = 60;
function animate(currentTime) {
requestAnimationFrame(animate);
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// 计算FPS
frameCount++;
if (currentTime - lastTime > 1000) {
fps = Math.round(frameCount * 1000 / (currentTime - lastTime));
document.getElementById('fps').textContent = `FPS: ${fps}`;
frameCount = 0;
lastTime = currentTime;
}
// 更新区块
chunkManager.update(camera.position.x, camera.position.z);
// 更新需要重新生成的区块
for (const chunk of chunkManager.chunks.values()) {
if (chunk.needsUpdate) {
chunk.generate();
}
}
// 更新玩家
if (controls.isLocked) {
updatePlayer(deltaTime);
updateInfo();
}
// 旋转太阳和月亮
sun.rotation.y += 0.0001;
moon.rotation.y += 0.00005;
// 渲染
renderer.render(scene, camera);
}
// ==================== 初始化 ====================
function init() {
// 生成初始区块
for (let x = -RENDER_DISTANCE; x <= RENDER_DISTANCE; x++) {
for (let z = -RENDER_DISTANCE; z <= RENDER_DISTANCE; z++) {
const chunk = chunkManager.createChunk(x, z);
chunk.generate();
}
}
// 设置初始相机位置
camera.position.set(0, 20, 0);
// 隐藏加载界面
setTimeout(() => {
document.getElementById('loading').classList.add('hidden');
}, 1000);
// 开始动画
animate(0);
}
// 等待 Three.js 加载完成
window.addEventListener('load', init);
</script>
</body>
</html>