Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cosmic Clash - Space War Game</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/cannon-es@0.19.0/dist/cannon-es.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.min.js"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #ui-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| color: white; | |
| } | |
| #start-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| } | |
| #game-over-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.8); | |
| display: none; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| } | |
| .btn { | |
| pointer-events: auto; | |
| background: linear-gradient(45deg, #4f46e5, #8b5cf6); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| font-size: 18px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| margin-top: 20px; | |
| font-family: 'Orbitron', sans-serif; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(79, 70, 229, 0.3); | |
| } | |
| .title { | |
| font-size: 4rem; | |
| background: linear-gradient(45deg, #8b5cf6, #4f46e5); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| margin-bottom: 20px; | |
| text-shadow: 0 0 10px rgba(139, 92, 246, 0.5); | |
| } | |
| .subtitle { | |
| font-size: 1.5rem; | |
| color: #a5b4fc; | |
| margin-bottom: 40px; | |
| } | |
| .health-bar { | |
| height: 20px; | |
| background: rgba(255, 0, 0, 0.2); | |
| border-radius: 10px; | |
| overflow: hidden; | |
| margin-bottom: 10px; | |
| } | |
| .health-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #ef4444, #f59e0b); | |
| transition: width 0.3s; | |
| } | |
| .score-display { | |
| font-size: 1.5rem; | |
| color: white; | |
| text-shadow: 0 0 5px #4f46e5; | |
| } | |
| .notification { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 15px 30px; | |
| border-radius: 8px; | |
| font-size: 1.5rem; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| pointer-events: none; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <div id="ui-overlay"> | |
| <div class="absolute top-0 left-0 p-4"> | |
| <div class="health-bar w-64"> | |
| <div id="health-fill" class="health-fill" style="width: 100%"></div> | |
| </div> | |
| <div id="score" class="score-display">Score: 0</div> | |
| </div> | |
| <div id="notification" class="notification"></div> | |
| </div> | |
| <div id="start-screen"> | |
| <h1 class="title pulse">COSMIC CLASH</h1> | |
| <p class="subtitle">Defend the galaxy against the alien invasion</p> | |
| <button id="start-btn" class="btn">Start Mission</button> | |
| </div> | |
| <div id="game-over-screen"> | |
| <h1 class="title">MISSION FAILED</h1> | |
| <p id="final-score" class="subtitle">Your score: 0</p> | |
| <button id="restart-btn" class="btn">Try Again</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| let scene, camera, renderer, world, physicsWorld; | |
| let playerShip, enemyShips = [], projectiles = [], explosions = [], stars = []; | |
| let score = 0; | |
| let playerHealth = 100; | |
| let gameStarted = false; | |
| let lastEnemySpawnTime = 0; | |
| let enemySpawnInterval = 2000; // ms | |
| let keys = {}; | |
| // DOM elements | |
| const startScreen = document.getElementById('start-screen'); | |
| const gameOverScreen = document.getElementById('game-over-screen'); | |
| const startBtn = document.getElementById('start-btn'); | |
| const restartBtn = document.getElementById('restart-btn'); | |
| const healthFill = document.getElementById('health-fill'); | |
| const scoreDisplay = document.getElementById('score'); | |
| const notification = document.getElementById('notification'); | |
| const finalScore = document.getElementById('final-score'); | |
| // Initialize the game | |
| function init() { | |
| // Create Three.js scene | |
| scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x000000); | |
| scene.fog = new THREE.FogExp2(0x000000, 0.001); | |
| // Create camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.set(0, 10, 30); | |
| camera.lookAt(0, 0, 0); | |
| // Create renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
| document.getElementById('game-container').prepend(renderer.domElement); | |
| // Create physics world | |
| physicsWorld = new CANNON.World(); | |
| physicsWorld.gravity.set(0, 0, 0); | |
| physicsWorld.broadphase = new CANNON.NaiveBroadphase(); | |
| physicsWorld.solver.iterations = 10; | |
| // Add event listeners | |
| window.addEventListener('resize', onWindowResize); | |
| document.addEventListener('keydown', onKeyDown); | |
| document.addEventListener('keyup', onKeyUp); | |
| // Create game elements | |
| createLights(); | |
| createStars(); | |
| createPlayerShip(); | |
| // Start animation loop | |
| animate(); | |
| } | |
| // Start the game | |
| function startGame() { | |
| gameStarted = true; | |
| startScreen.style.display = 'none'; | |
| playerHealth = 100; | |
| score = 0; | |
| updateUI(); | |
| showNotification('ENGAGE!', 2000); | |
| // Reset player position | |
| playerShip.mesh.position.set(0, 0, 0); | |
| playerShip.body.position.set(0, 0, 0); | |
| playerShip.body.velocity.set(0, 0, 0); | |
| // Clear any existing enemies and projectiles | |
| enemyShips.forEach(enemy => { | |
| scene.remove(enemy.mesh); | |
| physicsWorld.removeBody(enemy.body); | |
| }); | |
| projectiles.forEach(projectile => { | |
| scene.remove(projectile.mesh); | |
| physicsWorld.removeBody(projectile.body); | |
| }); | |
| explosions.forEach(explosion => { | |
| scene.remove(explosion); | |
| }); | |
| enemyShips = []; | |
| projectiles = []; | |
| explosions = []; | |
| } | |
| // Restart the game | |
| function restartGame() { | |
| gameOverScreen.style.display = 'none'; | |
| startGame(); | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameStarted = false; | |
| finalScore.textContent = `Your score: ${score}`; | |
| gameOverScreen.style.display = 'flex'; | |
| } | |
| // Create lights | |
| function createLights() { | |
| const ambientLight = new THREE.AmbientLight(0x404040); | |
| scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1); | |
| directionalLight.position.set(1, 1, 1); | |
| directionalLight.castShadow = true; | |
| directionalLight.shadow.mapSize.width = 1024; | |
| directionalLight.shadow.mapSize.height = 1024; | |
| scene.add(directionalLight); | |
| const pointLight = new THREE.PointLight(0x4f46e5, 2, 50); | |
| pointLight.position.set(0, 0, 10); | |
| scene.add(pointLight); | |
| } | |
| // Create starfield background | |
| function createStars() { | |
| const starGeometry = new THREE.BufferGeometry(); | |
| const starMaterial = new THREE.PointsMaterial({ | |
| color: 0xffffff, | |
| size: 0.1, | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const starVertices = []; | |
| for (let i = 0; i < 5000; i++) { | |
| const x = (Math.random() - 0.5) * 2000; | |
| const y = (Math.random() - 0.5) * 2000; | |
| const z = (Math.random() - 0.5) * 2000; | |
| starVertices.push(x, y, z); | |
| } | |
| starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3)); | |
| const starField = new THREE.Points(starGeometry, starMaterial); | |
| scene.add(starField); | |
| } | |
| // Create player ship | |
| function createPlayerShip() { | |
| const geometry = new THREE.ConeGeometry(2, 4, 4); | |
| const material = new THREE.MeshPhongMaterial({ | |
| color: 0x4f46e5, | |
| emissive: 0x4f46e5, | |
| emissiveIntensity: 0.5, | |
| shininess: 30 | |
| }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| mesh.position.set(0, 0, 0); | |
| mesh.rotation.y = Math.PI; | |
| mesh.castShadow = true; | |
| scene.add(mesh); | |
| // Create physics body | |
| const shape = new CANNON.Box(new CANNON.Vec3(1.5, 1.5, 2)); | |
| const body = new CANNON.Body({ | |
| mass: 5, | |
| shape: shape, | |
| position: new CANNON.Vec3(0, 0, 0), | |
| linearDamping: 0.5 | |
| }); | |
| physicsWorld.addBody(body); | |
| // Add engine glow | |
| const engineGlowGeometry = new THREE.ConeGeometry(1.5, 3, 4); | |
| const engineGlowMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x8b5cf6, | |
| transparent: true, | |
| opacity: 0.7 | |
| }); | |
| const engineGlow = new THREE.Mesh(engineGlowGeometry, engineGlowMaterial); | |
| engineGlow.position.z = -2; | |
| mesh.add(engineGlow); | |
| playerShip = { | |
| mesh: mesh, | |
| body: body, | |
| engineGlow: engineGlow, | |
| lastShot: 0, | |
| shotDelay: 300 | |
| }; | |
| } | |
| // Create enemy ship | |
| function createEnemyShip() { | |
| const x = (Math.random() - 0.5) * 40; | |
| const y = (Math.random() - 0.5) * 20; | |
| const z = -50; | |
| const geometry = new THREE.OctahedronGeometry(1.5); | |
| const material = new THREE.MeshPhongMaterial({ | |
| color: 0xef4444, | |
| emissive: 0xef4444, | |
| emissiveIntensity: 0.3, | |
| shininess: 20 | |
| }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| mesh.position.set(x, y, z); | |
| mesh.castShadow = true; | |
| scene.add(mesh); | |
| // Create physics body | |
| const shape = new CANNON.Sphere(1.5); | |
| const body = new CANNON.Body({ | |
| mass: 3, | |
| shape: shape, | |
| position: new CANNON.Vec3(x, y, z), | |
| linearDamping: 0.3 | |
| }); | |
| physicsWorld.addBody(body); | |
| // Add red glow | |
| const glowGeometry = new THREE.SphereGeometry(2); | |
| const glowMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0xef4444, | |
| transparent: true, | |
| opacity: 0.3 | |
| }); | |
| const glow = new THREE.Mesh(glowGeometry, glowMaterial); | |
| mesh.add(glow); | |
| const enemy = { | |
| mesh: mesh, | |
| body: body, | |
| glow: glow, | |
| health: 100, | |
| lastShot: 0, | |
| shotDelay: 1500 + Math.random() * 1000 | |
| }; | |
| enemyShips.push(enemy); | |
| return enemy; | |
| } | |
| // Create projectile | |
| function createProjectile(position, velocity, isPlayer) { | |
| const geometry = new THREE.SphereGeometry(0.2); | |
| const material = new THREE.MeshPhongMaterial({ | |
| color: isPlayer ? 0x4f46e5 : 0xef4444, | |
| emissive: isPlayer ? 0x4f46e5 : 0xef4444, | |
| emissiveIntensity: 0.5 | |
| }); | |
| const mesh = new THREE.Mesh(geometry, material); | |
| mesh.position.copy(position); | |
| mesh.castShadow = true; | |
| scene.add(mesh); | |
| // Create physics body | |
| const shape = new CANNON.Sphere(0.2); | |
| const body = new CANNON.Body({ | |
| mass: 0.1, | |
| shape: shape, | |
| position: new CANNON.Vec3(position.x, position.y, position.z), | |
| velocity: new CANNON.Vec3(velocity.x, velocity.y, velocity.z) | |
| }); | |
| body.timeCreated = Date.now(); | |
| physicsWorld.addBody(body); | |
| const projectile = { | |
| mesh: mesh, | |
| body: body, | |
| isPlayer: isPlayer, | |
| timeToLive: 3000 // ms | |
| }; | |
| projectiles.push(projectile); | |
| return projectile; | |
| } | |
| // Create explosion | |
| function createExplosion(position, scale = 1) { | |
| const particleCount = 100; | |
| const particles = new THREE.BufferGeometry(); | |
| const particleMaterial = new THREE.PointsMaterial({ | |
| color: 0xffa500, | |
| size: 0.2 * scale, | |
| transparent: true, | |
| opacity: 1, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const posArray = new Float32Array(particleCount * 3); | |
| for (let i = 0; i < particleCount * 3; i++) { | |
| posArray[i] = (Math.random() - 0.5) * 5 * scale; | |
| } | |
| particles.setAttribute('position', new THREE.BufferAttribute(posArray, 3)); | |
| const particleSystem = new THREE.Points(particles, particleMaterial); | |
| particleSystem.position.copy(position); | |
| scene.add(particleSystem); | |
| explosions.push({ | |
| mesh: particleSystem, | |
| createdAt: Date.now(), | |
| duration: 1000 | |
| }); | |
| return particleSystem; | |
| } | |
| // Handle keyboard input | |
| function onKeyDown(event) { | |
| keys[event.code] = true; | |
| // Space to shoot | |
| if (event.code === 'Space' && gameStarted) { | |
| shoot(); | |
| } | |
| } | |
| function onKeyUp(event) { | |
| keys[event.code] = false; | |
| } | |
| // Player shooting | |
| function shoot() { | |
| const now = Date.now(); | |
| if (now - playerShip.lastShot < playerShip.shotDelay) return; | |
| playerShip.lastShot = now; | |
| // Calculate projectile position and direction | |
| const position = new THREE.Vector3().copy(playerShip.mesh.position); | |
| position.z += 2; // Move slightly in front of the ship | |
| const direction = new THREE.Vector3(0, 0, 1); | |
| playerShip.mesh.localToWorld(direction); | |
| direction.sub(playerShip.mesh.position).normalize().multiplyScalar(30); | |
| // Create projectile | |
| createProjectile(position, direction, true); | |
| } | |
| // Enemy shooting | |
| function enemyShoot(enemy) { | |
| const now = Date.now(); | |
| if (now - enemy.lastShot < enemy.shotDelay) return; | |
| enemy.lastShot = now; | |
| // Calculate direction to player | |
| const direction = new THREE.Vector3().subVectors( | |
| playerShip.mesh.position, | |
| enemy.mesh.position | |
| ).normalize().multiplyScalar(20); | |
| // Create projectile | |
| createProjectile(enemy.mesh.position, direction, false); | |
| } | |
| // Update player movement based on input | |
| function updatePlayerMovement(deltaTime) { | |
| if (!gameStarted) return; | |
| const speed = 15; | |
| const rotationSpeed = 3; | |
| const force = new CANNON.Vec3(0, 0, 0); | |
| // Movement | |
| if (keys['ArrowUp'] || keys['KeyW']) { | |
| force.z -= speed; | |
| playerShip.engineGlow.scale.z = 1.5; | |
| } else { | |
| playerShip.engineGlow.scale.z = 1; | |
| } | |
| if (keys['ArrowDown'] || keys['KeyS']) force.z += speed; | |
| if (keys['ArrowLeft'] || keys['KeyA']) force.x -= speed; | |
| if (keys['ArrowRight'] || keys['KeyD']) force.x += speed; | |
| // Apply force | |
| playerShip.body.force.copy(force); | |
| // Rotation based on movement | |
| const targetRotationX = (force.x / speed) * 0.2; | |
| const targetRotationY = (force.z / speed) * 0.1; | |
| playerShip.mesh.rotation.x = THREE.MathUtils.lerp( | |
| playerShip.mesh.rotation.x, | |
| targetRotationY, | |
| 0.1 | |
| ); | |
| playerShip.mesh.rotation.z = THREE.MathUtils.lerp( | |
| playerShip.mesh.rotation.z, | |
| -targetRotationX, | |
| 0.1 | |
| ); | |
| // Keep player within bounds | |
| const maxX = 25; | |
| const maxY = 15; | |
| const maxZ = 10; | |
| if (Math.abs(playerShip.body.position.x) > maxX) { | |
| playerShip.body.position.x = Math.sign(playerShip.body.position.x) * maxX; | |
| playerShip.body.velocity.x = 0; | |
| } | |
| if (Math.abs(playerShip.body.position.y) > maxY) { | |
| playerShip.body.position.y = Math.sign(playerShip.body.position.y) * maxY; | |
| playerShip.body.velocity.y = 0; | |
| } | |
| if (Math.abs(playerShip.body.position.z) > maxZ) { | |
| playerShip.body.position.z = Math.sign(playerShip.body.position.z) * maxZ; | |
| playerShip.body.velocity.z = 0; | |
| } | |
| } | |
| // Update enemies | |
| function updateEnemies(deltaTime) { | |
| // Spawn new enemies | |
| const now = Date.now(); | |
| if (now - lastEnemySpawnTime > enemySpawnInterval) { | |
| lastEnemySpawnTime = now; | |
| createEnemyShip(); | |
| // Gradually increase difficulty | |
| enemySpawnInterval = Math.max(500, enemySpawnInterval - 10); | |
| } | |
| // Update existing enemies | |
| for (let i = 0; i < enemyShips.length; i++) { | |
| const enemy = enemyShips[i]; | |
| // Move toward player | |
| const direction = new CANNON.Vec3().copy(playerShip.body.position).vsub(enemy.body.position); | |
| direction.normalize(); | |
| direction.scale(5, direction); | |
| enemy.body.force.copy(direction); | |
| // Try to shoot at player | |
| if (Math.random() < 0.02) { | |
| enemyShoot(enemy); | |
| } | |
| // Update mesh position to match physics body | |
| enemy.mesh.position.copy(enemy.body.position); | |
| enemy.mesh.quaternion.copy(enemy.body.quaternion); | |
| // Make enemy face player | |
| enemy.mesh.lookAt(playerShip.mesh.position); | |
| // Pulsing glow effect | |
| enemy.glow.scale.setScalar(1 + Math.sin(now * 0.005) * 0.2); | |
| } | |
| } | |
| // Update projectiles | |
| function updateProjectiles(deltaTime) { | |
| for (let i = projectiles.length - 1; i >= 0; i--) { | |
| const projectile = projectiles[i]; | |
| // Update mesh position to match physics body | |
| projectile.mesh.position.copy(projectile.body.position); | |
| // Check time to live | |
| if (Date.now() - projectile.body.timeCreated > projectile.timeToLive) { | |
| removeProjectile(i); | |
| continue; | |
| } | |
| // Check collisions | |
| if (projectile.isPlayer) { | |
| // Player projectile hitting enemies | |
| for (let j = 0; j < enemyShips.length; j++) { | |
| const enemy = enemyShips[j]; | |
| const distance = projectile.mesh.position.distanceTo(enemy.mesh.position); | |
| if (distance < 2) { // Simple collision detection | |
| enemy.health -= 25; | |
| if (enemy.health <= 0) { | |
| // Enemy destroyed | |
| createExplosion(enemy.mesh.position, 2); | |
| scene.remove(enemy.mesh); | |
| physicsWorld.removeBody(enemy.body); | |
| enemyShips.splice(j, 1); | |
| score += 100; | |
| updateUI(); | |
| showNotification('TARGET DESTROYED!', 1000); | |
| } else { | |
| // Enemy hit | |
| createExplosion(projectile.mesh.position, 0.5); | |
| score += 10; | |
| updateUI(); | |
| } | |
| removeProjectile(i); | |
| break; | |
| } | |
| } | |
| } else { | |
| // Enemy projectile hitting player | |
| const distance = projectile.mesh.position.distanceTo(playerShip.mesh.position); | |
| if (distance < 2) { // Simple collision detection | |
| playerHealth -= 10; | |
| createExplosion(projectile.mesh.position, 0.5); | |
| removeProjectile(i); | |
| updateUI(); | |
| if (playerHealth <= 0) { | |
| // Player destroyed | |
| createExplosion(playerShip.mesh.position, 3); | |
| gameOver(); | |
| } else { | |
| showNotification('HULL DAMAGE!', 1000); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Remove projectile | |
| function removeProjectile(index) { | |
| const projectile = projectiles[index]; | |
| scene.remove(projectile.mesh); | |
| physicsWorld.removeBody(projectile.body); | |
| projectiles.splice(index, 1); | |
| } | |
| // Update explosions | |
| function updateExplosions() { | |
| const now = Date.now(); | |
| for (let i = explosions.length - 1; i >= 0; i--) { | |
| const explosion = explosions[i]; | |
| const elapsed = now - explosion.createdAt; | |
| const progress = elapsed / explosion.duration; | |
| if (progress >= 1) { | |
| scene.remove(explosion.mesh); | |
| explosions.splice(i, 1); | |
| } else { | |
| // Fade out | |
| explosion.mesh.material.opacity = 1 - progress; | |
| explosion.mesh.material.size = 0.2 * (1 - progress); | |
| } | |
| } | |
| } | |
| // Update UI | |
| function updateUI() { | |
| healthFill.style.width = `${playerHealth}%`; | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| } | |
| // Show notification | |
| function showNotification(text, duration) { | |
| notification.textContent = text; | |
| notification.style.opacity = 1; | |
| setTimeout(() => { | |
| notification.style.opacity = 0; | |
| }, duration); | |
| } | |
| // Handle window resize | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const time = Date.now(); | |
| const deltaTime = 16; // Approximate for simplicity | |
| if (gameStarted) { | |
| // Update physics | |
| physicsWorld.step(1/60); | |
| // Update player ship mesh to match physics body | |
| playerShip.mesh.position.copy(playerShip.body.position); | |
| playerShip.mesh.quaternion.copy(playerShip.body.quaternion); | |
| // Update game elements | |
| updatePlayerMovement(deltaTime); | |
| updateEnemies(deltaTime); | |
| updateProjectiles(deltaTime); | |
| updateExplosions(); | |
| // Update camera to follow player with slight delay | |
| const targetCameraPos = new THREE.Vector3( | |
| playerShip.mesh.position.x, | |
| playerShip.mesh.position.y + 10, | |
| playerShip.mesh.position.z + 30 | |
| ); | |
| camera.position.lerp(targetCameraPos, 0.05); | |
| camera.lookAt(playerShip.mesh.position); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| // Initialize the game when the page loads | |
| window.addEventListener('DOMContentLoaded', () => { | |
| init(); | |
| // Add event listeners for buttons after initialization | |
| startBtn.addEventListener('click', startGame); | |
| restartBtn.addEventListener('click', restartGame); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=cristianoaredes/cosmic-clash" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |