From d3640e0a32347a47358bd59f6af6e29ee7bdbb20 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 8 Dec 2025 20:02:13 +0700 Subject: [PATCH] Revert back changes --- Main.html | 12 +- Script.js | 903 +++++++++++++++++++++++++++++++++++++++++------------- Style.css | 55 ++-- 3 files changed, 717 insertions(+), 253 deletions(-) diff --git a/Main.html b/Main.html index 6f7524d..eb97206 100644 --- a/Main.html +++ b/Main.html @@ -7,18 +7,10 @@ - -
-
SPACE GAME
- - - - -
-
- +
+ diff --git a/Script.js b/Script.js index 57dca38..c8e5acb 100644 --- a/Script.js +++ b/Script.js @@ -1,6 +1,6 @@ "use strict"; -/* ------------- AUDIO & BGM ------------- */ +// ================== BGM SYSTEM ================== const bgmList = [ { normal: "music/Scary.mp3", gameover: "music/ScaryGO.mp3" }, { normal: "music/Fear.mp3", gameover: "music/FearGO.mp3" }, @@ -18,15 +18,26 @@ function pickRandomBGM() { gameOverBGM.src = bgm.gameover; } -/* ------------- CANVAS / GAME VARS ------------- */ +// ================== GLOBALS ================== var canvasWidth = 1280; var canvasHeight = 650; -var c = undefined; -var ctx = undefined; +var c, ctx; +var gameStarted = false; +var musicMuted = false; let lastFrameTime = 0; const frameInterval = 1000 / 80; +let cameraY = 0; + +let respawnCounter = 0; +let damageFlash = 0; + +let currentWave = null; +let waveCooldown = 0; + +let abilityCharges = 0; + var game = { level: 1, speed: 1, @@ -35,10 +46,6 @@ var game = { timer: 0, }; -/* gameState: "menu" | "playing" | "paused" */ -let gameState = "menu"; - -/* ------------- INPUT STATE ------------- */ var keys = { up: false, down: false, @@ -47,28 +54,46 @@ var keys = { fire: false, }; -/* ------------- IMAGES & ASSETS ------------- */ var playerShipImg = new Image(); playerShipImg.src = "img/Player/pesawat22.png"; var bg0 = new Image(); bg0.src = "img/bg_0.png"; - var bg1 = new Image(); bg1.src = "img/bg_1.png"; - var bg2 = new Image(); bg2.src = "img/bg_2.png"; var enemyImgArray = []; enemyImgArray.length = 4; + +let audioStarted = false; + +window.addEventListener("keydown", () => { + if (!audioStarted) { + currentBGM.play().catch(() => {}); + audioStarted = true; + } +}); + +window.addEventListener("click", () => { + if (!audioStarted) { + currentBGM.play().catch(() => {}); + audioStarted = true; + } +}); + for (var i = 0; i < enemyImgArray.length; i++) { enemyImgArray[i] = new Image(); - enemyImgArray[i].src = "img/alien_" + i + ".png"; + enemyImgArray[i].src = "img/alien_" + [i] + ".png"; } var missilesArray = []; var enemyShipArray = []; +var enemyBulletsArray = []; +var explosions = []; +var abilityTokens = []; +var particles = []; var laser = document.createElement("audio"); laser.src = "music/laser2.mp3"; @@ -83,95 +108,39 @@ for (let i = 1; i <= 4; i++) { planetImages.push(img); } +let currentPlanet = null; + window.onload = function () { - setup(); + init(); }; -function setup() { + +// Init +function init() { c = document.getElementById("canvas"); ctx = c.getContext("2d"); - window.addEventListener( - "keydown", - function (e) { - const block = ["Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]; - if (block.includes(e.code)) e.preventDefault(); - }, - { passive: false } - ); + c.width = window.innerWidth; + c.height = window.innerHeight; + canvasWidth = c.width; + canvasHeight = c.height; document.addEventListener("keydown", keyDownPressed, false); document.addEventListener("keyup", keyUpPressed, false); - const startBtn = document.getElementById("startBtn"); - const costumeBtn = document.getElementById("costumeBtn"); - const optionBtn = document.getElementById("optionBtn"); - const exitBtn = document.getElementById("exitBtn"); - - if (startBtn) { - startBtn.onclick = function () { - startGame(); - }; - } - if (costumeBtn) { - costumeBtn.onclick = function () { - alert("Costume menu belum dibuat (kalau mau, aku bisa bikinin!)"); - }; - } - if (optionBtn) { - optionBtn.onclick = function () { - alert("Options menu belum dibuat."); - }; - } - if (exitBtn) { - exitBtn.onclick = function () { - alert("Terima kasih telah bermain!"); - }; - } - - clearGame(); -} - -let audioStarted = false; -function startGame() { - const menu = document.getElementById("startMenu"); - if (menu) menu.style.display = "none"; - - game.gameOver = false; - game.frames = 0; - game.timer = 0; - player1.x = 100; - player1.y = canvasHeight / 2 - player1.height / 2; - player1.vx = 0; - player1.vy = 0; - player1.lives = 3; - player1.score = 0; - enemyShipArray.length = 0; - missilesArray.length = 0; - + gameStarted = true; pickRandomBGM(); currentBGM.volume = 1; - currentBGM.play().catch(() => { - }); - audioStarted = true; - gameState = "playing"; requestAnimationFrame(gameLoop); } -let gameStarted = false; function gameLoop(timestamp) { - if (gameState !== "playing") { - return; - } - - if (!gameStarted) { - gameStarted = true; - lastFrameTime = timestamp; - } + if (!gameStarted) return; if (game.gameOver) { + clearGame(); drawGameOver(); return; } @@ -179,98 +148,149 @@ function gameLoop(timestamp) { if (timestamp - lastFrameTime >= frameInterval) { lastFrameTime = timestamp; - clearGame(); - updateGame(); - drawGame(); + if (!gamePaused) { + clearGame(); + updateGame(); + drawGame(); + } else { + drawPauseOverlay(); + } } requestAnimationFrame(gameLoop); } +// ================== INPUT ================== +let gamePaused = false; + function keyDownPressed(e) { + if (e.keyCode === 87 || e.keyCode === 38) keys.up = true; + else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true; + if (e.keyCode === 65 || e.keyCode === 37) keys.left = true; + if (e.keyCode === 68 || e.keyCode === 39) keys.right = true; - if (gameState !== "playing") return; - - if (e.keyCode == 87 || e.keyCode == 38) { - keys.up = true; - } else if (e.keyCode == 83 || e.keyCode == 40) { - keys.down = true; - } - - if (e.keyCode == 65 || e.keyCode == 37) { - keys.left = true; - } - - if (e.keyCode == 68 || e.keyCode == 39) { - keys.right = true; - } - - if (e.keyCode == 32) { - + if (e.keyCode === 32) { keys.fire = true; - missilesArray.push( - new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2) - ); + if (!player1.dead) { + fireBullet(); + } + } - laser.currentTime = 0; - laser.volume = 0.4; - laser.play(); + if (e.keyCode === 80) togglePause(); + + if (e.keyCode === 16) { + if (abilityCharges > 0 && !game.gameOver && !gamePaused) { + useAbility(); + abilityCharges--; + } } } function keyUpPressed(e) { - if (gameState !== "playing") return; - - if (e.keyCode == 87 || e.keyCode == 38) { - keys.up = false; - } else if (e.keyCode == 83 || e.keyCode == 40) { - keys.down = false; - } - - if (e.keyCode == 65 || e.keyCode == 37) { - keys.left = false; - } - - if (e.keyCode == 68 || e.keyCode == 39) { - keys.right = false; - } - - if (e.keyCode == 32) { - keys.fire = false; - } + if (e.keyCode === 87 || e.keyCode === 38) keys.up = false; + else if (e.keyCode === 83 || e.keyCode === 40) keys.down = false; + if (e.keyCode === 65 || e.keyCode === 37) keys.left = false; + if (e.keyCode === 68 || e.keyCode === 39) keys.right = false; + if (e.keyCode === 32) keys.fire = false; } -/* ------------- RENDER / UPDATE ------------- */ +function fireBullet() { + missilesArray.push( + new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2) + ); + laser.currentTime = 0; + laser.volume = 0.4; + laser.play(); + createParticles( + player1.x + player1.width, + player1.y + player1.height / 2, + 5, + "#00e1ff" + ); +} + +// ================== CORE UPDATE / DRAW ================== function clearGame() { - // clear and draw solid background so canvas never transparent ctx.clearRect(0, 0, canvasWidth, canvasHeight); - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); } function updateGame() { - // Only update while playing (should already be true) - if (gameState !== "playing") return; + game.frames++; + + updateStarField(); - addStarField(); addShips(); + maybeSpawnAbilityToken(); - player1.update(); + // Auto-fire when holding space + if (keys.fire && !player1.dead && game.frames % 8 === 0) { + fireBullet(); + } - if (player1.invincible > 0) player1.invincible--; + // Player update / respawn + if (!player1.dead) { + player1.update(); + if (player1.invincible > 0) player1.invincible--; + updateCamera(); + } else { + if (respawnCounter > 0) { + respawnCounter--; + if (respawnCounter <= 0 && player1.lives > 0) { + player1.dead = false; + player1.invincible = 120; + player1.x = 100; + player1.y = canvasHeight / 2 - player1.height / 2; + player1.vx = 0; + player1.vy = 0; + } + } + } spawnPlanet(); if (currentPlanet) currentPlanet.update(); - game.frames++; + + updateParticles(); } function drawGame() { + ctx.save(); + ctx.translate(0, cameraY); + + drawStarField(); + if (currentPlanet) currentPlanet.draw(); - player1.draw(); + // Particles (background layer) + drawParticles(); - for (var i = 0; i < enemyShipArray.length; i++) { - var s = enemyShipArray[i]; + // Ability tokens + for (let i = 0; i < abilityTokens.length; i++) { + const t = abilityTokens[i]; + t.draw(); + t.update(); + + if (!player1.dead && Tabrakan(player1.getHitbox(), t)) { + abilityCharges++; + abilityTokens.splice(i, 1); + createParticles(t.x, t.y, 15, "#00ffea"); + i--; + continue; + } + + if (t.x + t.width < 0) { + abilityTokens.splice(i, 1); + i--; + } + } + + // Player + if (!player1.dead) { + player1.draw(); + } + + // Enemies + for (let i = 0; i < enemyShipArray.length; i++) { + let s = enemyShipArray[i]; s.draw(); s.update(); @@ -280,62 +300,136 @@ function drawGame() { continue; } - if (Tabrakan(player1, s)) { - if (player1.invincible <= 0) { - player1.lives--; - player1.invincible = 60; - explosion_enemy.play(); + // Enemy shooting (aimed, non-homing) + if (!player1.dead && Math.random() < 0.01) { + const ex = s.x; + const ey = s.y + s.height / 2; + const px = player1.x + player1.width / 2; + const py = player1.y + player1.height / 2; + enemyBulletsArray.push(new EnemyBullet(ex, ey, px, py)); + } - player1.x = 100; - player1.y = canvasHeight / 2 - player1.height / 2; - player1.vx = 0; - player1.vy = 0; - game.frames = 0; - - enemyShipArray.splice(i, 1); - i--; - - if (player1.lives <= 0) { - game.gameOver = true; - crossfadeToGameOver(); - } - continue; - } + // Collision with player (hitbox vs hitbox) + if (!player1.dead && Tabrakan(player1.getHitbox(), s.getHitbox())) { + explosions.push( + new Explosion(s.x + s.width / 2, s.y + s.height / 2) + ); + createParticles( + s.x + s.width / 2, + s.y + s.height / 2, + 20, + "#ff6600" + ); + enemyShipArray.splice(i, 1); + i--; + handlePlayerHit(); + continue; } } - for (var i = 0; i < missilesArray.length; i++) { - var m = missilesArray[i]; + // Player bullets + for (let i = 0; i < missilesArray.length; i++) { + let m = missilesArray[i]; m.draw(); m.update(); let hit = false; - for (var j = 0; j < enemyShipArray.length; j++) { - var en = enemyShipArray[j]; + for (let j = 0; j < enemyShipArray.length; j++) { + let en = enemyShipArray[j]; - if (Tabrakan(m, en)) { + if (Tabrakan(m, en.getHitbox())) { player1.score += 100; + explosion_enemy.currentTime = 0; explosion_enemy.play(); - - enemyShipArray.splice(j, 1); + explosions.push( + new Explosion(en.x + en.width / 2, en.y + en.height / 2) + ); + createParticles( + en.x + en.width / 2, + en.y + en.height / 2, + 15, + "#ff9900" + ); missilesArray.splice(i, 1); - - i--; + enemyShipArray.splice(j, 1); hit = true; break; } } - if (hit) continue; + if (hit) { + i--; + continue; + } - if (m.x > canvasWidth) { + if (m.x > canvasWidth + 50) { missilesArray.splice(i, 1); i--; } } - drawNewText("Score: " + player1.score, 30, 610, "white"); - drawNewText("Player Lives: " + player1.lives, 1100, 610, "white"); + // Enemy bullets + for (let i = 0; i < enemyBulletsArray.length; i++) { + let b = enemyBulletsArray[i]; + b.draw(); + b.update(); + + if (!player1.dead && Tabrakan(b, player1.getHitbox())) { + explosions.push( + new Explosion( + player1.x + player1.width / 2, + player1.y + player1.height / 2 + ) + ); + createParticles( + player1.x + player1.width / 2, + player1.y + player1.height / 2, + 12, + "#ff3300" + ); + enemyBulletsArray.splice(i, 1); + i--; + handlePlayerHit(); + continue; + } + + if ( + b.x + b.width < -100 || + b.x > canvasWidth + 100 || + b.y + b.height < -100 || + b.y > canvasHeight + 100 + ) { + enemyBulletsArray.splice(i, 1); + i--; + } + } + + // Explosions + for (let i = 0; i < explosions.length; i++) { + let ex = explosions[i]; + ex.draw(); + ex.update(); + if (ex.done) { + explosions.splice(i, 1); + i--; + } + } + + ctx.restore(); + + drawScreenShading(); + drawUI(); +} + +function drawUI() { + drawNewText(" " + player1.score, 1400, 760, "white"); + + let livesText = "Lives: "; + for (let i = 0; i < player1.lives; i++) { + livesText += "♥ "; + } + drawNewText(livesText, 60, 760, "#ff3366"); + drawNewText("Bombs: " + abilityCharges, 50, 40, "#ffffffff"); } class PlayerObject { @@ -356,6 +450,7 @@ class PlayerObject { this.score = 0; this.health = 100; this.invincible = 0; + this.dead = false; this.totalFrames = 5; this.frameIndex = 2; @@ -372,13 +467,22 @@ class PlayerObject { }; } - draw() { - ctx.save(); + getHitbox() { + const w = this.width * 0.45; + const h = this.height * 0.55; + const x = this.x + (this.width - w) / 2; + const y = this.y + (this.height - h) / 2; + return { x, y, width: w, height: h }; + } - if (this.invincible > 0 && this.invincible % 4 < 2) { - ctx.globalAlpha = 0.5; + draw() { + // Invi frames + if (this.invincible > 0 && game.frames % 10 < 5) { + return; } + ctx.save(); + if (this.spriteWidth > 0) { ctx.drawImage( this.image, @@ -400,22 +504,21 @@ class PlayerObject { } update() { - if (keys.up) { - this.vy -= this.acceleration; - } - if (keys.down) { - this.vy += this.acceleration; - } - if (keys.left) { - this.vx -= this.acceleration; - } - if (keys.right) { - this.vx += this.acceleration; - } + if (keys.up) this.vy -= this.acceleration; + if (keys.down) this.vy += this.acceleration; + if (keys.left) this.vx -= this.acceleration; + if (keys.right) this.vx += this.acceleration; this.vx *= this.friction; this.vy *= this.friction; + const speed = Math.sqrt(this.vx * this.vx + this.vy * this.vy); + if (speed > this.maxSpeed) { + const scale = this.maxSpeed / speed; + this.vx *= scale; + this.vy *= scale; + } + this.x += this.vx; this.y += this.vy; @@ -456,9 +559,30 @@ class PlayerObject { let player1 = new PlayerObject(100, 300); +function handlePlayerHit() { + if (player1.invincible > 0 || player1.dead || game.gameOver) return; + + explosion_enemy.currentTime = 0; + explosion_enemy.play(); + damageFlash = 20; + + player1.lives--; + + if (player1.lives <= 0) { + game.gameOver = true; + crossfadeToGameOver(); + return; + } + + player1.dead = true; + respawnCounter = 80 * 3; +} + +// ================== TEXT ================== function drawNewText(txt, x, y, color) { ctx.font = "20px Arial"; ctx.fillStyle = color; + ctx.textAlign = "left"; ctx.fillText(txt, x, y); } @@ -479,7 +603,6 @@ class backgroundObj { update() { this.x -= this.speed; - if (this.x < -2000) { this.x = 2000; } @@ -488,30 +611,42 @@ class backgroundObj { let background1 = new backgroundObj(bg0, 0, 0, game.speed * 3); let background1a = new backgroundObj(bg0, 2000, 0, game.speed * 3); - let background2 = new backgroundObj(bg1, 0, 0, game.speed * 2); let background2a = new backgroundObj(bg1, 2000, 0, game.speed * 2); - let background3 = new backgroundObj(bg2, 0, 0, game.speed * 1); let background3a = new backgroundObj(bg2, 2000, 0, game.speed * 1); -function addStarField() { - background3.draw(); +function updateStarField() { background3.update(); - background3a.draw(); background3a.update(); - - background2.draw(); background2.update(); - background2a.draw(); background2a.update(); - - background1.draw(); background1.update(); - background1a.draw(); background1a.update(); } +function drawStarField() { + background3.draw(); + background3a.draw(); + background2.draw(); + background2a.draw(); + background1.draw(); + background1a.draw(); +} + +// panning +function updateCamera() { + const offset = player1.y + player1.height / 2 - canvasHeight / 2; + const target = -offset * 0.7; + const bgHeight = 1200; + const minY = canvasHeight - bgHeight; + const maxY = 0; + + const clamped = Math.max(minY, Math.min(maxY, target)); + cameraY += (clamped - cameraY) * 0.1; +} + +// class LaserBullet { constructor(x, y) { this.x = x; @@ -543,8 +678,9 @@ class LaserBullet { } } +// class EnemyObj { - constructor(x, y, speed, img) { + constructor(x, y, speed, img, pattern = "straight") { this.x = x; this.y = y; this.width = 170; @@ -553,7 +689,18 @@ class EnemyObj { this.speed = speed; this.health = 100; this.damage = 10; + this.pattern = pattern; + this.angle = 0; } + + getHitbox() { + const w = this.width * 0.65; + const h = this.height * 0.65; + const x = this.x + (this.width - w) / 2; + const y = this.y + (this.height - h) / 2; + return { x, y, width: w, height: h }; + } + draw() { ctx.save(); ctx.drawImage(this.image, this.x, this.y, this.width, this.height); @@ -562,10 +709,51 @@ class EnemyObj { update() { this.x -= this.speed; + if (this.pattern === "sine") { + this.angle += 0.05 * this.speed; + this.y += Math.sin(this.angle) * 3; + } } } -let enemy = new EnemyObj(800, 200, 12, enemyImgArray[0]); +class EnemyBullet { + constructor(x, y, targetX, targetY) { + this.x = x; + this.y = y; + this.width = 10; + this.height = 4; + + const dx = targetX - x; + const dy = targetY - y; + const len = Math.sqrt(dx * dx + dy * dy) || 1; + const speed = 8; + + this.vx = (dx / len) * speed; + this.vy = (dy / len) * speed; + } + + draw() { + let g = ctx.createLinearGradient( + this.x + this.width, + this.y, + this.x, + this.y + ); + g.addColorStop(0, "#ff9900"); + g.addColorStop(0.5, "#ffffff"); + g.addColorStop(1, "#ff3300"); + ctx.fillStyle = g; + ctx.shadowColor = "#ff6600"; + ctx.shadowBlur = 10; + ctx.fillRect(this.x, this.y, this.width, this.height); + ctx.shadowBlur = 0; + } + + update() { + this.x += this.vx; + this.y += this.vy; + } +} class Planet { constructor(img) { @@ -590,8 +778,6 @@ class Planet { } } -let currentPlanet = null; - function spawnPlanet() { if (currentPlanet == null || currentPlanet.active === false) { let randomImg = @@ -601,18 +787,200 @@ function spawnPlanet() { } function addShips() { - if (game.frames > 200) { - if (game.frames % 150 == 0) { - var randomY = Math.floor(Math.random() * 500) + 20; - var randomSpeed = Math.floor(Math.random() * 10) + 1; - var randomShip = Math.floor(Math.random() * enemyImgArray.length); - enemyShipArray.push( - new EnemyObj(1300, randomY, randomSpeed, enemyImgArray[randomShip]) - ); + if (game.frames < 200) return; + + if (currentWave) { + if (currentWave.spawned < currentWave.count) { + if (currentWave.spawnTimer <= 0) { + spawnEnemyFromWave(currentWave); + currentWave.spawned++; + currentWave.spawnTimer = currentWave.spacing; + } else { + currentWave.spawnTimer--; + } + } else { + if (enemyShipArray.length === 0) { + currentWave = null; + waveCooldown = 120; + game.level++; + game.speed += 0.1; + } + } + } else { + if (waveCooldown > 0) { + waveCooldown--; + } else { + startNewWave(); } } } +function startNewWave() { + const patterns = ["line", "v", "sine"]; + const pattern = patterns[Math.floor(Math.random() * patterns.length)]; + const count = 4 + Math.floor(Math.random() * 4); + const spacing = 30 + Math.floor(Math.random() * 20); + + currentWave = { + pattern: pattern, + count: count, + spacing: spacing, + spawned: 0, + spawnTimer: 0, + }; +} + +function spawnEnemyFromWave(wave) { + const baseY = canvasHeight / 2; + const spread = 200; + const index = wave.spawned; + let y = Math.random() * (canvasHeight - 120) + 60; + + if (wave.pattern === "line") { + const step = (spread * 2) / Math.max(1, wave.count - 1); + y = baseY - spread + step * index; + } else if (wave.pattern === "v") { + const centerIndex = (wave.count - 1) / 2; + const offset = (index - centerIndex) * 40; + y = baseY + Math.abs(offset) * 1.5; + } else if (wave.pattern === "sine") { + const angle = (index / wave.count) * Math.PI * 2; + y = baseY + Math.sin(angle) * spread; + } + + y = Math.max(40, Math.min(canvasHeight - 140, y)); + + const randomShip = Math.floor(Math.random() * enemyImgArray.length); + const speed = 5 + Math.random() * 4; + + enemyShipArray.push( + new EnemyObj( + canvasWidth + 50, + y, + speed + game.speed, + enemyImgArray[randomShip], + wave.pattern === "sine" ? "sine" : "straight" + ) + ); +} + +// bombs +class AbilityToken { + constructor(x, y) { + this.x = x; + this.y = y; + this.width = 32; + this.height = 32; + this.speed = 4; + } + + draw() { + ctx.save(); + ctx.beginPath(); + const cx = this.x + this.width / 2; + const cy = this.y + this.height / 2; + ctx.arc(cx, cy, 12, 0, Math.PI * 2); + const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, 12); + g.addColorStop(0, "#ffffff"); + g.addColorStop(0.5, "#00ffea"); + g.addColorStop(1, "#0066ff"); + ctx.fillStyle = g; + ctx.fill(); + ctx.restore(); + } + + update() { + this.x -= this.speed; + } +} + +function maybeSpawnAbilityToken() { + if (Math.random() < 0.002 && abilityTokens.length < 3) { + const y = Math.random() * (canvasHeight - 120) + 60; + abilityTokens.push(new AbilityToken(canvasWidth + 40, y)); + } +} + +function useAbility() { + if (enemyShipArray.length === 0 && enemyBulletsArray.length === 0) return; + + explosion_enemy.currentTime = 0; + explosion_enemy.play(); + + // Explosions for all enemies + enemyShipArray.forEach((e) => { + explosions.push(new Explosion(e.x + e.width / 2, e.y + e.height / 2)); + createParticles( + e.x + e.width / 2, + e.y + e.height / 2, + 20, + "#ff9900" + ); + }); + + enemyShipArray = []; + enemyBulletsArray = []; + missilesArray = []; + + damageFlash = 10; +} + +// ================== PARTICLES ================== +class Particle { + constructor(x, y, color) { + this.x = x; + this.y = y; + this.vx = (Math.random() - 0.5) * 8; + this.vy = (Math.random() - 0.5) * 8; + this.life = 30; + this.maxLife = 30; + this.color = color; + this.size = Math.random() * 3 + 2; + } + + update() { + this.x += this.vx; + this.y += this.vy; + this.vx *= 0.95; + this.vy *= 0.95; + this.life--; + } + + draw() { + const alpha = this.life / this.maxLife; + ctx.save(); + ctx.globalAlpha = alpha; + ctx.fillStyle = this.color; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + } + + get isDead() { + return this.life <= 0; + } +} + +function createParticles(x, y, count, color) { + for (let i = 0; i < count; i++) { + particles.push(new Particle(x, y, color)); + } +} + +function updateParticles() { + for (let i = particles.length - 1; i >= 0; i--) { + particles[i].update(); + if (particles[i].isDead) { + particles.splice(i, 1); + } + } +} + +function drawParticles() { + particles.forEach((p) => p.draw()); +} + function Tabrakan(o, p) { if ( o.x + o.width > p.x && @@ -625,8 +993,51 @@ function Tabrakan(o, p) { return false; } +class Explosion { + constructor(x, y, scale = 1) { + this.x = x; + this.y = y; + this.frame = 0; + this.maxFrames = 30; + this.scale = scale; + } + + update() { + this.frame++; + } + + draw() { + let progress = this.frame / this.maxFrames; + let radius = (20 + 60 * progress) * this.scale; + ctx.save(); + ctx.globalAlpha = 1 - progress; + let gradient = ctx.createRadialGradient( + this.x, + this.y, + 0, + this.x, + this.y, + radius + ); + gradient.addColorStop(0, "#ffffff"); + gradient.addColorStop(0.2, "#ffe066"); + gradient.addColorStop(0.5, "#ff8c42"); + gradient.addColorStop(1, "#ff0000"); + ctx.fillStyle = gradient; + ctx.beginPath(); + ctx.arc(this.x, this.y, radius, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + } + + get done() { + return this.frame >= this.maxFrames; + } +} + +// GAME OVER / PAUSE function drawGameOver() { - ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillStyle = "rgba(53, 0, 0, 0.7)"; ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.font = "80px Arial"; @@ -636,7 +1047,69 @@ function drawGameOver() { ctx.font = "40px Arial"; ctx.fillStyle = "white"; - ctx.fillText("Refresh to Restart", canvasWidth / 2, canvasHeight / 2 + 30); + ctx.fillText( + "Final Score: " + player1.score, + canvasWidth / 2, + canvasHeight / 2 + 20 + ); + ctx.fillText( + "Refresh to Restart", + canvasWidth / 2, + canvasHeight / 2 + 70 + ); + + ctx.textAlign = "left"; +} + +function drawPauseOverlay() { + ctx.fillStyle = "rgba(0,0,0,0.5)"; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + ctx.font = "60px Arial"; + ctx.fillStyle = "white"; + ctx.textAlign = "center"; + ctx.fillText("PAUSED", canvasWidth / 2, canvasHeight / 2); + ctx.font = "24px Arial"; + ctx.fillText( + "Press P to Resume", + canvasWidth / 2, + canvasHeight / 2 + 50 + ); + ctx.textAlign = "left"; +} + +function drawScreenShading() { + let grd = ctx.createRadialGradient( + canvasWidth / 2, + canvasHeight / 2, + 200, + canvasWidth / 2, + canvasHeight / 2, + canvasWidth + ); + grd.addColorStop(0, "rgba(0,0,0,0)"); + grd.addColorStop(1, "rgba(0,0,0,0.6)"); + ctx.fillStyle = grd; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + + if (damageFlash > 0) { + let alpha = (damageFlash / 20) * 0.6; + ctx.fillStyle = "rgba(255,0,0," + alpha.toFixed(2) + ")"; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + damageFlash--; + } +} + +function togglePause() { + if (game.gameOver || !gameStarted) return; + gamePaused = !gamePaused; + + if (gamePaused) { + currentBGM.pause(); + } else if (!musicMuted && audioStarted) { + currentBGM.play().catch(() => {}); + lastFrameTime = performance.now ? performance.now() : Date.now(); + } } function crossfadeToGameOver() { diff --git a/Style.css b/Style.css index 37983ef..c224380 100644 --- a/Style.css +++ b/Style.css @@ -1,48 +1,49 @@ body { - margin: 0px; - padding: 0px; + margin: 0; + padding: 0; background: radial-gradient(circle at center, - rgb(255, 255, 255) 0%, - rgb(255, 150, 50) 20%, - rgb(255, 40, 0) 35%, - rgb(110, 0, 150) 55%, - rgb(5, 10, 35) 100% - ) + rgb(255, 255, 255) 0%, + rgb(255, 150, 50) 20%, + rgb(255, 40, 0) 35%, + rgb(110, 0, 150) 55%, + rgb(5, 10, 35) 100% + ); +} + +html, body { + height: 100%; + overflow: hidden; } #game { - margin: 3px auto 0px auto; - padding: 0px; - width: 1280px; - height: 650px; + margin: 0; + padding: 0; + width: 100vw; + height: 100vh; } #canvas { - width: 1280px; - height: 650px; - margin: 0px; - background-color: rgb(0, 0, 0); + width: 100%; + height: 100%; + margin: 0; + background-color: black; + display: block; } - #startMenu { position: absolute; - width: 1280px; - height: 650px; + width: 100vw; + height: 100vh; top: 0; left: 0; - background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(6px); - display: flex; flex-direction: column; justify-content: center; align-items: center; - z-index: 10; font-family: 'Poppins', Arial, sans-serif; - animation: fadeIn 1s ease-out; } @@ -53,11 +54,10 @@ body { .title { font-size: 90px; - margin-bottom: 20px; + margin-bottom: 40px; color: #ffffff; letter-spacing: 4px; font-weight: 900; - text-shadow: 2px 2px 0px #00bcd4, 4px 4px 0px #009bb0, @@ -65,7 +65,6 @@ body { 8px 8px 0px #005a68, 10px 10px 12px rgba(0, 234, 255, 0.7), 0 0 25px #00eaff; - animation: pixelGlow 2s infinite alternate ease-in-out; } @@ -103,7 +102,7 @@ body { 0 4px 0 #00bcd4, 0 0 12px rgba(0, 234, 255, 0.4), inset 0 0 15px rgba(0, 234, 255, 0.2); - transition: 0.3ease-in-out; + transition: 0.3s ease-in-out; } .menuButton:hover { @@ -113,4 +112,4 @@ body { box-shadow: 0 10px 20px rgba(0, 234, 255, 0.6), 0 0 30px #00eaff; -} \ No newline at end of file +}