Spaces:
Running
Running
<html> | |
<head> | |
<title>FPS Game</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
#gameCanvas { width: 100vw; height: 100vh; } | |
.ui { | |
position: fixed; | |
color: white; | |
font-family: Arial; | |
pointer-events: none; | |
text-shadow: 1px 1px 2px black; | |
} | |
#health { | |
top: 20px; | |
left: 20px; | |
width: 200px; | |
height: 20px; | |
background: rgba(255,0,0,0.3); | |
border: 2px solid red; | |
} | |
#healthBar { | |
position: absolute; | |
bottom: 20px; | |
left: 20px; | |
width: 400px; | |
height: 30px; | |
background: rgba(0,20,0,0.7); | |
border: 2px solid #0f0; | |
z-index: 1001; | |
border-radius: 15px; | |
overflow: hidden; | |
} | |
#ammo { | |
top: 50px; | |
left: 20px; | |
font-size: 24px; | |
} | |
#enemies { | |
top: 80px; | |
left: 20px; | |
font-size: 20px; | |
} | |
#crosshair { | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
font-size: 24px; | |
} | |
.modal { | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
background: rgba(0,0,0,0.9); | |
padding: 30px; | |
border-radius: 10px; | |
text-align: center; | |
color: white; | |
border: 2px solid white; | |
min-width: 300px; | |
} | |
.button { | |
margin: 10px; | |
padding: 15px 30px; | |
font-size: 18px; | |
cursor: pointer; | |
background: #4CAF50; | |
border: none; | |
color: white; | |
border-radius: 5px; | |
transition: background 0.3s; | |
} | |
.button:hover { | |
background: #45a049; | |
} | |
.modal h1 { | |
color: #4CAF50; | |
margin-bottom: 20px; | |
} | |
</style> | |
</head> | |
<body> | |
<canvas id="gameCanvas"></canvas> | |
<div class="ui"> | |
<div id="health"><div id="healthBar"></div></div> | |
<div id="ammo">90/90</div> | |
<div id="enemies">Enemies: 3</div> | |
<div id="crosshair">+</div> | |
</div> | |
<div id="menuModal" class="modal"> | |
<h1>FPS Game</h1> | |
<h2>Select Difficulty</h2> | |
<button class="button" onclick="startGame(1)">Level 1 (3 Enemies)</button> | |
<button class="button" onclick="startGame(2)">Level 2 (5 Enemies)</button> | |
<button class="button" onclick="startGame(3)">Level 3 (7 Enemies)</button> | |
<button class="button" onclick="startGame(4)">Level 4 (9 Enemies)</button> | |
<button class="button" onclick="startGame(5)">Level 5 (12 Enemies)</button> | |
</div> | |
<div id="gameOverModal" class="modal" style="display:none;"> | |
<h1>Game Over</h1> | |
<p>Your health reached 0!</p> | |
<button class="button" onclick="showMenu()">Try Again</button> | |
</div> | |
<div id="victoryModal" class="modal" style="display:none;"> | |
<h1>Victory!</h1> | |
<p>All enemies eliminated!</p> | |
<button class="button" onclick="showMenu()">Play Again</button> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
<script> | |
const GAME_CONFIG = { | |
PLAYER: { | |
HEALTH: 200, | |
AMMO: 90, | |
DAMAGE: 5, | |
SPEED: 0.15, | |
FIRE_RATE: 100 | |
}, | |
ENEMY: { | |
HEALTH: 30, | |
DAMAGE: 10, | |
FIRE_RATE: 1000, | |
GROUP_DISTANCE: 10, | |
SPEED: 0.05 | |
}, | |
LEVELS: { | |
1: { enemies: 3, obstacles: 5, mapSize: 100 }, | |
2: { enemies: 5, obstacles: 8, mapSize: 150 }, | |
3: { enemies: 7, obstacles: 12, mapSize: 200 }, | |
4: { enemies: 9, obstacles: 16, mapSize: 250 }, | |
5: { enemies: 12, obstacles: 20, mapSize: 300 } | |
} | |
}; | |
const scene = new THREE.Scene(); | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); | |
const renderer = new THREE.WebGLRenderer({canvas: document.getElementById('gameCanvas')}); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
let gameState = { | |
player: { | |
health: GAME_CONFIG.PLAYER.HEALTH, | |
ammo: GAME_CONFIG.PLAYER.AMMO, | |
lastShot: 0 | |
}, | |
enemies: [], | |
bullets: [], | |
enemyBullets: [], | |
isShooting: false, | |
isPlaying: false, | |
level: 1 | |
}; | |
const keys = { w: false, s: false, a: false, d: false }; | |
document.addEventListener('keydown', e => { | |
if(keys.hasOwnProperty(e.key.toLowerCase())) { | |
keys[e.key.toLowerCase()] = true; | |
} | |
}); | |
document.addEventListener('keyup', e => { | |
if(keys.hasOwnProperty(e.key.toLowerCase())) { | |
keys[e.key.toLowerCase()] = false; | |
} | |
}); | |
document.addEventListener('mousedown', e => { | |
if(e.button === 0) gameState.isShooting = true; | |
}); | |
document.addEventListener('mouseup', e => { | |
if(e.button === 0) gameState.isShooting = false; | |
}); | |
document.addEventListener('mousemove', e => { | |
if(document.pointerLockElement === document.body) { | |
camera.rotation.y -= e.movementX * 0.002; | |
} | |
}); | |
function startGame(level) { | |
gameState = { | |
player: { | |
health: GAME_CONFIG.PLAYER.HEALTH, | |
ammo: GAME_CONFIG.PLAYER.AMMO, | |
lastShot: 0 | |
}, | |
enemies: [], | |
bullets: [], | |
enemyBullets: [], | |
isShooting: false, | |
isPlaying: true, | |
level: level | |
}; | |
hideAllModals(); | |
document.body.requestPointerLock(); | |
createLevel(level); | |
gameLoop(); | |
} | |
function hideAllModals() { | |
document.getElementById('menuModal').style.display = 'none'; | |
document.getElementById('gameOverModal').style.display = 'none'; | |
document.getElementById('victoryModal').style.display = 'none'; | |
} | |
function showMenu() { | |
hideAllModals(); | |
document.getElementById('menuModal').style.display = 'block'; | |
} | |
function gameOver() { | |
gameState.isPlaying = false; | |
document.exitPointerLock(); | |
hideAllModals(); | |
document.getElementById('gameOverModal').style.display = 'block'; | |
} | |
function victory() { | |
gameState.isPlaying = false; | |
document.exitPointerLock(); | |
hideAllModals(); | |
document.getElementById('victoryModal').style.display = 'block'; | |
} | |
function createLevel(level) { | |
while(scene.children.length > 0) { | |
scene.remove(scene.children[0]); | |
} | |
const config = GAME_CONFIG.LEVELS[level]; | |
// Ground | |
const ground = new THREE.Mesh( | |
new THREE.PlaneGeometry(config.mapSize, config.mapSize), | |
new THREE.MeshBasicMaterial({color: 0x444444}) | |
); | |
ground.rotation.x = -Math.PI/2; | |
scene.add(ground); | |
// Obstacles | |
for(let i = 0; i < config.obstacles; i++) { | |
const obstacle = new THREE.Mesh( | |
new THREE.BoxGeometry(5, 10, 5), | |
new THREE.MeshBasicMaterial({color: 0x666666}) | |
); | |
obstacle.position.set( | |
(Math.random() - 0.5) * config.mapSize * 0.8, | |
5, | |
(Math.random() - 0.5) * config.mapSize * 0.8 | |
); | |
scene.add(obstacle); | |
} | |
// Enemies | |
for(let i = 0; i < config.enemies; i++) { | |
const enemy = new THREE.Mesh( | |
new THREE.BoxGeometry(2, 4, 2), | |
new THREE.MeshBasicMaterial({color: 0xff0000}) | |
); | |
const angle = (Math.PI * 2 / config.enemies) * i; | |
const radius = config.mapSize * 0.4; | |
enemy.position.set( | |
Math.cos(angle) * radius, | |
2, | |
Math.sin(angle) * radius | |
); | |
enemy.health = GAME_CONFIG.ENEMY.HEALTH; | |
enemy.lastShot = 0; | |
scene.add(enemy); | |
gameState.enemies.push(enemy); | |
} | |
camera.position.set(0, 2, 0); | |
camera.rotation.set(0, 0, 0); | |
updateUI(); | |
} | |
function movePlayer() { | |
const moveVector = new THREE.Vector3(); | |
if(keys.w) moveVector.z -= GAME_CONFIG.PLAYER.SPEED; | |
if(keys.s) moveVector.z += GAME_CONFIG.PLAYER.SPEED; | |
if(keys.a) moveVector.x -= GAME_CONFIG.PLAYER.SPEED; | |
if(keys.d) moveVector.x += GAME_CONFIG.PLAYER.SPEED; | |
moveVector.applyAxisAngle(new THREE.Vector3(0, 1, 0), camera.rotation.y); | |
camera.position.add(moveVector); | |
} | |
function shoot() { | |
if(gameState.player.ammo <= 0 || | |
Date.now() - gameState.player.lastShot < GAME_CONFIG.PLAYER.FIRE_RATE) return; | |
gameState.player.ammo--; | |
gameState.player.lastShot = Date.now(); | |
const bullet = new THREE.Mesh( | |
new THREE.SphereGeometry(0.2), | |
new THREE.MeshBasicMaterial({color: 0xffff00}) | |
); | |
bullet.position.copy(camera.position); | |
const direction = new THREE.Vector3(0, 0, -1); | |
direction.applyQuaternion(camera.quaternion); | |
bullet.velocity = direction.multiplyScalar(2); | |
scene.add(bullet); | |
gameState.bullets.push(bullet); | |
updateUI(); | |
} | |
function updateBullets() { | |
// Player bullets | |
for(let i = gameState.bullets.length - 1; i >= 0; i--) { | |
const bullet = gameState.bullets[i]; | |
bullet.position.add(bullet.velocity); | |
gameState.enemies.forEach((enemy, enemyIndex) => { | |
if(bullet.position.distanceTo(enemy.position) < 2) { | |
enemy.health -= GAME_CONFIG.PLAYER.DAMAGE; | |
scene.remove(bullet); | |
gameState.bullets.splice(i, 1); | |
if(enemy.health <= 0) { | |
scene.remove(enemy); | |
gameState.enemies.splice(enemyIndex, 1); | |
gameState.player.ammo += 30; | |
updateUI(); | |
if(gameState.enemies.length === 0) { | |
victory(); | |
} | |
} | |
} | |
}); | |
} | |
// Enemy bullets | |
for(let i = gameState.enemyBullets.length - 1; i >= 0; i--) { | |
const bullet = gameState.enemyBullets[i]; | |
bullet.position.add(bullet.velocity); | |
if(bullet.position.distanceTo(camera.position) < 1) { | |
gameState.player.health -= GAME_CONFIG.ENEMY.DAMAGE; | |
scene.remove(bullet); | |
gameState.enemyBullets.splice(i, 1); | |
updateUI(); | |
if(gameState.player.health <= 0) { | |
gameOver(); | |
} | |
} | |
} | |
} | |
function updateEnemies() { | |
gameState.enemies.forEach(enemy => { | |
// Movement with group avoidance | |
const toPlayer = new THREE.Vector3() | |
.subVectors(camera.position, enemy.position) | |
.normalize(); | |
const avoidance = new THREE.Vector3(); | |
gameState.enemies.forEach(other => { | |
if(other !== enemy) { | |
const dist = enemy.position.distanceTo(other.position); | |
if(dist < GAME_CONFIG.ENEMY.GROUP_DISTANCE) { | |
const away = new THREE.Vector3() | |
.subVectors(enemy.position, other.position) | |
.normalize() | |
.multiplyScalar(1/dist); | |
avoidance.add(away); | |
} | |
} | |
}); | |
const movement = toPlayer.add(avoidance.multiplyScalar(0.5)) | |
.normalize() | |
.multiplyScalar(GAME_CONFIG.ENEMY.SPEED); | |
enemy.position.add(movement); | |
// Shooting | |
if(Date.now() - enemy.lastShot > GAME_CONFIG.ENEMY.FIRE_RATE) { | |
enemy.lastShot = Date.now(); | |
const bullet = new THREE.Mesh( | |
new THREE.SphereGeometry(0.2), | |
new THREE.MeshBasicMaterial({color: 0xff0000}) | |
); | |
bullet.position.copy(enemy.position); | |
bullet.velocity = toPlayer.normalize().multiplyScalar(0.5); | |
scene.add(bullet); | |
gameState.enemyBullets.push(bullet); | |
} | |
}); | |
} | |
function updateUI() { | |
document.getElementById('healthBar').style.width = | |
(gameState.player.health / GAME_CONFIG.PLAYER.HEALTH * 100) + '%'; | |
document.getElementById('ammo').textContent = | |
`Ammo: ${gameState.player.ammo}/${GAME_CONFIG.PLAYER.AMMO}`; | |
document.getElementById('enemies').textContent = | |
`Enemies: ${gameState.enemies.length}`; | |
} | |
function gameLoop() { | |
if(!gameState.isPlaying) return; | |
requestAnimationFrame(gameLoop); | |
if(gameState.isShooting) shoot(); | |
movePlayer(); | |
updateEnemies(); | |
updateBullets(); | |
renderer.render(scene, camera); | |
} | |
window.addEventListener('resize', () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}); | |
// Initialize game | |
updateUI(); | |
</script> | |
</body> | |
</html> |