MC网页沙盒|可破坏方块火把修复版
by DriftTurtle56766 lines26.8 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>MC网页沙盒|可破坏方块火把修复版</title>
<style>
*{margin:0;padding:0}
body{overflow:hidden;background:#000;font-family:Arial}
.ui-info{
position:absolute;
top:15px;left:15px;
background:rgba(0,0,0,0.65);
color:#fff;padding:12px;border-radius:8px;
z-index:10;line-height:1.6;font-size:14px;
}
.crosshair{
position:absolute;
top:50%;left:50%;
transform:translate(-50%,-50%);
color:#fff;font-size:26px;
pointer-events:none;z-index:10;
}
</style>
</head>
<body>
<div class="ui-info">
左键拖拽转视角|左键打怪|中键放方块|右键拆方块<br>
WASD移动 空格跳跃|1草地 2泥土 3石头 4原木 5木板 6树叶 7水 8岩浆 9火把<br>
生命值:<span id="hpText">100</span>
<span id="dayTip">时段:白天</span>
</div>
<div class="crosshair">+</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script>
let scene, camera, renderer, sunLight, ambientLight;
const blockWorld = new Map();
const BLOCK_SIZE = 1;
let selectBlock = 0;
const keyMap = {};
let yaw = 0;
let pitch = 0;
let isDrag = false;
let lastX = 0, lastY = 0;
const SENSITIVITY = 0.0022;
const GRAVITY = -0.022;
let velocityY = 0;
const JUMP_FORCE = 0.35;
const MOVE_SPEED = 0.18;
const PLAYER_HALF_WIDTH = 0.35;
const PLAYER_HEIGHT = 1.8;
let hp = 100;
const hpText = document.getElementById("hpText");
const dayTip = document.getElementById("dayTip");
let dayTime = 0.45;
const DAY_SPEED = 0.00012;
let isNight = false;
let wasNightLastFrame = false;
const MONSTER_TYPE = {ZOMBIE:0,SKELETON:1,SPIDER:2};
const monsterList = [];
const monsterAnimations = [];
const arrowList = [];
let lastDamageTime = 0;
let lastAttackTime = 0;
const ATTACK_COOLDOWN = 350;
const ATTACK_RANGE = 4.5;
const villagerGeoBody = new THREE.BoxGeometry(0.45, 0.7, 0.35);
const villagerGeoHead = new THREE.BoxGeometry(0.35, 0.35, 0.35);
const villagerGeoLeg = new THREE.BoxGeometry(0.18, 0.55, 0.18);
const matVillager = new THREE.MeshLambertMaterial({color:0xc89979});
const matVillagerCloth = new THREE.MeshLambertMaterial({color:0x347eb8});
const blockGeo = new THREE.BoxGeometry(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
// 方形火把几何体
const torchStickBox = new THREE.BoxGeometry(0.12, 0.65, 0.12);
const torchFireBox = new THREE.BoxGeometry(0.22, 0.22, 0.22);
const boxSmall = new THREE.BoxGeometry(0.35, 0.45, 0.35);
const boxBody = new THREE.BoxGeometry(0.55, 0.75, 0.32);
const boxLeg = new THREE.BoxGeometry(0.22, 0.62, 0.22);
const spiderBodyGeo = new THREE.BoxGeometry(1.1, 0.45, 0.75);
const spiderHeadGeo = new THREE.BoxGeometry(0.42, 0.38, 0.42);
const spiderLegGeo = new THREE.BoxGeometry(0.72, 0.14, 0.14);
const arrowGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.6, 4);
const arrowMat = new THREE.MeshLambertMaterial({ color: 0x604020 });
const lavaLights = [];
const torchLights = [];
function createTex(canvasFn){
const canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext("2d");
canvasFn(ctx);
const tex = new THREE.CanvasTexture(canvas);
tex.magFilter = THREE.NearestFilter;
tex.minFilter = THREE.NearestMipmapNearestFilter;
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
return tex;
}
const texGrassTop = createTex(ctx=>{
ctx.fillStyle="#3a7d30";ctx.fillRect(0,0,64,64);
ctx.fillStyle="#2d6b24";for(let i=0;i<100;i++)ctx.fillRect(Math.random()*64,Math.random()*64,3,3);
});
const texGrassSide = createTex(ctx=>{
ctx.fillStyle="#8b5a2b";ctx.fillRect(0,0,64,64);
ctx.fillStyle="#3a7d30";ctx.fillRect(0,0,64,22);
ctx.fillStyle="#7a4c22";for(let i=0;i<80;i++)ctx.fillRect(Math.random()*64,22+Math.random()*42,3,3);
});
const texDirt = createTex(ctx=>{
ctx.fillStyle="#8b5a2b";ctx.fillRect(0,0,64,64);
ctx.fillStyle="#7a4c22";for(let i=0;i<90;i++)ctx.fillRect(Math.random()*64,Math.random()*64,3,3);
});
const texStone = createTex(ctx=>{
ctx.fillStyle="#777777";ctx.fillRect(0,0,64,64);
ctx.fillStyle="#666666";for(let y=0;y<64;y+=8)for(let x=0;x<64;x+=8)if((x+y)%16===0)ctx.fillRect(x,y,5,5);
});
const texLog = createTex(ctx=>{
ctx.fillStyle="#795537";ctx.fillRect(0,0,64,64);
ctx.strokeStyle="#5c4028";ctx.lineWidth=3;
for(let x=8;x<64;x+=12){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,64);ctx.stroke();}
});
const texPlank = createTex(ctx=>{
ctx.fillStyle="#b88c60";ctx.fillRect(0,0,64,64);
ctx.strokeStyle="#8c6640";ctx.lineWidth=2;
for(let y=0;y<64;y+=16){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(64,y);ctx.stroke();}
});
const texLeaves = createTex(ctx=>{
ctx.fillStyle="#3a7d30";ctx.fillRect(0,0,64,64);
ctx.fillStyle="#2d6b24";for(let i=0;i<90;i++)ctx.fillRect(Math.random()*64,Math.random()*64,4,4);
});
const texWater = createTex(ctx=>{
ctx.fillStyle="#4286f4";
ctx.fillRect(0,0,64,64);
ctx.fillStyle="#63a1ff";
for(let i=0;i<70;i++) ctx.fillRect(Math.random()*64,Math.random()*64,5,2);
});
const texLava = createTex(ctx=>{
ctx.fillStyle="#ff4500";
ctx.fillRect(0,0,64,64);
ctx.fillStyle="#ffaa00";
for(let i=0;i<80;i++) ctx.fillRect(Math.random()*64,Math.random()*64,6,6);
});
const matTorchStick = new THREE.MeshLambertMaterial({color:0x6B4423});
const matTorchFire = new THREE.MeshLambertMaterial({
color:0xFFE870,
emissive:0xFF7711,
emissiveIntensity:0.6
});
function createGrassMaterial(){
return [
new THREE.MeshLambertMaterial({map:texGrassSide}),
new THREE.MeshLambertMaterial({map:texGrassSide}),
new THREE.MeshLambertMaterial({map:texGrassTop}),
new THREE.MeshLambertMaterial({map:texDirt}),
new THREE.MeshLambertMaterial({map:texGrassSide}),
new THREE.MeshLambertMaterial({map:texGrassSide})
]
}
const matDirt = new THREE.MeshLambertMaterial({map:texDirt});
const matStone = new THREE.MeshLambertMaterial({map:texStone});
const matLog = new THREE.MeshLambertMaterial({map:texLog});
const matPlank = new THREE.MeshLambertMaterial({map:texPlank});
const matLeaves = new THREE.MeshLambertMaterial({map:texLeaves});
const matWater = new THREE.MeshLambertMaterial({
map: texWater,
transparent: true,
opacity: 0.7
});
const matLava = new THREE.MeshLambertMaterial({
map: texLava,
emissive: 0xff5500,
emissiveIntensity: 0.6
});
const matZombie = new THREE.MeshLambertMaterial({ color: 0x669944 });
const matSkeleton = new THREE.MeshLambertMaterial({ color: 0xdddddd });
const matSpider = new THREE.MeshLambertMaterial({ color: 0x222222 });
function getGroundHeight(worldX, worldZ){
let checkY = 20;
while(checkY > -20){
if(blockWorld.has(`${Math.round(worldX)},${checkY},${Math.round(worldZ)}`)){
return checkY + 1;
}
checkY--;
}
return Math.round(noise(worldX, worldZ));
}
function createTorchMesh(){
const group = new THREE.Group();
const stick = new THREE.Mesh(torchStickBox, matTorchStick);
stick.position.y = -0.25;
const fire = new THREE.Mesh(torchFireBox, matTorchFire);
fire.position.y = 0.18;
group.add(stick);
group.add(fire);
return group;
}
function createVillager(){
const root = new THREE.Group();
const head = new THREE.Mesh(villagerGeoHead, matVillager);
head.position.y = 1.35;
const body = new THREE.Mesh(villagerGeoBody, matVillagerCloth);
body.position.y = 0.9;
const legL = new THREE.Mesh(villagerGeoLeg, matVillager);
legL.position.set(-0.16, 0.28, 0);
const legR = new THREE.Mesh(villagerGeoLeg, matVillager);
legR.position.set(0.16, 0.28, 0);
[head,body,legL,legR].forEach(m=>{
m.castShadow = false;
m.receiveShadow = false;
root.add(m);
})
root.legL = legL;
root.legR = legR;
root.walkTimer = Math.random()*100;
root.targetPos = new THREE.Vector3();
root.wanderRange = 12;
return root;
}
const villagerList = [];
function spawnVillager(count, spawnX, spawnZ){
for(let i=0;i<count;i++){
const villager = createVillager();
const ox = spawnX + (Math.random()-0.5)*10;
const oz = spawnZ + (Math.random()-0.5)*10;
const groundY = getGroundHeight(ox, oz);
villager.position.set(ox, groundY, oz);
villager.targetPos.set(ox, groundY, oz);
villagerList.push(villager);
scene.add(villager);
}
}
function updateVillagers(){
for(let v of villagerList){
v.walkTimer += 1;
const dist = v.position.distanceTo(v.targetPos);
if(dist<0.8 || v.walkTimer>300){
v.walkTimer = 0;
const rx = v.position.x + (Math.random()-0.5)*v.wanderRange;
const rz = v.position.z + (Math.random()-0.5)*v.wanderRange;
const groundY = getGroundHeight(rx, rz);
v.targetPos.set(rx, groundY, rz);
}
const currentGround = getGroundHeight(v.position.x, v.position.z);
if(Math.abs(v.position.y - currentGround) > 0.1){
v.position.y = currentGround;
}
if(!isNight){
const dir = new THREE.Vector3().subVectors(v.targetPos, v.position);
dir.y=0;
if(dir.length()>0.1){
dir.normalize();
v.position.x += dir.x*0.025;
v.position.z += dir.z*0.025;
const swing = Math.sin(v.walkTimer*0.12)*0.18;
v.legL.rotation.x = swing;
v.legR.rotation.x = -swing;
}
v.lookAt(v.targetPos.x, v.position.y, v.targetPos.z);
}else{
v.legL.rotation.x = 0;
v.legR.rotation.x = 0;
}
}
}
function createMonsterState(mob, type){
return {mob,type,walkTime:0,lastPos:new THREE.Vector3(),legL:null,legR:null,head:null,legs:[]};
}
function buildZombie(){
const root = new THREE.Group();
const head = new THREE.Mesh(boxSmall, matZombie);head.position.y=1.42;
const body = new THREE.Mesh(boxBody, matZombie);body.position.y=0.9;
const legL = new THREE.Mesh(boxLeg, matZombie);legL.position.set(-0.22,0.31,0);
const legR = new THREE.Mesh(boxLeg, matZombie);legR.position.set(0.22,0.31,0);
[head,body,legL,legR].forEach(m=>{m.castShadow=false;m.receiveShadow=false;root.add(m)});
root.monsterType=MONSTER_TYPE.ZOMBIE;root.lastShoot=0;
const state=createMonsterState(root,MONSTER_TYPE.ZOMBIE);
state.legL=legL;state.legR=legR;state.head=head;monsterAnimations.push(state);
return root;
}
function buildSkeleton(){
const root = new THREE.Group();
const head = new THREE.Mesh(boxSmall, matSkeleton);head.position.y=1.42;
const body = new THREE.Mesh(boxBody, matSkeleton);body.position.y=0.9;
const legL = new THREE.Mesh(boxLeg, matSkeleton);legL.position.set(-0.22,0.31,0);
const legR = new THREE.Mesh(boxLeg, matSkeleton);legR.position.set(0.22,0.31,0);
[head,body,legL,legR].forEach(m=>{m.castShadow=false;m.receiveShadow=false;root.add(m)});
root.monsterType=MONSTER_TYPE.SKELETON;root.lastShoot=0;
const state=createMonsterState(root,MONSTER_TYPE.SKELETON);
state.legL=legL;state.legR=legR;state.head=head;monsterAnimations.push(state);
return root;
}
function buildSpider(){
const root = new THREE.Group();
const torso = new THREE.Mesh(spiderBodyGeo, matSpider);torso.position.y=0.26;
const head = new THREE.Mesh(spiderHeadGeo, matSpider);head.position.set(0,0.32,0.52);
const legPos=[[-0.6,0,0.25],[0.6,0,0.25],[-0.6,0,-0.25],[0.6,0,-0.25]];
const legList=[];
legPos.forEach(p=>{
const leg=new THREE.Mesh(spiderLegGeo,matSpider);leg.position.set(p[0],0.12,p[2]);
leg.castShadow=false;root.add(leg);legList.push(leg);
});
[torso,head].forEach(m=>{m.castShadow=false;m.receiveShadow=false;root.add(m)});
root.monsterType=MONSTER_TYPE.SPIDER;root.lastShoot=0;
const state=createMonsterState(root,MONSTER_TYPE.SPIDER);state.legs=legList;monsterAnimations.push(state);
return root;
}
function updateMonsterAnimations(){
for(const anim of monsterAnimations){
const mob=anim.mob;
const moveDelta=mob.position.clone().sub(anim.lastPos);
const isMoving=moveDelta.length()>0.0008;
anim.lastPos.copy(mob.position);
if(isMoving)anim.walkTime+=0.12;
if(anim.type===MONSTER_TYPE.ZOMBIE||anim.type===MONSTER_TYPE.SKELETON){
const swing=Math.sin(anim.walkTime)*0.2;
anim.legL.rotation.x=swing;anim.legR.rotation.x=-swing;
}
if(anim.type===MONSTER_TYPE.SPIDER){
anim.legs.forEach((leg,i)=>{
const phase=i%2===0?anim.walkTime:anim.walkTime+Math.PI;
leg.rotation.x=Math.sin(phase)*0.2;
})
}
}
}
function buildAdvHouse(hx,hz){
const w=7,d=8,wallH=3;
const groundY = getGroundHeight(hx, hz);
const baseY = groundY + 1;
for(let x=-1;x<=w+1;x++){
for(let z=-1;z<=d+1;z++){
createBlock(hx+x, groundY, hz+z, 2);
}
}
for(let y=0;y<=wallH;y++){
for(let x=0;x<=w;x++){
createBlock(hx+x, baseY+y, hz, 4);
createBlock(hx+x, baseY+y, hz+d, 4);
}
for(let z=1;z<d;z++){
createBlock(hx, baseY+y, hz+z, 4);
createBlock(hx+w, baseY+y, hz+z, 4);
}
}
createBlock(hx+3, baseY+1, hz, -1);
createBlock(hx+3, baseY+2, hz, -1);
createBlock(hx+1, baseY+2, hz, -1);
createBlock(hx+5, baseY+2, hz, -1);
createBlock(hx+1, baseY+2, hz+d, -1);
createBlock(hx+5, baseY+2, hz+d, -1);
for(let lay=0;lay<=3;lay++){
const shrink = lay;
for(let x=shrink;x<=w-shrink;x++){
for(let z=shrink;z<=d-shrink;z++){
createBlock(hx+x, baseY+wallH+1+lay, hz+z, 5);
}
}
}
}
function buildFarm(fx,fz){
const farmW=6,farmD=6;
const groundY = getGroundHeight(fx, fz);
for(let x=0;x<farmW;x++){
for(let z=0;z<farmD;z++){
createBlock(fx+x, groundY, fz+z, 1);
}
}
}
function buildWell(wx,wz){
const groundY = getGroundHeight(wx, wz);
createBlock(wx,groundY,wz,2);
createBlock(wx+1,groundY,wz,2);
createBlock(wx,groundY,wz+1,2);
createBlock(wx+1,groundY,wz+1,2);
createBlock(wx+0.5,groundY-1,wz+0.5,7);
}
function generateVillage(){
const villageX = 22;
const villageZ = -18;
buildAdvHouse(villageX, villageZ);
buildAdvHouse(villageX+9, villageZ+3);
buildAdvHouse(villageX-8, villageZ+6);
buildAdvHouse(villageX+5, villageZ-9);
buildFarm(villageX+12, villageZ-4);
buildFarm(villageX-9, villageZ-6);
buildWell(villageX+4, villageZ-8);
spawnVillager(6, villageX, villageZ);
}
function startGame(){
scene=new THREE.Scene();
scene.background=new THREE.Color(0x87CEEB);
scene.fog=new THREE.Fog(0x87CEEB,40,120);
camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,500);
camera.position.set(0,PLAYER_HEIGHT,12);
renderer=new THREE.WebGLRenderer({antialias:false});
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled=false;
document.body.appendChild(renderer.domElement);
sunLight=new THREE.DirectionalLight(0xffffff,1);
sunLight.position.set(40,60,30);
scene.add(sunLight);
ambientLight=new THREE.AmbientLight(0xffffff,0.4);
scene.add(ambientLight);
generateTerrain();
spawnManyTrees(15);
generateVillage();
window.addEventListener("mousedown",e=>{
if(e.button===0){attackMonster(performance.now());isDrag=true;lastX=e.clientX;lastY=e.clientY;}
});
document.addEventListener("mouseup",()=>isDrag=false);
document.addEventListener("mousemove",e=>{
if(!isDrag)return;
const dx=e.clientX-lastX,dy=e.clientY-lastY;
yaw+=dx*SENSITIVITY;pitch+=dy*SENSITIVITY;
pitch=Math.max(-Math.PI/2+0.1,Math.min(Math.PI/2-0.1,pitch));
lastX=e.clientX;lastY=e.clientY;
});
window.addEventListener("keydown",e=>{
keyMap[e.code]=true;
if(e.code==="Digit1")selectBlock=0;
if(e.code==="Digit2")selectBlock=1;
if(e.code==="Digit3")selectBlock=2;
if(e.code==="Digit4")selectBlock=3;
if(e.code==="Digit5")selectBlock=4;
if(e.code==="Digit6")selectBlock=5;
if(e.code==="Digit7")selectBlock=7;
if(e.code==="Digit8")selectBlock=8;
if(e.code==="Digit9")selectBlock=9;
});
window.addEventListener("keyup",e=>keyMap[e.code]=false);
const raycaster=new THREE.Raycaster();
renderer.domElement.addEventListener("mousedown",e=>{
raycaster.setFromCamera(new THREE.Vector2(0,0),camera);
const hits=raycaster.intersectObjects(scene.children,true);
if(hits.length<=0)return;
const hit=hits[0];
// 向上追溯找到最外层父物体(火把Group)
let targetObj = hit.object;
while(targetObj.parent !== scene){
targetObj = targetObj.parent;
}
const pos=hit.point.clone().add(hit.face.normal.clone().multiplyScalar(0.5));
if(e.button===1)createBlock(Math.round(pos.x),Math.round(pos.y),Math.round(pos.z),selectBlock);
if(e.button===2){
const bx = Math.round(targetObj.position.x);
const by = Math.round(targetObj.position.y);
const bz = Math.round(targetObj.position.z);
removeBlock(bx,by,bz);
}
});
renderer.domElement.addEventListener("contextmenu",e=>e.preventDefault());
loop();
}
function noise(x,z){
return Math.sin(x*0.08)*6+Math.cos(z*0.08)*6+Math.sin((x+z)*0.05)*4;
}
function generateTerrain(){
const MAP_SIZE=40;
for(let x=-MAP_SIZE;x<=MAP_SIZE;x++){
for(let z=-MAP_SIZE;z<=MAP_SIZE;z++){
const h=Math.round(noise(x,z));
for(let y=h;y>=h-3;y--)createBlock(x,y,z,1);
createBlock(x,h,z,0);
if(Math.random()<0.004)createBlock(x,h+1,z,2);
}
}
const spawnH=getGroundHeight(0,0);
camera.position.set(0,spawnH+PLAYER_HEIGHT,12);
}
function createBlock(x,y,z,matIndex){
if(matIndex===-1) return;
const key=`${x},${y},${z}`;
if(blockWorld.has(key))return;
let mesh;
if(matIndex === 9){
mesh = createTorchMesh();
const torchLight = new THREE.PointLight(0xffbb55, 0.75, 14);
torchLight.position.set(0,0.3,0);
mesh.add(torchLight);
torchLights.push(torchLight);
mesh.torchLight = torchLight;
}else{
mesh=new THREE.Mesh(blockGeo,getBlockMaterial(matIndex));
}
mesh.position.set(x,y,z);
mesh.castShadow=false;mesh.receiveShadow=false;
mesh.blockType = matIndex;
blockWorld.set(key,mesh);
scene.add(mesh);
if(matIndex === 8){
const pointLight = new THREE.PointLight(0xff5500, 0.8, 12);
pointLight.position.set(x,y,z);
scene.add(pointLight);
lavaLights.push(pointLight);
mesh.lightRef = pointLight;
}
}
function getBlockMaterial(index){
switch(index){
case 0:return createGrassMaterial();
case 1:return matDirt;
case 2:return matStone;
case 3:return matLog;
case 4:return matPlank;
case 5:return matLeaves;
case 7:return matWater;
case 8:return matLava;
}
}
function removeBlock(x,y,z){
const key=`${x},${y},${z}`;
if(blockWorld.has(key)){
const mesh = blockWorld.get(key);
if(mesh.torchLight){
mesh.remove(mesh.torchLight);
const idx = torchLights.indexOf(mesh.torchLight);
if(idx>-1) torchLights.splice(idx,1);
}
if(mesh.lightRef){
scene.remove(mesh.lightRef);
const idx = lavaLights.indexOf(mesh.lightRef);
if(idx>-1) lavaLights.splice(idx,1);
}
scene.remove(mesh);
blockWorld.delete(key);
}
}
function spawnTree(originX,originZ){
const trunkHeight=4;
const h = getGroundHeight(originX, originZ);
for(let y=h;y<=h+trunkHeight-1;y++)createBlock(originX,y,originZ,3);
const leafY=h+trunkHeight;
for(let dx=-2;dx<=2;dx++){
for(let dz=-2;dz<=2;dz++){
for(let dy=0;dy<=2;dy++){
if(Math.abs(dx)+Math.abs(dz)<=2)createBlock(originX+dx,leafY+dy,originZ+dz,5);
}
}
}
}
function spawnManyTrees(count){
for(let i=0;i<count;i++){
const tx=Math.floor(Math.random()*70-35);
const tz=Math.floor(Math.random()*70-35);
spawnTree(tx,tz);
}
}
function spawnMonsterGroup(count,type){
for(let i=0;i<count;i++){
const mx=Math.floor(Math.random()*60-30);
const mz=Math.floor(Math.random()*60-30);
const h=getGroundHeight(mx,mz);
let mobRoot;
if(type===MONSTER_TYPE.ZOMBIE)mobRoot=buildZombie();
else if(type===MONSTER_TYPE.SKELETON)mobRoot=buildSkeleton();
else mobRoot=buildSpider();
mobRoot.position.set(mx,h,mz);
monsterList.push(mobRoot);scene.add(mobRoot);
}
}
function despawnAllMonsters(){
monsterList.forEach(m=>scene.remove(m));
monsterList.length=0;monsterAnimations.length=0;arrowList.length=0;
}
function hasBlock(x,z,y){
return blockWorld.has(`${Math.round(x)},${Math.round(y)},${Math.round(z)}`);
}
function canMove(newX,newZ){
const r=PLAYER_HALF_WIDTH;
const floorY=camera.position.y-0.2;
return !(hasBlock(newX-r,newZ-r,floorY)||hasBlock(newX+r,newZ-r,floorY)||hasBlock(newX-r,newZ+r,floorY)||hasBlock(newX+r,newZ+r,floorY));
}
function isOnGround(){
return hasBlock(camera.position.x,camera.position.z,camera.position.y-1.05);
}
function attackMonster(time){
if(time-lastAttackTime<ATTACK_COOLDOWN)return;
lastAttackTime=time;
for(let i=monsterList.length-1;i>=0;i--){
const mob=monsterList[i];
if(camera.position.distanceTo(mob.position)<ATTACK_RANGE){
const idx=monsterAnimations.findIndex(a=>a.mob===mob);
if(idx>-1)monsterAnimations.splice(idx,1);
scene.remove(mob);monsterList.splice(i,1);break;
}
}
}
function updateMonsters(time){
if(!isNight){despawnAllMonsters();return;}
if(isNight&&!wasNightLastFrame){
spawnMonsterGroup(2,MONSTER_TYPE.ZOMBIE);
spawnMonsterGroup(2,MONSTER_TYPE.SKELETON);
spawnMonsterGroup(2,MONSTER_TYPE.SPIDER);
}
for(const mob of monsterList){
const dir=new THREE.Vector3().subVectors(camera.position,mob.position);dir.y=0;
const dist=dir.length();
const speed=mob.monsterType===MONSTER_TYPE.SPIDER?0.052:0.035;
if(dist>1.8){
dir.normalize();
if(!hasBlock(mob.position.x+dir.x*speed,mob.position.z,0))mob.position.x+=dir.x*speed;
if(!hasBlock(mob.position.x,mob.position.z+dir.z*speed,0))mob.position.z+=dir.z*speed;
}
if(mob.monsterType===MONSTER_TYPE.SKELETON&&dist<14&&dist>3){
if(time-mob.lastShoot>2800){
mob.lastShoot=time;
const arrow=new THREE.Mesh(arrowGeo,arrowMat);
arrow.position.copy(mob.position);arrow.position.y+=1.1;
arrow.vel=new THREE.Vector3().subVectors(camera.position,arrow.position).normalize().multiplyScalar(0.22);
arrowList.push(arrow);scene.add(arrow);
}
}
if(dist<1.6&&time-lastDamageTime>800){
hp-=10;hpText.textContent=hp;lastDamageTime=time;
if(hp<=0)alert("死亡,刷新页面重开");
}
mob.lookAt(camera.position.x,mob.position.y,camera.position.z);
}
for(let i=arrowList.length-1;i>=0;i--){
const arr=arrowList[i];
arr.position.add(arr.vel);
if(arr.position.distanceTo(camera.position)<1.2){
if(time-lastDamageTime>800){hp-=10;hpText.textContent=hp;lastDamageTime=time;if(hp<=0)alert("死亡");}
scene.remove(arr);arrowList.splice(i,1);
}else if(arr.position.distanceTo(camera.position)>25){
scene.remove(arr);arrowList.splice(i,1);
}
}
updateMonsterAnimations();
}
function updateDayNight(){
dayTime+=DAY_SPEED;
const angle=dayTime%(Math.PI*2);
sunLight.position.set(Math.cos(angle)*60,Math.sin(angle)*60,30);
wasNightLastFrame=isNight;
isNight=Math.sin(angle)<=0;
dayTip.textContent=isNight?"时段:夜晚(刷怪)":"时段:白天(怪物清空)";
let skyColor,sunIntensity,ambIntensity;
if(!isNight){
const bright=Math.sin(angle);
sunIntensity=1*bright;ambIntensity=0.25+bright*0.25;
skyColor=new THREE.Color(0x87CEEB);
}else{
const dark=-Math.sin(angle);
sunIntensity=0;ambIntensity=0.08+dark*0.04;
skyColor=new THREE.Color(0x0a1028);
}
scene.background=skyColor;scene.fog.color.copy(skyColor);
sunLight.intensity=sunIntensity;ambientLight.intensity=ambIntensity;
const pulse = Math.sin(dayTime * 15) * 0.3 + 0.7;
lavaLights.forEach(light=>{
light.intensity = 0.8 * pulse;
})
torchLights.forEach(light=>{
light.intensity = (0.65 + Math.sin(dayTime * 20 + light.parent.position.x) * 0.15);
})
}
function loop(time=0){
requestAnimationFrame(loop);
updateDayNight();
camera.rotation.order="YXZ";
camera.rotation.y=yaw;camera.rotation.x=pitch;
const forward=new THREE.Vector3(0,0,-1).applyQuaternion(camera.quaternion);forward.y=0;forward.normalize();
const right=new THREE.Vector3(1,0,0).applyQuaternion(camera.quaternion);
let moveVec=new THREE.Vector3();
if(keyMap["KeyW"])moveVec.add(forward);
if(keyMap["KeyS"])moveVec.add(forward.clone().multiplyScalar(-1));
if(keyMap["KeyA"])moveVec.add(right.clone().multiplyScalar(-1));
if(keyMap["KeyD"])moveVec.add(right);
if(moveVec.length()>0)moveVec.normalize().multiplyScalar(MOVE_SPEED);
const nx=camera.position.x+moveVec.x;
const nz=camera.position.z+moveVec.z;
if(canMove(nx,camera.position.z))camera.position.x=nx;
if(canMove(camera.position.x,nz))camera.position.z=nz;
const onGround=isOnGround();
if(onGround){velocityY=0;if(keyMap["Space"])velocityY=JUMP_FORCE;}
else velocityY+=GRAVITY;
camera.position.y+=velocityY;
updateMonsters(time);
updateVillagers();
renderer.render(scene,camera);
}
window.addEventListener("resize",()=>{
camera.aspect=window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight, false);
});
startGame();
</script>
</body>
</html>Game Source: MC网页沙盒|可破坏方块火把修复版
Creator: DriftTurtle56
Libraries: three
Complexity: complex (766 lines, 26.8 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: mc-driftturtle56" to link back to the original. Then publish at arcadelab.ai/publish.