diff --git a/Script.js b/Script.js index 651d94f..db1a1ee 100644 --- a/Script.js +++ b/Script.js @@ -41,7 +41,7 @@ let currentWave = null; let waveCooldown = 0; let abilityCharges = 0; -let missileAmmo = 0; // Missile Ammo +let missileAmmo = 0; var game = { level: 1, @@ -59,9 +59,15 @@ var keys = { fire: false, }; +// --- LOAD IMAGES --- var playerShipImg = new Image(); playerShipImg.src = "img/Player/pesawat22.png"; +// *** GAMBAR BARU UNTUK PICKUP MISSILE *** +// Pastikan file gambar "missile_pickup.png" ada di folder img Anda +var missilePickupImg = new Image(); +missilePickupImg.src = "img/Skills/missile.png"; + var bg0 = new Image(); bg0.src = "img/bg_0.png"; var bg1 = new Image(); @@ -93,8 +99,8 @@ for (var i = 0; i < enemyImgArray.length; i++) { enemyImgArray[i].src = "img/alien_" + [i] + ".png"; } -var missilesArray = []; // Laser Biasa -var playerMissilesArray = []; // Homing Missile +var missilesArray = []; +var playerMissilesArray = []; var enemyShipArray = []; var enemyBulletsArray = []; var explosions = []; @@ -131,6 +137,8 @@ function init() { document.addEventListener("keydown", keyDownPressed, false); document.addEventListener("keyup", keyUpPressed, false); + document.addEventListener("contextmenu", (event) => event.preventDefault()); + gameStarted = true; pickRandomBGM(); currentBGM.volume = 1; @@ -165,30 +173,27 @@ function gameLoop(timestamp) { let gamePaused = false; function keyDownPressed(e) { - if (e.keyCode === 87 || e.keyCode === 38) keys.up = true; // W / UP - else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true; // S / DOWN - if (e.keyCode === 65 || e.keyCode === 37) keys.left = true; // A / LEFT - if (e.keyCode === 68 || e.keyCode === 39) keys.right = true; // D / RIGHT + 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) { - // SPACE keys.fire = true; if (!player1.dead) { fireBullet(); } } - if (e.keyCode === 80) togglePause(); // P + if (e.keyCode === 80) togglePause(); if (e.keyCode === 16) { - // SHIFT (Bomb) if (abilityCharges > 0 && !game.gameOver && !gamePaused && !player1.dead) { useAbility(); abilityCharges--; } } - // --- TOMBOL Q UNTUK MISSILE --- if (e.keyCode === 81) { // Q if (!game.gameOver && !gamePaused && !player1.dead) { @@ -205,7 +210,6 @@ function keyUpPressed(e) { if (e.keyCode === 32) keys.fire = false; } -// --- NORMAL LASER --- function fireBullet() { if (player1.doubleLaserTimer > 0) { missilesArray.push( @@ -237,7 +241,6 @@ function fireBullet() { ); } -// --- FIRE HOMING MISSILE (Q) --- function firePlayerMissile() { if (missileAmmo > 0) { missileAmmo--; @@ -248,7 +251,6 @@ function firePlayerMissile() { ) ); - // Sound effect let sfx = explosion_enemy.cloneNode(); sfx.volume = 0.5; sfx.playbackRate = 2.0; @@ -289,7 +291,6 @@ function updateGame() { player1.y = canvasHeight / 2 - player1.height / 2; player1.vx = 0; player1.vy = 0; - // Reset skill jika mati player1.doubleLaserTimer = 0; } } @@ -311,7 +312,6 @@ function drawGame() { drawParticles(); - // --- LOGIKA PICKUP ORB --- for (let i = 0; i < abilityTokens.length; i++) { const t = abilityTokens[i]; t.draw(); @@ -327,15 +327,12 @@ function drawGame() { }) ) { if (t.type === "bomb") { - // KUNING = BOMB abilityCharges++; createParticles(t.x, t.y, 15, "#ffff00"); } else if (t.type === "double") { - // MERAH = DOUBLE LASER player1.doubleLaserTimer = 600; createParticles(t.x, t.y, 15, "#ff0000"); } else if (t.type === "missile") { - // BIRU TUA = MISSILE (+3 Ammo) missileAmmo += 3; createParticles(t.x, t.y, 15, "#0000ff"); } @@ -390,7 +387,6 @@ function drawGame() { } } - // --- UPDATE LASER BIASA --- for (let i = 0; i < missilesArray.length; i++) { let m = missilesArray[i]; m.draw(); @@ -439,7 +435,6 @@ function drawGame() { } } - // --- UPDATE PLAYER MISSILES (AUTO LOCK) --- for (let i = 0; i < playerMissilesArray.length; i++) { let pm = playerMissilesArray[i]; pm.draw(); @@ -450,11 +445,9 @@ function drawGame() { let en = enemyShipArray[j]; if (Tabrakan(pm.getHitbox(), en.getHitbox())) { - // DAMAGE MISSILE: 400 + (Level * 20) let missileDmg = 400 + game.level * 20; en.health -= missileDmg; - // Efek ledakan besar biru createParticles(pm.x + pm.width, pm.y, 20, "#0000ff"); explosions.push(new Explosion(pm.x + pm.width, pm.y, 0.5)); @@ -479,7 +472,6 @@ function drawGame() { continue; } - // Hapus jika keluar layar jauh if (pm.x > canvasWidth + 200 || pm.y < -200 || pm.y > canvasHeight + 200) { playerMissilesArray.splice(i, 1); i--; @@ -562,9 +554,8 @@ function drawUI() { } drawNewText(livesText, 30, canvasHeight - 50, "#ff3366"); - // UI SKILL drawNewText("Bombs (Shift): " + abilityCharges, 30, 50, "#ffff00"); - drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); // Updated Text + drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); } class PlayerObject { @@ -821,18 +812,17 @@ class LaserBullet { } } -// --- NEW CLASS: HOMING PLAYER MISSILE --- class PlayerMissile { constructor(x, y) { this.x = x; this.y = y; this.width = 30; this.height = 12; - this.speed = 2; // Kecepatan Awal + this.speed = 2; this.maxSpeed = 18; this.vx = 2; this.vy = 0; - this.target = null; // Target yang akan dikejar + this.target = null; } getHitbox() { @@ -841,7 +831,6 @@ class PlayerMissile { draw() { ctx.save(); - // Gambar body missile (Biru) let g = ctx.createLinearGradient( this.x, this.y, @@ -859,7 +848,6 @@ class PlayerMissile { ctx.lineTo(this.x, this.y + this.height); ctx.fill(); - // Trail Effect if (Math.random() < 0.5) { createParticles(this.x, this.y + this.height / 2, 2, "#00bfff"); } @@ -867,11 +855,9 @@ class PlayerMissile { } update() { - // 1. Akselerasi (makin lama makin cepat) this.speed *= 1.08; if (this.speed > this.maxSpeed) this.speed = this.maxSpeed; - // 2. Logic Cari Musuh Terdekat (Auto Lock) if (!this.target || !enemyShipArray.includes(this.target)) { let minDist = 100000; let closest = null; @@ -887,7 +873,6 @@ class PlayerMissile { this.target = closest; } - // 3. Gerak Mengejar Target if (this.target) { let tx = this.target.x + this.target.width / 2; let ty = this.target.y + this.target.height / 2; @@ -895,11 +880,9 @@ class PlayerMissile { let dy = ty - this.y; let angle = Math.atan2(dy, dx); - // Update velocity ke arah musuh this.vx = Math.cos(angle) * this.speed; this.vy = Math.sin(angle) * this.speed; } else { - // Jika tidak ada target, terbang lurus this.vx = this.speed; this.vy = 0; } @@ -1058,8 +1041,6 @@ function addShips() { function startNewWave() { // --- CHAOS WAVE MODE --- - // Tidak ada pattern 'line', 'v', 'zigzag'. - // Kita hanya menentukan jumlah musuh yang akan muncul di wave ini. let baseCount = 3; let scalingCount = Math.floor(game.level / 2); @@ -1116,17 +1097,14 @@ function spawnEnemyFromWave(wave) { enemyShipArray.push(enemy); } -// --- CLASS ORB UPDATE: Support Type --- +// --- CLASS ORB UPDATE: SUPPORT IMAGE & OUTLINE --- class AbilityToken { constructor(x, y) { this.x = x; this.y = y; - this.width = 32; - this.height = 32; + this.width = 40; // Ukuran sedikit diperbesar untuk gambar + this.height = 40; this.speed = 4; - // Random Type saat spawn: "bomb" atau "double" - this.type = Math.random() < 0.5 ? "bomb" : "double"; - // RAND: 0-0.33=Bomb, 0.33-0.66=Double, 0.66-1.0=Missile let r = Math.random(); if (r < 0.33) this.type = "bomb"; @@ -1136,31 +1114,44 @@ class AbilityToken { 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); - // --- WARNA ORB BERDASARKAN TIPE --- - const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, 12); - g.addColorStop(0, "#ffffff"); + if (this.type === "missile") { + // --- METAL SLUG STYLE OUTLINE (KOTAK PUTIH TEBAL) --- + ctx.strokeStyle = "#ffffff"; + ctx.lineWidth = 3; + ctx.strokeRect(this.x, this.y, this.width, this.height); - if (this.type === "bomb") { - // Kuning (Bomb) - g.addColorStop(0.5, "#ffff00"); - g.addColorStop(1, "#ff9900"); - } else if (this.type === "double") { - // Merah (Double Laser) - g.addColorStop(0.5, "#ff3333"); - g.addColorStop(1, "#990000"); + // Gambar Roket Piksel di dalamnya dengan sedikit padding + const padding = 4; + ctx.drawImage( + missilePickupImg, + this.x + padding, + this.y + padding, + this.width - padding * 2, + this.height - padding * 2 + ); } else { - // Biru Tua (Missile) - g.addColorStop(0.5, "#0000ff"); - g.addColorStop(1, "#00008b"); + // --- GAYA LAMA (ORB LINGKARAN) UNTUK BOMB & DOUBLE --- + ctx.beginPath(); + const cx = this.x + this.width / 2; + const cy = this.y + this.height / 2; + ctx.arc(cx, cy, 16, 0, Math.PI * 2); // Radius disesuaikan dengan ukuran baru + + const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, 16); + g.addColorStop(0, "#ffffff"); + + if (this.type === "bomb") { + g.addColorStop(0.5, "#ffff00"); // Kuning + g.addColorStop(1, "#ff9900"); + } else if (this.type === "double") { + g.addColorStop(0.5, "#ff3333"); // Merah + g.addColorStop(1, "#990000"); + } + + ctx.fillStyle = g; + ctx.fill(); } - ctx.fillStyle = g; - ctx.fill(); ctx.restore(); } @@ -1170,8 +1161,8 @@ class AbilityToken { } function maybeSpawnAbilityToken() { - // --- KEMBALI KE 0.2% (0.002) --- - if (Math.random() < 0.003 && abilityTokens.length < 3) { + // --- 0.2% (0.002) --- + if (Math.random() < 0.002 && abilityTokens.length < 3) { const y = Math.random() * (canvasHeight - 120) + 60; abilityTokens.push(new AbilityToken(canvasWidth + 40, y)); } diff --git a/img/Skills/missile.png b/img/Skills/missile.png new file mode 100644 index 0000000..fdd944f Binary files /dev/null and b/img/Skills/missile.png differ