🎮ArcadeLab

MultiBlox - Part 1

by DriftBear16
342 lines10.1 KB🛠️ Three.js (3D graphics)
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>MultiBlox - Part 1</title>
  <style>
    :root {
      --bg-dark: #0b0e14;
      --panel-bg: rgba(18, 22, 34, 0.85);
      --accent-blue: #00d2ff;
      --accent-purple: #7928ca;
      --accent-gold: #ffb703;
      --text-main: #f8fafc;
      --text-muted: #94a3b8;
      --border: rgba(255, 255, 255, 0.12);
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
      -webkit-user-select: none;
    }

    body, html {
      width: 100%;
      height: 100%;
      overflow: hidden;
      background-color: var(--bg-dark);
      font-family: 'Segoe UI', Roboto, sans-serif;
      color: var(--text-main);
    }

    #canvas-container {
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      z-index: 1;
    }

    #ui-layer {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      z-index: 10;
      pointer-events: none;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      padding: 16px;
    }

    .interactive {
      pointer-events: auto;
    }

    .top-bar {
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: var(--panel-bg);
      backdrop-filter: blur(12px);
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 10px 20px;
    }

    .brand-logo {
      font-size: 20px;
      font-weight: 900;
      letter-spacing: 1px;
      background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      text-transform: uppercase;
    }

    .stats-group {
      display: flex;
      align-items: center;
      gap: 16px;
    }

    .stat-badge {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-size: 14px;
      font-weight: 600;
      background: rgba(255, 255, 255, 0.05);
      padding: 6px 14px;
      border-radius: 20px;
      border: 1px solid var(--border);
    }

    .currency-icon {
      color: var(--accent-gold);
    }

    .crosshair {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 6px;
      height: 6px;
      background: rgba(255, 255, 255, 0.5);
      border-radius: 50%;
      transform: translate(-50%, -50%);
      pointer-events: none;
    }

    .instructions {
      background: var(--panel-bg);
      border: 1px solid var(--border);
      border-radius: 8px;
      padding: 8px 14px;
      font-size: 12px;
      color: var(--text-muted);
      width: fit-content;
    }
  </style>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
  <div id="canvas-container"></div>

  <div id="ui-layer">
    <div class="top-bar interactive">
      <div class="brand-logo">MultiBlox</div>
      <div class="stats-group">
        <div class="stat-badge">
          <span class="currency-icon">⛃</span>
          <span id="multibux-val">100</span>
          <span style="font-size: 11px; color: var(--text-muted);">(₹<span id="inr-val">500</span>)</span>
        </div>
        <div class="stat-badge">
          <span>Score: <span id="score-val">0</span></span>
        </div>
      </div>
    </div>

    <div class="crosshair"></div>

    <div class="instructions">
      Click screen to lock mouse &bull; <b>WASD</b> to Move &bull; <b>SPACE</b> to Jump &bull; <b>SHIFT</b> to Sprint
    </div>
  </div>

  <script>
    // --- 1. Scene, Camera & Renderer ---
    const container = document.getElementById('canvas-container');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0b0e14);
    scene.fog = new THREE.FogExp2(0x0b0e14, 0.015);

    const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    container.appendChild(renderer.domElement);

    // --- 2. Lighting ---
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x223344, 0.6);
    scene.add(hemiLight);

    const sunLight = new THREE.DirectionalLight(0xfffaed, 0.9);
    sunLight.position.set(30, 50, 20);
    sunLight.castShadow = true;
    sunLight.shadow.mapSize.width = 2048;
    sunLight.shadow.mapSize.height = 2048;
    scene.add(sunLight);

    // --- 3. Arena Platform & Grid ---
    const arenaSize = 100;
    const floorGeo = new THREE.PlaneGeometry(arenaSize, arenaSize);
    const floorMat = new THREE.MeshStandardMaterial({ color: 0x161b26, roughness: 0.8 });
    const floorMesh = new THREE.Mesh(floorGeo, floorMat);
    floorMesh.rotation.x = -Math.PI / 2;
    floorMesh.receiveShadow = true;
    scene.add(floorMesh);

    const grid = new THREE.GridHelper(arenaSize, 50, 0x00d2ff, 0x243049);
    grid.position.y = 0.01;
    scene.add(grid);

    // --- 4. 3D Character Rig ---
    const playerGroup = new THREE.Group();

    // Torso
    const torso = new THREE.Mesh(
      new THREE.BoxGeometry(1.2, 1.4, 0.8),
      new THREE.MeshStandardMaterial({ color: 0x00d2ff, roughness: 0.3 })
    );
    torso.position.y = 1.6;
    torso.castShadow = true;
    playerGroup.add(torso);

    // Head
    const head = new THREE.Mesh(
      new THREE.BoxGeometry(0.8, 0.8, 0.8),
      new THREE.MeshStandardMaterial({ color: 0xf8fafc, roughness: 0.4 })
    );
    head.position.y = 2.7;
    head.castShadow = true;
    playerGroup.add(head);

    // Legs
    const legGeo = new THREE.BoxGeometry(0.5, 1.0, 0.5);
    const legMat = new THREE.MeshStandardMaterial({ color: 0x1e293b, roughness: 0.5 });
    const leftLeg = new THREE.Mesh(legGeo, legMat);
    leftLeg.position.set(-0.35, 0.5, 0);
    leftLeg.castShadow = true;
    playerGroup.add(leftLeg);

    const rightLeg = new THREE.Mesh(legGeo, legMat);
    rightLeg.position.set(0.35, 0.5, 0);
    rightLeg.castShadow = true;
    playerGroup.add(rightLeg);

    scene.add(playerGroup);

    // --- 5. Movement & Camera Controls ---
    const Player = {
      speed: 12,
      sprintMultiplier: 1.6,
      jumpForce: 13,
      velocity: new THREE.Vector3(),
      isGrounded: true
    };

    let cameraPitch = 0.3;
    let cameraYaw = 0;
    const cameraDistance = 8;
    const keys = {};

    window.addEventListener('keydown', (e) => { keys[e.code] = true; });
    window.addEventListener('keyup', (e) => { keys[e.code] = false; });

    let isLocked = false;
    renderer.domElement.addEventListener('click', () => {
      renderer.domElement.requestPointerLock();
    });

    document.addEventListener('pointerlockchange', () => {
      isLocked = document.pointerLockElement === renderer.domElement;
    });

    window.addEventListener('mousemove', (e) => {
      if (!isLocked) return;
      cameraYaw -= e.movementX * 0.003;
      cameraPitch -= e.movementY * 0.003;
      cameraPitch = Math.max(-0.2, Math.min(1.2, cameraPitch));
    });

    // --- 6. Physics & Animation Loop ---
    const clock = new THREE.Clock();
    const gravity = 30;

    function update(dt) {
      const forward = new THREE.Vector3(-Math.sin(cameraYaw), 0, -Math.cos(cameraYaw));
      const right = new THREE.Vector3(Math.cos(cameraYaw), 0, -Math.sin(cameraYaw));

      let moveZ = 0;
      let moveX = 0;

      if (keys['KeyW'] || keys['ArrowUp']) moveZ += 1;
      if (keys['KeyS'] || keys['ArrowDown']) moveZ -= 1;
      if (keys['KeyA'] || keys['ArrowLeft']) moveX -= 1;
      if (keys['KeyD'] || keys['ArrowRight']) moveX += 1;

      const moveDir = new THREE.Vector3()
        .addScaledVector(forward, moveZ)
        .addScaledVector(right, moveX);

      const isSprinting = keys['ShiftLeft'] || keys['ShiftRight'];
      const speed = Player.speed * (isSprinting ? Player.sprintMultiplier : 1.0);

      if (moveDir.lengthSq() > 0.001) {
        moveDir.normalize();
        playerGroup.position.x += moveDir.x * speed * dt;
        playerGroup.position.z += moveDir.z * speed * dt;
        playerGroup.rotation.y = Math.atan2(moveDir.x, moveDir.z);

        const walkCycle = Math.sin(clock.getElapsedTime() * (isSprinting ? 16 : 10));
        leftLeg.rotation.x = walkCycle * 0.6;
        rightLeg.rotation.x = -walkCycle * 0.6;
      } else {
        leftLeg.rotation.x = 0;
        rightLeg.rotation.x = 0;
      }

      // Jump & Gravity
      if (keys['Space'] && Player.isGrounded) {
        Player.velocity.y = Player.jumpForce;
        Player.isGrounded = false;
      }

      Player.velocity.y -= gravity * dt;
      playerGroup.position.y += Player.velocity.y * dt;

      if (playerGroup.position.y <= 0) {
        playerGroup.position.y = 0;
        Player.velocity.y = 0;
        Player.isGrounded = true;
      }

      // Camera Follow Orbit
      const targetPos = playerGroup.position.clone().add(new THREE.Vector3(0, 1.8, 0));
      const camOffset = new THREE.Vector3(
        Math.sin(cameraYaw) * Math.cos(cameraPitch) * cameraDistance,
        Math.sin(cameraPitch) * cameraDistance,
        Math.cos(cameraYaw) * Math.cos(cameraPitch) * cameraDistance
      );

      camera.position.copy(targetPos).add(camOffset);
      camera.lookAt(targetPos);
    }

    function animate() {
      requestAnimationFrame(animate);
      const dt = Math.min(clock.getDelta(), 0.1);
      update(dt);
      renderer.render(scene, camera);
    }
    animate();

    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  </script>
</body>
</html>

Game Source: MultiBlox - Part 1

Creator: DriftBear16

Libraries: three

Complexity: complex (342 lines, 10.1 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: multiblox-part-1-driftbear16" to link back to the original. Then publish at arcadelab.ai/publish.