🎮ArcadeLab

SKYBOUND 3D - Jetpack Cloud Escape

by ThunderPhoenix57
478 lines16.0 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, maximum-scale=1.0, user-scalable=no">
  <title>SKYBOUND 3D - Jetpack Cloud Escape</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; user-select: none; touch-action: none; }
    body {
      background: #1e3c72;
      color: #ffffff;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      overflow: hidden;
      width: 100vw;
      height: 100vh;
    }
    #game-container { width: 100%; height: 100%; position: absolute; top: 0; left: 0; }
    
    /* HUD Overlays */
    .hud {
      position: absolute;
      top: 15px;
      left: 0;
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 20px;
      pointer-events: none;
      z-index: 10;
    }
    .hud-card {
      background: rgba(15, 30, 60, 0.75);
      border: 2px solid #64b5f6;
      padding: 10px 18px;
      border-radius: 12px;
      backdrop-filter: blur(5px);
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    }
    .hud-title { font-size: 0.65rem; color: #90caf9; text-transform: uppercase; letter-spacing: 1px; }
    .hud-val { font-size: 1.3rem; font-weight: 800; color: #ffffff; }

    /* Jetpack Fuel Gauge */
    .fuel-bar-container { width: 120px; height: 12px; background: rgba(0,0,0,0.4); border-radius: 6px; overflow: hidden; margin-top: 4px; border: 1px solid #90caf9; }
    .fuel-bar-fill { width: 100%; height: 100%; background: #4caf50; transition: width 0.1s; }

    /* Touch Control Buttons */
    .touch-controls {
      position: absolute;
      bottom: 20px;
      left: 0;
      width: 100%;
      display: none;
      justify-content: space-between;
      padding: 0 25px;
      z-index: 15;
      pointer-events: none;
    }
    .touch-btn {
      width: 65px;
      height: 65px;
      background: rgba(100, 181, 246, 0.85);
      border: 2px solid #ffffff;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.5rem;
      color: #0d1b2a;
      font-weight: 900;
      pointer-events: auto;
    }
    .touch-btn:active { background: #ffffff; transform: scale(0.95); }
    .touch-group { display: flex; gap: 12px; }

    /* Menu / Modal Screen */
    .modal {
      position: absolute;
      top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(13, 27, 42, 0.92);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      z-index: 20;
      padding: 20px;
      text-align: center;
    }
    h1 { font-size: 2.8rem; color: #e3f2fd; margin-bottom: 8px; text-shadow: 2px 2px 0px #1e88e5; letter-spacing: 2px; }
    p { color: #90caf9; margin-bottom: 25px; max-width: 420px; font-size: 1rem; line-height: 1.5; }
    .btn {
      background: #1e88e5;
      color: #ffffff;
      border: none;
      padding: 15px 45px;
      font-size: 1.2rem;
      font-weight: 800;
      border-radius: 30px;
      cursor: pointer;
      box-shadow: 0 5px 0px #1565c0;
    }
    .btn:active { transform: translateY(3px); box-shadow: 0 2px 0px #1565c0; }
  </style>

  <!-- Three.js Library -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>

  <div id="game-container"></div>

  <div class="hud">
    <div class="hud-card">
      <div class="hud-title">Altitude</div>
      <div class="hud-val" id="alt-txt">0m</div>
    </div>
    <div class="hud-card">
      <div class="hud-title">Jetpack Fuel</div>
      <div class="fuel-bar-container"><div class="fuel-bar-fill" id="fuel-bar"></div></div>
    </div>
  </div>

  <div class="touch-controls" id="touchOverlay">
    <div class="touch-group">
      <div class="touch-btn" id="btnLeft">◀</div>
      <div class="touch-btn" id="btnRight">▶</div>
    </div>
    <div class="touch-group">
      <div class="touch-btn" id="btnThrust" style="width: 80px; border-radius: 20px;">THRUST</div>
    </div>
  </div>

  <div class="modal" id="modal">
    <h1 id="modalTitle">SKYBOUND 3D</h1>
    <p id="modalDesc">Use WASD/Arrows to steer and SPACE to thrust your jetpack! Land on safe clouds to recharge fuel, dodge storm hazards, and reach the Summit Gate!</p>
    <button class="btn" onclick="startGame()">LAUNCH ENGINE</button>
  </div>

  <script>
    // --- 1. WEB AUDIO SYNTHESIS ENGINE --- //
    let audioCtx = null;
    function initAudio() {
      if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      if (audioCtx.state === 'suspended') audioCtx.resume();
    }

    function playAudioTone(freq, type, duration) {
      if (!audioCtx) return;
      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();
      osc.type = type; osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
      gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
      osc.connect(gain); gain.connect(audioCtx.destination);
      osc.start(); osc.stop(audioCtx.currentTime + duration);
    }

    // --- 2. THREE.JS SCENE & PHYSICS ENGINE --- //
    let scene, camera, renderer;
    let playerGroup, playerMesh, jetpackFire;
    let clouds = [], stormClouds = [], meteors = [], summitGate;
    let gameRunning = false;

    // Physics Constants
    const gravity = -0.012;
    const thrustPower = 0.028;
    let velocity = new THREE.Vector3(0, 0, 0);
    let fuel = 100;
    let isGrounded = false;
    let playerAltitude = 0;
    const SUMMIT_ALTITUDE = 180;

    // Inputs
    const keys = { left: false, right: false, forward: false, backward: false, thrust: false };

    function init3D() {
      const container = document.getElementById('game-container');

      // Blue Atmospheric Sky Scene
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0x3a7bd5);
      scene.fog = new THREE.FogExp2(0x3a7bd5, 0.008);

      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 300);

      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      container.appendChild(renderer.domElement);

      // Atmospheric Sky Lighting
      const ambientLight = new THREE.AmbientLight(0xffffff, 0.85);
      scene.add(ambientLight);

      const sunLight = new THREE.DirectionalLight(0xfffaed, 1.2);
      sunLight.position.set(20, 100, 50);
      scene.add(sunLight);

      // Create 3D Character with Jetpack
      playerGroup = new THREE.Group();
      
      // Character Body
      const bodyGeo = new THREE.CylinderGeometry(0.5, 0.5, 1.4, 8);
      const bodyMat = new THREE.MeshStandardMaterial({ color: 0x1e88e5, flatShading: true });
      playerMesh = new THREE.Mesh(bodyGeo, bodyMat);
      playerMesh.position.y = 0.7;
      playerGroup.add(playerMesh);

      // Head / Helmet
      const headGeo = new THREE.SphereGeometry(0.45, 8, 8);
      const headMat = new THREE.MeshStandardMaterial({ color: 0xffcc80, flatShading: true });
      const head = new THREE.Mesh(headGeo, headMat);
      head.position.y = 1.6;
      playerGroup.add(head);

      // Jetpack Thrusters
      const packGeo = new THREE.BoxGeometry(0.6, 0.8, 0.4);
      const packMat = new THREE.MeshStandardMaterial({ color: 0x424242 });
      const pack = new THREE.Mesh(packGeo, packMat);
      pack.position.set(0, 0.8, -0.45);
      playerGroup.add(pack);

      // Thruster Flame
      const fireGeo = new THREE.ConeGeometry(0.25, 0.6, 6);
      const fireMat = new THREE.MeshBasicMaterial({ color: 0xff9800 });
      jetpackFire = new THREE.Mesh(fireGeo, fireMat);
      jetpackFire.rotation.x = Math.PI;
      jetpackFire.position.set(0, 0.2, -0.45);
      jetpackFire.visible = false;
      playerGroup.add(jetpackFire);

      scene.add(playerGroup);

      // Build Level Architecture
      buildLevel();

      window.addEventListener('resize', onResize);
      setupInputs();
    }

    // Procedural Low-Poly Natural Cloud Generation
    function createCloudMesh(isStorm = false) {
      const group = new THREE.Group();
      const mat = new THREE.MeshStandardMaterial({
        color: isStorm ? 0x455a64 : 0xffffff,
        roughness: 0.9,
        flatShading: true
      });

      const puffCount = 5 + Math.floor(Math.random() * 4);
      for (let i = 0; i < puffCount; i++) {
        const radius = 1.2 + Math.random() * 1.5;
        const geo = new THREE.DodecahedronGeometry(radius, 1);
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.set(
          (Math.random() - 0.5) * 3.5,
          (Math.random() - 0.5) * 0.8,
          (Math.random() - 0.5) * 3.5
        );
        group.add(mesh);
      }
      return group;
    }

    function buildLevel() {
      // Clear existing arrays
      clouds.forEach(c => scene.remove(c.mesh));
      stormClouds.forEach(s => scene.remove(s.mesh));
      clouds = []; stormClouds = [];

      // Starting Base Cloud
      const baseCloud = createCloudMesh(false);
      baseCloud.position.set(0, -1, 0);
      baseCloud.scale.set(2.5, 1, 2.5);
      scene.add(baseCloud);
      clouds.push({ mesh: baseCloud, y: -1, radius: 6.0, isMoving: false });

      // Generate Staircase of Clouds to the Top
      for (let y = 10; y <= SUMMIT_ALTITUDE; y += 8) {
        const isStorm = Math.random() < 0.25 && y > 30;
        const cloud = createCloudMesh(isStorm);
        
        const x = (Math.random() - 0.5) * 24;
        const z = (Math.random() - 0.5) * 20;
        cloud.position.set(x, y, z);
        scene.add(cloud);

        const cloudObj = {
          mesh: cloud,
          y: y,
          radius: 3.5,
          isMoving: Math.random() < 0.3,
          moveSpeed: 0.03 + Math.random() * 0.03,
          moveDir: 1
        };

        if (isStorm) stormClouds.push(cloudObj);
        else clouds.push(cloudObj);
      }

      // Summit Gate at the Top
      const gateGeo = new THREE.TorusGeometry(5, 0.4, 8, 24);
      const gateMat = new THREE.MeshBasicMaterial({ color: 0xffd54f });
      summitGate = new THREE.Mesh(gateGeo, gateMat);
      summitGate.rotation.x = Math.PI / 2;
      summitGate.position.set(0, SUMMIT_ALTITUDE + 5, 0);
      scene.add(summitGate);
    }

    // --- 3. PHYSICS UPDATES & COLLISION DETECTORS --- //
    function updatePhysics() {
      if (!gameRunning) return;

      // Jetpack Horizontal Thrust Controls
      if (keys.left) velocity.x -= 0.012;
      if (keys.right) velocity.x += 0.012;
      if (keys.forward) velocity.z -= 0.012;
      if (keys.backward) velocity.z += 0.012;

      // Vertical Jetpack Thrust
      if (keys.thrust && fuel > 0) {
        velocity.y += thrustPower;
        fuel = Math.max(0, fuel - 0.65);
        jetpackFire.visible = true;
        if (Math.random() < 0.2) playAudioTone(220, 'sawtooth', 0.05);
      } else {
        jetpackFire.visible = false;
      }

      // Apply Gravity & Air Resistance
      velocity.y += gravity;
      velocity.x *= 0.92;
      velocity.z *= 0.92;

      // Move Player Character
      playerGroup.position.add(velocity);
      playerAltitude = Math.max(0, Math.floor(playerGroup.position.y));
      document.getElementById('alt-txt').innerText = `${playerAltitude}m`;

      // Update Fuel Bar UI
      const fuelBar = document.getElementById('fuel-bar');
      fuelBar.style.width = `${fuel}%`;
      fuelBar.style.background = fuel < 25 ? '#f44336' : '#4caf50';

      // Cloud Collision Check
      isGrounded = false;
      clouds.forEach(c => {
        // Moving Cloud Logic
        if (c.isMoving) {
          c.mesh.position.x += c.moveSpeed * c.moveDir;
          if (Math.abs(c.mesh.position.x) > 15) c.moveDir *= -1;
        }

        const dx = playerGroup.position.x - c.mesh.position.x;
        const dz = playerGroup.position.z - c.mesh.position.z;
        const horizontalDist = Math.sqrt(dx * dx + dz * dz);

        // Landing Top Surface Collision
        if (horizontalDist < c.radius && 
            playerGroup.position.y >= c.mesh.position.y + 0.5 && 
            playerGroup.position.y <= c.mesh.position.y + 1.6 && 
            velocity.y <= 0) {
          
          playerGroup.position.y = c.mesh.position.y + 1.2;
          velocity.y = 0;
          isGrounded = true;

          // Fuel Recharge on Safe Clouds
          if (fuel < 100) fuel = Math.min(100, fuel + 0.8);
        }
      });

      // Storm Cloud Hazard Collision
      stormClouds.forEach(s => {
        const dx = playerGroup.position.x - s.mesh.position.x;
        const dy = playerGroup.position.y - s.mesh.position.y;
        const dz = playerGroup.position.z - s.mesh.position.z;
        const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);

        if (dist < 3.2) {
          // Zap Fuel & Knockback
          fuel = Math.max(0, fuel - 2.5);
          velocity.y = -0.1;
          playAudioTone(120, 'sawtooth', 0.1);
        }
      });

      // Fall Below Base Failure
      if (playerGroup.position.y < -12) {
        endGame(false, "Fell through the clouds! Manage your jetpack fuel carefully.");
      }

      // Check Summit Gate Victory
      if (playerGroup.position.distanceTo(summitGate.position) < 5.0) {
        endGame(true, "VICTORY! You reached the High Altitude Summit!");
      }

      // Smooth Camera Follow
      camera.position.x = playerGroup.position.x;
      camera.position.y = playerGroup.position.y + 6;
      camera.position.z = playerGroup.position.z + 16;
      camera.lookAt(playerGroup.position.x, playerGroup.position.y + 2, playerGroup.position.z);
    }

    // --- 4. GAME LOOP & INPUT BINDINGS --- //
    function animate() {
      requestAnimationFrame(animate);
      updatePhysics();
      if (summitGate) summitGate.rotation.z += 0.02;
      renderer.render(scene, camera);
    }

    function startGame() {
      initAudio();
      document.getElementById('modal').style.display = 'none';

      // Reset Variables
      playerGroup.position.set(0, 2, 0);
      velocity.set(0, 0, 0);
      fuel = 100;
      gameRunning = true;

      buildLevel();
    }

    function endGame(isWin, msg) {
      gameRunning = false;
      document.getElementById('modal').style.display = 'flex';
      document.getElementById('modalTitle').innerText = isWin ? "SUMMIT REACHED!" : "ENGINE FAILURE";
      document.getElementById('modalDesc').innerText = msg;
      document.querySelector('#modal .btn').innerText = "FLY AGAIN";
    }

    function setupInputs() {
      window.addEventListener('keydown', e => {
        if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
        if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
        if (e.key === 'ArrowUp' || e.key === 'w') keys.forward = true;
        if (e.key === 'ArrowDown' || e.key === 's') keys.backward = true;
        if (e.key === ' ' || e.key === 'Shift') keys.thrust = true;
      });

      window.addEventListener('keyup', e => {
        if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
        if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
        if (e.key === 'ArrowUp' || e.key === 'w') keys.forward = false;
        if (e.key === 'ArrowDown' || e.key === 's') keys.backward = false;
        if (e.key === ' ' || e.key === 'Shift') keys.thrust = false;
      });

      // Touch Overlay Controls
      if ('ontouchstart' in window) {
        document.getElementById('touchOverlay').style.display = 'flex';
        
        const bindTouch = (id, key) => {
          const btn = document.getElementById(id);
          btn.addEventListener('touchstart', (e) => { e.preventDefault(); keys[key] = true; });
          btn.addEventListener('touchend', (e) => { e.preventDefault(); keys[key] = false; });
        };

        bindTouch('btnLeft', 'left');
        bindTouch('btnRight', 'right');
        bindTouch('btnThrust', 'thrust');
      }
    }

    function onResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }

    init3D();
    animate();
  </script>
</body>
</html>

Game Source: SKYBOUND 3D - Jetpack Cloud Escape

Creator: ThunderPhoenix57

Libraries: three

Complexity: complex (478 lines, 16.0 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: skybound-3d-jetpack-cloud-escape-thunderphoenix57" to link back to the original. Then publish at arcadelab.ai/publish.