Add major game features and improvements
- Add interval management system to fix memory leaks and cleanup bugs - Implement win condition (survive 3 minutes) with victory screen - Add pause functionality (ESC/P key) with pause overlay menu - Add progressive difficulty (enemy speed/spawn rate increase over time) - Implement enemy variety: Normal, Fast (green), and Tank (larger, 2 HP) - Add boss fights every 60 seconds with health bar - Add power-ups: Extra Life, Triple Shot, Shield (10-15s spawn interval) - Implement Hardcore Mode (1 life, faster enemies, no power-ups) - Add high score system with localStorage persistence - Add background music support with volume control - Add visual feedback (player hit flash, enemy death animation, shield glow) - Add mobile touch controls (auto-detected on touch devices) - Improve collision detection using getBoundingClientRect - Add responsive styles for mobile devices
This commit is contained in:
parent
19276b11fe
commit
d49bed1541
3 changed files with 1161 additions and 144 deletions
43
index.html
43
index.html
|
|
@ -10,10 +10,15 @@
|
|||
<body>
|
||||
<main>
|
||||
<div id="game-intro">
|
||||
<img src="./images/logo.png" alt="" class="logo-img" />
|
||||
<img src="./images/logo.png" alt="" class="logo-img" />
|
||||
<br />
|
||||
<button id="start-button">Start Game</button>
|
||||
<div class="button-container">
|
||||
<button id="start-button">Start Game</button>
|
||||
<button id="hardcore-button">Hardcore Mode</button>
|
||||
</div>
|
||||
<p id="high-score-display">High Score: 0<br>Hardcore High: 0</p>
|
||||
<h2>Use the arrows keys to move and spacebar to shoot, try survive if you are able.</h2>
|
||||
<p class="controls-hint">Press ESC or P to pause</p>
|
||||
<img src="./images/arrows.png" />
|
||||
</div>
|
||||
|
||||
|
|
@ -22,20 +27,48 @@
|
|||
<label for="volume">Volume:</label>
|
||||
<input type="range" id="volume" min="0" max="1" step="0.1" value="0.4">
|
||||
</div>
|
||||
<div id ="stats">
|
||||
<div id="stats">
|
||||
<h2>Stats</h2>
|
||||
<h2 id="timer"></h2>
|
||||
<h2>Lives: <span id="lives">3</span></h2>
|
||||
<h2>Score: <span id="score">0</span></h2>
|
||||
</div>
|
||||
|
||||
<!-- Pause Overlay -->
|
||||
<div id="pause-overlay">
|
||||
<div class="pause-content">
|
||||
<h1>PAUSED</h1>
|
||||
<button id="resume-button">Resume</button>
|
||||
<button id="main-menu-button">Main Menu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="game-end">
|
||||
|
||||
<div id="game-end">
|
||||
<h1>Game Over</h1>
|
||||
<p>Final Score: <span id="final-score">0</span></p>
|
||||
<p><span id="final-time">Time elapsed: 0:00</span></p>
|
||||
<p id="final-high-score">High Score: 0</p>
|
||||
<button id="restart-button">Restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="game-victory">
|
||||
<h1>VICTORY!</h1>
|
||||
<p>You survived the apocalypse!</p>
|
||||
<p>Final Score: <span id="victory-score">0</span></p>
|
||||
<p><span id="victory-time">Time survived: 3:00</span></p>
|
||||
<p id="victory-high-score">High Score: 0</p>
|
||||
<button id="victory-restart-button">Play Again</button>
|
||||
</div>
|
||||
|
||||
<!-- Touch Controls for Mobile -->
|
||||
<div id="touch-controls">
|
||||
<div class="touch-move">
|
||||
<button id="touch-up" class="touch-btn">UP</button>
|
||||
<button id="touch-down" class="touch-btn">DOWN</button>
|
||||
</div>
|
||||
<button id="touch-shoot" class="touch-btn touch-shoot-btn">SHOOT</button>
|
||||
</div>
|
||||
</main>
|
||||
<script type="text/javascript" src="script.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
843
script.js
843
script.js
|
|
@ -1,107 +1,175 @@
|
|||
// DOM Elements
|
||||
const gameIntro = document.getElementById('game-intro');
|
||||
const gameArea = document.getElementById("gameArea");
|
||||
const gameEnd = document.getElementById('game-end');
|
||||
const gameVictory = document.getElementById('game-victory');
|
||||
const pauseOverlay = document.getElementById('pause-overlay');
|
||||
const startButton = document.getElementById('start-button');
|
||||
const hardcoreButton = document.getElementById('hardcore-button');
|
||||
const restartButton = document.getElementById('restart-button');
|
||||
const victoryRestartButton = document.getElementById('victory-restart-button');
|
||||
const scoreElement = document.getElementById('score');
|
||||
const livesElement = document.getElementById('lives');
|
||||
const timerElement = document.getElementById('timer');
|
||||
const finalScoreElement = document.getElementById('final-score');
|
||||
const finalTimeElement = document.getElementById('final-time');
|
||||
const victoryScoreElement = document.getElementById('victory-score');
|
||||
const victoryTimeElement = document.getElementById('victory-time');
|
||||
const gameAreaHeight = window.innerHeight * 0.8;
|
||||
const gameAreaWidth = window.innerWidth * 0.8;
|
||||
const stats = document.getElementById('stats');
|
||||
const volumeSlider = document.getElementById('volume');
|
||||
const highScoreDisplay = document.getElementById('high-score-display');
|
||||
const finalHighScore = document.getElementById('final-high-score');
|
||||
const victoryHighScore = document.getElementById('victory-high-score');
|
||||
const touchControls = document.getElementById('touch-controls');
|
||||
|
||||
let player = document.createElement("img");
|
||||
let enemyInterval;
|
||||
//let bulletInterval;
|
||||
// Game state variables
|
||||
let player = null;
|
||||
let activeIntervals = [];
|
||||
let activeTimeouts = [];
|
||||
let sec = 0;
|
||||
let min = 0;
|
||||
let intervalTimer;
|
||||
let lives = 3;
|
||||
let score = 0;
|
||||
let playerY = gameAreaHeight / 2 - 25;
|
||||
let gameVolume = 0.4;
|
||||
let isPaused = false;
|
||||
let isGameRunning = false;
|
||||
let isHardcoreMode = false;
|
||||
let difficultyLevel = 1;
|
||||
let hasShield = false;
|
||||
let hasTripleShot = false;
|
||||
let tripleShotTimeout = null;
|
||||
let shieldTimeout = null;
|
||||
let backgroundMusic = null;
|
||||
let bossSpawned = false;
|
||||
let lastBossTime = 0;
|
||||
let keydownHandler = null;
|
||||
|
||||
|
||||
// Set background image
|
||||
|
||||
// Setup Game Intro state
|
||||
function introPage() {
|
||||
gameIntro.style.display = 'block';
|
||||
gameArea.style.display = 'none';
|
||||
gameEnd.style.display = 'none';
|
||||
stats.style.display = 'none';
|
||||
// Interval management helpers
|
||||
function addInterval(fn, ms, label = '') {
|
||||
const id = setInterval(() => {
|
||||
if (!isPaused) {
|
||||
fn();
|
||||
}
|
||||
}, ms);
|
||||
activeIntervals.push({ id, label });
|
||||
return id;
|
||||
}
|
||||
|
||||
// Run the game when start button is pressed
|
||||
function startGame() {
|
||||
startButton.addEventListener('click', () => {
|
||||
gameArea.style.width = gameAreaWidth + "px";
|
||||
gameArea.style.height = gameAreaHeight + "px";
|
||||
gameArea.style.position = "relative";
|
||||
gameArea.style.overflow = "hidden";
|
||||
gameIntro.style.display = 'none';
|
||||
gameArea.style.display = 'block';
|
||||
gameEnd.style.display = 'none';
|
||||
stats.style.display = 'block';
|
||||
sec = 0;
|
||||
min = 0;
|
||||
lives = 3;
|
||||
score = 0;
|
||||
scoreElement.innerText = score;
|
||||
livesElement.innerText = lives;
|
||||
clearInterval(intervalTimer);
|
||||
intervalTimer = setInterval(timeGame, 1000);
|
||||
spawnPlayer();
|
||||
spawnEnemy();
|
||||
function addTimeout(fn, ms, label = '') {
|
||||
const id = setTimeout(() => {
|
||||
if (!isPaused && isGameRunning) {
|
||||
fn();
|
||||
}
|
||||
// Remove from tracking
|
||||
activeTimeouts = activeTimeouts.filter(t => t.id !== id);
|
||||
}, ms);
|
||||
activeTimeouts.push({ id, label });
|
||||
return id;
|
||||
}
|
||||
|
||||
function clearAllIntervals() {
|
||||
activeIntervals.forEach(interval => clearInterval(interval.id));
|
||||
activeIntervals = [];
|
||||
activeTimeouts.forEach(timeout => clearTimeout(timeout.id));
|
||||
activeTimeouts = [];
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
clearAllIntervals();
|
||||
|
||||
// Clear power-up effects
|
||||
if (tripleShotTimeout) clearTimeout(tripleShotTimeout);
|
||||
if (shieldTimeout) clearTimeout(shieldTimeout);
|
||||
hasShield = false;
|
||||
hasTripleShot = false;
|
||||
|
||||
// Remove all game elements
|
||||
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
||||
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
||||
document.querySelectorAll(".boss").forEach(b => b.remove());
|
||||
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
||||
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
||||
|
||||
// Remove player if exists
|
||||
if (player && player.parentNode) {
|
||||
player.remove();
|
||||
}
|
||||
player = null;
|
||||
|
||||
// Remove keydown listener
|
||||
if (keydownHandler) {
|
||||
document.removeEventListener("keydown", keydownHandler);
|
||||
keydownHandler = null;
|
||||
}
|
||||
|
||||
// Stop background music
|
||||
stopBackgroundMusic();
|
||||
|
||||
// Reset state
|
||||
sec = 0;
|
||||
min = 0;
|
||||
lives = isHardcoreMode ? 1 : 3;
|
||||
score = 0;
|
||||
playerY = gameAreaHeight / 2 - 25;
|
||||
isPaused = false;
|
||||
isGameRunning = false;
|
||||
difficultyLevel = 1;
|
||||
bossSpawned = false;
|
||||
lastBossTime = 0;
|
||||
}
|
||||
|
||||
// High Score functions
|
||||
function getHighScore() {
|
||||
const key = isHardcoreMode ? 'scriptocalypse_highscore_hardcore' : 'scriptocalypse_highscore';
|
||||
return parseInt(localStorage.getItem(key)) || 0;
|
||||
}
|
||||
|
||||
function updateHighScore() {
|
||||
const key = isHardcoreMode ? 'scriptocalypse_highscore_hardcore' : 'scriptocalypse_highscore';
|
||||
const currentHigh = getHighScore();
|
||||
if (score > currentHigh) {
|
||||
localStorage.setItem(key, score);
|
||||
return true; // New record
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function displayHighScores() {
|
||||
const normalHigh = parseInt(localStorage.getItem('scriptocalypse_highscore')) || 0;
|
||||
const hardcoreHigh = parseInt(localStorage.getItem('scriptocalypse_highscore_hardcore')) || 0;
|
||||
if (highScoreDisplay) {
|
||||
highScoreDisplay.innerHTML = `High Score: ${normalHigh}<br>Hardcore High: ${hardcoreHigh}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Background Music
|
||||
function playBackgroundMusic() {
|
||||
if (!backgroundMusic) {
|
||||
backgroundMusic = new Audio('sounds/music.mp3');
|
||||
backgroundMusic.loop = true;
|
||||
}
|
||||
backgroundMusic.volume = gameVolume;
|
||||
backgroundMusic.play().catch(() => {
|
||||
// Autoplay blocked, will play on first interaction
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Game Time
|
||||
function timeGame() {
|
||||
sec++;
|
||||
sec = sec < 10 ? "0" + sec : sec;
|
||||
if (sec == 60) {
|
||||
min++;
|
||||
sec = 0;
|
||||
function stopBackgroundMusic() {
|
||||
if (backgroundMusic) {
|
||||
backgroundMusic.pause();
|
||||
backgroundMusic.currentTime = 0;
|
||||
}
|
||||
timerElement.innerText = 'Time elapsed: ' + min + ':' + sec;
|
||||
}
|
||||
|
||||
function spawnPlayer() {
|
||||
player.src = "images/shooter.gif";
|
||||
player.classList.add("player");
|
||||
gameArea.appendChild(player);
|
||||
playerMove();
|
||||
// Player movement and shooting
|
||||
player.style.bottom = playerY + "px";
|
||||
let bulletInterval = setInterval(() => {
|
||||
let checkPlayerColision = document.querySelectorAll(".enemy");
|
||||
checkPlayerColision.forEach(enemy => {
|
||||
checkCollisionEnemy(player, enemy);
|
||||
});
|
||||
},20);
|
||||
}
|
||||
|
||||
function playerMove(){
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowUp" && playerY < gameAreaHeight - 60) {
|
||||
playerY += 40;
|
||||
} else if (e.key === "ArrowDown" && playerY > 0) {
|
||||
playerY -= 40;
|
||||
} else if (e.key === " ") {
|
||||
shoot();
|
||||
}
|
||||
player.style.bottom = playerY + "px";
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Volume control
|
||||
volumeSlider.addEventListener('input', (event) => {
|
||||
gameVolume = event.target.value;
|
||||
gameVolume = parseFloat(event.target.value);
|
||||
if (backgroundMusic) {
|
||||
backgroundMusic.volume = gameVolume;
|
||||
}
|
||||
});
|
||||
|
||||
function playSound(src) {
|
||||
|
|
@ -110,85 +178,544 @@ function playSound(src) {
|
|||
sound.play();
|
||||
}
|
||||
|
||||
function shoot() {
|
||||
const bullet = document.createElement("div");
|
||||
bullet.classList.add("bullet");
|
||||
gameArea.appendChild(bullet);
|
||||
bullet.style.left = "60px";
|
||||
bullet.style.bottom = playerY + 20 + "px";
|
||||
|
||||
let bulletInterval = setInterval(() => {
|
||||
let bulletX = bullet.offsetLeft;
|
||||
// Touch device detection
|
||||
function isTouchDevice() {
|
||||
return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
|
||||
}
|
||||
|
||||
let checkShoot = document.querySelectorAll(".enemy");
|
||||
checkShoot.forEach(enemy => {
|
||||
checkCollision(bullet, enemy);
|
||||
function setupTouchControls() {
|
||||
if (isTouchDevice() && touchControls) {
|
||||
touchControls.style.display = 'flex';
|
||||
|
||||
const upBtn = document.getElementById('touch-up');
|
||||
const downBtn = document.getElementById('touch-down');
|
||||
const shootBtn = document.getElementById('touch-shoot');
|
||||
|
||||
if (upBtn) {
|
||||
upBtn.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
if (playerY < gameAreaHeight - 60 && !isPaused) {
|
||||
playerY += 40;
|
||||
player.style.bottom = playerY + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (downBtn) {
|
||||
downBtn.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
if (playerY > 0 && !isPaused) {
|
||||
playerY -= 40;
|
||||
player.style.bottom = playerY + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (shootBtn) {
|
||||
shootBtn.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
if (!isPaused) {
|
||||
shoot();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pause functionality
|
||||
function togglePause() {
|
||||
if (!isGameRunning) return;
|
||||
|
||||
isPaused = !isPaused;
|
||||
|
||||
if (pauseOverlay) {
|
||||
pauseOverlay.style.display = isPaused ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (isPaused) {
|
||||
if (backgroundMusic) backgroundMusic.pause();
|
||||
} else {
|
||||
if (backgroundMusic) backgroundMusic.play().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Visual feedback
|
||||
function flashPlayer() {
|
||||
if (!player) return;
|
||||
player.classList.add('player-hit');
|
||||
setTimeout(() => {
|
||||
if (player) player.classList.remove('player-hit');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function enemyDeathEffect(enemy) {
|
||||
enemy.classList.add('enemy-death');
|
||||
setTimeout(() => {
|
||||
enemy.remove();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Setup Game Intro state
|
||||
function introPage() {
|
||||
gameIntro.style.display = 'block';
|
||||
gameArea.style.display = 'none';
|
||||
gameEnd.style.display = 'none';
|
||||
if (gameVictory) gameVictory.style.display = 'none';
|
||||
stats.style.display = 'none';
|
||||
if (touchControls) touchControls.style.display = 'none';
|
||||
displayHighScores();
|
||||
}
|
||||
|
||||
// Initialize game
|
||||
function initGame(hardcore = false) {
|
||||
resetGame();
|
||||
isHardcoreMode = hardcore;
|
||||
lives = isHardcoreMode ? 1 : 3;
|
||||
|
||||
gameArea.style.width = gameAreaWidth + "px";
|
||||
gameArea.style.height = gameAreaHeight + "px";
|
||||
gameArea.style.position = "relative";
|
||||
gameArea.style.overflow = "hidden";
|
||||
gameIntro.style.display = 'none';
|
||||
gameArea.style.display = 'block';
|
||||
gameEnd.style.display = 'none';
|
||||
if (gameVictory) gameVictory.style.display = 'none';
|
||||
if (pauseOverlay) pauseOverlay.style.display = 'none';
|
||||
stats.style.display = 'block';
|
||||
|
||||
scoreElement.innerText = score;
|
||||
livesElement.innerText = lives;
|
||||
timerElement.innerText = 'Time elapsed: 0:00';
|
||||
|
||||
isGameRunning = true;
|
||||
|
||||
// Start game systems
|
||||
addInterval(timeGame, 1000, 'timer');
|
||||
spawnPlayer();
|
||||
spawnEnemy();
|
||||
|
||||
if (!isHardcoreMode) {
|
||||
// Spawn power-ups every 15-20 seconds (not in hardcore)
|
||||
schedulePowerUp();
|
||||
}
|
||||
|
||||
playBackgroundMusic();
|
||||
setupTouchControls();
|
||||
}
|
||||
|
||||
// Start button handlers
|
||||
function startGame() {
|
||||
startButton.addEventListener('click', () => {
|
||||
initGame(false);
|
||||
});
|
||||
|
||||
if (hardcoreButton) {
|
||||
hardcoreButton.addEventListener('click', () => {
|
||||
initGame(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Game Time
|
||||
function timeGame() {
|
||||
if (!isGameRunning) return;
|
||||
|
||||
sec++;
|
||||
if (sec >= 60) {
|
||||
min++;
|
||||
sec = 0;
|
||||
}
|
||||
|
||||
const displaySec = sec < 10 ? "0" + sec : sec;
|
||||
timerElement.innerText = 'Time elapsed: ' + min + ':' + displaySec;
|
||||
|
||||
// Update difficulty every 30 seconds
|
||||
const totalSeconds = min * 60 + sec;
|
||||
difficultyLevel = 1 + Math.floor(totalSeconds / 30) * 0.2;
|
||||
|
||||
// Check for boss spawn (every 60 seconds)
|
||||
if (totalSeconds > 0 && totalSeconds % 60 === 0 && totalSeconds !== lastBossTime) {
|
||||
lastBossTime = totalSeconds;
|
||||
spawnBoss();
|
||||
}
|
||||
|
||||
// Win condition: survive 3 minutes
|
||||
if (min >= 3) {
|
||||
victoryGame();
|
||||
}
|
||||
}
|
||||
|
||||
function spawnPlayer() {
|
||||
player = document.createElement("img");
|
||||
player.src = "images/shooter.gif";
|
||||
player.classList.add("player");
|
||||
gameArea.appendChild(player);
|
||||
player.style.bottom = playerY + "px";
|
||||
|
||||
// Setup keyboard controls (only once)
|
||||
keydownHandler = (e) => {
|
||||
if (isPaused && e.key !== "Escape" && e.key !== "p" && e.key !== "P") return;
|
||||
|
||||
if (e.key === "Escape" || e.key === "p" || e.key === "P") {
|
||||
togglePause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowUp" && playerY < gameAreaHeight - 60) {
|
||||
playerY += 40;
|
||||
} else if (e.key === "ArrowDown" && playerY > 0) {
|
||||
playerY -= 40;
|
||||
} else if (e.key === " ") {
|
||||
e.preventDefault();
|
||||
shoot();
|
||||
}
|
||||
player.style.bottom = playerY + "px";
|
||||
};
|
||||
document.addEventListener("keydown", keydownHandler);
|
||||
|
||||
// Collision check interval
|
||||
addInterval(() => {
|
||||
if (!player) return;
|
||||
|
||||
// Check enemy collisions
|
||||
document.querySelectorAll(".enemy").forEach(enemy => {
|
||||
checkCollisionEnemy(player, enemy);
|
||||
});
|
||||
|
||||
if (bulletX > gameAreaWidth) {
|
||||
bullet.remove();
|
||||
clearInterval(bulletInterval);
|
||||
} else {
|
||||
bullet.style.left = bulletX + 10 + "px";
|
||||
}
|
||||
}, 20);
|
||||
// Check boss collisions
|
||||
document.querySelectorAll(".boss").forEach(boss => {
|
||||
checkCollisionEnemy(player, boss);
|
||||
});
|
||||
|
||||
// Check power-up collisions
|
||||
document.querySelectorAll(".power-up").forEach(powerUp => {
|
||||
checkPowerUpCollision(player, powerUp);
|
||||
});
|
||||
}, 20, 'playerCollision');
|
||||
}
|
||||
|
||||
function shoot() {
|
||||
if (isPaused || !isGameRunning) return;
|
||||
|
||||
if (hasTripleShot) {
|
||||
// Shoot 3 bullets
|
||||
createBullet(playerY + 10);
|
||||
createBullet(playerY + 20);
|
||||
createBullet(playerY + 30);
|
||||
} else {
|
||||
createBullet(playerY + 20);
|
||||
}
|
||||
|
||||
playSound("sounds/shoot.mp3");
|
||||
}
|
||||
|
||||
function createBullet(yPosition) {
|
||||
const bullet = document.createElement("div");
|
||||
bullet.classList.add("bullet");
|
||||
gameArea.appendChild(bullet);
|
||||
bullet.style.left = "60px";
|
||||
bullet.style.bottom = yPosition + "px";
|
||||
|
||||
const bulletInterval = addInterval(() => {
|
||||
let bulletX = bullet.offsetLeft;
|
||||
|
||||
// Check collision with enemies
|
||||
document.querySelectorAll(".enemy").forEach(enemy => {
|
||||
checkCollision(bullet, enemy);
|
||||
});
|
||||
|
||||
// Check collision with boss
|
||||
document.querySelectorAll(".boss").forEach(boss => {
|
||||
checkBossCollision(bullet, boss);
|
||||
});
|
||||
|
||||
if (bulletX > gameAreaWidth || !bullet.parentNode) {
|
||||
bullet.remove();
|
||||
clearInterval(bulletInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== bulletInterval);
|
||||
} else {
|
||||
bullet.style.left = bulletX + 10 + "px";
|
||||
}
|
||||
}, 20, 'bullet');
|
||||
}
|
||||
|
||||
// Calculate spawn delay based on difficulty
|
||||
function getSpawnDelay() {
|
||||
const baseDelay = isHardcoreMode ? 1500 : 2000;
|
||||
const minDelay = isHardcoreMode ? 200 : 300;
|
||||
const totalSeconds = min * 60 + sec;
|
||||
return Math.max(minDelay, baseDelay - (totalSeconds * 5));
|
||||
}
|
||||
|
||||
// Calculate enemy speed based on difficulty and type
|
||||
function getEnemySpeed(baseSpeed) {
|
||||
const multiplier = isHardcoreMode ? 1.5 : 1;
|
||||
return Math.round(baseSpeed * difficultyLevel * multiplier);
|
||||
}
|
||||
|
||||
// Enemy types
|
||||
function getRandomEnemyType() {
|
||||
const rand = Math.random();
|
||||
if (rand < 0.6) {
|
||||
return { type: 'normal', speed: 5, health: 1, class: 'enemy-normal' };
|
||||
} else if (rand < 0.85) {
|
||||
return { type: 'fast', speed: 8, health: 1, class: 'enemy-fast' };
|
||||
} else {
|
||||
return { type: 'tank', speed: 3, health: 2, class: 'enemy-tank' };
|
||||
}
|
||||
}
|
||||
|
||||
function spawnEnemy() {
|
||||
if (!isGameRunning) return;
|
||||
|
||||
const enemyType = getRandomEnemyType();
|
||||
const enemy = document.createElement("img");
|
||||
enemy.src = "images/zombie.gif";
|
||||
enemy.classList.add("enemy");
|
||||
enemy.classList.add("enemy", enemyType.class);
|
||||
enemy.dataset.health = enemyType.health;
|
||||
enemy.dataset.type = enemyType.type;
|
||||
gameArea.appendChild(enemy);
|
||||
enemy.style.right = "0px";
|
||||
enemy.style.bottom = Math.random() * (gameAreaHeight - 50) + "px";
|
||||
|
||||
enemyInterval = setInterval(() => {
|
||||
const speed = getEnemySpeed(enemyType.speed);
|
||||
|
||||
const enemyInterval = addInterval(() => {
|
||||
if (!enemy.parentNode) {
|
||||
clearInterval(enemyInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== enemyInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
let enemyX = enemy.offsetLeft;
|
||||
if (enemyX < 0) {
|
||||
enemy.remove();
|
||||
clearInterval(enemyInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== enemyInterval);
|
||||
} else {
|
||||
enemy.style.right = parseInt(enemy.style.right) + 5 + "px";
|
||||
enemy.style.right = parseInt(enemy.style.right) + speed + "px";
|
||||
}
|
||||
}, 50);
|
||||
}, 50, 'enemy');
|
||||
|
||||
setTimeout(spawnEnemy, Math.random() * 2000 + 100);
|
||||
// Schedule next enemy spawn
|
||||
addTimeout(spawnEnemy, Math.random() * getSpawnDelay() + 100, 'spawnEnemy');
|
||||
}
|
||||
|
||||
// Boss functionality
|
||||
function spawnBoss() {
|
||||
if (!isGameRunning) return;
|
||||
|
||||
const boss = document.createElement("img");
|
||||
boss.src = "images/zombie.gif";
|
||||
boss.classList.add("boss");
|
||||
boss.dataset.health = 10;
|
||||
boss.dataset.maxHealth = 10;
|
||||
gameArea.appendChild(boss);
|
||||
boss.style.right = "0px";
|
||||
boss.style.bottom = (gameAreaHeight / 2 - 75) + "px";
|
||||
|
||||
// Create health bar
|
||||
const healthBar = document.createElement("div");
|
||||
healthBar.classList.add("boss-health-bar");
|
||||
healthBar.innerHTML = '<div class="boss-health-fill"></div>';
|
||||
boss.healthBar = healthBar;
|
||||
gameArea.appendChild(healthBar);
|
||||
|
||||
const bossInterval = addInterval(() => {
|
||||
if (!boss.parentNode) {
|
||||
if (healthBar.parentNode) healthBar.remove();
|
||||
clearInterval(bossInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== bossInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
let bossX = boss.offsetLeft;
|
||||
|
||||
// Update health bar position
|
||||
healthBar.style.left = (bossX - 20) + "px";
|
||||
healthBar.style.bottom = (parseInt(boss.style.bottom) + 160) + "px";
|
||||
|
||||
if (bossX < 0) {
|
||||
boss.remove();
|
||||
healthBar.remove();
|
||||
clearInterval(bossInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== bossInterval);
|
||||
} else {
|
||||
boss.style.right = parseInt(boss.style.right) + 2 + "px";
|
||||
}
|
||||
}, 50, 'boss');
|
||||
|
||||
playSound("sounds/lostlife.mp3"); // Boss spawn alert
|
||||
}
|
||||
|
||||
function checkBossCollision(bullet, boss) {
|
||||
if (!bullet.parentNode || !boss.parentNode) return;
|
||||
|
||||
if (bullet.offsetLeft < boss.offsetLeft + boss.offsetWidth &&
|
||||
bullet.offsetLeft + bullet.offsetWidth > boss.offsetLeft &&
|
||||
bullet.offsetTop < boss.offsetTop + boss.offsetHeight &&
|
||||
bullet.offsetTop + bullet.offsetHeight > boss.offsetTop) {
|
||||
|
||||
bullet.remove();
|
||||
|
||||
let health = parseInt(boss.dataset.health);
|
||||
health--;
|
||||
boss.dataset.health = health;
|
||||
|
||||
// Update health bar
|
||||
const maxHealth = parseInt(boss.dataset.maxHealth);
|
||||
const healthPercent = (health / maxHealth) * 100;
|
||||
const healthFill = boss.healthBar.querySelector('.boss-health-fill');
|
||||
if (healthFill) {
|
||||
healthFill.style.width = healthPercent + '%';
|
||||
}
|
||||
|
||||
if (health <= 0) {
|
||||
score += 10; // Boss worth more points
|
||||
scoreElement.innerText = score;
|
||||
if (boss.healthBar) boss.healthBar.remove();
|
||||
enemyDeathEffect(boss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power-ups
|
||||
function schedulePowerUp() {
|
||||
if (!isGameRunning || isHardcoreMode) return;
|
||||
|
||||
const delay = 10000 + Math.random() * 5000; // 10-15 seconds
|
||||
addTimeout(() => {
|
||||
spawnPowerUp();
|
||||
schedulePowerUp();
|
||||
}, delay, 'powerUpSchedule');
|
||||
}
|
||||
|
||||
function spawnPowerUp() {
|
||||
if (!isGameRunning || isPaused) return;
|
||||
|
||||
const types = ['extraLife', 'tripleShot', 'shield'];
|
||||
const type = types[Math.floor(Math.random() * types.length)];
|
||||
|
||||
const powerUp = document.createElement("div");
|
||||
powerUp.classList.add("power-up", `power-up-${type}`);
|
||||
powerUp.dataset.type = type;
|
||||
gameArea.appendChild(powerUp);
|
||||
powerUp.style.right = "0px";
|
||||
powerUp.style.bottom = Math.random() * (gameAreaHeight - 40) + "px";
|
||||
|
||||
// Set icon based on type
|
||||
switch (type) {
|
||||
case 'extraLife': powerUp.innerHTML = '+'; break;
|
||||
case 'tripleShot': powerUp.innerHTML = '3'; break;
|
||||
case 'shield': powerUp.innerHTML = 'S'; break;
|
||||
}
|
||||
|
||||
const powerUpInterval = addInterval(() => {
|
||||
if (!powerUp.parentNode) {
|
||||
clearInterval(powerUpInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== powerUpInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move faster than enemies so players can catch them (7px/50ms)
|
||||
powerUp.style.right = parseInt(powerUp.style.right) + 7 + "px";
|
||||
|
||||
// Remove when off-screen (left side)
|
||||
if (parseInt(powerUp.style.right) > gameAreaWidth + 50) {
|
||||
powerUp.remove();
|
||||
clearInterval(powerUpInterval);
|
||||
activeIntervals = activeIntervals.filter(i => i.id !== powerUpInterval);
|
||||
}
|
||||
}, 50, 'powerUp');
|
||||
}
|
||||
|
||||
function checkPowerUpCollision(player, powerUp) {
|
||||
if (!player || !powerUp.parentNode) return;
|
||||
|
||||
// Use getBoundingClientRect for accurate collision with right-positioned elements
|
||||
const playerRect = player.getBoundingClientRect();
|
||||
const powerUpRect = powerUp.getBoundingClientRect();
|
||||
|
||||
if (playerRect.left < powerUpRect.right &&
|
||||
playerRect.right > powerUpRect.left &&
|
||||
playerRect.top < powerUpRect.bottom &&
|
||||
playerRect.bottom > powerUpRect.top) {
|
||||
|
||||
collectPowerUp(powerUp.dataset.type);
|
||||
powerUp.remove();
|
||||
playSound("sounds/shoot.mp3");
|
||||
}
|
||||
}
|
||||
|
||||
function collectPowerUp(type) {
|
||||
switch (type) {
|
||||
case 'extraLife':
|
||||
lives++;
|
||||
livesElement.innerText = lives;
|
||||
break;
|
||||
|
||||
case 'tripleShot':
|
||||
hasTripleShot = true;
|
||||
if (tripleShotTimeout) clearTimeout(tripleShotTimeout);
|
||||
tripleShotTimeout = setTimeout(() => {
|
||||
hasTripleShot = false;
|
||||
}, 10000);
|
||||
break;
|
||||
|
||||
case 'shield':
|
||||
hasShield = true;
|
||||
player.classList.add('player-shield');
|
||||
if (shieldTimeout) clearTimeout(shieldTimeout);
|
||||
shieldTimeout = setTimeout(() => {
|
||||
hasShield = false;
|
||||
if (player) player.classList.remove('player-shield');
|
||||
}, 5000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function checkCollision(bullet, enemy) {
|
||||
if (!bullet.parentNode || !enemy.parentNode) return;
|
||||
|
||||
if (bullet.offsetLeft < enemy.offsetLeft + enemy.offsetWidth &&
|
||||
bullet.offsetLeft + bullet.offsetWidth > enemy.offsetLeft &&
|
||||
bullet.offsetTop < enemy.offsetTop + enemy.offsetHeight &&
|
||||
bullet.offsetTop + bullet.offsetHeight > enemy.offsetTop) {
|
||||
|
||||
bullet.remove();
|
||||
enemy.remove();
|
||||
score++;
|
||||
scoreElement.innerText = score;
|
||||
|
||||
let health = parseInt(enemy.dataset.health);
|
||||
health--;
|
||||
enemy.dataset.health = health;
|
||||
|
||||
if (health <= 0) {
|
||||
score++;
|
||||
scoreElement.innerText = score;
|
||||
enemyDeathEffect(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkPlayerCollisions(){
|
||||
let checkPlayerColision = document.querySelectorAll(".enemy");
|
||||
checkPlayerColision.forEach(enemy => {
|
||||
checkCollisionEnemy(player, enemy);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function checkCollisionEnemy(player, enemy) {
|
||||
if (!player || !enemy.parentNode) return;
|
||||
|
||||
if (player.offsetLeft < enemy.offsetLeft + enemy.offsetWidth &&
|
||||
player.offsetLeft + player.offsetWidth > enemy.offsetLeft &&
|
||||
player.offsetTop < enemy.offsetTop + enemy.offsetHeight &&
|
||||
player.offsetTop + player.offsetHeight > enemy.offsetTop) {
|
||||
|
||||
enemy.remove();
|
||||
|
||||
// Shield protects from damage
|
||||
if (hasShield) {
|
||||
return;
|
||||
}
|
||||
|
||||
lives--;
|
||||
livesElement.innerText = lives;
|
||||
flashPlayer();
|
||||
|
||||
if (lives === 0) {
|
||||
if (lives <= 0) {
|
||||
endGame();
|
||||
} else {
|
||||
playSound("sounds/lostlife.mp3");
|
||||
|
|
@ -196,42 +723,96 @@ function checkCollisionEnemy(player, enemy) {
|
|||
}
|
||||
}
|
||||
|
||||
// Victory screen
|
||||
function victoryGame() {
|
||||
isGameRunning = false;
|
||||
clearAllIntervals();
|
||||
stopBackgroundMusic();
|
||||
|
||||
gameArea.style.display = 'none';
|
||||
if (gameVictory) {
|
||||
gameVictory.style.display = 'block';
|
||||
}
|
||||
stats.style.display = 'block';
|
||||
if (touchControls) touchControls.style.display = 'none';
|
||||
|
||||
const displaySec = sec < 10 ? "0" + sec : sec;
|
||||
if (victoryScoreElement) victoryScoreElement.innerText = score;
|
||||
if (victoryTimeElement) victoryTimeElement.innerText = 'Time survived: ' + min + ':' + displaySec;
|
||||
|
||||
const isNewRecord = updateHighScore();
|
||||
if (victoryHighScore) {
|
||||
victoryHighScore.innerText = isNewRecord ? 'NEW HIGH SCORE!' : 'High Score: ' + getHighScore();
|
||||
}
|
||||
|
||||
playSound("sounds/shoot.mp3"); // Victory sound
|
||||
|
||||
// Cleanup
|
||||
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
||||
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
||||
document.querySelectorAll(".boss").forEach(b => b.remove());
|
||||
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
||||
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
||||
}
|
||||
|
||||
// Setup End game state
|
||||
function endGame() {
|
||||
clearInterval(intervalTimer);
|
||||
isGameRunning = false;
|
||||
clearAllIntervals();
|
||||
stopBackgroundMusic();
|
||||
|
||||
gameArea.style.display = 'none';
|
||||
gameEnd.style.display = 'block';
|
||||
stats.style.display = 'block';
|
||||
if (touchControls) touchControls.style.display = 'none';
|
||||
|
||||
// stats on Game Over screen
|
||||
const displaySec = sec < 10 ? "0" + sec : sec;
|
||||
finalScoreElement.innerText = score;
|
||||
finalTimeElement.innerText = 'Time elapsed: ' + min + ':' + sec;
|
||||
finalTimeElement.innerText = 'Time elapsed: ' + min + ':' + displaySec;
|
||||
|
||||
const isNewRecord = updateHighScore();
|
||||
if (finalHighScore) {
|
||||
finalHighScore.innerText = isNewRecord ? 'NEW HIGH SCORE!' : 'High Score: ' + getHighScore();
|
||||
}
|
||||
|
||||
playSound("sounds/gameover.mp3");
|
||||
|
||||
// Remove enemys and bullets
|
||||
document.querySelectorAll(".enemy").forEach(enemy => enemy.remove());
|
||||
document.querySelectorAll(".bullet").forEach(bullet => bullet.remove());
|
||||
// Cleanup
|
||||
document.querySelectorAll(".enemy").forEach(e => e.remove());
|
||||
document.querySelectorAll(".bullet").forEach(b => b.remove());
|
||||
document.querySelectorAll(".boss").forEach(b => b.remove());
|
||||
document.querySelectorAll(".power-up").forEach(p => p.remove());
|
||||
document.querySelectorAll(".boss-health-bar").forEach(h => h.remove());
|
||||
}
|
||||
|
||||
// Restart game when the button restart is pressed
|
||||
// Restart game handlers
|
||||
restartButton.addEventListener('click', () => {
|
||||
gameEnd.style.display = 'none';
|
||||
gameArea.style.display = 'block';
|
||||
stats.style.display = 'block';
|
||||
sec = 0;
|
||||
min = 0;
|
||||
lives = 3;
|
||||
score = 0;
|
||||
scoreElement.innerText = score;
|
||||
livesElement.innerText = lives;
|
||||
clearInterval(intervalTimer); // clear time interval
|
||||
clearInterval(enemyInterval); // clear enemy interval
|
||||
document.querySelectorAll(".enemy").forEach(enemy => enemy.remove()); //make sure have no enemys and also fix the bug of have a ghost enemy after restart
|
||||
intervalTimer = setInterval(timeGame, 1000);
|
||||
//spawnEnemy();
|
||||
initGame(isHardcoreMode);
|
||||
});
|
||||
|
||||
// Start the initial function introPage() and startGame();
|
||||
if (victoryRestartButton) {
|
||||
victoryRestartButton.addEventListener('click', () => {
|
||||
initGame(isHardcoreMode);
|
||||
});
|
||||
}
|
||||
|
||||
// Resume button in pause menu
|
||||
const resumeButton = document.getElementById('resume-button');
|
||||
if (resumeButton) {
|
||||
resumeButton.addEventListener('click', () => {
|
||||
togglePause();
|
||||
});
|
||||
}
|
||||
|
||||
// Main menu button in pause
|
||||
const mainMenuButton = document.getElementById('main-menu-button');
|
||||
if (mainMenuButton) {
|
||||
mainMenuButton.addEventListener('click', () => {
|
||||
resetGame();
|
||||
introPage();
|
||||
});
|
||||
}
|
||||
|
||||
// Start the initial function
|
||||
introPage();
|
||||
startGame();
|
||||
|
|
|
|||
419
style.css
419
style.css
|
|
@ -1,6 +1,15 @@
|
|||
/* Base styles */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Verdana', sans-serif;
|
||||
}
|
||||
|
||||
/* Game Area */
|
||||
#gameArea {
|
||||
position: relative;
|
||||
/*width: 100vw;*/
|
||||
height: 100vh;
|
||||
background: radial-gradient(rgb(230, 231, 230), rgb(223, 226, 223));
|
||||
padding: 20px 0px;
|
||||
|
|
@ -9,11 +18,11 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
/* Intro Screen */
|
||||
#game-intro {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#game-intro::before {
|
||||
|
|
@ -32,9 +41,9 @@ body {
|
|||
|
||||
.game-intro p {
|
||||
font-size: 18px;
|
||||
font-family: "Verdana";
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
body button {
|
||||
font-size: 30px;
|
||||
background-color: #870007;
|
||||
|
|
@ -44,21 +53,99 @@ body button {
|
|||
box-shadow: 0;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, transform 0.1s;
|
||||
}
|
||||
|
||||
body button:hover {
|
||||
background-color: #a50009;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
body button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#hardcore-button {
|
||||
background-color: #2a0003;
|
||||
font-size: 24px;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
#hardcore-button:hover {
|
||||
background-color: #4a0005;
|
||||
}
|
||||
|
||||
/* High Score Display */
|
||||
#high-score-display {
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.controls-hint {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
padding: 5px 15px;
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Player */
|
||||
.player {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 5 0%;
|
||||
bottom: 50%;
|
||||
width: 75px;
|
||||
height: auto;
|
||||
transition: bottom 0.05s ease-out;
|
||||
}
|
||||
|
||||
/* Player hit animation */
|
||||
.player-hit {
|
||||
animation: playerHitFlash 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes playerHitFlash {
|
||||
0%, 100% { filter: none; }
|
||||
25% { filter: brightness(0.5) sepia(1) hue-rotate(-50deg) saturate(5); }
|
||||
50% { filter: brightness(2); }
|
||||
75% { filter: brightness(0.5) sepia(1) hue-rotate(-50deg) saturate(5); }
|
||||
}
|
||||
|
||||
/* Player shield effect */
|
||||
.player-shield {
|
||||
animation: shieldGlow 0.5s ease-in-out infinite alternate;
|
||||
filter: drop-shadow(0 0 10px #00bfff);
|
||||
}
|
||||
|
||||
@keyframes shieldGlow {
|
||||
0% { filter: drop-shadow(0 0 5px #00bfff); }
|
||||
100% { filter: drop-shadow(0 0 15px #00bfff) brightness(1.2); }
|
||||
}
|
||||
|
||||
/* Bullet */
|
||||
.bullet {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 5px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
/* Enemy Base Styles */
|
||||
.enemy {
|
||||
position: absolute;
|
||||
width: 75px;
|
||||
|
|
@ -67,3 +154,319 @@ body button {
|
|||
bottom: 50%;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* Enemy Types */
|
||||
.enemy-normal {
|
||||
/* Default zombie appearance */
|
||||
}
|
||||
|
||||
.enemy-fast {
|
||||
filter: hue-rotate(90deg) brightness(1.2);
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.enemy-tank {
|
||||
filter: hue-rotate(-30deg) saturate(1.5);
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
/* Enemy death animation */
|
||||
.enemy-death {
|
||||
animation: enemyDeath 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes enemyDeath {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: scaleX(-1) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scaleX(-1) scale(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* Boss */
|
||||
.boss {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: auto;
|
||||
right: 0;
|
||||
transform: scaleX(-1);
|
||||
filter: hue-rotate(180deg) saturate(2) brightness(0.8);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Boss Health Bar */
|
||||
.boss-health-bar {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 12px;
|
||||
background-color: #333;
|
||||
border: 2px solid #000;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.boss-health-fill {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, #ff4444, #aa0000);
|
||||
transition: width 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Power-ups */
|
||||
.power-up {
|
||||
position: absolute;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
animation: powerUpPulse 0.5s ease-in-out infinite alternate;
|
||||
z-index: 5;
|
||||
box-shadow: 0 0 15px rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
@keyframes powerUpPulse {
|
||||
0% { transform: scale(1); box-shadow: 0 0 10px currentColor; }
|
||||
100% { transform: scale(1.1); box-shadow: 0 0 20px currentColor; }
|
||||
}
|
||||
|
||||
.power-up-extraLife {
|
||||
background: linear-gradient(135deg, #00ff00, #008800);
|
||||
border: 3px solid #00cc00;
|
||||
}
|
||||
|
||||
.power-up-tripleShot {
|
||||
background: linear-gradient(135deg, #ffff00, #ccaa00);
|
||||
border: 3px solid #ffcc00;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.power-up-shield {
|
||||
background: linear-gradient(135deg, #00bfff, #0066cc);
|
||||
border: 3px solid #00aaff;
|
||||
}
|
||||
|
||||
/* Pause Overlay */
|
||||
#pause-overlay {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.pause-content {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pause-content h1 {
|
||||
font-size: 48px;
|
||||
margin-bottom: 30px;
|
||||
text-shadow: 2px 2px 4px #000;
|
||||
}
|
||||
|
||||
.pause-content button {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Game End Screen */
|
||||
#game-end {
|
||||
display: none;
|
||||
padding: 50px;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(to bottom, #1a0000, #4a0000);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#game-end h1 {
|
||||
font-size: 48px;
|
||||
color: #ff4444;
|
||||
text-shadow: 2px 2px 4px #000;
|
||||
}
|
||||
|
||||
#game-end p {
|
||||
font-size: 24px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
#final-high-score {
|
||||
color: #ffcc00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Victory Screen */
|
||||
#game-victory {
|
||||
display: none;
|
||||
padding: 50px;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(to bottom, #001a00, #004a00);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#game-victory h1 {
|
||||
font-size: 64px;
|
||||
color: #44ff44;
|
||||
text-shadow: 2px 2px 4px #000;
|
||||
animation: victoryPulse 1s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes victoryPulse {
|
||||
0% { transform: scale(1); }
|
||||
100% { transform: scale(1.05); text-shadow: 0 0 30px #44ff44; }
|
||||
}
|
||||
|
||||
#game-victory p {
|
||||
font-size: 24px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
#victory-high-score {
|
||||
color: #ffcc00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Volume Control */
|
||||
#volume-control {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 50;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#volume-control label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
#stats {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
text-align: left;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
#stats h2 {
|
||||
margin: 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Touch Controls */
|
||||
#touch-controls {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
z-index: 200;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.touch-move {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.touch-btn {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
background-color: rgba(135, 0, 7, 0.8);
|
||||
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||
border-radius: 10px;
|
||||
pointer-events: auto;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.touch-btn:active {
|
||||
background-color: rgba(200, 0, 10, 0.9);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.touch-shoot-btn {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 20px;
|
||||
border-radius: 50%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
body button {
|
||||
font-size: 24px;
|
||||
padding: 15px 30px;
|
||||
}
|
||||
|
||||
#hardcore-button {
|
||||
font-size: 18px;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.player {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.enemy {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.enemy-fast {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.enemy-tank {
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.boss {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
#stats h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#game-end h1,
|
||||
#game-victory h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
#game-end p,
|
||||
#game-victory p {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.pause-content h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue