BloqueCraft 3D - VideojuCan
by CosmicCobra59298 lines12.6 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>BloqueCraft 3D - VideojuCan</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
body { background:#000; overflow:hidden; font-family:Arial; touch-action:manipulation; user-select:none; -webkit-user-select:none; -webkit-tap-highlight-color:transparent; }
#ui { position:absolute; top:10px; left:10px; background:rgba(0,0,0,0.5); color:#fff; padding:6px 10px; border-radius:6px; z-index:20; pointer-events:none; font-size:12px; }
.crosshair {
position:absolute; top:50%; left:50%;
transform:translate(-50%,-50%);
color:#fff; font-size:26px;
pointer-events:none; z-index:20;
text-shadow: 0 0 4px #000, 0 0 4px #000, 0 0 4px #000;
}
#prog { position:absolute; bottom:120px; left:50%; transform:translateX(-50%); width:100px; height:8px; background:rgba(0,0,0,0.6); border:1px solid #fff; border-radius:4px; z-index:25; display:none; overflow:hidden; }
#progFill { height:100%; background:#f1c40f; width:0%; }
#hotbar { position:absolute; bottom:75px; left:50%; transform:translateX(-50%); display:flex; gap:4px; background:rgba(0,0,0,0.5); padding:5px 8px; border-radius:6px; z-index:20; }
.slot { width:36px; height:36px; border:2px solid #555; border-radius:4px; display:flex; align-items:center; justify-content:center; font-size:16px; cursor:pointer; background:#333; position:relative; }
.slot.sel { border-color:#fff; background:#555; box-shadow:0 0 6px #fff; }
.slot .count { position:absolute; bottom:1px; right:2px; font-size:9px; color:#fff; text-shadow:1px 1px 0 #000; pointer-events:none; }
#ctrls { position:absolute; bottom:8px; left:0; right:0; display:flex; justify-content:space-between; padding:0 8px; pointer-events:none; z-index:20; }
.cg { display:flex; gap:5px; pointer-events:all; }
.cb { width:38px; height:38px; border-radius:10px; background:rgba(255,255,255,0.2); border:1px solid rgba(255,255,255,0.4); color:#fff; font-size:16px; display:flex; align-items:center; justify-content:center; cursor:pointer; }
.cb:active { background:rgba(255,255,255,0.4); transform:scale(0.9); }
#btnJ { width:46px; height:46px; font-size:20px; border-radius:50%; }
</style>
</head>
<body>
<div id="ui">❤️ 100 | 🎯 <span id="bn">MANO (rompe)</span></div>
<div class="crosshair">✚</div>
<div id="prog"><div id="progFill"></div></div>
<div id="hotbar"></div>
<div id="ctrls">
<div class="cg">
<button class="cb" id="bl">◀</button>
<button class="cb" id="br">▶</button>
<button class="cb" id="bf">▲</button>
<button class="cb" id="bb">▼</button>
</div>
<button class="cb" id="btnJ">⬆️</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script>
(function() {
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0x87CEEB, 25, 80);
const camera = new THREE.PerspectiveCamera(70, innerWidth/innerHeight, 0.5, 200);
camera.position.set(0, 8, 6);
camera.lookAt(0, 5, 0);
const renderer = new THREE.WebGLRenderer({ antialias:false });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const sun = new THREE.DirectionalLight(0xffffff, 1.6);
sun.position.set(40, 70, 30);
scene.add(sun);
scene.add(new THREE.AmbientLight(0xaaccff, 0.8));
const blockMap = new Map();
const blockGeo = new THREE.BoxGeometry(1,1,1);
const colors = [0x5a8a3a, 0x8B5E3C, 0x888888, 0x8B6914, 0x2d8c2d];
const names = ['Hierba','Tierra','Piedra','Madera','Hojas'];
const emojis = ['🌿','🟫','🪨','🪵','🍃'];
let mode = 'break';
let placeType = 1;
let inventory = [0,0,0,0,0];
function addBlock(x,y,z,type) {
const k = `${x},${y},${z}`;
if (blockMap.has(k)) return;
const mat = new THREE.MeshLambertMaterial({ color:colors[type] });
const mesh = new THREE.Mesh(blockGeo, mat);
mesh.position.set(x,y,z);
mesh.userData = { type };
blockMap.set(k, mesh);
scene.add(mesh);
}
function removeBlock(x,y,z) {
const k = `${Math.round(x)},${Math.round(y)},${Math.round(z)}`;
if (!blockMap.has(k)) return;
const mesh = blockMap.get(k);
const type = mesh.userData.type;
inventory[type] = (inventory[type]||0) + 1;
scene.remove(mesh);
blockMap.delete(k);
refreshHotbar();
}
function placeBlock() {
if (inventory[placeType] <= 0) return;
const rc = new THREE.Raycaster();
rc.setFromCamera(new THREE.Vector2(0,0), camera);
const all = [...blockMap.values()];
const hits = rc.intersectObjects(all);
if (hits.length === 0) return;
const p = hits[0].point.clone()
.add(hits[0].face.normal.clone().multiplyScalar(0.5));
const bx = Math.round(p.x), by = Math.round(p.y), bz = Math.round(p.z);
const dx = bx - camera.position.x;
const dz = bz - camera.position.z;
if (Math.abs(dx)<0.6 && Math.abs(dz)<0.6 && by < camera.position.y+0.6) return;
addBlock(bx, by, bz, placeType);
inventory[placeType]--;
refreshHotbar();
}
function refreshHotbar() {
const hb = document.getElementById('hotbar');
hb.innerHTML = '';
const hand = document.createElement('div');
hand.className = 'slot' + (mode === 'break' ? ' sel' : '');
hand.textContent = '✋';
hand.onclick = ()=>{
mode = 'break';
document.getElementById('bn').textContent = 'MANO (rompe)';
refreshHotbar();
};
hb.appendChild(hand);
for (let i=0; i<5; i++) {
if (inventory[i] > 0) {
const el = document.createElement('div');
el.className = 'slot' + (mode === 'place' && placeType === i ? ' sel' : '');
el.innerHTML = emojis[i] + '<span class="count">'+inventory[i]+'</span>';
el.onclick = ()=>{
mode = 'place';
placeType = i;
document.getElementById('bn').textContent = names[i].toUpperCase() + ' (coloca)';
refreshHotbar();
};
hb.appendChild(el);
}
}
}
for (let x=-12; x<=12; x++)
for (let z=-12; z<=12; z++)
addBlock(x, 0, z, Math.random()<0.06 ? 2 : 0);
for (let i=0; i<12; i++) {
const tx = Math.floor(Math.random()*18)-9;
const tz = Math.floor(Math.random()*18)-9;
for (let j=1; j<=3; j++) addBlock(tx, j, tz, 3);
for (let dx=-1; dx<=1; dx++)
for (let dz=-1; dz<=1; dz++)
addBlock(tx+dx, 4, tz+dz, 4);
}
refreshHotbar();
let yaw = 0, pitch = -0.4, vy = 0;
const keys = { f:false, b:false, l:false, r:false, j:false };
const SPEED = 0.12, JUMP = 0.28, PH = 1.6, PR = 0.3;
function collide(x,y,z) {
for (let dx=-PR; dx<=PR; dx+=PR*2)
for (let dy=0; dy<=PH; dy+=PH/2)
for (let dz=-PR; dz<=PR; dz+=PR*2)
if (blockMap.has(`${Math.round(x+dx)},${Math.round(y+dy)},${Math.round(z+dz)}`))
return true;
return false;
}
function bind(id, key) {
const el = document.getElementById(id);
if (!el) return;
el.addEventListener('pointerdown', e=>{
e.preventDefault(); e.stopPropagation();
keys[key] = true;
});
el.addEventListener('pointerup', ()=> keys[key]=false);
el.addEventListener('pointerleave', ()=> keys[key]=false);
}
bind('bl','l'); bind('br','r');
bind('bf','f'); bind('bb','b');
bind('btnJ','j');
let holding = false, ht = 0, hx = 0, hy = 0;
const HT = 18;
renderer.domElement.addEventListener('pointerdown', e=>{
if (e.target.tagName === 'BUTTON') return;
holding = true; ht = 0;
hx = e.clientX; hy = e.clientY;
if (mode === 'break') {
document.getElementById('prog').style.display = 'block';
}
});
renderer.domElement.addEventListener('pointermove', e=>{
if (!holding) return;
const dx = e.clientX - hx;
const dy = e.clientY - hy;
if (Math.abs(dx)>2 || Math.abs(dy)>2) {
// Movimiento más fluido (SENSIBILIDAD MEJORADA)
yaw -= dx * 0.006;
pitch -= dy * 0.006;
pitch = Math.max(-1.3, Math.min(1.3, pitch));
hx = e.clientX; hy = e.clientY;
ht = 0;
document.getElementById('progFill').style.width = '0%';
}
});
renderer.domElement.addEventListener('pointerup', ()=>{
if (!holding) return;
if (mode === 'break') {
const rc = new THREE.Raycaster();
rc.setFromCamera(new THREE.Vector2(0,0), camera);
const hits = rc.intersectObjects([...blockMap.values()]);
if (hits.length) {
const obj = hits[0].object;
removeBlock(obj.position.x, obj.position.y, obj.position.z);
}
} else {
if (ht < 6) placeBlock();
}
holding = false; ht = 0;
document.getElementById('prog').style.display = 'none';
document.getElementById('progFill').style.width = '0%';
});
function update() {
if (holding && mode === 'break') {
ht++;
document.getElementById('progFill').style.width =
Math.min(100, (ht/HT)*100) + '%';
if (ht >= HT) {
const rc = new THREE.Raycaster();
rc.setFromCamera(new THREE.Vector2(0,0), camera);
const hits = rc.intersectObjects([...blockMap.values()]);
if (hits.length) {
const obj = hits[0].object;
removeBlock(obj.position.x, obj.position.y, obj.position.z);
}
ht = 0;
}
}
const fwd = new THREE.Vector3(-Math.sin(yaw), 0, -Math.cos(yaw)).normalize();
const rgt = new THREE.Vector3(Math.cos(yaw), 0, -Math.sin(yaw)).normalize();
const mov = new THREE.Vector3();
if (keys.f) mov.add(fwd);
if (keys.b) mov.sub(fwd);
if (keys.l) mov.sub(rgt);
if (keys.r) mov.add(rgt);
if (mov.length() > 0) {
mov.normalize().multiplyScalar(SPEED);
const nx = camera.position.x + mov.x;
const nz = camera.position.z + mov.z;
if (!collide(nx, camera.position.y - PH, nz)) {
camera.position.x = nx;
camera.position.z = nz;
}
}
if (keys.j && camera.position.y <= PH + 0.15 &&
!collide(camera.position.x, camera.position.y - PH + 0.2, camera.position.z)) {
vy = JUMP;
keys.j = false;
}
vy -= 0.012;
const ny = camera.position.y + vy;
if (ny <= PH || collide(camera.position.x, ny - PH, camera.position.z)) {
camera.position.y = Math.max(PH, camera.position.y);
vy = 0;
} else {
camera.position.y = ny;
}
camera.rotation.order = 'YXZ';
camera.rotation.y = yaw;
camera.rotation.x = pitch;
}
function loop() {
requestAnimationFrame(loop);
update();
renderer.render(scene, camera);
}
loop();
})();
</script>
</body>
</html>Game Source: BloqueCraft 3D - VideojuCan
Creator: CosmicCobra59
Libraries: three
Complexity: complex (298 lines, 12.6 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: bloquecraft-3d-videojucan-cosmiccobra59" to link back to the original. Then publish at arcadelab.ai/publish.