🎮ArcadeLab

MultiBlox Platform - Prototype v0.1

by DriftBear16
603 lines18.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 Platform - Prototype v0.1</title>
  <style>
    :root {
      --bg-dark: #0f111a;
      --panel-bg: rgba(20, 24, 38, 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, Helvetica, Arial, sans-serif;
      color: var(--text-main);
    }

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

    /* MultiBlox UI Overlay */
    #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 Platform Header */
    .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;
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
    }

    .brand-group {
      display: flex;
      align-items: center;
      gap: 12px;
    }

    .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: 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);
      font-weight: 900;
    }

    /* Health HUD */
    .hud-bottom-left {
      display: flex;
      flex-direction: column;
      gap: 8px;
      width: 240px;
    }

    .health-bar-container {
      width: 100%;
      height: 14px;
      background: rgba(0, 0, 0, 0.6);
      border-radius: 7px;
      overflow: hidden;
      border: 1px solid var(--border);
    }

    .health-bar-fill {
      width: 100%;
      height: 100%;
      background: linear-gradient(90deg, #ff4b4b, #ff8533);
      transition: width 0.2s ease;
    }

    .hud-label {
      font-size: 12px;
      font-weight: 700;
      color: var(--text-muted);
      text-transform: uppercase;
      letter-spacing: 0.5px;
    }

    /* Mobile Controls Overlay */
    .mobile-controls {
      display: none;
      position: absolute;
      bottom: 24px;
      width: calc(100% - 48px);
      justify-content: space-between;
      align-items: flex-end;
      pointer-events: none;
    }

    .joystick-zone {
      width: 120px;
      height: 120px;
      background: rgba(255, 255, 255, 0.1);
      border: 2px dashed rgba(255, 255, 255, 0.3);
      border-radius: 50%;
      position: relative;
      pointer-events: auto;
      touch-action: none;
    }

    .joystick-knob {
      width: 50px;
      height: 50px;
      background: var(--accent-blue);
      border-radius: 50%;
      position: absolute;
      top: 35px;
      left: 35px;
      opacity: 0.8;
    }

    .action-btn {
      width: 65px;
      height: 65px;
      border-radius: 50%;
      background: var(--panel-bg);
      border: 2px solid var(--accent-blue);
      color: var(--text-main);
      font-weight: bold;
      font-size: 14px;
      display: flex;
      justify-content: center;
      align-items: center;
      pointer-events: auto;
      touch-action: none;
    }

    .action-btn:active {
      background: var(--accent-blue);
      color: #000;
    }

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

    @media (max-width: 768px) {
      .mobile-controls {
        display: flex;
      }
    }
  </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">
    <!-- Top Platform Header -->
    <div class="top-bar interactive">
      <div class="brand-group">
        <div class="brand-logo">MultiBlox</div>
      </div>
      <div class="stats-group">
        <div class="stat-badge">
          <span class="currency-icon">⛃</span>
          <span id="multibux-val">0</span>
          <span style="font-size: 10px; color: var(--text-muted);">(₹<span id="inr-val">0</span>)</span>
        </div>
        <div class="stat-badge">
          <span>🏆 Score:</span>
          <span id="score-val">0</span>
        </div>
        <div class="stat-badge">
          <span>Lv. <span id="level-val">1</span></span>
        </div>
      </div>
    </div>

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

    <!-- HUD Status -->
    <div class="hud-bottom-left">
      <div class="hud-label">Player Health</div>
      <div class="health-bar-container">
        <div id="health-fill" class="health-bar-fill"></div>
      </div>
    </div>

    <!-- Mobile Virtual Controls -->
    <div class="mobile-controls">
      <div class="joystick-zone" id="joystick-zone">
        <div class="joystick-knob" id="joystick-knob"></div>
      </div>
      <div class="action-btn" id="mobile-jump-btn">JUMP</div>
    </div>
  </div>

  <script>
    /* =========================================================================
       1. STATE & CURRENCY (1 MultiBux = ₹5 INR)
       ========================================================================= */
    const MULTIBUX_TO_INR_RATE = 5;
    const PlayerState = {
      name: "Player_1",
      health: 100,
      maxHealth: 100,
      multiBux: 100,
      score: 0,
      level: 1,
      speed: 12,
      sprintMultiplier: 1.6,
      jumpVelocity: 14,
      isGrounded: false
    };

    function updateEconomyHUD() {
      document.getElementById('multibux-val').innerText = PlayerState.multiBux;
      document.getElementById('inr-val').innerText = PlayerState.multiBux * MULTIBUX_TO_INR_RATE;
      document.getElementById('score-val').innerText = PlayerState.score;
      document.getElementById('level-val').innerText = PlayerState.level;
    }
    updateEconomyHUD();

    /* =========================================================================
       2. 3D SCENE & LIGHTING SETUP
       ========================================================================= */
    const container = document.getElementById('canvas-container');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0a0c14);
    scene.fog = new THREE.FogExp2(0x0a0c14, 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);

    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x334455, 0.6);
    scene.add(hemiLight);

    const dirLight = new THREE.DirectionalLight(0xfffaed, 0.9);
    dirLight.position.set(40, 60, 30);
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    dirLight.shadow.camera.near = 0.5;
    dirLight.shadow.camera.far = 150;
    const shadowD = 40;
    dirLight.shadow.camera.left = -shadowD;
    dirLight.shadow.camera.right = shadowD;
    dirLight.shadow.camera.top = shadowD;
    dirLight.shadow.camera.bottom = -shadowD;
    scene.add(dirLight);

    // Floor Base
    const arenaSize = 120;
    const floorGeo = new THREE.PlaneGeometry(arenaSize, arenaSize);
    const floorMat = new THREE.MeshStandardMaterial({ 
      color: 0x1a2035, 
      roughness: 0.8, 
      metalness: 0.2 
    });
    const floorMesh = new THREE.Mesh(floorGeo, floorMat);
    floorMesh.rotation.x = -Math.PI / 2;
    floorMesh.receiveShadow = true;
    scene.add(floorMesh);

    const gridHelper = new THREE.GridHelper(arenaSize, 60, 0x00d2ff, 0x223344);
    gridHelper.position.y = 0.01;
    scene.add(gridHelper);

    // Modular Obstacles
    const obstacles = [];
    function createObstacle(x, y, z, w, h, d, color = 0x3b4252) {
      const geo = new THREE.BoxGeometry(w, h, d);
      const mat = new THREE.MeshStandardMaterial({ color, roughness: 0.5 });
      const mesh = new THREE.Mesh(geo, mat);
      mesh.position.set(x, y + h / 2, z);
      mesh.castShadow = true;
      mesh.receiveShadow = true;
      scene.add(mesh);
      obstacles.push({
        box: new THREE.Box3().setFromObject(mesh),
        mesh: mesh
      });
    }

    createObstacle(0, 0, -15, 8, 2, 8, 0x4c566a);
    createObstacle(12, 0, -10, 6, 4, 6, 0x5e81ac);
    createObstacle(-15, 0, 10, 10, 1.5, 10, 0x88c0d0);
    createObstacle(20, 0, 15, 4, 6, 4, 0x81a1c1);

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

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

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

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

    playerGroup.position.set(0, 0, 0);
    scene.add(playerGroup);

    /* =========================================================================
       4. CONTROLS & CAMERA DYNAMICS
       ========================================================================= */
    let cameraDistance = 8;
    let cameraPitch = 0.3;
    let cameraYaw = 0;
    const playerVelocity = new THREE.Vector3();
    const gravity = 32;

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

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

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

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

    // Mobile Virtual Joystick
    const joystick = { active: false, x: 0, y: 0 };
    const joystickZone = document.getElementById('joystick-zone');
    const joystickKnob = document.getElementById('joystick-knob');
    let touchId = null;

    joystickZone.addEventListener('touchstart', (e) => {
      const touch = e.changedTouches[0];
      touchId = touch.identifier;
      joystick.active = true;
      handleJoystickMove(touch);
    });

    window.addEventListener('touchmove', (e) => {
      if (!joystick.active) return;
      for (let i = 0; i < e.changedTouches.length; i++) {
        if (e.changedTouches[i].identifier === touchId) {
          handleJoystickMove(e.changedTouches[i]);
          break;
        }
      }
    });

    const resetJoystick = () => {
      joystick.active = false;
      joystick.x = 0;
      joystick.y = 0;
      joystickKnob.style.transform = 'translate(0px, 0px)';
    };

    window.addEventListener('touchend', (e) => {
      for (let i = 0; i < e.changedTouches.length; i++) {
        if (e.changedTouches[i].identifier === touchId) {
          resetJoystick();
          break;
        }
      }
    });

    function handleJoystickMove(touch) {
      const rect = joystickZone.getBoundingClientRect();
      const centerX = rect.left + rect.width / 2;
      const centerY = rect.top + rect.height / 2;
      const dx = touch.clientX - centerX;
      const dy = touch.clientY - centerY;
      const dist = Math.min(40, Math.hypot(dx, dy));
      const angle = Math.atan2(dy, dx);
      
      const offsetX = Math.cos(angle) * dist;
      const offsetY = Math.sin(angle) * dist;
      joystickKnob.style.transform = `translate(${offsetX}px, ${offsetY}px)`;

      joystick.x = offsetX / 40;
      joystick.y = offsetY / 40;
    }

    document.getElementById('mobile-jump-btn').addEventListener('touchstart', (e) => {
      e.preventDefault();
      if (PlayerState.isGrounded) {
        playerVelocity.y = PlayerState.jumpVelocity;
        PlayerState.isGrounded = false;
      }
    });

    /* =========================================================================
       5. MAIN ENGINE LOOP
       ========================================================================= */
    const clock = new THREE.Clock();

    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 moveX = 0;
      let moveZ = 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;

      if (joystick.active) {
        moveX += joystick.x;
        moveZ -= joystick.y;
      }

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

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

      if (moveDir.lengthSq() > 0.001) {
        moveDir.normalize();
        playerGroup.position.x += moveDir.x * currentSpeed * dt;
        playerGroup.position.z += moveDir.z * currentSpeed * 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']) && PlayerState.isGrounded) {
        playerVelocity.y = PlayerState.jumpVelocity;
        PlayerState.isGrounded = false;
      }

      playerVelocity.y -= gravity * dt;
      playerGroup.position.y += playerVelocity.y * dt;

      if (playerGroup.position.y <= 0) {
        playerGroup.position.y = 0;
        playerVelocity.y = 0;
        PlayerState.isGrounded = true;
      }

      // Obstacle Collision
      const playerRadius = 0.8;
      const playerHeight = 2.8;
      const pPos = playerGroup.position;

      for (let obs of obstacles) {
        const min = obs.box.min;
        const max = obs.box.max;

        if (pPos.x > min.x - playerRadius && pPos.x < max.x + playerRadius &&
            pPos.z > min.z - playerRadius && pPos.z < max.z + playerRadius) {
          
          if (pPos.y + playerHeight > min.y && pPos.y < max.y) {
            if (pPos.y >= max.y - 0.5 && playerVelocity.y <= 0) {
              pPos.y = max.y;
              playerVelocity.y = 0;
              PlayerState.isGrounded = true;
            }
          }
        }
      }

      // Camera Orbit
      const targetPos = playerGroup.position.clone().add(new THREE.Vector3(0, 2, 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 Platform - Prototype v0.1

Creator: DriftBear16

Libraries: three

Complexity: complex (603 lines, 18.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-platform-prototype-v0-1-driftbear16" to link back to the original. Then publish at arcadelab.ai/publish.