• <!DOCTYPE html>

      <html lang=”es”>

      <head>

      <meta charset=”UTF-8″>

      <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>

      <title>Simulador 3D – Ciudad Futurista</title>

      <style>

      * {

      margin: 0;

      padding: 0;

      box-sizing: border-box;

      }

      body {

      font-family: ‘Arial’, sans-serif;

      overflow: hidden;

      background-color: #000;

      }

      #canvas {

      display: block;

      width: 100%;

      height: 100vh;

      }

      #ui {

      position: absolute;

      top: 20px;

      left: 20px;

      color: #00ff00;

      font-size: 14px;

      font-family: ‘Courier New’, monospace;

      text-shadow: 0 0 10px rgba(0, 255, 0, 0.8);

      z-index: 100;

      }

      .info-box {

      background-color: rgba(0, 0, 0, 0.7);

      border: 2px solid #00ff00;

      padding: 15px;

      margin-bottom: 10px;

      border-radius: 5px;

      min-width: 300px;

      }

      .info-title {

      font-weight: bold;

      margin-bottom: 8px;

      text-decoration: underline;

      }

      .info-line {

      margin: 4px 0;

      }

      #controls {

      position: absolute;

      bottom: 20px;

      left: 20px;

      color: #00ff00;

      font-size: 12px;

      font-family: ‘Courier New’, monospace;

      text-shadow: 0 0 10px rgba(0, 255, 0, 0.8);

      background-color: rgba(0, 0, 0, 0.7);

      border: 2px solid #00ff00;

      padding: 15px;

      border-radius: 5px;

      z-index: 100;

      }

      .control-item {

      margin: 4px 0;

      }

      .key {

      background-color: #00ff00;

      color: #000;

      padding: 2px 6px;

      border-radius: 3px;

      font-weight: bold;

      display: inline-block;

      min-width: 30px;

      text-align: center;

      }

      </style>

      </head>

      <body>

      <div id=”ui”>

      <div class=”info-box”>

      <div class=”info-title”>SIMULADOR 3D – CIUDAD FUTURISTA</div>

      <div class=”info-line”>Posición: <span id=”posX”>0.00</span>, <span id=”posY”>0.00</span>, <span id=”posZ”>0.00</span></div>

      <div class=”info-line”>Rotación: <span id=”rotY”>0.00</span> rad</div>

      <div class=”info-line”>Velocidad: <span id=”speed”>0.00</span></div>

      <div class=”info-line”>FPS: <span id=”fps”>60</span></div>

      </div>

      </div>

      <div id=”controls”>

      <div class=”info-title”>CONTROLES</div>

      <div class=”control-item”><span class=”key”>W</span> – Adelante</div>

      <div class=”control-item”><span class=”key”>S</span> – Atrás</div>

      <div class=”control-item”><span class=”key”>A</span> – Girar Izquierda</div>

      <div class=”control-item”><span class=”key”>D</span> – Girar Derecha</div>

      <div class=”control-item”><span class=”key”>MOUSE</span> – Rotar Cámara</div>

      </div>

      <!– Three.js desde CDN –>

      <script src=”https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js”></script>

      <script>

      // ==================== CONFIGURACIÓN INICIAL ====================

      const scene = new THREE.Scene();

      scene.background = new THREE.Color(0x0a0e27);

      scene.fog = new THREE.Fog(0x0a0e27, 200, 500);

      const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

      const renderer = new THREE.WebGLRenderer({ antialias: true });

      renderer.setSize(window.innerWidth, window.innerHeight);

      renderer.shadowMap.enabled = true;

      renderer.shadowMap.type = THREE.PCFShadowShadowMap;

      document.body.appendChild(renderer.domElement);

      // ==================== ILUMINACIÓN ====================

      const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);

      scene.add(ambientLight);

      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);

      directionalLight.position.set(100, 100, 100);

      directionalLight.castShadow = true;

      directionalLight.shadow.mapSize.width = 2048;

      directionalLight.shadow.mapSize.height = 2048;

      directionalLight.shadow.camera.far = 500;

      directionalLight.shadow.camera.left = -200;

      directionalLight.shadow.camera.right = 200;

      directionalLight.shadow.camera.top = 200;

      directionalLight.shadow.camera.bottom = -200;

      scene.add(directionalLight);

      // Luces neón futuristas

      const neonLight1 = new THREE.PointLight(0x00ff00, 1, 150);

      neonLight1.position.set(50, 30, 50);

      scene.add(neonLight1);

      const neonLight2 = new THREE.PointLight(0xff00ff, 1, 150);

      neonLight2.position.set(-50, 30, -50);

      scene.add(neonLight2);

      // ==================== CREACIÓN DEL TERRENO ====================

      const groundGeometry = new THREE.PlaneGeometry(400, 400);

      const groundMaterial = new THREE.MeshStandardMaterial({

      color: 0x1a1f3a,

      metalness: 0.3,

      roughness: 0.8

      });

      const ground = new THREE.Mesh(groundGeometry, groundMaterial);

      ground.rotation.x = -Math.PI / 2;

      ground.receiveShadow = true;

      scene.add(ground);

      // Líneas del suelo (efecto de ciudad futurista)

      const lineGeometry = new THREE.BufferGeometry();

      const linePositions = [];

      for (let i = -200; i <= 200; i += 20) {

      linePositions.push(i, 0.01, -200);

      linePositions.push(i, 0.01, 200);

      linePositions.push(-200, 0.01, i);

      linePositions.push(200, 0.01, i);

      }

      lineGeometry.setAttribute(‘position’, new THREE.BufferAttribute(new Float32Array(linePositions), 3));

      const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 1 });

      const gridLines = new THREE.LineSegments(lineGeometry, lineMaterial);

      scene.add(gridLines);

      // ==================== CREACIÓN DE EDIFICIOS ====================

      function createBuilding(x, z, width, height, depth, color) {

      const geometry = new THREE.BoxGeometry(width, height, depth);

      const material = new THREE.MeshStandardMaterial({

      color: color,

      metalness: 0.6,

      roughness: 0.4

      });

      const building = new THREE.Mesh(geometry, material);

      building.position.set(x, height / 2, z);

      building.castShadow = true;

      building.receiveShadow = true;

      // Ventanas neón

      const windowGeometry = new THREE.PlaneGeometry(2, 2);

      const windowMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });

      for (let i = 0; i < width – 5; i += 5) {

      for (let j = 0; j < height – 5; j += 5) {

      const window = new THREE.Mesh(windowGeometry, windowMaterial);

      window.position.set(x – width / 2 + i + 2.5, j + 2.5, z + depth / 2 + 0.1);

      scene.add(window);

      }

      }

      scene.add(building);

      return building;

      }

      // Crear ciudad futurista

      createBuilding(60, 60, 30, 80, 30, 0x0066ff);

      createBuilding(-60, 60, 40, 100, 40, 0xff00ff);

      createBuilding(60, -60, 35, 90, 35, 0x00ffff);

      createBuilding(-60, -60, 45, 110, 45, 0xffff00);

      createBuilding(0, 80, 25, 70, 25, 0xff6600);

      createBuilding(80, 0, 30, 85, 30, 0x00ff99);

      createBuilding(-80, 0, 35, 95, 35, 0xff0099);

      createBuilding(0, -80, 28, 75, 28, 0x00ccff);

      // ==================== CREACIÓN DEL PERSONAJE HUMANOIDE ====================

      const character = new THREE.Group();

      character.position.set(0, 0, 0);

      // Cuerpo

      const bodyGeometry = new THREE.BoxGeometry(2, 4, 1.5);

      const bodyMaterial = new THREE.MeshStandardMaterial({

      color: 0xff6600,

      metalness: 0.3,

      roughness: 0.7

      });

      const body = new THREE.Mesh(bodyGeometry, bodyMaterial);

      body.position.y = 2;

      body.castShadow = true;

      body.receiveShadow = true;

      character.add(body);

      // Cabeza

      const headGeometry = new THREE.SphereGeometry(0.8, 32, 32);

      const headMaterial = new THREE.MeshStandardMaterial({

      color: 0xffaa88,

      metalness: 0.2,

      roughness: 0.8

      });

      const head = new THREE.Mesh(headGeometry, headMaterial);

      head.position.y = 5;

      head.castShadow = true;

      head.receiveShadow = true;

      character.add(head);

      // Brazo izquierdo

      const armGeometry = new THREE.BoxGeometry(0.8, 3, 0.8);

      const armMaterial = new THREE.MeshStandardMaterial({

      color: 0xffaa88,

      metalness: 0.2,

      roughness: 0.8

      });

      const leftArm = new THREE.Mesh(armGeometry, armMaterial);

      leftArm.position.set(-1.5, 3, 0);

      leftArm.castShadow = true;

      leftArm.receiveShadow = true;

      character.add(leftArm);

      // Brazo derecho

      const rightArm = new THREE.Mesh(armGeometry, armMaterial);

      rightArm.position.set(1.5, 3, 0);

      rightArm.castShadow = true;

      rightArm.receiveShadow = true;

      character.add(rightArm);

      // Pierna izquierda

      const legGeometry = new THREE.BoxGeometry(0.8, 3, 0.8);

      const legMaterial = new THREE.MeshStandardMaterial({

      color: 0x333333,

      metalness: 0.4,

      roughness: 0.6

      });

      const leftLeg = new THREE.Mesh(legGeometry, legMaterial);

      leftLeg.position.set(-0.6, 1, 0);

      leftLeg.castShadow = true;

      leftLeg.receiveShadow = true;

      character.add(leftLeg);

      // Pierna derecha

      const rightLeg = new THREE.Mesh(legGeometry, legMaterial);

      rightLeg.position.set(0.6, 1, 0);

      rightLeg.castShadow = true;

      rightLeg.receiveShadow = true;

      character.add(rightLeg);

      scene.add(character);

      // ==================== SISTEMA DE CONTROL ====================

      const keys = {};

      const characterSpeed = 0.5;

      const rotationSpeed = 0.05;

      window.addEventListener(‘keydown’, (e) => {

      keys[e.key.toUpperCase()] = true;

      });

      window.addEventListener(‘keyup’, (e) => {

      keys[e.key.toUpperCase()] = false;

      });

      // ==================== CÁMARA EN TERCERA PERSONA ====================

      const cameraOffset = new THREE.Vector3(0, 4, -12);

      function updateCameraPosition() {

      const targetX = character.position.x + cameraOffset.x;

      const targetY = character.position.y + cameraOffset.y;

      const targetZ = character.position.z + cameraOffset.z;

      camera.position.lerp(new THREE.Vector3(targetX, targetY, targetZ), 0.1);

      camera.lookAt(character.position.x, character.position.y + 2, character.position.z);

      }

      // ==================== ANIMACIÓN DE MOVIMIENTO ====================

      let armSwing = 0;

      let legSwing = 0;

      let isMoving = false;

      function updateCharacterAnimation() {

      if (isMoving) {

      armSwing += 0.1;

      legSwing += 0.1;

      leftArm.rotation.z = Math.sin(armSwing) * 0.5;

      rightArm.rotation.z = -Math.sin(armSwing) * 0.5;

      leftLeg.rotation.z = Math.sin(legSwing) * 0.3;

      rightLeg.rotation.z = -Math.sin(legSwing) * 0.3;

      } else {

      armSwing = 0;

      legSwing = 0;

      leftArm.rotation.z = 0;

      rightArm.rotation.z = 0;

      leftLeg.rotation.z = 0;

      rightLeg.rotation.z = 0;

      }

      }

      // ==================== BUCLE DE ANIMACIÓN ====================

      let lastTime = Date.now();

      let frameCount = 0;

      let fps = 60;

      function animate() {

      requestAnimationFrame(animate);

      const currentTime = Date.now();

      const deltaTime = (currentTime – lastTime) / 1000;

      lastTime = currentTime;

      // Actualizar FPS

      frameCount++;

      if (frameCount % 10 === 0) {

      fps = Math.round(1 / deltaTime);

      }

      // Control del personaje

      isMoving = false;

      let moveX = 0;

      let moveZ = 0;

      if (keys[‘W’]) {

      moveZ += characterSpeed;

      isMoving = true;

      }

      if (keys[‘S’]) {

      moveZ -= characterSpeed;

      isMoving = true;

      }

      if (keys[‘A’]) {

      character.rotation.y += rotationSpeed;

      }

      if (keys[‘D’]) {

      character.rotation.y -= rotationSpeed;

      }

      // Aplicar movimiento relativo a la rotación del personaje

      if (moveZ !== 0) {

      const angle = character.rotation.y;

      character.position.x += Math.sin(angle) * moveZ;

      character.position.z += Math.cos(angle) * moveZ;

      }

      // Limitar el movimiento dentro del mapa

      character.position.x = Math.max(-180, Math.min(180, character.position.x));

      character.position.z = Math.max(-180, Math.min(180, character.position.z));

      // Actualizar animaciones

      updateCharacterAnimation();

      // Actualizar cámara

      updateCameraPosition();

      // Actualizar UI

      document.getElementById(‘posX’).textContent = character.position.x.toFixed(2);

      document.getElementById(‘posY’).textContent = character.position.y.toFixed(2);

      document.getElementById(‘posZ’).textContent = character.position.z.toFixed(2);

      document.getElementById(‘rotY’).textContent = character.rotation.y.toFixed(2);

      document.getElementById(‘speed’).textContent = (isMoving ? characterSpeed : 0).toFixed(2);

      document.getElementById(‘fps’).textContent = fps;

      renderer.render(scene, camera);

      }

      // ==================== MANEJO DE REDIMENSIONAMIENTO ====================

      window.addEventListener(‘resize’, () => {

      camera.aspect = window.innerWidth / window.innerHeight;

      camera.updateProjectionMatrix();

      renderer.setSize(window.innerWidth, window.innerHeight);

      });

      // Iniciar animación

      animate();

      </script>

      </body>

      </html>

      Miguel García Hernández, Ángel Iván Remigio Paulino y Iván Abdiel Ríos Cerón
      1 Comentario