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> |