<!DOCTYPE html> <html> <head> <title>3D Runner Game</title> <meta charset="utf-8"> <script> function isMobile() { return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent); } function checkDeviceAndRedirect() { if (isMobile()) { // Redirect mobile users to the specified URL window.location.href = "https://itsrealm12c.github.io/3dcuberunner/mo"; } } // Call the function to check device type and possibly redirect checkDeviceAndRedirect(); </script> <style> body { margin: 0; overflow: hidden; } #score { position: fixed; top: 20px; left: 20px; color: white; font-family: Arial, sans-serif; font-size: 24px; z-index: 100; } #gameOver { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-family: Arial, sans-serif; font-size: 48px; display: none; text-align: center; z-index: 100; } #gameOver button { padding: 10px 20px; font-size: 24px; margin-top: 20px; cursor: pointer; } #preview { position: fixed; bottom: 20px; right: 20px; width: 200px; height: 150px; border: 2px solid white; z-index: 100; } #timer { position: fixed; bottom: 20px; left: 20px; color: white; font-family: Arial, sans-serif; font-size: 24px; z-index: 100; display: none; } </style> </head> <body> <div id="score">Score: 0</div> <div id="gameOver"> Game Over! <br> <button onclick="restartGame()">Restart</button> </div> <div id="preview"></div> <div id="timer">Quadruple Jump: 10s</div> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.159.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; let scene, camera, renderer, player; let obstacles = []; let platforms = []; let blueBlocks = []; let yellowBlocks = []; let orangeBlocks = []; let purpleBlocks = []; let gameSpeed = 0.2; let score = 0; let isGameOver = false; let playerVelocity = 0; let isJumping = false; let moveLeft = false; let moveRight = false; let maxJumps = 1; let jumpCount = 0; const MOVEMENT_SPEED = 0.15; const PLATFORM_WIDTH = 5; let previewCamera, previewRenderer; let previewPlayer; let powerUpTimer = null; let remainingTime = 10; function init() { scene = new THREE.Scene(); const loader = new THREE.CubeTextureLoader(); const skyboxTexture = new THREE.Color(0x87CEEB); scene.background = skyboxTexture; for(let i = 0; i < 20; i++) { const cloudGeometry = new THREE.SphereGeometry(2, 16, 16); const cloudMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 }); const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial); cloud.position.set( (Math.random() - 0.5) * 100, 20 + (Math.random() - 0.5) * 10, (Math.random() - 0.5) * 100 ); cloud.scale.set(1, 0.3, 1); scene.add(cloud); } camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 20, 0); scene.add(directionalLight); const playerGeometry = new THREE.BoxGeometry(1, 1, 1); const playerMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); player = new THREE.Mesh(playerGeometry, playerMaterial); player.position.set(0, 1, 0); scene.add(player); const previewGeometry = new THREE.BoxGeometry(1, 1, 1); const previewMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); previewPlayer = new THREE.Mesh(previewGeometry, previewMaterial); previewPlayer.position.copy(player.position); scene.add(previewPlayer); camera.position.set(0, 3, 7); camera.lookAt(player.position); createInitialPlatforms(); document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); // Setup preview previewCamera = new THREE.OrthographicCamera( -5, 5, 3.75, -3.75, 0.1, 1000 ); previewCamera.position.set(0, 10, 0); previewCamera.lookAt(0, 0, 0); previewCamera.rotation.z = Math.PI; previewRenderer = new THREE.WebGLRenderer(); previewRenderer.setSize(200, 150); const previewContainer = document.getElementById('preview'); previewContainer.appendChild(previewRenderer.domElement); animate(); } function createInitialPlatforms() { for (let i = 0; i < 5; i++) { createPlatform(-20 * i); } } function createPlatform(zPosition) { const platformGeometry = new THREE.BoxGeometry(5, 0.5, 20); const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; const context = canvas.getContext('2d'); const tileSize = 32; for (let x = 0; x < canvas.width; x += tileSize) { for (let y = 0; y < canvas.height; y += tileSize) { context.fillStyle = ((x + y) / tileSize) % 2 === 0 ? '#808080' : '#606060'; context.fillRect(x, y, tileSize, tileSize); context.fillStyle = 'rgba(0,0,0,0.1)'; for (let i = 0; i < 5; i++) { context.fillRect( x + Math.random() * tileSize, y + Math.random() * tileSize, 2, 2 ); } } } const texture = new THREE.CanvasTexture(canvas); texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(2, 8); const platformMaterial = new THREE.MeshPhongMaterial({ map: texture, bumpMap: texture, bumpScale: 0.05 }); const platform = new THREE.Mesh(platformGeometry, platformMaterial); platform.position.set(0, -0.25, zPosition); scene.add(platform); platforms.push(platform); if (Math.random() > 0.3) { const obstacleGeometry = new THREE.BoxGeometry(1, 1, 1); const obstacleMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 }); const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial); obstacle.position.set( (Math.random() - 0.5) * 3, 0.5, zPosition + (Math.random() - 0.5) * 8 ); scene.add(obstacle); obstacles.push(obstacle); if (Math.random() > 0.7) { const yellowGeometry = new THREE.BoxGeometry(1, 1, 1); const yellowMaterial = new THREE.MeshPhongMaterial({ color: 0xffff00 }); const yellowBlock = new THREE.Mesh(yellowGeometry, yellowMaterial); yellowBlock.position.set( (Math.random() - 0.5) * 3, 2, zPosition + (Math.random() - 0.5) * 8 ); scene.add(yellowBlock); yellowBlocks.push(yellowBlock); } if (Math.random() > 0.5) { createBlueBlock(zPosition); } if (Math.random() > 0.6) { createOrangeBlock(zPosition); } if (Math.random() > 0.8) { createPurpleBlock(zPosition); } } } function createBlueBlock(zPosition) { const blueGeometry = new THREE.BoxGeometry(2, 0.5, 2); const blueMaterial = new THREE.MeshPhongMaterial({ color: 0x0000ff }); const blueBlock = new THREE.Mesh(blueGeometry, blueMaterial); const randomY = Math.random() * 2 + 2; blueBlock.position.set( (Math.random() - 0.5) * 3, randomY, zPosition + (Math.random() - 0.5) * 8 ); scene.add(blueBlock); blueBlocks.push(blueBlock); } function createOrangeBlock(zPosition) { const orangeGeometry = new THREE.BoxGeometry(2, 0.5, 2); const orangeMaterial = new THREE.MeshPhongMaterial({ color: 0xff8c00 }); const orangeBlock = new THREE.Mesh(orangeGeometry, orangeMaterial); const randomY = Math.random() * 2 + 1.5; orangeBlock.position.set( (Math.random() - 0.5) * 3, randomY, zPosition + (Math.random() - 0.5) * 8 ); scene.add(orangeBlock); orangeBlocks.push(orangeBlock); } function createPurpleBlock(zPosition) { const purpleGeometry = new THREE.BoxGeometry(1, 1, 1); const purpleMaterial = new THREE.MeshPhongMaterial({ color: 0x800080 }); const purpleBlock = new THREE.Mesh(purpleGeometry, purpleMaterial); purpleBlock.position.set( (Math.random() - 0.5) * 3, 2, zPosition + (Math.random() - 0.5) * 8 ); scene.add(purpleBlock); purpleBlocks.push(purpleBlock); } function onKeyDown(event) { if (isGameOver) { if (event.code === 'Enter') { restartGame(); } return; } switch(event.code) { case 'ArrowUp': if (jumpCount < maxJumps) { playerVelocity = 0.3; isJumping = true; jumpCount++; } break; case 'ArrowLeft': moveLeft = true; break; case 'ArrowRight': moveRight = true; break; } } function onKeyUp(event) { switch(event.code) { case 'ArrowLeft': moveLeft = false; break; case 'ArrowRight': moveRight = false; break; } } function updateGame() { if (isGameOver) return; previewPlayer.position.copy(player.position); if (moveLeft && player.position.x > -(PLATFORM_WIDTH/2 - 0.5)) { player.position.x -= MOVEMENT_SPEED; } if (moveRight && player.position.x < (PLATFORM_WIDTH/2 - 0.5)) { player.position.x += MOVEMENT_SPEED; } player.scale.set(1, 1, 1); if (isJumping) { previewPlayer.scale.set(2, 2, 2); } else { previewPlayer.scale.set(1, 1, 1); } platforms.forEach((platform, index) => { platform.position.z += gameSpeed; if (platform.position.z > 20) { scene.remove(platform); platforms.splice(index, 1); createPlatform(-80); } }); obstacles.forEach((obstacle, index) => { obstacle.position.z += gameSpeed; if (obstacle.position.z > 20) { scene.remove(obstacle); obstacles.splice(index, 1); } }); blueBlocks.forEach((block, index) => { block.position.z += gameSpeed; if (block.position.z > 20) { scene.remove(block); blueBlocks.splice(index, 1); } }); yellowBlocks.forEach((block, index) => { block.position.z += gameSpeed; if (block.position.z > 20) { scene.remove(block); yellowBlocks.splice(index, 1); } if (checkCollision(player, block)) { scene.remove(block); yellowBlocks.splice(index, 1); for (let i = 0; i < 3; i++) { createBlueBlock(block.position.z - 20 - (i * 5)); } } }); orangeBlocks.forEach((block, index) => { block.position.z += gameSpeed; if (block.position.z > 20) { scene.remove(block); orangeBlocks.splice(index, 1); } if (checkBlockCollision(player, block)) { playerVelocity = 0.4; isJumping = true; } }); purpleBlocks.forEach((block, index) => { block.position.z += gameSpeed; if (block.position.z > 20) { scene.remove(block); purpleBlocks.splice(index, 1); } if (checkCollision(player, block)) { scene.remove(block); purpleBlocks.splice(index, 1); maxJumps = 4; remainingTime = 10; document.getElementById('timer').style.display = 'block'; if (powerUpTimer) clearInterval(powerUpTimer); powerUpTimer = setInterval(updateTimer, 1000); } }); let isOnBlueBlock = false; blueBlocks.forEach(block => { if (checkBlockCollision(player, block)) { isOnBlueBlock = true; player.position.y = block.position.y + 0.75; isJumping = false; playerVelocity = 0; } }); if (!isOnBlueBlock && !isJumping && player.position.y > 1) { isJumping = true; playerVelocity = 0; } if (isJumping) { player.position.y += playerVelocity; playerVelocity -= 0.01; if (player.position.y <= 1) { player.position.y = 1; isJumping = false; playerVelocity = 0; } } if (!isJumping && player.position.y <= 1) { jumpCount = 0; } obstacles.forEach(obstacle => { if (checkCollision(player, obstacle)) { gameOver(); } }); score += gameSpeed; document.getElementById('score').textContent = `Score: ${Math.floor(score)}`; gameSpeed += 0.0001; } function checkCollision(obj1, obj2) { const dx = obj1.position.x - obj2.position.x; const dy = obj1.position.y - obj2.position.y; const dz = obj1.position.z - obj2.position.z; const distance = Math.sqrt(dx * dx + dy * dy + dz * dz); return distance < 1.5; } function checkBlockCollision(player, block) { const dx = Math.abs(player.position.x - block.position.x); const dy = player.position.y - block.position.y; const dz = Math.abs(player.position.z - block.position.z); return dx < 1.5 && dy < 1 && dy > 0 && dz < 1.5 && player.position.y > block.position.y; } function gameOver() { isGameOver = true; document.getElementById('gameOver').style.display = 'block'; } function updateTimer() { const timerElement = document.getElementById('timer'); remainingTime--; if (remainingTime >= 0) { timerElement.textContent = `Quadruple Jump: ${remainingTime}s`; } else { timerElement.style.display = 'none'; maxJumps = 1; clearInterval(powerUpTimer); } } function restartGame() { if (powerUpTimer) { clearInterval(powerUpTimer); } yellowBlocks.forEach(block => scene.remove(block)); yellowBlocks = []; blueBlocks.forEach(block => scene.remove(block)); blueBlocks = []; orangeBlocks.forEach(block => scene.remove(block)); orangeBlocks = []; purpleBlocks.forEach(block => scene.remove(block)); purpleBlocks = []; scene.remove(previewPlayer); maxJumps = 1; jumpCount = 0; location.reload(); } function animate() { requestAnimationFrame(animate); updateGame(); renderer.render(scene, camera); // Render preview previewRenderer.render(scene, previewCamera); } window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); const aspect = 200 / 150; previewCamera.left = -5; previewCamera.right = 5; previewCamera.top = 5 / aspect; previewCamera.bottom = -5 / aspect; previewCamera.updateProjectionMatrix(); previewRenderer.setSize(200, 150); }); window.restartGame = restartGame; init(); </script> </body> </html>