Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>2D Elden Ring</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=MedievalSharp&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background-color: #111; | |
| font-family: 'MedievalSharp', cursive; | |
| overflow: hidden; | |
| height: 100vh; | |
| color: #ccc; | |
| } | |
| #game-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(to bottom, #001a33, #000); | |
| overflow: hidden; | |
| } | |
| #game-canvas { | |
| display: block; | |
| background-color: #000; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| #hud { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 100; | |
| color: #fff; | |
| text-shadow: 0 0 5px #000; | |
| font-size: 24px; | |
| } | |
| #health-bar { | |
| width: 200px; | |
| height: 30px; | |
| border: 3px solid #500; | |
| background-color: #300; | |
| position: relative; | |
| margin-bottom: 10px; | |
| } | |
| #health-fill { | |
| height: 100%; | |
| width: 100%; | |
| background-color: #f00; | |
| position: absolute; | |
| transition: width 0.3s; | |
| } | |
| #stamina-bar { | |
| width: 200px; | |
| height: 15px; | |
| border: 2px solid #060; | |
| background-color: #030; | |
| position: relative; | |
| } | |
| #stamina-fill { | |
| height: 100%; | |
| width: 100%; | |
| background-color: #0f0; | |
| position: absolute; | |
| transition: width 0.1s; | |
| } | |
| #enemy-info { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| z-index: 100; | |
| color: #fff; | |
| text-shadow: 0 0 5px #000; | |
| font-size: 24px; | |
| } | |
| #title-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: radial-gradient(circle, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.95) 100%); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 200; | |
| } | |
| #title { | |
| font-size: 72px; | |
| color: #d4af37; | |
| text-shadow: 0 0 10px #000, 0 0 20px #d4af37; | |
| margin-bottom: 40px; | |
| letter-spacing: 5px; | |
| } | |
| #start-button { | |
| padding: 15px 40px; | |
| font-size: 24px; | |
| font-family: 'MedievalSharp', cursive; | |
| background: linear-gradient(to bottom, #d4af37, #a67c00); | |
| color: #000; | |
| border: none; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| box-shadow: 0 5px 15px rgba(0,0,0,0.5); | |
| } | |
| #start-button:hover { | |
| transform: translateY(-3px); | |
| box-shadow: 0 8px 20px rgba(0,0,0,0.6); | |
| } | |
| #controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| font-size: 16px; | |
| color: #aaa; | |
| } | |
| #message { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0,0,0,0.7); | |
| color: #fff; | |
| padding: 20px 40px; | |
| border: 2px solid #d4af37; | |
| border-radius: 5px; | |
| font-size: 24px; | |
| z-index: 300; | |
| display: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="game-container"> | |
| <canvas id="game-canvas"></canvas> | |
| <div id="hud"> | |
| <div id="health-bar"> | |
| <div id="health-fill"></div> | |
| </div> | |
| <div id="stamina-bar"> | |
| <div id="stamina-fill"></div> | |
| </div> | |
| <div id="runes">Runes: 0</div> | |
| </div> | |
| <div id="enemy-info"> | |
| <div id="enemy-health-bar-container"> | |
| <div id="enemy-health-bar"></div> | |
| </div> | |
| <div id="enemy-name">—</div> | |
| </div> | |
| <div id="controls"> | |
| <p>WASD: Move | Space: Jump | Left Click: Attack | Shift: Roll</p> | |
| <p>E: Interact | Q: Use Item | R: Cast Spell</p> | |
| </div> | |
| <div id="title-screen"> | |
| <h1 id="title">ELDEN RING</h1> | |
| <button id="start-button">START GAME</button> | |
| </div> | |
| <div id="message"></div> | |
| </div> | |
| <script> | |
| // Game setup | |
| const canvas = document.getElementById('game-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const titleScreen = document.getElementById('title-screen'); | |
| const startButton = document.getElementById('start-button'); | |
| const healthFill = document.getElementById('health-fill'); | |
| const staminaFill = document.getElementById('stamina-fill'); | |
| const runesDisplay = document.getElementById('runes'); | |
| const enemyHealthBar = document.getElementById('enemy-health-bar'); | |
| const enemyNameDisplay = document.getElementById('enemy-name'); | |
| const messageDisplay = document.getElementById('message'); | |
| // Resize canvas to full window | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| // Game state | |
| let gameRunning = false; | |
| let runes = 0; | |
| let playerHealth = 100; | |
| let playerMaxHealth = 100; | |
| let playerStamina = 100; | |
| let playerMaxStamina = 100; | |
| let selectedEnemy = null; | |
| // Player properties | |
| const player = { | |
| x: canvas.width / 4, | |
| y: canvas.height / 2, | |
| width: 60, | |
| height: 100, | |
| speed: 5, | |
| jumpPower: 15, | |
| isJumping: false, | |
| isAttacking: false, | |
| isRolling: false, | |
| direction: 1, // 1 = right, -1 = left | |
| velX: 0, | |
| velY: 0, | |
| frame: 0, | |
| attackCooldown: 0, | |
| rollCooldown: 0, | |
| invulnerable: 0 | |
| }; | |
| // Enemy types | |
| const enemies = [ | |
| { | |
| name: "Godrick Soldier", | |
| health: 50, | |
| maxHealth: 50, | |
| damage: 10, | |
| width: 60, | |
| height: 90, | |
| speed: 2, | |
| x: 800, | |
| y: canvas.height - 100 | |
| }, | |
| { | |
| name: "Margit's Phantom", | |
| health: 100, | |
| maxHealth: 100, | |
| damage: 20, | |
| width: 70, | |
| height: 110, | |
| speed: 3, | |
| x: 1200, | |
| y: canvas.height - 110 | |
| } | |
| ]; | |
| // Level elements | |
| const platforms = [ | |
| { x: 0, y: canvas.height - 50, width: canvas.width * 2, height: 50 }, | |
| { x: 500, y: canvas.height - 200, width: 200, height: 20 }, | |
| { x: 900, y: canvas.height - 300, width: 200, height: 20 } | |
| ]; | |
| const bonfire = { | |
| x: 400, | |
| y: canvas.height - 70, | |
| width: 40, | |
| height: 40, | |
| lit: false | |
| }; | |
| // Start game | |
| startButton.addEventListener('click', () => { | |
| titleScreen.style.display = 'none'; | |
| gameRunning = true; | |
| startGame(); | |
| }); | |
| function startGame() { | |
| // Reset player | |
| player.x = canvas.width / 4; | |
| player.y = canvas.height / 2; | |
| player.health = 100; | |
| player.stamina = 100; | |
| player.velX = 0; | |
| player.velY = 0; | |
| player.isJumping = false; | |
| player.isAttacking = false; | |
| player.isRolling = false; | |
| player.invulnerable = 0; | |
| // Reset enemies | |
| enemies.forEach(enemy => { | |
| enemy.health = enemy.maxHealth; | |
| }); | |
| bonfire.lit = false; | |
| runes = 0; | |
| updateHUD(); | |
| // Start game loop | |
| gameLoop(); | |
| } | |
| // Controls | |
| const keys = {}; | |
| document.addEventListener('keydown', (e) => { | |
| keys[e.key.toLowerCase()] = true; | |
| // Quick restart | |
| if (e.key === '`') { | |
| if (!gameRunning) { | |
| titleScreen.style.display = 'none'; | |
| gameRunning = true; | |
| startGame(); | |
| } | |
| } | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| keys[e.key.toLowerCase()] = false; | |
| }); | |
| canvas.addEventListener('click', (e) => { | |
| if (player.attackCooldown <= 0 && player.stamina > 10 && !player.isJumping && gameRunning) { | |
| player.isAttacking = true; | |
| player.attackCooldown = 20; | |
| player.stamina -= 10; | |
| // Check if attack hits enemy | |
| const attackRange = player.direction === 1 ? | |
| { x: player.x + player.width, y: player.y, width: 50, height: player.height } : | |
| { x: player.x - 50, y: player.y, width: 50, height: player.height }; | |
| for (const enemy of enemies) { | |
| if (checkCollision(attackRange, enemy)) { | |
| enemy.health -= 20; | |
| if (enemy.health <= 0) { | |
| runes += 50; | |
| enemy.health = 0; | |
| displayMessage(`${enemy.name} defeated! +50 Runes`); | |
| } | |
| updateHUD(); | |
| } | |
| } | |
| } | |
| }); | |
| // Game loop | |
| function gameLoop() { | |
| if (!gameRunning) return; | |
| update(); | |
| render(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| function update() { | |
| // Player movement | |
| if (player.isRolling || player.isAttacking) { | |
| player.velX *= 0.9; | |
| } else { | |
| if (keys['a'] || keys['arrowleft']) { | |
| player.velX = -player.speed; | |
| player.direction = -1; | |
| } else if (keys['d'] || keys['arrowright']) { | |
| player.velX = player.speed; | |
| player.direction = 1; | |
| } else { | |
| player.velX *= 0.8; | |
| } | |
| } | |
| // Jumping | |
| if ((keys['w'] || keys['arrowup'] || keys[' ']) && !player.isJumping && player.velY === 0) { | |
| player.velY = -player.jumpPower; | |
| player.isJumping = true; | |
| } | |
| // Rolling | |
| if (keys['shift'] && !player.isRolling && !player.isAttacking && player.stamina > 15) { | |
| player.isRolling = true; | |
| player.rollCooldown = 40; | |
| player.invulnerable = 30; | |
| player.stamina -= 15; | |
| player.velX = player.direction * player.speed * 2; | |
| } | |
| // Gravity | |
| player.velY += 0.8; | |
| // Apply velocity | |
| player.x += player.velX; | |
| player.y += player.velY; | |
| // Platform collision | |
| let onGround = false; | |
| for (const platform of platforms) { | |
| if ( | |
| player.x < platform.x + platform.width && | |
| player.x + player.width > platform.x && | |
| player.y < platform.y + platform.height && | |
| player.y + player.height > platform.y && | |
| player.velY > 0 | |
| ) { | |
| player.y = platform.y - player.height; | |
| player.velY = 0; | |
| player.isJumping = false; | |
| onGround = true; | |
| } | |
| } | |
| // Boundary checks | |
| if (player.x < 0) player.x = 0; | |
| if (player.x + player.width > canvas.width * 2) player.x = canvas.width * 2 - player.width; | |
| if (player.y + player.height > canvas.height) { | |
| player.y = canvas.height - player.height; | |
| player.velY = 0; | |
| player.isJumping = false; | |
| onGround = true; | |
| } | |
| // Check bonfire | |
| if ( | |
| Math.abs(player.x - bonfire.x) < 50 && | |
| Math.abs((player.y + player.height) - (bonfire.y + bonfire.height)) < 50 && | |
| keys['e'] | |
| ) { | |
| if (!bonfire.lit) { | |
| bonfire.lit = true; | |
| playerHealth = playerMaxHealth; | |
| playerStamina = playerMaxStamina; | |
| displayMessage("Bonfire lit. Health and stamina restored."); | |
| // Reset enemies | |
| enemies.forEach(enemy => { | |
| enemy.health = enemy.maxHealth; | |
| }); | |
| } | |
| } | |
| // Enemy updates | |
| selectedEnemy = null; | |
| for (const enemy of enemies) { | |
| if (enemy.health <= 0) continue; | |
| // Simple AI - follow player if on same platform | |
| const playerPlatform = getPlatformUnder(player); | |
| const enemyPlatform = getPlatformUnder(enemy); | |
| if (playerPlatform === enemyPlatform) { | |
| if (player.x < enemy.x) { | |
| enemy.x -= enemy.speed; | |
| } else { | |
| enemy.x += enemy.speed; | |
| } | |
| } | |
| // Check if enemy is attacking player | |
| if ( | |
| Math.abs(player.x - enemy.x) < 80 && | |
| Math.abs(player.y - enemy.y) < player.height && | |
| player.invulnerable <= 0 | |
| ) { | |
| if (playerHealth > 0) { | |
| playerHealth -= enemy.damage; | |
| player.invulnerable = 30; | |
| displayMessage(`Hit by ${enemy.name}! -${enemy.damage} HP`); | |
| if (playerHealth <= 0) { | |
| playerHealth = 0; | |
| displayMessage("YOU DIED", 3000); | |
| setTimeout(() => { | |
| gameRunning = false; | |
| titleScreen.style.display = 'flex'; | |
| }, 3000); | |
| } | |
| } | |
| } | |
| // Check if player has targeted this enemy | |
| if (Math.abs(player.x - enemy.x) < 200 && Math.abs(player.y - enemy.y) < 100) { | |
| selectedEnemy = enemy; | |
| } | |
| } | |
| // Update cooldowns | |
| if (player.attackCooldown > 0) player.attackCooldown--; | |
| else player.isAttacking = false; | |
| if (player.rollCooldown > 0) player.rollCooldown--; | |
| else player.isRolling = false; | |
| if (player.invulnerable > 0) player.invulnerable--; | |
| // Regenerate stamina when not moving | |
| if (player.velX === 0 && player.velY === 0 && onGround) { | |
| playerStamina = Math.min(playerMaxStamina, playerStamina + 0.5); | |
| } else { | |
| playerStamina = Math.min(playerMaxStamina, playerStamina + 0.1); | |
| } | |
| updateHUD(); | |
| updateCamera(); | |
| } | |
| function getPlatformUnder(entity) { | |
| for (let i = 0; i < platforms.length; i++) { | |
| if ( | |
| entity.x + entity.width > platforms[i].x && | |
| entity.x < platforms[i].x + platforms[i].width && | |
| Math.abs((entity.y + entity.height) - platforms[i].y) < 5 | |
| ) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| function updateHUD() { | |
| healthFill.style.width = `${(playerHealth / playerMaxHealth) * 100}%`; | |
| staminaFill.style.width = `${(playerStamina / playerMaxStamina) * 100}%`; | |
| runesDisplay.textContent = `Runes: ${runes}`; | |
| if (selectedEnemy) { | |
| enemyHealthBar.style.width = `${(selectedEnemy.health / selectedEnemy.maxHealth) * 100}%`; | |
| enemyHealthBar.style.backgroundColor = selectedEnemy.health > 50 ? '#0f0' : | |
| selectedEnemy.health > 25 ? '#ff0' : '#f00'; | |
| enemyNameDisplay.textContent = selectedEnemy.name; | |
| } else { | |
| enemyHealthBar.style.width = '0%'; | |
| enemyNameDisplay.textContent = '—'; | |
| } | |
| } | |
| let cameraX = 0; | |
| function updateCamera() { | |
| // Simple camera follow with some lead space | |
| cameraX = player.x - canvas.width / 4; | |
| cameraX = Math.max(0, Math.min(canvas.width * 2 - canvas.width, cameraX)); | |
| } | |
| function displayMessage(text, duration = 1500) { | |
| messageDisplay.textContent = text; | |
| messageDisplay.style.display = 'block'; | |
| setTimeout(() => { | |
| messageDisplay.style.display = 'none'; | |
| }, duration); | |
| } | |
| function checkCollision(rect1, rect2) { | |
| return ( | |
| rect1.x < rect2.x + rect2.width && | |
| rect1.x + rect1.width > rect2.x && | |
| rect1.y < rect2.y + rect2.height && | |
| rect1.y + rect1.height > rect2.y | |
| ); | |
| } | |
| function render() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw background | |
| if (canvas.width >= 768) { // Only draw detailed background on larger screens | |
| // Stars | |
| ctx.fillStyle = '#fff'; | |
| for (let i = 0; i < 100; i++) { | |
| const x = (i * 300 + (Math.sin(Date.now() / 1000 + i) * 100)) % (canvas.width * 2); | |
| const y = (Math.sin(i) * 100 + i * 50) % canvas.height; | |
| const size = 1 + Math.random(); | |
| ctx.fillRect(x - cameraX, y, size, size); | |
| } | |
| // Moon | |
| ctx.beginPath(); | |
| ctx.arc( | |
| 200 - cameraX * 0.2, | |
| 200, | |
| 50, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fillStyle = '#d4af37'; | |
| ctx.fill(); | |
| } | |
| // Draw platforms | |
| ctx.fillStyle = '#654321'; | |
| for (const platform of platforms) { | |
| ctx.fillRect(platform.x - cameraX, platform.y, platform.width, platform.height); | |
| } | |
| // Draw bonfire | |
| ctx.fillStyle = bonfire.lit ? '#f80' : '#666'; | |
| ctx.fillRect(bonfire.x - cameraX, bonfire.y, bonfire.width, bonfire.height); | |
| // Draw enemies | |
| for (const enemy of enemies) { | |
| if (enemy.health <= 0) continue; | |
| // Enemy body | |
| ctx.fillStyle = enemy.name.includes('Margit') ? '#0af' : '#864'; | |
| ctx.fillRect(enemy.x - cameraX, enemy.y, enemy.width, enemy.height); | |
| // Enemy head | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| enemy.x - cameraX + enemy.width / 2, | |
| enemy.y - 10, | |
| 15, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| // Enemy weapon | |
| ctx.fillStyle = '#555'; | |
| ctx.fillRect( | |
| enemy.x - cameraX + (enemy.x < player.x ? enemy.width : -20), | |
| enemy.y + enemy.height / 2, | |
| 30, | |
| 5 | |
| ); | |
| } | |
| // Draw player | |
| if (player.invulnerable > 0 && Math.floor(player.invulnerable / 5) % 2 === 0) { | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; | |
| } else { | |
| ctx.fillStyle = player.isRolling ? '#369' : '#47a'; | |
| } | |
| ctx.fillRect(player.x - cameraX, player.y, player.width, player.height); | |
| // Player head | |
| ctx.fillStyle = '#fff'; | |
| ctx.beginPath(); | |
| ctx.arc( | |
| player.x - cameraX + player.width / 2, | |
| player.y - 10, | |
| 20, | |
| 0, | |
| Math.PI * 2 | |
| ); | |
| ctx.fill(); | |
| // Player weapon | |
| ctx.fillStyle = '#d4af37'; | |
| if (player.isAttacking) { | |
| ctx.fillRect( | |
| player.x - cameraX + (player.direction === 1 ? player.width : -40), | |
| player.y + player.height / 2, | |
| 40, | |
| 8 | |
| ); | |
| } else { | |
| ctx.fillRect( | |
| player.x - cameraX + (player.direction === 1 ? 10 : player.width - 30), | |
| player.y + player.height / 2, | |
| 20, | |
| 4 | |
| ); | |
| } | |
| // Draw erdtree in background | |
| if (canvas.width >= 768) { | |
| ctx.fillStyle = 'rgba(0, 180, 60, 0.3)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(1800 - cameraX * 0.1, canvas.height); | |
| ctx.lineTo(1850 - cameraX * 0.1, canvas.height - 150); | |
| ctx.lineTo(1900 - cameraX * 0.1, canvas.height - 300); | |
| ctx.lineTo(1950 - cameraX * 0.1, canvas.height - 450); | |
| ctx.lineTo(2000 - cameraX * 0.1, canvas.height - 600); | |
| ctx.lineTo(1950 - cameraX * 0.1, canvas.height - 450); | |
| ctx.lineTo(1900 - cameraX * 0.1, canvas.height - 300); | |
| ctx.lineTo(1850 - cameraX * 0.1, canvas.height - 150); | |
| ctx.lineTo(1800 - cameraX * 0.1, canvas.height); | |
| ctx.fill(); | |
| ctx.fillStyle = 'rgba(200, 180, 0, 0.6)'; | |
| ctx.beginPath(); | |
| ctx.arc(2000 - cameraX * 0.1, canvas.height - 650, 100, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| </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 <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
| </html> |