Meta Description" name="description" />

Share this result

Previews are deleted daily. Get a permanent share link sent to your inbox:
Script
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <title>CraftSim 3D - Móvil</title> <style> body { margin: 0; overflow: hidden; background-color: #729FFF; font-family: sans-serif; touch-action: none; user-select: none; } canvas { display: block; } /* Controles Táctiles - Estilo Android */ #ui-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } .touch-zone { pointer-events: auto; background: rgba(0,0,0,0.2); color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: bold; } .touch-zone:active { background: rgba(0,0,0,0.5); } /* Joystick (Moverse) */ #joystick-container { position: absolute; bottom: 40px; left: 40px; width: 120px; height: 120px; } #joystick-base { position: absolute; width: 100%; height: 100%; background: rgba(0,0,0,0.2); border-radius: 50%; } #joystick-knob { position: absolute; top: 30px; left: 30px; width: 60px; height: 60px; background: rgba(255,255,255,0.7); border-radius: 50%; pointer-events: auto; } /* Zona de Cámara (Girar) */ #camera-zone { position: absolute; top: 0; right: 0; width: 50%; height: 70%; pointer-events: auto; background: rgba(0,0,0,0.01); } /* Botones de Acción (Derecha) */ .action-btns { position: absolute; bottom: 40px; right: 40px; display: flex; flex-direction: column; gap: 15px; } .btn { width: 60px; height: 60px; } #jump-btn { background-color: rgba(255,255,255,0.5); border-radius: 10px; } #mine-btn { background-color: rgba(200,50,50,0.5); font-size: 18px; } #place-btn { background-color: rgba(50,200,50,0.5); font-size: 18px; } /* Punto de Mira (Crosshair) */ #crosshair { position: fixed; top: 50%; left: 50%; width: 10px; height: 10px; background-color: rgba(255,255,255,0.7); border-radius: 50%; transform: translate(-50%, -50%); z-index: 5; } /* Mensaje de Carga */ #loading { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 24px; z-index: 20; } </style> </head> <body> <div id="loading">Cargando mundo...</div> <div id="crosshair"></div> <div id="ui-overlay"> <div id="joystick-container"> <div id="joystick-base"></div> <div id="joystick-knob"></div> </div> <div id="camera-zone"></div> <div class="action-btns"> <div class="touch-zone btn" id="mine-btn">⛏️</div> <div class="touch-zone btn" id="place-btn">🧱</div> <div class="touch-zone btn" id="jump-btn">⬆️</div> </div> </div> <script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.156.1/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.156.1/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; // --- CONFIGURACIÓN GRÁFICA --- const TILE_SIZE = 16; // Texturas pixeladas de 16x16 const RENDER_DISTANCE = 3; // Distancia de renderizado (menor = más rápido) // --- VARIABLES GLOBALES --- let scene, camera, renderer, clock, raycaster; let player, playerVelocity, playerGrounded; let input = { forward: 0, right: 0, jump: false, cameraDx: 0, cameraDy: 0 }; let cameraRotation = { x: 0, y: 0 }; let blocks = []; // Aquí guardaremos los cubos const gravity = 0.5; init(); async function init() { // 1. Escena y Cámara scene = new THREE.Scene(); scene.background = new THREE.Color(0x729FFF); scene.fog = new THREE.Fog(0x729FFF, RENDER_DISTANCE * 16, RENDER_DISTANCE * 20); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 2. Renderer renderer = new THREE.WebGLRenderer({ antialias: false }); // Desactivar antialias para el look pixelado renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 3. Luz const ambientLight = new THREE.AmbientLight(0xcccccc, 1.2); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(1, 1, 0.5).normalize(); scene.add(directionalLight); clock = new THREE.Clock(); raycaster = new THREE.Raycaster(); // 4. Cargar Texturas y Crear Mundo try { const textureLoader = new THREE.TextureLoader(); // --- ¡IMPORTANTE! Reemplaza estas URLs por texturas de Minecraft reales (.png) --- const dirtTex = textureLoader.load('https://raw.githubusercontent.com/Anand-R4/Minecraft-Clone/master/assets/dirt.png'); const grassTopTex = textureLoader.load('https://raw.githubusercontent.com/Anand-R4/Minecraft-Clone/master/assets/grass.png'); const grassSideTex = textureLoader.load('https://raw.githubusercontent.com/Anand-R4/Minecraft-Clone/master/assets/grass_side.png'); // Optimizar texturas para que sean pixeladas y se repitan bien [dirtTex, grassTopTex, grassSideTex].forEach(tex => { tex.magFilter = THREE.NearestFilter; // El look de píxeles grandes tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping; }); // Definir Materiales (un cubo tiene 6 caras: x+, x-, y+, y-, z+, z-) const matDirt = new THREE.MeshLambertMaterial({ map: dirtTex }); const matGrass = [ new THREE.MeshLambertMaterial({ map: grassSideTex }), // Right new THREE.MeshLambertMaterial({ map: grassSideTex }), // Left new THREE.MeshLambertMaterial({ map: grassTopTex }), // Top new THREE.MeshLambertMaterial({ map: dirtTex }), // Bottom new THREE.MeshLambertMaterial({ map: grassSideTex }), // Front new THREE.MeshLambertMaterial({ map: grassSideTex }) // Back ]; const geometry = new THREE.BoxGeometry(1, 1, 1); // Generación de Mundo Simple (Piso de 32x32) for (let x = -16; x < 16; x++) { for (let z = -16; z < 16; z++) { // Capa superior (Pasto) const blockPasto = new THREE.Mesh(geometry, matGrass); blockPasto.position.set(x, 0, z); scene.add(blockPasto); blocks.push(blockPasto); // Capa inferior (Tierra) const blockTierra = new THREE.Mesh(geometry, matDirt); blockTierra.position.set(x, -1, z); scene.add(blockTierra); blocks.push(blockTierra); } } } catch (e) { console.error("Error cargando texturas:", e); } // 5. Configurar Jugador camera.position.set(0, 1.8, 0); // Altura de los ojos (1.8 cubos) scene.add(camera); player = camera; playerVelocity = new THREE.Vector3(); playerGrounded = false; // 6. Setup de Controles Táctiles (Joysticks y Botones) initTouchControls(); window.addEventListener('resize', onWindowResize); document.getElementById('loading').style.display = 'none'; animate(); } // --- LÓGICA DE CONTROLES TÁCTILES --- function initTouchControls() { // A. JOYSTICK (Movimiento) const knob = document.getElementById('joystick-knob'); const container = document.getElementById('joystick-container'); let joystickTouchId = null; let startX, startY; knob.addEventListener('touchstart', (e) => { if (joystickTouchId !== null) return; const touch = e.targetTouches[0]; joystickTouchId = touch.identifier; const rect = container.getBoundingClientRect(); startX = touch.clientX - knob.offsetLeft - (knob.offsetWidth / 2); startY = touch.clientY - knob.offsetTop - (knob.offsetHeight / 2); e.stopPropagation(); }); window.addEventListener('touchmove', (e) => { for (let i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === joystickTouchId) { const touch = e.changedTouches[i]; const rect = container.getBoundingClientRect(); let dx = touch.clientX - startX - rect.left - rect.width/2; let dy = touch.clientY - startY - rect.top - rect.height/2; const dist = Math.sqrt(dx*dx + dy*dy); const maxDist = 30; // Radio máximo del joystick if (dist > maxDist) { dx *= maxDist / dist; dy *= maxDist / dist; } knob.style.transform = `translate(${dx}px, ${dy}px)`; // Normalizar input (-1 a 1) input.right = dx / maxDist; input.forward = dy / maxDist; } } }); const endJoystick = (e) => { for (let i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === joystickTouchId) { joystickTouchId = null; knob.style.transform = `translate(0px, 0px)`; input.forward = 0; input.right = 0; } } }; window.addEventListener('touchend', endJoystick); window.addEventListener('touchcancel', endJoystick); // B. ZONA DE CÁMARA (Girar) const cameraZone = document.getElementById('camera-zone'); let cameraTouchId = null; let prevX, prevY; cameraZone.addEventListener('touchstart', (e) => { const touch = e.targetTouches[0]; cameraTouchId = touch.identifier; prevX = touch.clientX; prevY = touch.clientY; }); window.addEventListener('touchmove', (e) => { for (let i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === cameraTouchId) { const touch = e.changedTouches[i]; const speed = 0.005; // Sensibilidad de cámara input.cameraDx = (touch.clientX - prevX) * speed; input.cameraDy = (touch.clientY - prevY) * speed; prevX = touch.clientX; prevY = touch.clientY; } } }); const endCamera = (e) => { for (let i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === cameraTouchId) { cameraTouchId = null; input.cameraDx = 0; input.cameraDy = 0; } } }; window.addEventListener('touchend', endCamera); // C. BOTONES DE ACCIÓN (Saltar, Minar, Colocar) const jumpBtn = document.getElementById('jump-btn'); jumpBtn.addEventListener('touchstart', (e) => { e.preventDefault(); input.jump = true; }); jumpBtn.addEventListener('touchend', (e) => { e.preventDefault(); input.jump = false; }); document.getElementById('mine-btn').addEventListener('touchstart', mineBlock); document.getElementById('place-btn').addEventListener('touchstart', placeBlock); } // --- FÍSICAS Y ACTUALIZACIÓN --- function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); // 1. Girar Cámara if (cameraTouchId !== null) { cameraRotation.y -= input.cameraDx; cameraRotation.x -= input.cameraDy; cameraRotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, cameraRotation.x)); // Limitar mirar arriba/abajo camera.rotation.set(cameraRotation.x, cameraRotation.y, 0, 'YXZ'); // Orden de rotación muy importante } // 2. Movimiento y Gravedad const moveSpeed = 6 * delta; const jumpPower = 10; // Calcular vectores forward/right relativos a la cámara const forwardVector = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion); const rightVector = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion); // Aplanar vectores (no volar si miras arriba) forwardVector.y = 0; forwardVector.normalize(); rightVector.y = 0; rightVector.normalize(); // Aplicar Input const moveVector = new THREE.Vector3(); moveVector.addScaledVector(forwardVector, -input.forward); moveVector.addScaledVector(rightVector, input.right); moveVector.normalize().multiplyScalar(moveSpeed); playerVelocity.x = moveVector.x; playerVelocity.z = moveVector.z; playerVelocity.y -= gravity * delta * jumpPower; // Gravedad // Saltar if (playerGrounded && input.jump) { playerVelocity.y = jumpPower * delta; playerGrounded = false; } player.position.add(playerVelocity); // Colisión Simple con el Piso (y = 1.8) if (player.position.y < 1.8) { playerVelocity.y = 0; player.position.y = 1.8; playerGrounded = true; } renderer.render(scene, camera); } // --- ACCIONES DE BLOQUES (Minar/Colocar) --- function mineBlock(e) { if (e) e.preventDefault(); // Disparamos un rayo desde el centro de la pantalla raycaster.setFromCamera(new THREE.Vector2(), camera); raycaster.far = 4; // Distancia máxima de minado const intersects = raycaster.intersectObjects(blocks); if (intersects.length > 0) { const block = intersects[0].object; scene.remove(block); // Quitar de la escena blocks.splice(blocks.indexOf(block), 1); // Quitar de la lista } } function placeBlock(e) { if (e) e.preventDefault(); raycaster.setFromCamera(new THREE.Vector2(), camera); raycaster.far = 4; const intersects = raycaster.intersectObjects(blocks); if (intersects.length > 0) { const intersect = intersects[0]; const blockNormal = intersect.face.normal; // La cara donde tocamos (arriba, abajo, lado) const blockPos = intersect.object.position; // Calcular la posición del nuevo bloque sumando la normal const newPos = blockPos.clone().add(blockNormal); // Crear nuevo bloque (copiando el material de pasto) const matGrassCopy = intersect.object.material.slice ? intersect.object.material : [intersect.object.material]; const newBlock = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), matGrassCopy); newBlock.position.copy(newPos); scene.add(newBlock); blocks.push(newBlock); } } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } </script> </body> </html>
Landing Page
This ad does not have a landing page available
Network Timeline
Performance Summary

7

Requests

4

Domains

275KB

Transfer Size

1269KB

Content Size

311.0ms

Dom Content Loaded

120.0ms

First Paint

504.0ms

Load Time
Domain Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...
Timings (ms)
Loading...
Total Time
Loading...
Content Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...