2025-12-08 16:28:37 +07:00

661 lines
14 KiB
JavaScript

"use strict";
/* ------------- AUDIO & BGM ------------- */
const bgmList = [
{ normal: "music/Scary.mp3", gameover: "music/ScaryGO.mp3" },
{ normal: "music/Fear.mp3", gameover: "music/FearGO.mp3" },
{ normal: "music/Chill.mp3", gameover: "music/ChillGO.mp3" },
];
let currentBGM = new Audio();
let gameOverBGM = new Audio();
currentBGM.loop = true;
gameOverBGM.loop = true;
function pickRandomBGM() {
const bgm = bgmList[Math.floor(Math.random() * bgmList.length)];
currentBGM.src = bgm.normal;
gameOverBGM.src = bgm.gameover;
}
/* ------------- CANVAS / GAME VARS ------------- */
var canvasWidth = 1280;
var canvasHeight = 650;
var c = undefined;
var ctx = undefined;
let lastFrameTime = 0;
const frameInterval = 1000 / 80;
var game = {
level: 1,
speed: 1,
gameOver: false,
frames: 0,
timer: 0,
};
/* gameState: "menu" | "playing" | "paused" */
let gameState = "menu";
/* ------------- INPUT STATE ------------- */
var keys = {
up: false,
down: false,
left: false,
right: false,
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;
for (var i = 0; i < enemyImgArray.length; i++) {
enemyImgArray[i] = new Image();
enemyImgArray[i].src = "img/alien_" + i + ".png";
}
var missilesArray = [];
var enemyShipArray = [];
var laser = document.createElement("audio");
laser.src = "music/laser2.mp3";
var explosion_enemy = document.createElement("audio");
explosion_enemy.src = "music/explosion-small.mp3";
var planetImages = [];
for (let i = 1; i <= 4; i++) {
let img = new Image();
img.src = `img/SpritesPlanet/planet_${i}.png`;
planetImages.push(img);
}
window.onload = function () {
setup();
};
function setup() {
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 }
);
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;
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 (game.gameOver) {
drawGameOver();
return;
}
if (timestamp - lastFrameTime >= frameInterval) {
lastFrameTime = timestamp;
clearGame();
updateGame();
drawGame();
}
requestAnimationFrame(gameLoop);
}
function keyDownPressed(e) {
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) {
keys.fire = true;
missilesArray.push(
new LaserBullet(player1.x + player1.width, player1.y + player1.height / 2)
);
laser.currentTime = 0;
laser.volume = 0.4;
laser.play();
}
}
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;
}
}
/* ------------- RENDER / UPDATE ------------- */
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;
addStarField();
addShips();
player1.update();
if (player1.invincible > 0) player1.invincible--;
spawnPlanet();
if (currentPlanet) currentPlanet.update();
game.frames++;
}
function drawGame() {
if (currentPlanet) currentPlanet.draw();
player1.draw();
for (var i = 0; i < enemyShipArray.length; i++) {
var s = enemyShipArray[i];
s.draw();
s.update();
if (s.x < -200) {
enemyShipArray.splice(i, 1);
i--;
continue;
}
if (Tabrakan(player1, s)) {
if (player1.invincible <= 0) {
player1.lives--;
player1.invincible = 60;
explosion_enemy.play();
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;
}
}
}
for (var i = 0; i < missilesArray.length; i++) {
var m = missilesArray[i];
m.draw();
m.update();
let hit = false;
for (var j = 0; j < enemyShipArray.length; j++) {
var en = enemyShipArray[j];
if (Tabrakan(m, en)) {
player1.score += 100;
explosion_enemy.play();
enemyShipArray.splice(j, 1);
missilesArray.splice(i, 1);
i--;
hit = true;
break;
}
}
if (hit) continue;
if (m.x > canvasWidth) {
missilesArray.splice(i, 1);
i--;
}
}
drawNewText("Score: " + player1.score, 30, 610, "white");
drawNewText("Player Lives: " + player1.lives, 1100, 610, "white");
}
class PlayerObject {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 100;
this.height = 64;
this.image = playerShipImg;
this.vx = 0;
this.vy = 0;
this.acceleration = 0.8;
this.friction = 0.92;
this.maxSpeed = 10;
this.lives = 3;
this.score = 0;
this.health = 100;
this.invincible = 0;
this.totalFrames = 5;
this.frameIndex = 2;
this.spriteWidth = 0;
this.sourceHeight = 0;
this.scale = 1.3;
this.image.onload = () => {
this.spriteWidth = this.image.width / this.totalFrames;
this.sourceHeight = this.image.height;
this.width = this.spriteWidth * this.scale;
this.height = this.sourceHeight * this.scale;
this.y = canvasHeight / 2 - this.height / 2;
};
}
draw() {
ctx.save();
if (this.invincible > 0 && this.invincible % 4 < 2) {
ctx.globalAlpha = 0.5;
}
if (this.spriteWidth > 0) {
ctx.drawImage(
this.image,
this.frameIndex * this.spriteWidth,
0,
this.spriteWidth,
this.sourceHeight,
this.x,
this.y,
this.width,
this.height
);
} else {
ctx.fillStyle = "red";
ctx.fillRect(this.x, this.y, 50, 50);
}
ctx.restore();
}
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;
}
this.vx *= this.friction;
this.vy *= this.friction;
this.x += this.vx;
this.y += this.vy;
const bleedY = this.height * 0.4;
const bleedX = this.width * 0.4;
if (this.y < -bleedY) {
this.y = -bleedY;
if (this.vy < 0) this.vy = 0;
}
if (this.y > canvasHeight - this.height + bleedY) {
this.y = canvasHeight - this.height + bleedY;
if (this.vy > 0) this.vy = 0;
}
if (this.x < -bleedX) {
this.x = -bleedX;
if (this.vx < 0) this.vx = 0;
}
if (this.x > canvasWidth - this.width + bleedX) {
this.x = canvasWidth - this.width + bleedX;
if (this.vx > 0) this.vx = 0;
}
if (this.vy < -2.5) {
this.frameIndex = 4;
} else if (this.vy < -0.5) {
this.frameIndex = 3;
} else if (this.vy > 2.5) {
this.frameIndex = 0;
} else if (this.vy > 0.5) {
this.frameIndex = 1;
} else {
this.frameIndex = 2;
}
}
}
let player1 = new PlayerObject(100, 300);
function drawNewText(txt, x, y, color) {
ctx.font = "20px Arial";
ctx.fillStyle = color;
ctx.fillText(txt, x, y);
}
class backgroundObj {
constructor(img, x, y, speed) {
this.x = x;
this.y = y;
this.width = 2000;
this.height = 1200;
this.img = img;
this.speed = speed;
}
draw() {
ctx.save();
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
ctx.restore();
}
update() {
this.x -= this.speed;
if (this.x < -2000) {
this.x = 2000;
}
}
}
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();
background3.update();
background3a.draw();
background3a.update();
background2.draw();
background2.update();
background2a.draw();
background2a.update();
background1.draw();
background1.update();
background1a.draw();
background1a.update();
}
class LaserBullet {
constructor(x, y) {
this.x = x;
this.y = y;
this.width = 14;
this.height = 4;
this.speed = 16;
}
draw() {
let g = ctx.createLinearGradient(
this.x,
this.y,
this.x + this.width,
this.y
);
g.addColorStop(0, "#00e1ff");
g.addColorStop(0.5, "#ffffff");
g.addColorStop(1, "#00e1ff");
ctx.fillStyle = g;
ctx.shadowColor = "#00ffff";
ctx.shadowBlur = 15;
ctx.fillRect(this.x, this.y, this.width, this.height);
ctx.shadowBlur = 0;
}
update() {
this.x += this.speed;
}
}
class EnemyObj {
constructor(x, y, speed, img) {
this.x = x;
this.y = y;
this.width = 170;
this.height = 105;
this.image = img;
this.speed = speed;
this.health = 100;
this.damage = 10;
}
draw() {
ctx.save();
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
ctx.restore();
}
update() {
this.x -= this.speed;
}
}
let enemy = new EnemyObj(800, 200, 12, enemyImgArray[0]);
class Planet {
constructor(img) {
this.image = img;
this.width = 160;
this.height = 160;
this.x = canvasWidth + 50;
this.y = Math.random() * 300 + 50;
this.speed = 1.2;
this.active = true;
}
draw() {
ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
}
update() {
this.x -= this.speed;
if (this.x < -this.width) {
this.active = false;
}
}
}
let currentPlanet = null;
function spawnPlanet() {
if (currentPlanet == null || currentPlanet.active === false) {
let randomImg =
planetImages[Math.floor(Math.random() * planetImages.length)];
currentPlanet = new Planet(randomImg);
}
}
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])
);
}
}
}
function Tabrakan(o, p) {
if (
o.x + o.width > p.x &&
o.x < p.x + p.width &&
o.y + o.height > p.y &&
o.y < p.y + p.height
) {
return true;
}
return false;
}
function drawGameOver() {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.font = "80px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", canvasWidth / 2, canvasHeight / 2 - 50);
ctx.font = "40px Arial";
ctx.fillStyle = "white";
ctx.fillText("Refresh to Restart", canvasWidth / 2, canvasHeight / 2 + 30);
}
function crossfadeToGameOver() {
let fadeSpeed = 0.02;
gameOverBGM.volume = 0;
gameOverBGM.play();
let fadeInterval = setInterval(() => {
currentBGM.volume -= fadeSpeed;
if (currentBGM.volume < 0) currentBGM.volume = 0;
gameOverBGM.volume += fadeSpeed;
if (gameOverBGM.volume > 1) gameOverBGM.volume = 1;
if (currentBGM.volume === 0) {
currentBGM.pause();
clearInterval(fadeInterval);
}
}, 1000 / 30);
}