🎮ArcadeLab

BloqueCraft 3D - VideojuCan

by CosmicCobra59
298 lines12.6 KB🛠️ Three.js (3D graphics)
▶ Play
<!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.