From e71b5a73bb9eb8d44242cbb4a911faa3f4e98bcc Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 16 Dec 2025 02:44:44 +0700 Subject: [PATCH] up --- Main.html | 16 ++- Script.js | 300 +++++++++++++++++++++++++++++++------------ img/Player/lives.png | Bin 0 -> 7760 bytes 3 files changed, 230 insertions(+), 86 deletions(-) create mode 100644 img/Player/lives.png diff --git a/Main.html b/Main.html index eb97206..f0cfc34 100644 --- a/Main.html +++ b/Main.html @@ -1,16 +1,20 @@ + Space Game + - -
- -
- + +
+ +
+ + - + + \ No newline at end of file diff --git a/Script.js b/Script.js index 682b7bc..8ba69e0 100644 --- a/Script.js +++ b/Script.js @@ -19,14 +19,13 @@ function pickRandomBGM() { gameOverBGM.src = bgm.gameover; } -// --- OPTIMASI 1: Resolusi Tetap (HD 720p) --- var canvasWidth = 1280; var canvasHeight = 720; +var worldHeight = 900; var c, ctx; var gameStarted = false; var musicMuted = false; -// --- OPTIMASI 2: Cache Vignette --- let vignetteCanvas = null; let lastFrameTime = 0; @@ -49,6 +48,10 @@ var game = { gameOver: false, frames: 0, timer: 0, + surge: 1.0, + surgePhase: 0, + surgeTimer: 0, + surgeCooldown: 2400, // Start with 40s cooldown }; var keys = { @@ -75,6 +78,9 @@ var bombPickupImg = new Image(); // Pastikan file gambar bom clay Anda disimpan di sini bombPickupImg.src = "img/Skills/bomb.png"; +var livesImg = new Image(); +livesImg.src = "img/Player/lives.png"; + var bg0 = new Image(); bg0.src = "img/bg_0.png"; var bg1 = new Image(); @@ -89,14 +95,14 @@ let audioStarted = false; window.addEventListener("keydown", () => { if (!audioStarted) { - currentBGM.play().catch(() => {}); + currentBGM.play().catch(() => { }); audioStarted = true; } }); window.addEventListener("click", () => { if (!audioStarted) { - currentBGM.play().catch(() => {}); + currentBGM.play().catch(() => { }); audioStarted = true; } }); @@ -129,11 +135,78 @@ for (let i = 1; i <= 4; i++) { let currentPlanet = null; +let laserSprite, enemyBulletSprite, missileSprite; + +function preRenderAssets() { + // 1. CACHE LASER BULLET + // Padding for shadowBlur (15px) + const lPad = 20; + const lW = 13 + lPad * 2; + const lH = 4 + lPad * 2; + laserSprite = document.createElement("canvas"); + laserSprite.width = lW; + laserSprite.height = lH; + const lCtx = laserSprite.getContext("2d"); + + let lg = lCtx.createLinearGradient(lPad, lPad, lPad + 13, lPad); + lg.addColorStop(0, "#00e1ff"); + lg.addColorStop(0.5, "#ffffff"); + lg.addColorStop(1, "#00e1ff"); + + lCtx.fillStyle = lg; + lCtx.shadowColor = "#00ffff"; + lCtx.shadowBlur = 15; + lCtx.fillRect(lPad, lPad, 13, 4); + + + // 2. CACHE ENEMY BULLET + const ePad = 15; + const eW = 10 + ePad * 2; + const eH = 4 + ePad * 2; + enemyBulletSprite = document.createElement("canvas"); + enemyBulletSprite.width = eW; + enemyBulletSprite.height = eH; + const eCtx = enemyBulletSprite.getContext("2d"); + + let eg = eCtx.createLinearGradient(ePad + 10, ePad, ePad, ePad); // Right to Left + eg.addColorStop(0, "#ff9900"); + eg.addColorStop(0.5, "#ffffff"); + eg.addColorStop(1, "#ff3300"); + + eCtx.fillStyle = eg; + eCtx.shadowColor = "#ff6600"; + eCtx.shadowBlur = 10; + eCtx.fillRect(ePad, ePad, 10, 4); + + + // 3. CACHE PLAYER MISSILE (Body Only) + const mPad = 5; + const mW = 30 + mPad * 2; + const mH = 12 + mPad * 2; + missileSprite = document.createElement("canvas"); + missileSprite.width = mW; + missileSprite.height = mH; + const mCtx = missileSprite.getContext("2d"); + + let mg = mCtx.createLinearGradient(mPad, mPad, mPad + 30, mPad); + mg.addColorStop(0, "#00008b"); + mg.addColorStop(0.5, "#4169e1"); + mg.addColorStop(1, "#ffffff"); + + mCtx.fillStyle = mg; + mCtx.beginPath(); + mCtx.moveTo(mPad, mPad); + mCtx.lineTo(mPad + 30, mPad + 6); // height/2 + mCtx.lineTo(mPad, mPad + 12); // height + mCtx.fill(); +} + window.onload = function () { init(); }; function init() { + preRenderAssets(); // Generate sprites before game starts c = document.getElementById("canvas"); ctx = c.getContext("2d", { alpha: false }); @@ -238,7 +311,8 @@ function fireBullet() { } laser.currentTime = 0; - laser.volume = 0.4; + // IMPORTANT!! EXPERIMENT WITH THIS VALUE + laser.volume = 0.2; laser.play(); createParticles( player1.x + player1.width, @@ -272,8 +346,12 @@ function clearGame() { function updateGame() { game.frames++; + updateSurge(); + + // Base speed + Surge game.level = 1 + Math.floor(player1.score / 500); - game.speed = 1 + game.level * 0.1; + let baseSpeed = 1 + game.level * 0.1; + game.speed = baseSpeed * game.surge; updateStarField(); @@ -311,6 +389,9 @@ function updateGame() { function drawGame() { ctx.save(); + + + ctx.translate(0, cameraY); drawStarField(); @@ -480,7 +561,7 @@ function drawGame() { continue; } - if (pm.x > canvasWidth + 200 || pm.y < -200 || pm.y > canvasHeight + 200) { + if (pm.x > canvasWidth + 200 || pm.y < -200 || pm.y > worldHeight + 200) { playerMissilesArray.splice(i, 1); i--; } @@ -516,7 +597,7 @@ function drawGame() { b.x + b.width < -100 || b.x > canvasWidth + 100 || b.y + b.height < -100 || - b.y > canvasHeight + 100 + b.y > worldHeight + 100 ) { enemyBulletsArray.splice(i, 1); i--; @@ -549,21 +630,49 @@ function drawDebugHitbox(rect, color) { function drawUI() { drawNewText( - "Score: " + player1.score, - canvasWidth - 200, + player1.score, + canvasWidth - 140, canvasHeight - 50, - "white" + "white", + "30px" ); - drawNewText("LVL " + game.level, canvasWidth - 150, 50, "#00ff00"); + drawNewText(game.level, canvasWidth - 140, 50, "#00ff00", "30px"); - let livesText = "Lives: "; - for (let i = 0; i < player1.lives; i++) { - livesText += "♥ "; + // Lives (Stacked Icons) + const lifeSize = 40; // Scaled down from 104px source + const lifePadding = 5; + + if (livesImg.complete) { + for (let i = 0; i < player1.lives; i++) { + ctx.drawImage(livesImg, 30 + i * (lifeSize + lifePadding), canvasHeight - 60, lifeSize, lifeSize); + } + } else { + // Fallback if image not loaded + let livesText = "Lives: "; + for (let i = 0; i < player1.lives; i++) { + livesText += "♥ "; + } + drawNewText(livesText, 30, canvasHeight - 50, "#ff3366"); } - drawNewText(livesText, 30, canvasHeight - 50, "#ff3366"); - drawNewText("Bombs (Shift): " + abilityCharges, 30, 50, "#ffff00"); - drawNewText("Missiles (Q): " + missileAmmo, 30, 85, "#00ccff"); + // Bombs (Shift) + const iconSize = 32; + const padding = 10; + + if (bombPickupImg.complete) { + ctx.drawImage(bombPickupImg, 30, 30, iconSize, iconSize); + drawNewText("x " + abilityCharges, 30 + iconSize + padding, 30 + 24, "#ffff00"); + } else { + drawNewText("Bombs: " + abilityCharges, 30, 50, "#ffff00"); + } + + // Missiles (Q) + if (missilePickupImg.complete) { + ctx.drawImage(missilePickupImg, 30, 70, iconSize, iconSize); + drawNewText("x " + missileAmmo, 30 + iconSize + padding, 70 + 24, "#00ccff"); + } else { + drawNewText("Missiles: " + missileAmmo, 30, 85, "#00ccff"); + } } class PlayerObject { @@ -580,7 +689,7 @@ class PlayerObject { this.friction = 0.92; this.maxSpeed = 10; - this.lives = 3; + this.lives = 6; this.score = 0; this.health = 100; this.invincible = 0; @@ -666,8 +775,8 @@ class PlayerObject { 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.y > worldHeight - this.height + bleedY) { + this.y = worldHeight - this.height + bleedY; if (this.vy > 0) this.vy = 0; } @@ -719,8 +828,8 @@ function handlePlayerHit() { respawnCounter = 80 * 3; } -function drawNewText(txt, x, y, color) { - ctx.font = "20px Arial"; +function drawNewText(txt, x, y, color, fontSize = "20px") { + ctx.font = fontSize + " 'Orbitron', sans-serif"; ctx.fillStyle = color; ctx.textAlign = "left"; ctx.fillText(txt, x, y); @@ -731,9 +840,10 @@ class backgroundObj { this.x = x; this.y = y; this.width = 2000; - this.height = 1200; + this.height = 900; this.img = img; - this.speed = speed; + this.img = img; + this.factor = speed; // Rename speed to factor for parallax } draw() { ctx.save(); @@ -742,19 +852,19 @@ class backgroundObj { } update() { - this.x -= this.speed; + this.x -= this.factor * game.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); +let background1 = new backgroundObj(bg0, 0, 0, 3); +let background1a = new backgroundObj(bg0, 2000, 0, 3); +let background2 = new backgroundObj(bg1, 0, 0, 2); +let background2a = new backgroundObj(bg1, 2000, 0, 2); +let background3 = new backgroundObj(bg2, 0, 0, 1); +let background3a = new backgroundObj(bg2, 2000, 0, 1); function updateStarField() { background3.update(); @@ -777,7 +887,7 @@ function drawStarField() { function updateCamera() { const offset = player1.y + player1.height / 2 - canvasHeight / 2; const target = -offset * 0.7; - const bgHeight = 1200; + const bgHeight = worldHeight; const minY = canvasHeight - bgHeight; const maxY = 0; @@ -799,20 +909,8 @@ class LaserBullet { } 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; + const padding = 20; + ctx.drawImage(laserSprite, this.x - padding, this.y - padding); } update() { @@ -839,23 +937,10 @@ class PlayerMissile { draw() { ctx.save(); - // WARNA BIRU - let g = ctx.createLinearGradient( - this.x, - this.y, - this.x + this.width, - this.y - ); - g.addColorStop(0, "#00008b"); // Dark Blue - g.addColorStop(0.5, "#4169e1"); // Royal Blue - g.addColorStop(1, "#ffffff"); // White nose - 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(); + // Draw Cached Missile Body + const padding = 5; + ctx.drawImage(missileSprite, this.x - padding, this.y - padding); // TRAIL BIRU LANGIT if (Math.random() < 0.5) { @@ -949,7 +1034,8 @@ class EnemyBullet { const dx = targetX - x; const dy = targetY - y; const len = Math.sqrt(dx * dx + dy * dy) || 1; - const speed = 8; + // bullet speedd - mark + const speed = 5; this.vx = (dx / len) * speed; this.vy = (dy / len) * speed; @@ -966,20 +1052,8 @@ class EnemyBullet { } draw() { - let g = ctx.createLinearGradient( - this.x + this.width, - this.y, - this.x, - this.y - ); - g.addColorStop(0, "#ff9900"); - g.addColorStop(0.5, "#ffffff"); - g.addColorStop(1, "#ff3300"); - ctx.fillStyle = g; - ctx.shadowColor = "#ff6600"; - ctx.shadowBlur = 10; - ctx.fillRect(this.x, this.y, this.width, this.height); - ctx.shadowBlur = 0; + const padding = 15; + ctx.drawImage(enemyBulletSprite, this.x - padding, this.y - padding); } update() { @@ -1004,7 +1078,7 @@ class Planet { } update() { - this.x -= this.speed; + this.x -= this.speed * game.surge; if (this.x < -this.width) { this.active = false; } @@ -1360,6 +1434,72 @@ function drawScreenShading() { ctx.fillRect(0, 0, canvasWidth, canvasHeight); damageFlash--; } + + // --- BRIGHTNESS SURGE OVERLAY --- + // Use 'hard-light' or 'screen' to make dark backgrounds brighter + if (game.surge > 1.0) { + let intensity = (game.surge - 1.0) / (7.0 - 1.0); // Normalized 0 to 1 + if (intensity > 0) { + ctx.save(); + ctx.globalCompositeOperation = "hard-light"; // Better for space haze + ctx.fillStyle = "white"; + // Intensity 0.0 to 1.0 -> Alpha 0.0 to 0.4 + ctx.globalAlpha = intensity * 0.4; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + ctx.restore(); + } + } +} + +function updateSurge() { + const RAMP_UP_FRAMES = 240; // 4 seconds + const HOLD_FRAMES = 780; // 13 seconds + const RAMP_DOWN_FRAMES = 300;// 5 seconds + const MAX_SURGE_SPEED = 7.0; + + // Phase 0: Cooldown + if (game.surgePhase === 0) { + if (game.surgeCooldown > 0) { + game.surgeCooldown--; + } else { + // Cooldown finished, check probability + // "2/10 chance on incidents in 40 seconds" -> 20% chance every 40s check + if (Math.random() < 0.2) { + game.surgePhase = 1; // Start Surge + } else { + game.surgeCooldown = 2400; // Wait another 40s + } + } + } + + // Phase 1: Ramping Up (Ease In) + else if (game.surgePhase === 1) { + let step = (MAX_SURGE_SPEED - 1.0) / RAMP_UP_FRAMES; + game.surge += step; + // Safety Clamp + if (game.surge >= MAX_SURGE_SPEED) { + game.surge = MAX_SURGE_SPEED; + game.surgePhase = 2; + game.surgeTimer = HOLD_FRAMES; + } + } + + // Phase 2: Holding Speed + else if (game.surgePhase === 2) { + game.surgeTimer--; + if (game.surgeTimer <= 0) game.surgePhase = 3; + } + + // Phase 3: Ramping Down (Ease Out) + else if (game.surgePhase === 3) { + let step = (MAX_SURGE_SPEED - 1.0) / RAMP_DOWN_FRAMES; + game.surge -= step; + if (game.surge <= 1.0) { + game.surge = 1.0; + game.surgePhase = 0; + game.surgeCooldown = 2400; // Start cooldown for next cycle + } + } } function togglePause() { @@ -1369,7 +1509,7 @@ function togglePause() { if (gamePaused) { currentBGM.pause(); } else if (!musicMuted && audioStarted) { - currentBGM.play().catch(() => {}); + currentBGM.play().catch(() => { }); lastFrameTime = performance.now ? performance.now() : Date.now(); } } diff --git a/img/Player/lives.png b/img/Player/lives.png new file mode 100644 index 0000000000000000000000000000000000000000..aadaa5917e8714319e4440b3c23048ca3bebe285 GIT binary patch literal 7760 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 crn@HB