BLOCK WORLD 3D
by AtomicBolt46419 lines9.0 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BLOCK WORLD 3D</title>
<style>
* { box-sizing:border-box; }
body {
margin:0;
overflow:hidden;
font-family:Arial;
background:#87ceeb;
}
#info {
position:absolute;
top:12px;
left:12px;
background:#17212ddd;
color:white;
padding:14px;
border-radius:10px;
z-index:2;
}
#info h2 { color:#7dff75; margin:0 0 8px; }
#start {
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
padding:18px 30px;
background:#58ce48;
border:3px solid white;
border-radius:12px;
font-size:22px;
font-weight:bold;
cursor:pointer;
z-index:5;
}
#crosshair {
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
color:white;
font-size:30px;
pointer-events:none;
text-shadow:1px 1px 3px black;
}
#hotbar {
position:absolute;
bottom:20px;
left:50%;
transform:translateX(-50%);
display:flex;
gap:5px;
}
.slot {
width:65px;
height:65px;
background:#222b;
border:3px solid #aaa;
border-radius:7px;
color:white;
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
font-size:12px;
}
.slot.selected { border:4px solid yellow; }
.emoji { font-size:26px; }
#message {
position:absolute;
bottom:100px;
width:100%;
text-align:center;
color:white;
font-weight:bold;
text-shadow:1px 1px 4px black;
pointer-events:none;
}
</style>
</head>
<body>
<div id="info">
<h2>🌍 BLOCK WORLD</h2>
<div>WASD - Move</div>
<div>Mouse - Look around</div>
<div>SPACE - Jump</div>
<div>Left click - Break block</div>
<div>Right click - Place block</div>
<div>1-5 - Select block</div>
<div>ESC - Pause</div>
</div>
<button id="start">▶ PLAY GAME</button>
<div id="crosshair">+</div>
<div id="message">Explore and build your world!</div>
<div id="hotbar"></div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
// SCENE
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, 1000
);
const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// LIGHTS
scene.add(new THREE.HemisphereLight(0xffffff,0x526b42,2));
const sun = new THREE.DirectionalLight(0xffffff,2);
sun.position.set(15,30,10);
sun.castShadow = true;
scene.add(sun);
// MATERIALS
const materials = {
Grass: new THREE.MeshLambertMaterial({color:0x55a832}),
Dirt: new THREE.MeshLambertMaterial({color:0x80502d}),
Stone: new THREE.MeshLambertMaterial({color:0x888888}),
Wood: new THREE.MeshLambertMaterial({color:0x80502d}),
Leaves: new THREE.MeshLambertMaterial({
color:0x237b27, transparent:true, opacity:0.95
}),
Sand: new THREE.MeshLambertMaterial({color:0xd9c27d})
};
const blockTypes = ["Grass","Dirt","Stone","Wood","Sand"];
const icons = ["🟩","🟫","🪨","🪵","🟨"];
let selected = 0;
// BLOCK STORAGE
const blocks = new Map();
const meshes = [];
const geometry = new THREE.BoxGeometry(1,1,1);
function key(x,y,z) {
return `${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(geometry,materials[type]);
mesh.position.set(x,y,z);
mesh.castShadow = true;
mesh.receiveShadow = true;
mesh.userData.block = true;
mesh.userData.type = type;
scene.add(mesh);
blocks.set(k,mesh);
meshes.push(mesh);
}
function removeBlock(mesh) {
const p = mesh.position;
blocks.delete(key(p.x,p.y,p.z));
scene.remove(mesh);
const index = meshes.indexOf(mesh);
if(index >= 0) meshes.splice(index,1);
}
// TERRAIN
const heights = new Map();
function terrainHeight(x,z) {
return Math.floor(
2 + Math.sin(x*0.22)*1.5 + Math.cos(z*0.2)*1.3
);
}
for(let x=-20;x<=20;x++) {
for(let z=-20;z<=20;z++) {
const h = terrainHeight(x,z);
heights.set(`${x},${z}`,h);
for(let y=-3;y<=h;y++) {
let type = y===h ? "Grass" : "Dirt";
if(y===h && h<=1) type="Sand";
if(y<h-2) type="Stone";
addBlock(x,y,z,type);
}
}
}
// TREES
function makeTree(x,z) {
const h = terrainHeight(x,z);
if(h<=1) return;
for(let y=1;y<=3;y++)
addBlock(x,h+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,h+dy,z+dz,"Leaves");
}
}
}
}
}
[
[-7,-6],[-12,5],[6,-9],[11,7],
[15,-3],[-16,-9],[3,13],[-3,8]
].forEach(p=>makeTree(p[0],p[1]));
// PLAYER
let yaw = 0;
let pitch = 0;
let velocityY = 0;
let onGround = false;
const keys = {};
camera.position.set(0,terrainHeight(0,0)+2,0);
document.addEventListener("keydown",e=>{
keys[e.code]=true;
if(e.code.startsWith("Digit")) {
const n=Number(e.code.slice(5))-1;
if(n>=0 && n<5) selectBlock(n);
}
if(e.code==="Space" && onGround) {
velocityY=0.18;
onGround=false;
}
});
document.addEventListener("keyup",e=>{
keys[e.code]=false;
});
document.addEventListener("mousemove",e=>{
if(document.pointerLockElement===renderer.domElement) {
yaw -= e.movementX*0.002;
pitch -= e.movementY*0.002;
pitch=Math.max(-1.5,Math.min(1.5,pitch));
camera.rotation.set(pitch,yaw,0,"YXZ");
}
});
// START GAME
document.getElementById("start").onclick=()=>{
renderer.domElement.requestPointerLock();
};
document.addEventListener("pointerlockchange",()=>{
document.getElementById("start").style.display =
document.pointerLockElement===renderer.domElement
? "none":"block";
});
// HOTBAR
function selectBlock(n) {
selected=n;
updateHotbar();
}
function updateHotbar() {
const bar=document.getElementById("hotbar");
bar.innerHTML="";
blockTypes.forEach((type,i)=>{
const slot=document.createElement("div");
slot.className="slot"+(selected===i?" selected":"");
slot.innerHTML=
`<div class="emoji">${icons[i]}</div>${i+1}. ${type}`;
bar.appendChild(slot);
});
}
updateHotbar();
// RAYCASTING
const raycaster = new THREE.Raycaster();
raycaster.far=6;
function getTarget() {
raycaster.setFromCamera(new THREE.Vector2(0,0),camera);
return raycaster.intersectObjects(meshes,false)[0];
}
// BREAK BLOCK
renderer.domElement.addEventListener("mousedown",e=>{
if(document.pointerLockElement!==renderer.domElement) return;
if(e.button===0) {
const hit=getTarget();
if(hit) {
removeBlock(hit.object);
showMessage("Block broken!");
}
}
if(e.button===2) {
const hit=getTarget();
if(hit) {
const p=hit.object.position;
const n=hit.face.normal;
const x=Math.round(p.x+n.x);
const y=Math.round(p.y+n.y);
const z=Math.round(p.z+n.z);
// Keep blocks away from the player's position
if(Math.abs(x-camera.position.x)>0.8 ||
Math.abs(z-camera.position.z)>0.8 ||
Math.abs(y-camera.position.y)>1.5) {
addBlock(x,y,z,blockTypes[selected]);
showMessage(blockTypes[selected]+" placed!");
}
}
}
});
document.addEventListener("contextmenu",e=>e.preventDefault());
function showMessage(msg) {
const el=document.getElementById("message");
el.textContent=msg;
clearTimeout(showMessage.timer);
showMessage.timer=setTimeout(()=>{
el.textContent="Explore and build your world!";
},1500);
}
// MOVEMENT
function updatePlayer() {
const speed=keys["ShiftLeft"]?0.13:0.09;
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"]||keys["ArrowUp"]) move.add(forward);
if(keys["KeyS"]||keys["ArrowDown"]) move.sub(forward);
if(keys["KeyD"]||keys["ArrowRight"]) move.add(right);
if(keys["KeyA"]||keys["ArrowLeft"]) move.sub(right);
if(move.length()>0) {
move.normalize().multiplyScalar(speed);
camera.position.x+=move.x;
camera.position.z+=move.z;
}
camera.position.x=THREE.MathUtils.clamp(camera.position.x,-19,19);
camera.position.z=THREE.MathUtils.clamp(camera.position.z,-19,19);
const px=Math.round(camera.position.x);
const pz=Math.round(camera.position.z);
const ground=heights.get(`${px},${pz}`) ?? 2;
velocityY-=0.009;
camera.position.y+=velocityY;
if(camera.position.y<ground+2) {
camera.position.y=ground+2;
velocityY=0;
onGround=true;
}
}
// ANIMATION
function animate() {
requestAnimationFrame(animate);
if(document.pointerLockElement===renderer.domElement) {
updatePlayer();
}
renderer.render(scene,camera);
}
animate();
// RESIZE
window.addEventListener("resize",()=>{
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
</script>
</body>
</html>Game Source: BLOCK WORLD 3D
Creator: AtomicBolt46
Libraries: three
Complexity: complex (419 lines, 9.0 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: block-world-3d-atomicbolt46" to link back to the original. Then publish at arcadelab.ai/publish.