missile
This commit is contained in:
parent
7cc449240f
commit
71389ebb08
227
Script.js
227
Script.js
@ -41,6 +41,7 @@ let currentWave = null;
|
||||
let waveCooldown = 0;
|
||||
|
||||
let abilityCharges = 0;
|
||||
let missileAmmo = 0; // Missile Ammo
|
||||
|
||||
var game = {
|
||||
level: 1,
|
||||
@ -92,7 +93,8 @@ for (var i = 0; i < enemyImgArray.length; i++) {
|
||||
enemyImgArray[i].src = "img/alien_" + [i] + ".png";
|
||||
}
|
||||
|
||||
var missilesArray = [];
|
||||
var missilesArray = []; // Laser Biasa
|
||||
var playerMissilesArray = []; // Homing Missile
|
||||
var enemyShipArray = [];
|
||||
var enemyBulletsArray = [];
|
||||
var explosions = [];
|
||||
@ -163,26 +165,36 @@ function gameLoop(timestamp) {
|
||||
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 (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 === 32) {
|
||||
// SPACE
|
||||
keys.fire = true;
|
||||
if (!player1.dead) {
|
||||
fireBullet();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.keyCode === 80) togglePause();
|
||||
if (e.keyCode === 80) togglePause(); // P
|
||||
|
||||
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) {
|
||||
firePlayerMissile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyUpPressed(e) {
|
||||
@ -193,18 +205,15 @@ function keyUpPressed(e) {
|
||||
if (e.keyCode === 32) keys.fire = false;
|
||||
}
|
||||
|
||||
// --- UPDATE FIRE BULLET: Cek Skill Double Laser ---
|
||||
// --- NORMAL LASER ---
|
||||
function fireBullet() {
|
||||
if (player1.doubleLaserTimer > 0) {
|
||||
// --- SKILL AKTIF: TEMBAK 2 PELURU ---
|
||||
// Atas
|
||||
missilesArray.push(
|
||||
new LaserBullet(
|
||||
player1.x + player1.width,
|
||||
player1.y + player1.height / 2 - 15
|
||||
)
|
||||
);
|
||||
// Bawah
|
||||
missilesArray.push(
|
||||
new LaserBullet(
|
||||
player1.x + player1.width,
|
||||
@ -212,7 +221,6 @@ function fireBullet() {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// --- NORMAL: TEMBAK 1 PELURU ---
|
||||
missilesArray.push(
|
||||
new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2)
|
||||
);
|
||||
@ -229,6 +237,25 @@ function fireBullet() {
|
||||
);
|
||||
}
|
||||
|
||||
// --- FIRE HOMING MISSILE (Q) ---
|
||||
function firePlayerMissile() {
|
||||
if (missileAmmo > 0) {
|
||||
missileAmmo--;
|
||||
playerMissilesArray.push(
|
||||
new PlayerMissile(
|
||||
player1.x + player1.width,
|
||||
player1.y + player1.height / 2 - 10
|
||||
)
|
||||
);
|
||||
|
||||
// Sound effect
|
||||
let sfx = explosion_enemy.cloneNode();
|
||||
sfx.volume = 0.5;
|
||||
sfx.playbackRate = 2.0;
|
||||
sfx.play();
|
||||
}
|
||||
}
|
||||
|
||||
function clearGame() {
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
}
|
||||
@ -299,16 +326,18 @@ function drawGame() {
|
||||
height: t.height,
|
||||
})
|
||||
) {
|
||||
// Cek Tipe Orb
|
||||
if (t.type === "bomb") {
|
||||
// --- ORB KUNING (Bomb) ---
|
||||
// KUNING = BOMB
|
||||
abilityCharges++;
|
||||
createParticles(t.x, t.y, 15, "#ffff00"); // Partikel Kuning
|
||||
createParticles(t.x, t.y, 15, "#ffff00");
|
||||
} else if (t.type === "double") {
|
||||
// --- ORB MERAH (Double Laser) ---
|
||||
// 10 Detik = 60 FPS * 10 = 600 Frames
|
||||
// MERAH = DOUBLE LASER
|
||||
player1.doubleLaserTimer = 600;
|
||||
createParticles(t.x, t.y, 15, "#ff0000"); // Partikel Merah
|
||||
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");
|
||||
}
|
||||
|
||||
abilityTokens.splice(i, 1);
|
||||
@ -340,7 +369,6 @@ function drawGame() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// BALANCING TEMBAKAN (Low Rate)
|
||||
let shootChance = 0.005 + game.level * 0.0012;
|
||||
if (shootChance > 0.04) shootChance = 0.04;
|
||||
|
||||
@ -362,6 +390,7 @@ function drawGame() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- UPDATE LASER BIASA ---
|
||||
for (let i = 0; i < missilesArray.length; i++) {
|
||||
let m = missilesArray[i];
|
||||
m.draw();
|
||||
@ -374,7 +403,6 @@ function drawGame() {
|
||||
let en = enemyShipArray[j];
|
||||
|
||||
if (Tabrakan(m.getHitbox(), en.getHitbox())) {
|
||||
// --- BALANCING DAMAGE PLAYER ---
|
||||
let playerDamage = 100 + game.level * 5;
|
||||
en.health -= playerDamage;
|
||||
|
||||
@ -411,6 +439,53 @@ function drawGame() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- UPDATE PLAYER MISSILES (AUTO LOCK) ---
|
||||
for (let i = 0; i < playerMissilesArray.length; i++) {
|
||||
let pm = playerMissilesArray[i];
|
||||
pm.draw();
|
||||
pm.update();
|
||||
|
||||
let hit = false;
|
||||
for (let j = 0; j < enemyShipArray.length; j++) {
|
||||
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));
|
||||
|
||||
playerMissilesArray.splice(i, 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Hapus jika keluar layar jauh
|
||||
if (pm.x > canvasWidth + 200 || pm.y < -200 || pm.y > canvasHeight + 200) {
|
||||
playerMissilesArray.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < enemyBulletsArray.length; i++) {
|
||||
let b = enemyBulletsArray[i];
|
||||
b.draw();
|
||||
@ -486,10 +561,10 @@ function drawUI() {
|
||||
livesText += "♥ ";
|
||||
}
|
||||
drawNewText(livesText, 30, canvasHeight - 50, "#ff3366");
|
||||
drawNewText("Bombs: " + abilityCharges, 30, 50, "#ffffff");
|
||||
|
||||
// --- (HAPUS) INDIKATOR TIMER DOUBLE LASER ---
|
||||
// Teks tidak lagi ditampilkan, tapi durasi skill tetap berjalan di update()
|
||||
// UI SKILL
|
||||
drawNewText("Bombs (Shift): " + abilityCharges, 30, 50, "#ffff00");
|
||||
drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); // Updated Text
|
||||
}
|
||||
|
||||
class PlayerObject {
|
||||
@ -512,14 +587,12 @@ class PlayerObject {
|
||||
this.invincible = 0;
|
||||
this.dead = false;
|
||||
|
||||
// --- SKILL TIMER ---
|
||||
this.doubleLaserTimer = 0; // Timer dalam frame
|
||||
this.doubleLaserTimer = 0;
|
||||
|
||||
this.totalFrames = 5;
|
||||
this.frameIndex = 2;
|
||||
this.spriteWidth = 0;
|
||||
this.sourceHeight = 0;
|
||||
// --- SIZE PLAYER: Middle Ground ---
|
||||
this.scale = 1.0;
|
||||
|
||||
this.image.onload = () => {
|
||||
@ -620,7 +693,6 @@ class PlayerObject {
|
||||
this.frameIndex = 2;
|
||||
}
|
||||
|
||||
// --- KURANGI TIMER SKILL ---
|
||||
if (this.doubleLaserTimer > 0) {
|
||||
this.doubleLaserTimer--;
|
||||
}
|
||||
@ -749,11 +821,98 @@ 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.maxSpeed = 18;
|
||||
this.vx = 2;
|
||||
this.vy = 0;
|
||||
this.target = null; // Target yang akan dikejar
|
||||
}
|
||||
|
||||
getHitbox() {
|
||||
return { x: this.x, y: this.y, width: this.width, height: this.height };
|
||||
}
|
||||
|
||||
draw() {
|
||||
ctx.save();
|
||||
// Gambar body missile (Biru)
|
||||
let g = ctx.createLinearGradient(
|
||||
this.x,
|
||||
this.y,
|
||||
this.x + this.width,
|
||||
this.y
|
||||
);
|
||||
g.addColorStop(0, "#00008b");
|
||||
g.addColorStop(0.5, "#4169e1");
|
||||
g.addColorStop(1, "#ffffff");
|
||||
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.x, this.y);
|
||||
ctx.lineTo(this.x + this.width, this.y + this.height / 2);
|
||||
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");
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
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;
|
||||
for (let e of enemyShipArray) {
|
||||
let dx = e.x - this.x;
|
||||
let dy = e.y - this.y;
|
||||
let d = Math.sqrt(dx * dx + dy * dy);
|
||||
if (d < minDist) {
|
||||
minDist = d;
|
||||
closest = e;
|
||||
}
|
||||
}
|
||||
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;
|
||||
let dx = tx - this.x;
|
||||
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;
|
||||
}
|
||||
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
}
|
||||
}
|
||||
|
||||
class EnemyObj {
|
||||
constructor(x, y, speed, img, pattern = "straight") {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
// --- SIZE MUSUH: Middle Ground ---
|
||||
this.width = 145;
|
||||
this.height = 90;
|
||||
this.image = img;
|
||||
@ -967,6 +1126,12 @@ class AbilityToken {
|
||||
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";
|
||||
else if (r < 0.66) this.type = "double";
|
||||
else this.type = "missile";
|
||||
}
|
||||
|
||||
draw() {
|
||||
@ -984,10 +1149,14 @@ class AbilityToken {
|
||||
// Kuning (Bomb)
|
||||
g.addColorStop(0.5, "#ffff00");
|
||||
g.addColorStop(1, "#ff9900");
|
||||
} else {
|
||||
} else if (this.type === "double") {
|
||||
// Merah (Double Laser)
|
||||
g.addColorStop(0.5, "#ff3333");
|
||||
g.addColorStop(1, "#990000");
|
||||
} else {
|
||||
// Biru Tua (Missile)
|
||||
g.addColorStop(0.5, "#0000ff");
|
||||
g.addColorStop(1, "#00008b");
|
||||
}
|
||||
|
||||
ctx.fillStyle = g;
|
||||
@ -1002,7 +1171,7 @@ class AbilityToken {
|
||||
|
||||
function maybeSpawnAbilityToken() {
|
||||
// --- KEMBALI KE 0.2% (0.002) ---
|
||||
if (Math.random() < 0.002 && abilityTokens.length < 3) {
|
||||
if (Math.random() < 0.003 && abilityTokens.length < 3) {
|
||||
const y = Math.random() * (canvasHeight - 120) + 60;
|
||||
abilityTokens.push(new AbilityToken(canvasWidth + 40, y));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user