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 waveCooldown = 0;
|
||||||
|
|
||||||
let abilityCharges = 0;
|
let abilityCharges = 0;
|
||||||
|
let missileAmmo = 0; // Missile Ammo
|
||||||
|
|
||||||
var game = {
|
var game = {
|
||||||
level: 1,
|
level: 1,
|
||||||
@ -92,7 +93,8 @@ for (var i = 0; i < enemyImgArray.length; i++) {
|
|||||||
enemyImgArray[i].src = "img/alien_" + [i] + ".png";
|
enemyImgArray[i].src = "img/alien_" + [i] + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
var missilesArray = [];
|
var missilesArray = []; // Laser Biasa
|
||||||
|
var playerMissilesArray = []; // Homing Missile
|
||||||
var enemyShipArray = [];
|
var enemyShipArray = [];
|
||||||
var enemyBulletsArray = [];
|
var enemyBulletsArray = [];
|
||||||
var explosions = [];
|
var explosions = [];
|
||||||
@ -163,26 +165,36 @@ function gameLoop(timestamp) {
|
|||||||
let gamePaused = false;
|
let gamePaused = false;
|
||||||
|
|
||||||
function keyDownPressed(e) {
|
function keyDownPressed(e) {
|
||||||
if (e.keyCode === 87 || e.keyCode === 38) keys.up = true;
|
if (e.keyCode === 87 || e.keyCode === 38) keys.up = true; // W / UP
|
||||||
else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true;
|
else if (e.keyCode === 83 || e.keyCode === 40) keys.down = true; // S / DOWN
|
||||||
if (e.keyCode === 65 || e.keyCode === 37) keys.left = true;
|
if (e.keyCode === 65 || e.keyCode === 37) keys.left = true; // A / LEFT
|
||||||
if (e.keyCode === 68 || e.keyCode === 39) keys.right = true;
|
if (e.keyCode === 68 || e.keyCode === 39) keys.right = true; // D / RIGHT
|
||||||
|
|
||||||
if (e.keyCode === 32) {
|
if (e.keyCode === 32) {
|
||||||
|
// SPACE
|
||||||
keys.fire = true;
|
keys.fire = true;
|
||||||
if (!player1.dead) {
|
if (!player1.dead) {
|
||||||
fireBullet();
|
fireBullet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 80) togglePause();
|
if (e.keyCode === 80) togglePause(); // P
|
||||||
|
|
||||||
if (e.keyCode === 16) {
|
if (e.keyCode === 16) {
|
||||||
|
// SHIFT (Bomb)
|
||||||
if (abilityCharges > 0 && !game.gameOver && !gamePaused && !player1.dead) {
|
if (abilityCharges > 0 && !game.gameOver && !gamePaused && !player1.dead) {
|
||||||
useAbility();
|
useAbility();
|
||||||
abilityCharges--;
|
abilityCharges--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- TOMBOL Q UNTUK MISSILE ---
|
||||||
|
if (e.keyCode === 81) {
|
||||||
|
// Q
|
||||||
|
if (!game.gameOver && !gamePaused && !player1.dead) {
|
||||||
|
firePlayerMissile();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyUpPressed(e) {
|
function keyUpPressed(e) {
|
||||||
@ -193,18 +205,15 @@ function keyUpPressed(e) {
|
|||||||
if (e.keyCode === 32) keys.fire = false;
|
if (e.keyCode === 32) keys.fire = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UPDATE FIRE BULLET: Cek Skill Double Laser ---
|
// --- NORMAL LASER ---
|
||||||
function fireBullet() {
|
function fireBullet() {
|
||||||
if (player1.doubleLaserTimer > 0) {
|
if (player1.doubleLaserTimer > 0) {
|
||||||
// --- SKILL AKTIF: TEMBAK 2 PELURU ---
|
|
||||||
// Atas
|
|
||||||
missilesArray.push(
|
missilesArray.push(
|
||||||
new LaserBullet(
|
new LaserBullet(
|
||||||
player1.x + player1.width,
|
player1.x + player1.width,
|
||||||
player1.y + player1.height / 2 - 15
|
player1.y + player1.height / 2 - 15
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// Bawah
|
|
||||||
missilesArray.push(
|
missilesArray.push(
|
||||||
new LaserBullet(
|
new LaserBullet(
|
||||||
player1.x + player1.width,
|
player1.x + player1.width,
|
||||||
@ -212,7 +221,6 @@ function fireBullet() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// --- NORMAL: TEMBAK 1 PELURU ---
|
|
||||||
missilesArray.push(
|
missilesArray.push(
|
||||||
new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2)
|
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() {
|
function clearGame() {
|
||||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||||
}
|
}
|
||||||
@ -299,16 +326,18 @@ function drawGame() {
|
|||||||
height: t.height,
|
height: t.height,
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
// Cek Tipe Orb
|
|
||||||
if (t.type === "bomb") {
|
if (t.type === "bomb") {
|
||||||
// --- ORB KUNING (Bomb) ---
|
// KUNING = BOMB
|
||||||
abilityCharges++;
|
abilityCharges++;
|
||||||
createParticles(t.x, t.y, 15, "#ffff00"); // Partikel Kuning
|
createParticles(t.x, t.y, 15, "#ffff00");
|
||||||
} else if (t.type === "double") {
|
} else if (t.type === "double") {
|
||||||
// --- ORB MERAH (Double Laser) ---
|
// MERAH = DOUBLE LASER
|
||||||
// 10 Detik = 60 FPS * 10 = 600 Frames
|
|
||||||
player1.doubleLaserTimer = 600;
|
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);
|
abilityTokens.splice(i, 1);
|
||||||
@ -340,7 +369,6 @@ function drawGame() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BALANCING TEMBAKAN (Low Rate)
|
|
||||||
let shootChance = 0.005 + game.level * 0.0012;
|
let shootChance = 0.005 + game.level * 0.0012;
|
||||||
if (shootChance > 0.04) shootChance = 0.04;
|
if (shootChance > 0.04) shootChance = 0.04;
|
||||||
|
|
||||||
@ -362,6 +390,7 @@ function drawGame() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- UPDATE LASER BIASA ---
|
||||||
for (let i = 0; i < missilesArray.length; i++) {
|
for (let i = 0; i < missilesArray.length; i++) {
|
||||||
let m = missilesArray[i];
|
let m = missilesArray[i];
|
||||||
m.draw();
|
m.draw();
|
||||||
@ -374,7 +403,6 @@ function drawGame() {
|
|||||||
let en = enemyShipArray[j];
|
let en = enemyShipArray[j];
|
||||||
|
|
||||||
if (Tabrakan(m.getHitbox(), en.getHitbox())) {
|
if (Tabrakan(m.getHitbox(), en.getHitbox())) {
|
||||||
// --- BALANCING DAMAGE PLAYER ---
|
|
||||||
let playerDamage = 100 + game.level * 5;
|
let playerDamage = 100 + game.level * 5;
|
||||||
en.health -= playerDamage;
|
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++) {
|
for (let i = 0; i < enemyBulletsArray.length; i++) {
|
||||||
let b = enemyBulletsArray[i];
|
let b = enemyBulletsArray[i];
|
||||||
b.draw();
|
b.draw();
|
||||||
@ -486,10 +561,10 @@ function drawUI() {
|
|||||||
livesText += "♥ ";
|
livesText += "♥ ";
|
||||||
}
|
}
|
||||||
drawNewText(livesText, 30, canvasHeight - 50, "#ff3366");
|
drawNewText(livesText, 30, canvasHeight - 50, "#ff3366");
|
||||||
drawNewText("Bombs: " + abilityCharges, 30, 50, "#ffffff");
|
|
||||||
|
|
||||||
// --- (HAPUS) INDIKATOR TIMER DOUBLE LASER ---
|
// UI SKILL
|
||||||
// Teks tidak lagi ditampilkan, tapi durasi skill tetap berjalan di update()
|
drawNewText("Bombs (Shift): " + abilityCharges, 30, 50, "#ffff00");
|
||||||
|
drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); // Updated Text
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerObject {
|
class PlayerObject {
|
||||||
@ -512,14 +587,12 @@ class PlayerObject {
|
|||||||
this.invincible = 0;
|
this.invincible = 0;
|
||||||
this.dead = false;
|
this.dead = false;
|
||||||
|
|
||||||
// --- SKILL TIMER ---
|
this.doubleLaserTimer = 0;
|
||||||
this.doubleLaserTimer = 0; // Timer dalam frame
|
|
||||||
|
|
||||||
this.totalFrames = 5;
|
this.totalFrames = 5;
|
||||||
this.frameIndex = 2;
|
this.frameIndex = 2;
|
||||||
this.spriteWidth = 0;
|
this.spriteWidth = 0;
|
||||||
this.sourceHeight = 0;
|
this.sourceHeight = 0;
|
||||||
// --- SIZE PLAYER: Middle Ground ---
|
|
||||||
this.scale = 1.0;
|
this.scale = 1.0;
|
||||||
|
|
||||||
this.image.onload = () => {
|
this.image.onload = () => {
|
||||||
@ -620,7 +693,6 @@ class PlayerObject {
|
|||||||
this.frameIndex = 2;
|
this.frameIndex = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- KURANGI TIMER SKILL ---
|
|
||||||
if (this.doubleLaserTimer > 0) {
|
if (this.doubleLaserTimer > 0) {
|
||||||
this.doubleLaserTimer--;
|
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 {
|
class EnemyObj {
|
||||||
constructor(x, y, speed, img, pattern = "straight") {
|
constructor(x, y, speed, img, pattern = "straight") {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
// --- SIZE MUSUH: Middle Ground ---
|
|
||||||
this.width = 145;
|
this.width = 145;
|
||||||
this.height = 90;
|
this.height = 90;
|
||||||
this.image = img;
|
this.image = img;
|
||||||
@ -967,6 +1126,12 @@ class AbilityToken {
|
|||||||
this.speed = 4;
|
this.speed = 4;
|
||||||
// Random Type saat spawn: "bomb" atau "double"
|
// Random Type saat spawn: "bomb" atau "double"
|
||||||
this.type = Math.random() < 0.5 ? "bomb" : "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() {
|
draw() {
|
||||||
@ -984,10 +1149,14 @@ class AbilityToken {
|
|||||||
// Kuning (Bomb)
|
// Kuning (Bomb)
|
||||||
g.addColorStop(0.5, "#ffff00");
|
g.addColorStop(0.5, "#ffff00");
|
||||||
g.addColorStop(1, "#ff9900");
|
g.addColorStop(1, "#ff9900");
|
||||||
} else {
|
} else if (this.type === "double") {
|
||||||
// Merah (Double Laser)
|
// Merah (Double Laser)
|
||||||
g.addColorStop(0.5, "#ff3333");
|
g.addColorStop(0.5, "#ff3333");
|
||||||
g.addColorStop(1, "#990000");
|
g.addColorStop(1, "#990000");
|
||||||
|
} else {
|
||||||
|
// Biru Tua (Missile)
|
||||||
|
g.addColorStop(0.5, "#0000ff");
|
||||||
|
g.addColorStop(1, "#00008b");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fillStyle = g;
|
ctx.fillStyle = g;
|
||||||
@ -1002,7 +1171,7 @@ class AbilityToken {
|
|||||||
|
|
||||||
function maybeSpawnAbilityToken() {
|
function maybeSpawnAbilityToken() {
|
||||||
// --- KEMBALI KE 0.2% (0.002) ---
|
// --- 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;
|
const y = Math.random() * (canvasHeight - 120) + 60;
|
||||||
abilityTokens.push(new AbilityToken(canvasWidth + 40, y));
|
abilityTokens.push(new AbilityToken(canvasWidth + 40, y));
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user