diff --git a/Script.js b/Script.js index 182be80..ea37334 100644 --- a/Script.js +++ b/Script.js @@ -1,8 +1,7 @@ "use strict"; -const DEBUG_HITBOX = true; +const DEBUG_HITBOX = false; -// ================== BGM SYSTEM ================== const bgmList = [ { normal: "music/Scary.mp3", gameover: "music/ScaryGO.mp3" }, { normal: "music/Fear.mp3", gameover: "music/FearGO.mp3" }, @@ -20,7 +19,6 @@ function pickRandomBGM() { gameOverBGM.src = bgm.gameover; } -// ================== GLOBALS ================== var canvasWidth = 1280; var canvasHeight = 650; var c, ctx; @@ -116,7 +114,6 @@ window.onload = function () { init(); }; -// Init function init() { c = document.getElementById("canvas"); ctx = c.getContext("2d"); @@ -160,7 +157,6 @@ function gameLoop(timestamp) { requestAnimationFrame(gameLoop); } -// ================== INPUT ================== let gamePaused = false; function keyDownPressed(e) { @@ -209,7 +205,6 @@ function fireBullet() { ); } -// ================== CORE UPDATE / DRAW ================== function clearGame() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); } @@ -217,17 +212,19 @@ function clearGame() { function updateGame() { game.frames++; + + game.level = 1 + Math.floor(player1.score / 500); + game.speed = 1 + game.level * 0.1; + updateStarField(); addShips(); maybeSpawnAbilityToken(); - // Auto-fire when holding space if (keys.fire && !player1.dead && game.frames % 8 === 0) { fireBullet(); } - // Player update / respawn if (!player1.dead) { player1.update(); if (player1.invincible > 0) player1.invincible--; @@ -260,17 +257,13 @@ function drawGame() { if (currentPlanet) currentPlanet.draw(); - // Particles (background layer) drawParticles(); - // Ability tokens for (let i = 0; i < abilityTokens.length; i++) { const t = abilityTokens[i]; t.draw(); t.update(); - // Hitbox token sederhana (tidak perlu .getHitbox karena bentuknya simple) - // Tapi kita pakai logika bounding box manual di sini if ( !player1.dead && Tabrakan(player1.getHitbox(), { @@ -293,14 +286,11 @@ function drawGame() { } } - // Player if (!player1.dead) { player1.draw(); - // DEBUG: Lihat Hitbox Player if (DEBUG_HITBOX) drawDebugHitbox(player1.getHitbox(), "lime"); } - // Enemies for (let i = 0; i < enemyShipArray.length; i++) { let s = enemyShipArray[i]; s.draw(); @@ -314,16 +304,17 @@ function drawGame() { continue; } - // Enemy shooting (aimed, non-homing) - if (!player1.dead && Math.random() < 0.01 && s.x > player1.x + 50) { - 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)); - } + let shootChance = 0.02 + game.level * 0.003; + if (shootChance > 0.1) shootChance = 0.1; + + if (!player1.dead && Math.random() < shootChance && s.x > player1.x + 50) { + 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)); + } - // 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"); @@ -334,7 +325,6 @@ function drawGame() { } } - // Player bullets for (let i = 0; i < missilesArray.length; i++) { let m = missilesArray[i]; m.draw(); @@ -346,23 +336,26 @@ function drawGame() { for (let j = 0; j < enemyShipArray.length; j++) { let en = enemyShipArray[j]; - // PERBAIKAN: Gunakan .getHitbox() untuk kedua objek if (Tabrakan(m.getHitbox(), en.getHitbox())) { - player1.score += 100; - explosion_enemy.currentTime = 0; - explosion_enemy.play(); - explosions.push( - new Explosion(en.x + en.width / 2, en.y + en.height / 2) - ); + en.health -= 100; createParticles( en.x + en.width / 2, en.y + en.height / 2, - 15, + 5, "#ff9900" ); missilesArray.splice(i, 1); - enemyShipArray.splice(j, 1); hit = true; + + if (en.health <= 0) { + player1.score += 100 + game.level * 10; + explosion_enemy.currentTime = 0; + explosion_enemy.play(); + explosions.push( + new Explosion(en.x + en.width / 2, en.y + en.height / 2) + ); + enemyShipArray.splice(j, 1); + } break; } } @@ -378,7 +371,6 @@ function drawGame() { } } - // Enemy bullets for (let i = 0; i < enemyBulletsArray.length; i++) { let b = enemyBulletsArray[i]; b.draw(); @@ -386,7 +378,6 @@ function drawGame() { if (DEBUG_HITBOX) drawDebugHitbox(b.getHitbox(), "orange"); - // PERBAIKAN: Gunakan b.getHitbox() agar collision lebih akurat (tidak kena glow) if (!player1.dead && Tabrakan(b.getHitbox(), player1.getHitbox())) { explosions.push( new Explosion( @@ -417,7 +408,6 @@ function drawGame() { } } - // Explosions for (let i = 0; i < explosions.length; i++) { let ex = explosions[i]; ex.draw(); @@ -444,6 +434,7 @@ function drawDebugHitbox(rect, color) { function drawUI() { drawNewText(" " + player1.score, 1400, 760, "white"); + drawNewText("LVL " + game.level, 1400, 50, "#00ff00"); let livesText = "Lives: "; for (let i = 0; i < player1.lives; i++) { @@ -488,7 +479,6 @@ class PlayerObject { }; } - getHitbox() { const h = this.height * 0.05; @@ -601,7 +591,6 @@ function handlePlayerHit() { respawnCounter = 80 * 3; } -// ================== TEXT ================== function drawNewText(txt, x, y, color) { ctx.font = "20px Arial"; ctx.fillStyle = color; @@ -657,7 +646,6 @@ function drawStarField() { background1a.draw(); } -// panning function updateCamera() { const offset = player1.y + player1.height / 2 - canvasHeight / 2; const target = -offset * 0.7; @@ -669,7 +657,6 @@ function updateCamera() { cameraY += (clamped - cameraY) * 0.1; } -// class LaserBullet { constructor(x, y) { this.x = x; @@ -705,7 +692,6 @@ class LaserBullet { } } -// class EnemyObj { constructor(x, y, speed, img, pattern = "straight") { this.x = x; @@ -720,7 +706,6 @@ class EnemyObj { this.angle = 0; } - getHitbox() { const w = this.width * 0.55; const h = this.height * 0.55; @@ -760,7 +745,6 @@ class EnemyBullet { this.vy = (dy / len) * speed; } - getHitbox() { const padding = 1; return { @@ -840,9 +824,7 @@ function addShips() { } else { if (enemyShipArray.length === 0) { currentWave = null; - waveCooldown = 120; - game.level++; - game.speed += 0.1; + waveCooldown = Math.max(60, 120 - game.level * 2); } } } else { @@ -855,10 +837,16 @@ function addShips() { } function startNewWave() { - const patterns = ["line", "v", "sine"]; + const patterns = ["line", "v", "sine", "scatter"]; 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); + + let baseCount = 3; + let scalingCount = Math.floor(game.level / 2); + let count = Math.min( + 15, + baseCount + scalingCount + Math.floor(Math.random() * 3) + ); + let spacing = Math.max(20, 50 - game.level); currentWave = { pattern: pattern, @@ -871,7 +859,7 @@ function startNewWave() { function spawnEnemyFromWave(wave) { const baseY = canvasHeight / 2; - const spread = 200; + const spread = 250; const index = wave.spawned; let y = Math.random() * (canvasHeight - 120) + 60; @@ -880,30 +868,32 @@ function spawnEnemyFromWave(wave) { 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; + const offset = (index - centerIndex) * 50; + y = baseY + Math.abs(offset) * 1.2; } else if (wave.pattern === "sine") { const angle = (index / wave.count) * Math.PI * 2; y = baseY + Math.sin(angle) * spread; + } else if (wave.pattern === "scatter") { + y = Math.random() * (canvasHeight - 150) + 75; } - y = Math.max(40, Math.min(canvasHeight - 140, y)); + y = Math.max(60, Math.min(canvasHeight - 100, y)); const randomShip = Math.floor(Math.random() * enemyImgArray.length); - const speed = 5 + Math.random() * 4; + const speed = 4 + Math.random() * 3 + game.level * 0.25; - enemyShipArray.push( - new EnemyObj( - canvasWidth + 50, - y, - speed + game.speed, - enemyImgArray[randomShip], - wave.pattern === "sine" ? "sine" : "straight" - ) + let enemy = new EnemyObj( + canvasWidth + 50, + y, + speed, + enemyImgArray[randomShip], + wave.pattern === "sine" ? "sine" : "straight" ); + + enemy.health = 100 + game.level * 25; + enemyShipArray.push(enemy); } -// bombs class AbilityToken { constructor(x, y) { this.x = x; @@ -946,7 +936,6 @@ function useAbility() { 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"); @@ -959,7 +948,6 @@ function useAbility() { damageFlash = 10; } -// ================== PARTICLES ================== class Particle { constructor(x, y, color) { this.x = x; @@ -1069,7 +1057,6 @@ class Explosion { } } -// GAME OVER / PAUSE function drawGameOver() { ctx.fillStyle = "rgba(53, 0, 0, 0.7)"; ctx.fillRect(0, 0, canvasWidth, canvasHeight);