/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (initialSpeed) { var self = Container.call(this); // Added initialSpeed parameter //Create and attach asset. self.visual = self.attachAsset('bullet', { // Uses the existing 'bullet' image asset anchorX: 0.5, anchorY: 0.5 }); self.speed = initialSpeed; // Use the passed initialSpeed for movement // If this instance of bullet is attached, this method will be called every tick by the LK engine automatically. self.update = function () { self.y += self.speed; }; return self; }); // Coin collectible class (was PointCollectible) var CoinCollectible = Container.expand(function () { var self = Container.call(this); var coin = self.attachAsset('coin_icon', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0; self.update = function () { self.y += self.speed; }; return self; }); // Player character controlled by face var FaceChar = Container.expand(function () { var self = Container.call(this); var _char = self.attachAsset('faceChar', { anchorX: 0.5, anchorY: 0.5 }); // For possible future effects self.flash = function () { LK.effects.flashObject(self, 0xffffff, 300); }; return self; }); var Gun = Container.expand(function () { var self = Container.call(this); // The visual asset for the gun self.visual = self.attachAsset('gun_visual', { anchorX: 0.5, // Anchor to its horizontal center anchorY: 1.0 // Anchor to its bottom edge (base of the gun) }); return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Speed is set on creation self.speed = 0; self.update = function () { self.y += self.speed; }; return self; }); // Shop class for purchasing items var Shop = Container.expand(function () { var self = Container.call(this); var items = [{ name: "Speed Boost", cost: 10 }, { name: "Extra Life", cost: 20 }, { name: "Shield", cost: 15 }]; var shopText = new Text2("Shop", { size: 100, fill: 0xFFFFFF }); shopText.anchor.set(0.5, 0); self.addChild(shopText); shopText.y = -200; // Show current persistent points var pointsText = new Text2("", { size: 70, fill: 0xFFFF00 }); pointsText.anchor.set(0.5, 0); pointsText.y = -100; self.addChild(pointsText); function updatePointsText() { var totalPoints = 0; var extraLives = 0; var shields = 0; if (typeof storage !== "undefined") { totalPoints = storage.totalPoints || 0; extraLives = typeof storage.extraLives !== "undefined" ? storage.extraLives : 3; shields = storage.shields || 0; } pointsText.setText("Points: " + totalPoints + " | Extra Lives: " + extraLives + " | Shields: " + shields); // Also update global scoreTxt if shop is open if (typeof scoreTxt !== "undefined") { scoreTxt.setText(totalPoints); } } updatePointsText(); // Add shop items and purchase logic items.forEach(function (item, index) { var itemText = new Text2(item.name + " - " + item.cost + " points", { size: 70, fill: 0xFFFFFF }); itemText.anchor.set(0.5, 0); itemText.y = index * 100; self.addChild(itemText); // Enable purchase on tap/click itemText.interactive = true; itemText.buttonMode = true; itemText.down = function (x, y, obj) { var totalPoints = 0; if (typeof storage !== "undefined") { totalPoints = storage.totalPoints || 0; } if (totalPoints >= item.cost) { // Deduct cost and update storage totalPoints -= item.cost; if (typeof storage !== "undefined") { storage.totalPoints = totalPoints; } updatePointsText(); if (typeof updatePersistentPointsDisplay === "function") { updatePersistentPointsDisplay(); } // Show feedback (flash) LK.effects.flashObject(itemText, 0x00FF00, 400); // Apply item effect in game if (item.name === "Extra Life") { // Give player an extra life (persist for next game) if (typeof storage !== "undefined") { var extraLives = storage.extraLives || 0; storage.extraLives = extraLives + 1; if (typeof updateLivesDisplay === "function") { updateLivesDisplay(); } // Update heart icons UI // updatePointsText(); // This is already called after storage.totalPoints update, which also updates lives text in shop } } if (item.name === "Shield") { // Give player a shield (persist for next game) if (typeof storage !== "undefined") { var shields = storage.shields || 0; storage.shields = shields + 1; } } // Optionally: disable item after purchase (one-time buy) // itemText.alpha = 0.5; // itemText.interactive = false; } else { // Not enough points, flash red LK.effects.flashObject(itemText, 0xFF0000, 400); } }; }); return self; }); /**** * Initialize Game ****/ // var game = new LK.Game({ backgroundColor: 0x181c2c }); /**** * Game Code ****/ // Level variables var currentLevel = 1; var levelText; var levelUpInterval = 30 * 60; // Level up every 30 seconds (1800 ticks) var lastLevelUpTick = 0; // Show shop on demand (e.g. after game over or via button) // --- Shop display --- // Asset for life display // New asset for Kimchi function showShop() { var shop = new Shop(); // Center shop based on its size shop.x = GAME_W / 2; shop.y = GAME_H / 2; shop.anchorX = 0.5; shop.anchorY = 0.5; game.addChild(shop); // Bring shop to top (after all gameplay elements) if (shop.parent && shop.parent.children) { shop.parent.removeChild(shop); shop.parent.addChild(shop); } // Remove shop after 5 seconds LK.setTimeout(function () { if (shop && shop.parent) { shop.destroy(); } }, 5000); } // --- Main Menu Overlay --- var mainMenuOverlay; function showMainMenu() { // Prevent duplicate overlays if (mainMenuOverlay && mainMenuOverlay.parent) return; mainMenuOverlay = new Container(); // Semi-transparent background var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 30, scaleY: 40, x: GAME_W / 2, y: GAME_H / 2, tint: 0x000000 }); bg.alpha = 0.7; mainMenuOverlay.addChild(bg); // Game Title var title = new Text2("Ana Menü", { size: 180, fill: 0xFFF700 }); title.anchor.set(0.5, 0.5); title.x = GAME_W / 2; title.y = GAME_H / 2 - 350; mainMenuOverlay.addChild(title); // Start Button var startBtn = new Text2("Başla", { size: 140, fill: 0xFFFFFF }); startBtn.anchor.set(0.5, 0.5); startBtn.x = GAME_W / 2; startBtn.y = GAME_H / 2 + 100; startBtn.interactive = true; startBtn.buttonMode = true; startBtn.down = function (x, y, obj) { if (mainMenuOverlay && mainMenuOverlay.parent) { mainMenuOverlay.destroy(); } // Activate gameplay gameActive = true; // Reset timer for new session gameStartTime = LK.ticks; lastLevelUpTick = LK.ticks; // Initialize level timer with current game ticks elapsedSeconds = 0; if (elapsedTimeText) { elapsedTimeText.setText('Time: 0s'); } // Reset score and scoreTxt at game start score = 0; LK.setScore(0); if (typeof scoreTxt !== "undefined") { scoreTxt.setText('0'); } }; mainMenuOverlay.addChild(startBtn); // Optionally, add instructions var info = new Text2("Ekrana tıklayarak ateş et\nYüzünü hareket ettirerek karakteri kontrol et", { size: 70, fill: 0xFFFFFF }); info.anchor.set(0.5, 0.5); info.x = GAME_W / 2; info.y = GAME_H / 2 + 300; mainMenuOverlay.addChild(info); game.addChild(mainMenuOverlay); } // --- Gameplay lock until Başla is pressed --- var gameActive = false; // Only true after Başla is pressed // Show main menu at game start LK.setTimeout(function () { showMainMenu(); }, 10); // Timer variables var elapsedTimeText; var gameStartTime = 0; // To store LK.ticks at the start of the game var elapsedSeconds = 0; // To store the calculated elapsed seconds // Obstacle: red rectangle // Character: main player controlled by face // Game area dimensions var GAME_W = 2048; var GAME_H = 2732; // Player character var faceChar = new FaceChar(); game.addChild(faceChar); // Gun instance var gun; // Gun instance // Create and add the gun gun = new Gun(); game.addChild(gun); // Initial position: bottom center, 400px from bottom faceChar.x = GAME_W / 2; faceChar.y = GAME_H - 400; // Arrays for obstacles and points var obstacles = []; var points = []; var bullets = []; // Array to store active bullets // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFF700 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Level Display levelText = new Text2('Level: 1', { size: 80, fill: 0xADD8E6 // A nice light blue color for the level text }); levelText.anchor.set(0, 0); // Anchor to its own top-left LK.gui.topLeft.addChild(levelText); // Position it carefully in the top-left, avoiding the 100x100px menu icon area levelText.x = 250; // Increased X to move it further right (100px for menu icon + 150px padding) levelText.y = 20; // 20px from the top edge of the screen // Persistent Currency ("Para") Display will be initialized by updatePersistentPointsDisplay // Lives display elements var livesContainer = new Container(); LK.gui.topRight.addChild(livesContainer); // Add to top-right GUI area // livesContainer.anchor.set(1, 0); // Removed: Containers do not have an 'anchor' property for self-positioning. livesContainer.x = -30; // Position 30px from the screen's right edge (relative to gui.topRight) livesContainer.y = 30; // Position 30px from the screen's top edge (relative to gui.topRight) var heartIcons = []; // Array to keep track of heart icon objects function updateLivesDisplay() { // Clear previously displayed hearts heartIcons.forEach(function (icon) { if (icon && icon.parent) { // Check if icon exists and is attached icon.destroy(); } }); heartIcons = []; // Reset the array var currentLives = 0; // storage.extraLives is initialized by game logic (e.g., to 3 at game start/reset) // before this function is typically called. if (typeof storage !== "undefined" && typeof storage.extraLives === "number") { currentLives = storage.extraLives; } // If storage.extraLives were somehow not a number here, 0 hearts would be displayed, // accurately reflecting an issue with the life count data, rather than masking it. // Optionally cap the number of hearts displayed to avoid UI clutter var displayableLives = Math.min(currentLives, 5); // e.g., display max 5 hearts for (var i = 0; i < displayableLives; i++) { var heart = LK.getAsset('heart_icon', { anchorX: 1, anchorY: 0.5 }); // Anchor heart's right-middle if (heart) { // Position hearts horizontally from right to left within the livesContainer // The rightmost heart (i=0) is at x=0 relative to container's (anchored) right edge heart.x = -i * (heart.width + 10); // 10px spacing between hearts heart.y = heart.height / 2; // Vertically center hearts in the container livesContainer.addChild(heart); heartIcons.push(heart); } } } // For keeping track of score // Load score from storage if available (after game over) var score = 0; if (typeof storage !== "undefined") { // Use lastScore for current run, totalPoints for shop var lastScore = storage.lastScore; if (typeof lastScore === "number" && !isNaN(lastScore)) { score = lastScore; LK.setScore(score); } // Ensure extraLives, shields and totalPoints are defined if (typeof storage.extraLives === "undefined" || storage.extraLives === 0) { storage.extraLives = 3; } if (typeof storage.shields === "undefined") { storage.shields = 0; } if (typeof storage.totalPoints === "undefined") { storage.totalPoints = 0; } // Ensure totalPoints is initialized updateLivesDisplay(); // Initialize hearts display if (typeof updatePersistentPointsDisplay === "function") { updatePersistentPointsDisplay(); } // Initialize Para display } // Elapsed Time Display elapsedTimeText = new Text2('Time: 0s', { size: 70, fill: 0xFFFFFF // White color for the text }); elapsedTimeText.anchor.set(0, 1); // Anchor to the bottom-left of the text LK.gui.bottomLeft.addChild(elapsedTimeText); // Position it 30px from the left and 30px from the bottom of the screen elapsedTimeText.x = 30; elapsedTimeText.y = -30; // Initialize timer variables for the current game session gameStartTime = LK.ticks; elapsedSeconds = 0; elapsedTimeText.setText('Time: ' + elapsedSeconds + 's'); // Set initial text // For obstacle/point spawn timing var lastObstacleTick = 0; var lastPointTick = 0; var lastShotTick = 0; // Tracks the game tick of the last shot var fireCooldownTicks = 60; // Minimum ticks between shots (60 ticks = 1 second at 60 FPS) var initialBulletSpeed = -30; // Base speed for bullets at level 1 var bulletSpeedIncrementPerLevel = 2; // How much faster (more negative) bullets get per level // For difficulty scaling var minObstacleInterval = 45; // ticks var minPointInterval = 60; // ticks var obstacleSpeed = 12; // px per frame, increases over time // For collision state var lastCollided = false; // For point collection state var lastPointCollided = {}; // Facekit smoothing var lastFaceX = faceChar.x; var lastFaceY = faceChar.y; // Persistent currency display var persistentPointsContainer; var persistentPointsText; var persistentCoinIcon; function updatePersistentPointsDisplay() { var spacing = 15; // Spacing between text and icon // Ensure container exists and is on GUI if (!persistentPointsContainer) { persistentPointsContainer = new Container(); LK.gui.topRight.addChild(persistentPointsContainer); // Attach to top-right persistentPointsContainer.x = -30; // Position 30px from the screen's right edge persistentPointsContainer.y = 140; // Position below lives display (lives at y=30, hearts ~70px high) } // Ensure Text2 object for points exists if (!persistentPointsText) { persistentPointsText = new Text2("Coin: 0", { size: 70, fill: 0xFFFF00 }); persistentPointsText.anchor.set(1, 0.5); // Anchor right-middle for positioning } // Ensure text is child of container if (persistentPointsText.parent !== persistentPointsContainer) { if (persistentPointsText.parent) { persistentPointsText.parent.removeChild(persistentPointsText); } persistentPointsContainer.addChild(persistentPointsText); } // Ensure coin icon asset exists if (!persistentCoinIcon) { persistentCoinIcon = LK.getAsset('coin_icon', { // width/height will come from asset definition in Assets section anchorX: 1, // Anchor right anchorY: 0.5 // Anchor middle for vertical alignment with text }); } // Ensure icon is child of container if (persistentCoinIcon.parent !== persistentPointsContainer) { if (persistentCoinIcon.parent) { persistentCoinIcon.parent.removeChild(persistentCoinIcon); } persistentPointsContainer.addChild(persistentCoinIcon); } // Update text content var currentPoints = 0; if (typeof storage !== "undefined" && typeof storage.totalPoints === "number") { currentPoints = storage.totalPoints; } persistentPointsText.setText("Coin: " + currentPoints); // Position text and icon within the container for right alignment. // The container's top-left is positioned by persistentPointsContainer.x and .y relative to LK.gui.topRight. // Children x,y are relative to the container's top-left. // persistentCoinIcon is rightmost. Its anchor (1, 0.5) means its right-middle point is at its x,y. // We position its right-middle point at (0,0) relative to container's origin (top-left). persistentCoinIcon.x = 0; persistentCoinIcon.y = 0; // Vertically centered due to anchorY 0.5 and container's y // persistentPointsText is to the left. Its anchor (1, 0.5) means its right-middle point is at its x,y. // We position its right-middle point to be (persistentCoinIcon.width + spacing) to the left of the icon's right-middle. persistentPointsText.x = -(persistentCoinIcon.width + spacing); persistentPointsText.y = 0; // Vertically centered due to anchorY 0.5 and container's y } // Handle screen tap to fire bullets game.down = function (x, y, obj) { if (!gameActive) return; // Prevent firing if game not started // Check cooldown: If current time (LK.ticks) minus last shot time is less than the cooldown period, do nothing. if (LK.ticks - lastShotTick < fireCooldownTicks) { return; // Still in cooldown period, prevent firing } if (gun && gun.visual && faceChar) { // Ensure gun, its visual, and character exist var currentBulletSpeed = initialBulletSpeed - (currentLevel - 1) * bulletSpeedIncrementPerLevel; var newBullet = new Bullet(currentBulletSpeed); newBullet.x = gun.x; // Gun's anchorY is 1.0 (bottom). gun.y is its base. // Gun's tip is gun.y - gun.visual.height. // Bullet's anchorY is 0.5 (center). newBullet.y = gun.y - gun.visual.height; // Initialize lastY for off-screen detection logic newBullet.lastY = newBullet.y; bullets.push(newBullet); game.addChild(newBullet); lastShotTick = LK.ticks; // Update the time of the last shot // Optional: Play a shooting sound if an asset 'shoot' is available // if (LK.getSound('shoot')) { // LK.getSound('shoot').play(); // Optionally, reset other game state here if needed lastPointCollided = {}; } ; }; // Main update loop game.update = function () { if (!gameActive) return; // Block all gameplay updates until Başla is pressed // --- Timer Update --- // Calculate total elapsed seconds since the game started var currentTotalSeconds = Math.floor((LK.ticks - gameStartTime) / 60); // Update the text only if the number of seconds has changed if (currentTotalSeconds !== elapsedSeconds) { elapsedSeconds = currentTotalSeconds; if (elapsedTimeText) { // Check if the text object exists elapsedTimeText.setText('Time: ' + elapsedSeconds + 's'); } } // --- Level Progression --- if (LK.ticks - lastLevelUpTick > levelUpInterval) { currentLevel++; if (levelText) { levelText.setText('Level: ' + currentLevel); } lastLevelUpTick = LK.ticks; // Increase difficulty with the new level obstacleSpeed += 1.0; // Make obstacles faster // Decrease spawn intervals (more frequent spawns), but with a minimum cap if (minObstacleInterval > 15) { // Don't let obstacles spawn too frenetically minObstacleInterval -= 2; } if (minPointInterval > 20) { // Same for points/coins minPointInterval -= 2; } // Optional: Cap the maximum obstacle speed to prevent it from becoming unplayably fast if (obstacleSpeed > 40) { obstacleSpeed = 40; } // You could add other effects or difficulty adjustments here when a new level is reached! } // --- Facekit control --- // Use mouthCenter for X/Y, clamp to game area var fx = facekit.mouthCenter && typeof facekit.mouthCenter.x === "number" ? facekit.mouthCenter.x : GAME_W / 2; var fy = facekit.mouthCenter && typeof facekit.mouthCenter.y === "number" ? facekit.mouthCenter.y : GAME_H - 400; // Optionally, if mouth is open, move faster (boost) var boost = facekit.mouthOpen ? 1.7 : 1.0; // Smooth movement (lerp) lastFaceX += (fx - lastFaceX) * 0.25 * boost; lastFaceY += (fy - lastFaceY) * 0.25 * boost; // Clamp to bounds (keep inside screen) var charW = faceChar.width; var charH = faceChar.height; var minX = charW / 2 + 40; var maxX = GAME_W - charW / 2 - 40; var minY = charH / 2 + 120; var maxY = GAME_H - charH / 2 - 40; faceChar.x = Math.max(minX, Math.min(maxX, lastFaceX)); faceChar.y = Math.max(minY, Math.min(maxY, lastFaceY)); // --- Spawn obstacles --- // Update gun position to follow faceChar if (gun && faceChar) { gun.x = faceChar.x; // Position the base of the gun (anchorY = 1.0) at the top edge of faceChar gun.y = faceChar.y - faceChar.height / 2; } // --- Manage Bullets --- for (var k = bullets.length - 1; k >= 0; k--) { var bullet = bullets[k]; // Initialize lastY for tracking changes on Y if not already done (e.g. if not set at creation) if (bullet.lastY === undefined) { bullet.lastY = bullet.y; } // bullet.update(); // Bullet's own update method is called automatically by LK engine if bullet is added to game stage // Off-Screen Detection (top of screen) // A bullet's y is its center (anchorY: 0.5). // It's off-screen if its center y is < -(bullet.visual.height / 2). var bulletHeight = bullet.visual ? bullet.visual.height : 50; // Fallback height var offScreenThreshold = -(bulletHeight / 2) - 10; // Add a small buffer beyond the edge if (bullet.y < offScreenThreshold) { // More robust check: if it *crossed* the boundary in this frame if (bullet.lastY >= offScreenThreshold) { bullet.destroy(); // Destroy can only be called from the main 'Game' class bullets.splice(k, 1); continue; // Skip further processing for this bullet } else if (bullet.y < -GAME_H) { // Failsafe: if it's way off screen (e.g. due to lag/teleport) bullet.destroy(); bullets.splice(k, 1); continue; } } bullet.lastY = bullet.y; // Update last known Y position // Bullet-Obstacle Collision // Iterate through obstacles to check for collision with the current bullet for (var obs_idx = obstacles.length - 1; obs_idx >= 0; obs_idx--) { var obstacle = obstacles[obs_idx]; // Ensure the bullet still exists and is part of the game scene. // This check is important because the bullet might have been destroyed by off-screen logic // or by a previous collision in this same frame if multiple collisions were possible. if (!bullet || !bullet.parent) { // If bullet is no longer valid (e.g., already destroyed and removed from its parent container), // then stop checking it against further obstacles. break; } // Ensure the obstacle also still exists and is part of the game scene. if (obstacle && obstacle.parent && bullet.intersects(obstacle)) { // Collision detected! // Destroy the obstacle obstacle.destroy(); obstacles.splice(obs_idx, 1); // Award points for destroying obstacle score += 5; LK.setScore(score); if (scoreTxt) { scoreTxt.setText(score); } // Destroy the bullet bullet.destroy(); bullets.splice(k, 1); // k is the index from the outer bullets loop // Since this bullet has been destroyed, break from iterating through more obstacles for it. // The outer loop (for bullets) will correctly handle the modified bullets array due to the splice. break; } } // End of Bullet-Obstacle Collision logic for the current bullet } // --- Spawn obstacles --- if (LK.ticks - lastObstacleTick > minObstacleInterval + Math.floor(Math.random() * 40)) { var obs = new Obstacle(); // Random X, avoid leftmost 100px (menu) var obsW = obs.width; var obsX = 100 + obsW / 2 + Math.random() * (GAME_W - obsW - 200); obs.x = obsX; obs.y = -obs.height / 2; obs.speed = obstacleSpeed + Math.random() * 3; obstacles.push(obs); game.addChild(obs); lastObstacleTick = LK.ticks; } // --- Spawn points --- if (LK.ticks - lastPointTick > minPointInterval + Math.floor(Math.random() * 60)) { var pt = new CoinCollectible(); var ptW = pt.width; var ptX = 100 + ptW / 2 + Math.random() * (GAME_W - ptW - 200); pt.x = ptX; pt.y = -pt.height / 2; pt.speed = obstacleSpeed * 0.8 + Math.random() * 2; points.push(pt); game.addChild(pt); lastPointTick = LK.ticks; } // --- Move obstacles, check collision --- var collided = false; for (var i = obstacles.length - 1; i >= 0; i--) { var o = obstacles[i]; o.update(); // Off screen if (o.y - o.height / 2 > GAME_H + 100) { o.destroy(); obstacles.splice(i, 1); continue; } // Collision with player if (faceChar.intersects(o)) { collided = true; } } // --- Move points, check collection --- for (var j = points.length - 1; j >= 0; j--) { var p = points[j]; p.update(); // Off screen if (p.y - p.height / 2 > GAME_H + 100) { p.destroy(); points.splice(j, 1); continue; } // Collision with player var pointId = p._lkid || p._id || j; if (!lastPointCollided[pointId]) { if (faceChar.intersects(p)) { // Collect point score += 1; LK.setScore(score); scoreTxt.setText(score); // Flash effect LK.effects.flashObject(p, 0xffffff, 200); p.destroy(); points.splice(j, 1); lastPointCollided[pointId] = true; continue; } } } // --- Collision state transitions --- if (!lastCollided && collided) { // Check for shield first var usedShield = false; if (typeof storage !== "undefined" && storage.shields && storage.shields > 0) { storage.shields = storage.shields - 1; usedShield = true; LK.effects.flashScreen(0x00ffff, 600); faceChar.flash(); // Don't trigger game over, just consume shield } // If no shield, check for extra life else if (typeof storage !== "undefined" && storage.extraLives && storage.extraLives > 0) { storage.extraLives = storage.extraLives - 1; if (typeof updateLivesDisplay === "function") { updateLivesDisplay(); } // Update hearts display LK.effects.flashScreen(0x00ff00, 600); faceChar.flash(); // Don't trigger game over, just consume extra life } // No shield or extra life, trigger game over else { LK.effects.flashScreen(0xff0000, 800); faceChar.flash(); LK.showGameOver(); return; } } lastCollided = collided; // --- Difficulty scaling --- if (LK.ticks % 300 === 0 && obstacleSpeed < 32) { obstacleSpeed += 1.2; if (minObstacleInterval > 20) { minObstacleInterval -= 2; } if (minPointInterval > 30) { minPointInterval -= 2; } } }; // --- Instructions text removed --- // --- Center score text --- scoreTxt.x = 0; scoreTxt.y = 20; // --- Clean up on game over --- game.on('destroy', function () { // Save score to persistent storage before reset if (typeof storage !== "undefined") { storage.lastScore = score; // Only add this run's score to totalPoints if score > 0 if (score > 0) { var totalPoints = storage.totalPoints || 0; // Convert 2 game points to 1 Coin storage.totalPoints = totalPoints + Math.floor(score / 2); } if (typeof updatePersistentPointsDisplay === "function") { updatePersistentPointsDisplay(); } if (persistentPointsText) { persistentPointsText.setText("Coin: 0"); } } // Destroy all active bullets for (var i_bullet_cleanup = bullets.length - 1; i_bullet_cleanup >= 0; i_bullet_cleanup--) { if (bullets[i_bullet_cleanup] && bullets[i_bullet_cleanup].destroy) { bullets[i_bullet_cleanup].destroy(); } } bullets = []; obstacles = []; points = []; lastPointCollided = {}; lastCollided = false; lastFaceX = GAME_W / 2; lastFaceY = GAME_H - 400; if (faceChar) { faceChar.x = GAME_W / 2; faceChar.y = GAME_H - 400; } score = 0; LK.setScore(0); scoreTxt.setText('0'); // Reset level variables currentLevel = 1; if (levelText) { levelText.setText('Level: ' + currentLevel); levelText.x = 250; levelText.y = 20; } // lastLevelUpTick will be reset correctly when the game starts again via the main menu button. // Setting to 0 here is a safe default if the game could restart without the menu. lastLevelUpTick = 0; elapsedSeconds = 0; if (elapsedTimeText) { elapsedTimeText.setText('Time: 0s'); } if (gun) { // gun instance is a child of game, so it will be destroyed automatically by LK. // We just need to nullify the reference. gun = null; } // Recreate gun for new game gun = new Gun(); game.addChild(gun); // Reset lives to 3 for new game if (typeof storage !== "undefined") { storage.extraLives = 3; if (typeof updateLivesDisplay === "function") { updateLivesDisplay(); } // Update hearts display } // Show shop after game over showShop(); // Reset scoreTxt to show 0 for new game if (typeof scoreTxt !== "undefined") { scoreTxt.setText('0'); } });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (initialSpeed) {
var self = Container.call(this);
// Added initialSpeed parameter
//Create and attach asset.
self.visual = self.attachAsset('bullet', {
// Uses the existing 'bullet' image asset
anchorX: 0.5,
anchorY: 0.5
});
self.speed = initialSpeed; // Use the passed initialSpeed for movement
// If this instance of bullet is attached, this method will be called every tick by the LK engine automatically.
self.update = function () {
self.y += self.speed;
};
return self;
});
// Coin collectible class (was PointCollectible)
var CoinCollectible = Container.expand(function () {
var self = Container.call(this);
var coin = self.attachAsset('coin_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player character controlled by face
var FaceChar = Container.expand(function () {
var self = Container.call(this);
var _char = self.attachAsset('faceChar', {
anchorX: 0.5,
anchorY: 0.5
});
// For possible future effects
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 300);
};
return self;
});
var Gun = Container.expand(function () {
var self = Container.call(this);
// The visual asset for the gun
self.visual = self.attachAsset('gun_visual', {
anchorX: 0.5,
// Anchor to its horizontal center
anchorY: 1.0 // Anchor to its bottom edge (base of the gun)
});
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Speed is set on creation
self.speed = 0;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Shop class for purchasing items
var Shop = Container.expand(function () {
var self = Container.call(this);
var items = [{
name: "Speed Boost",
cost: 10
}, {
name: "Extra Life",
cost: 20
}, {
name: "Shield",
cost: 15
}];
var shopText = new Text2("Shop", {
size: 100,
fill: 0xFFFFFF
});
shopText.anchor.set(0.5, 0);
self.addChild(shopText);
shopText.y = -200;
// Show current persistent points
var pointsText = new Text2("", {
size: 70,
fill: 0xFFFF00
});
pointsText.anchor.set(0.5, 0);
pointsText.y = -100;
self.addChild(pointsText);
function updatePointsText() {
var totalPoints = 0;
var extraLives = 0;
var shields = 0;
if (typeof storage !== "undefined") {
totalPoints = storage.totalPoints || 0;
extraLives = typeof storage.extraLives !== "undefined" ? storage.extraLives : 3;
shields = storage.shields || 0;
}
pointsText.setText("Points: " + totalPoints + " | Extra Lives: " + extraLives + " | Shields: " + shields);
// Also update global scoreTxt if shop is open
if (typeof scoreTxt !== "undefined") {
scoreTxt.setText(totalPoints);
}
}
updatePointsText();
// Add shop items and purchase logic
items.forEach(function (item, index) {
var itemText = new Text2(item.name + " - " + item.cost + " points", {
size: 70,
fill: 0xFFFFFF
});
itemText.anchor.set(0.5, 0);
itemText.y = index * 100;
self.addChild(itemText);
// Enable purchase on tap/click
itemText.interactive = true;
itemText.buttonMode = true;
itemText.down = function (x, y, obj) {
var totalPoints = 0;
if (typeof storage !== "undefined") {
totalPoints = storage.totalPoints || 0;
}
if (totalPoints >= item.cost) {
// Deduct cost and update storage
totalPoints -= item.cost;
if (typeof storage !== "undefined") {
storage.totalPoints = totalPoints;
}
updatePointsText();
if (typeof updatePersistentPointsDisplay === "function") {
updatePersistentPointsDisplay();
}
// Show feedback (flash)
LK.effects.flashObject(itemText, 0x00FF00, 400);
// Apply item effect in game
if (item.name === "Extra Life") {
// Give player an extra life (persist for next game)
if (typeof storage !== "undefined") {
var extraLives = storage.extraLives || 0;
storage.extraLives = extraLives + 1;
if (typeof updateLivesDisplay === "function") {
updateLivesDisplay();
} // Update heart icons UI
// updatePointsText(); // This is already called after storage.totalPoints update, which also updates lives text in shop
}
}
if (item.name === "Shield") {
// Give player a shield (persist for next game)
if (typeof storage !== "undefined") {
var shields = storage.shields || 0;
storage.shields = shields + 1;
}
}
// Optionally: disable item after purchase (one-time buy)
// itemText.alpha = 0.5;
// itemText.interactive = false;
} else {
// Not enough points, flash red
LK.effects.flashObject(itemText, 0xFF0000, 400);
}
};
});
return self;
});
/****
* Initialize Game
****/
//
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Level variables
var currentLevel = 1;
var levelText;
var levelUpInterval = 30 * 60; // Level up every 30 seconds (1800 ticks)
var lastLevelUpTick = 0;
// Show shop on demand (e.g. after game over or via button)
// --- Shop display ---
// Asset for life display
// New asset for Kimchi
function showShop() {
var shop = new Shop();
// Center shop based on its size
shop.x = GAME_W / 2;
shop.y = GAME_H / 2;
shop.anchorX = 0.5;
shop.anchorY = 0.5;
game.addChild(shop);
// Bring shop to top (after all gameplay elements)
if (shop.parent && shop.parent.children) {
shop.parent.removeChild(shop);
shop.parent.addChild(shop);
}
// Remove shop after 5 seconds
LK.setTimeout(function () {
if (shop && shop.parent) {
shop.destroy();
}
}, 5000);
}
// --- Main Menu Overlay ---
var mainMenuOverlay;
function showMainMenu() {
// Prevent duplicate overlays
if (mainMenuOverlay && mainMenuOverlay.parent) return;
mainMenuOverlay = new Container();
// Semi-transparent background
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 30,
scaleY: 40,
x: GAME_W / 2,
y: GAME_H / 2,
tint: 0x000000
});
bg.alpha = 0.7;
mainMenuOverlay.addChild(bg);
// Game Title
var title = new Text2("Ana Menü", {
size: 180,
fill: 0xFFF700
});
title.anchor.set(0.5, 0.5);
title.x = GAME_W / 2;
title.y = GAME_H / 2 - 350;
mainMenuOverlay.addChild(title);
// Start Button
var startBtn = new Text2("Başla", {
size: 140,
fill: 0xFFFFFF
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = GAME_W / 2;
startBtn.y = GAME_H / 2 + 100;
startBtn.interactive = true;
startBtn.buttonMode = true;
startBtn.down = function (x, y, obj) {
if (mainMenuOverlay && mainMenuOverlay.parent) {
mainMenuOverlay.destroy();
}
// Activate gameplay
gameActive = true;
// Reset timer for new session
gameStartTime = LK.ticks;
lastLevelUpTick = LK.ticks; // Initialize level timer with current game ticks
elapsedSeconds = 0;
if (elapsedTimeText) {
elapsedTimeText.setText('Time: 0s');
}
// Reset score and scoreTxt at game start
score = 0;
LK.setScore(0);
if (typeof scoreTxt !== "undefined") {
scoreTxt.setText('0');
}
};
mainMenuOverlay.addChild(startBtn);
// Optionally, add instructions
var info = new Text2("Ekrana tıklayarak ateş et\nYüzünü hareket ettirerek karakteri kontrol et", {
size: 70,
fill: 0xFFFFFF
});
info.anchor.set(0.5, 0.5);
info.x = GAME_W / 2;
info.y = GAME_H / 2 + 300;
mainMenuOverlay.addChild(info);
game.addChild(mainMenuOverlay);
}
// --- Gameplay lock until Başla is pressed ---
var gameActive = false; // Only true after Başla is pressed
// Show main menu at game start
LK.setTimeout(function () {
showMainMenu();
}, 10);
// Timer variables
var elapsedTimeText;
var gameStartTime = 0; // To store LK.ticks at the start of the game
var elapsedSeconds = 0; // To store the calculated elapsed seconds
// Obstacle: red rectangle
// Character: main player controlled by face
// Game area dimensions
var GAME_W = 2048;
var GAME_H = 2732;
// Player character
var faceChar = new FaceChar();
game.addChild(faceChar);
// Gun instance
var gun; // Gun instance
// Create and add the gun
gun = new Gun();
game.addChild(gun);
// Initial position: bottom center, 400px from bottom
faceChar.x = GAME_W / 2;
faceChar.y = GAME_H - 400;
// Arrays for obstacles and points
var obstacles = [];
var points = [];
var bullets = []; // Array to store active bullets
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Level Display
levelText = new Text2('Level: 1', {
size: 80,
fill: 0xADD8E6 // A nice light blue color for the level text
});
levelText.anchor.set(0, 0); // Anchor to its own top-left
LK.gui.topLeft.addChild(levelText);
// Position it carefully in the top-left, avoiding the 100x100px menu icon area
levelText.x = 250; // Increased X to move it further right (100px for menu icon + 150px padding)
levelText.y = 20; // 20px from the top edge of the screen
// Persistent Currency ("Para") Display will be initialized by updatePersistentPointsDisplay
// Lives display elements
var livesContainer = new Container();
LK.gui.topRight.addChild(livesContainer); // Add to top-right GUI area
// livesContainer.anchor.set(1, 0); // Removed: Containers do not have an 'anchor' property for self-positioning.
livesContainer.x = -30; // Position 30px from the screen's right edge (relative to gui.topRight)
livesContainer.y = 30; // Position 30px from the screen's top edge (relative to gui.topRight)
var heartIcons = []; // Array to keep track of heart icon objects
function updateLivesDisplay() {
// Clear previously displayed hearts
heartIcons.forEach(function (icon) {
if (icon && icon.parent) {
// Check if icon exists and is attached
icon.destroy();
}
});
heartIcons = []; // Reset the array
var currentLives = 0;
// storage.extraLives is initialized by game logic (e.g., to 3 at game start/reset)
// before this function is typically called.
if (typeof storage !== "undefined" && typeof storage.extraLives === "number") {
currentLives = storage.extraLives;
}
// If storage.extraLives were somehow not a number here, 0 hearts would be displayed,
// accurately reflecting an issue with the life count data, rather than masking it.
// Optionally cap the number of hearts displayed to avoid UI clutter
var displayableLives = Math.min(currentLives, 5); // e.g., display max 5 hearts
for (var i = 0; i < displayableLives; i++) {
var heart = LK.getAsset('heart_icon', {
anchorX: 1,
anchorY: 0.5
}); // Anchor heart's right-middle
if (heart) {
// Position hearts horizontally from right to left within the livesContainer
// The rightmost heart (i=0) is at x=0 relative to container's (anchored) right edge
heart.x = -i * (heart.width + 10); // 10px spacing between hearts
heart.y = heart.height / 2; // Vertically center hearts in the container
livesContainer.addChild(heart);
heartIcons.push(heart);
}
}
}
// For keeping track of score
// Load score from storage if available (after game over)
var score = 0;
if (typeof storage !== "undefined") {
// Use lastScore for current run, totalPoints for shop
var lastScore = storage.lastScore;
if (typeof lastScore === "number" && !isNaN(lastScore)) {
score = lastScore;
LK.setScore(score);
}
// Ensure extraLives, shields and totalPoints are defined
if (typeof storage.extraLives === "undefined" || storage.extraLives === 0) {
storage.extraLives = 3;
}
if (typeof storage.shields === "undefined") {
storage.shields = 0;
}
if (typeof storage.totalPoints === "undefined") {
storage.totalPoints = 0;
} // Ensure totalPoints is initialized
updateLivesDisplay(); // Initialize hearts display
if (typeof updatePersistentPointsDisplay === "function") {
updatePersistentPointsDisplay();
} // Initialize Para display
}
// Elapsed Time Display
elapsedTimeText = new Text2('Time: 0s', {
size: 70,
fill: 0xFFFFFF // White color for the text
});
elapsedTimeText.anchor.set(0, 1); // Anchor to the bottom-left of the text
LK.gui.bottomLeft.addChild(elapsedTimeText);
// Position it 30px from the left and 30px from the bottom of the screen
elapsedTimeText.x = 30;
elapsedTimeText.y = -30;
// Initialize timer variables for the current game session
gameStartTime = LK.ticks;
elapsedSeconds = 0;
elapsedTimeText.setText('Time: ' + elapsedSeconds + 's'); // Set initial text
// For obstacle/point spawn timing
var lastObstacleTick = 0;
var lastPointTick = 0;
var lastShotTick = 0; // Tracks the game tick of the last shot
var fireCooldownTicks = 60; // Minimum ticks between shots (60 ticks = 1 second at 60 FPS)
var initialBulletSpeed = -30; // Base speed for bullets at level 1
var bulletSpeedIncrementPerLevel = 2; // How much faster (more negative) bullets get per level
// For difficulty scaling
var minObstacleInterval = 45; // ticks
var minPointInterval = 60; // ticks
var obstacleSpeed = 12; // px per frame, increases over time
// For collision state
var lastCollided = false;
// For point collection state
var lastPointCollided = {};
// Facekit smoothing
var lastFaceX = faceChar.x;
var lastFaceY = faceChar.y;
// Persistent currency display
var persistentPointsContainer;
var persistentPointsText;
var persistentCoinIcon;
function updatePersistentPointsDisplay() {
var spacing = 15; // Spacing between text and icon
// Ensure container exists and is on GUI
if (!persistentPointsContainer) {
persistentPointsContainer = new Container();
LK.gui.topRight.addChild(persistentPointsContainer); // Attach to top-right
persistentPointsContainer.x = -30; // Position 30px from the screen's right edge
persistentPointsContainer.y = 140; // Position below lives display (lives at y=30, hearts ~70px high)
}
// Ensure Text2 object for points exists
if (!persistentPointsText) {
persistentPointsText = new Text2("Coin: 0", {
size: 70,
fill: 0xFFFF00
});
persistentPointsText.anchor.set(1, 0.5); // Anchor right-middle for positioning
}
// Ensure text is child of container
if (persistentPointsText.parent !== persistentPointsContainer) {
if (persistentPointsText.parent) {
persistentPointsText.parent.removeChild(persistentPointsText);
}
persistentPointsContainer.addChild(persistentPointsText);
}
// Ensure coin icon asset exists
if (!persistentCoinIcon) {
persistentCoinIcon = LK.getAsset('coin_icon', {
// width/height will come from asset definition in Assets section
anchorX: 1,
// Anchor right
anchorY: 0.5 // Anchor middle for vertical alignment with text
});
}
// Ensure icon is child of container
if (persistentCoinIcon.parent !== persistentPointsContainer) {
if (persistentCoinIcon.parent) {
persistentCoinIcon.parent.removeChild(persistentCoinIcon);
}
persistentPointsContainer.addChild(persistentCoinIcon);
}
// Update text content
var currentPoints = 0;
if (typeof storage !== "undefined" && typeof storage.totalPoints === "number") {
currentPoints = storage.totalPoints;
}
persistentPointsText.setText("Coin: " + currentPoints);
// Position text and icon within the container for right alignment.
// The container's top-left is positioned by persistentPointsContainer.x and .y relative to LK.gui.topRight.
// Children x,y are relative to the container's top-left.
// persistentCoinIcon is rightmost. Its anchor (1, 0.5) means its right-middle point is at its x,y.
// We position its right-middle point at (0,0) relative to container's origin (top-left).
persistentCoinIcon.x = 0;
persistentCoinIcon.y = 0; // Vertically centered due to anchorY 0.5 and container's y
// persistentPointsText is to the left. Its anchor (1, 0.5) means its right-middle point is at its x,y.
// We position its right-middle point to be (persistentCoinIcon.width + spacing) to the left of the icon's right-middle.
persistentPointsText.x = -(persistentCoinIcon.width + spacing);
persistentPointsText.y = 0; // Vertically centered due to anchorY 0.5 and container's y
}
// Handle screen tap to fire bullets
game.down = function (x, y, obj) {
if (!gameActive) return; // Prevent firing if game not started
// Check cooldown: If current time (LK.ticks) minus last shot time is less than the cooldown period, do nothing.
if (LK.ticks - lastShotTick < fireCooldownTicks) {
return; // Still in cooldown period, prevent firing
}
if (gun && gun.visual && faceChar) {
// Ensure gun, its visual, and character exist
var currentBulletSpeed = initialBulletSpeed - (currentLevel - 1) * bulletSpeedIncrementPerLevel;
var newBullet = new Bullet(currentBulletSpeed);
newBullet.x = gun.x;
// Gun's anchorY is 1.0 (bottom). gun.y is its base.
// Gun's tip is gun.y - gun.visual.height.
// Bullet's anchorY is 0.5 (center).
newBullet.y = gun.y - gun.visual.height;
// Initialize lastY for off-screen detection logic
newBullet.lastY = newBullet.y;
bullets.push(newBullet);
game.addChild(newBullet);
lastShotTick = LK.ticks; // Update the time of the last shot
// Optional: Play a shooting sound if an asset 'shoot' is available
// if (LK.getSound('shoot')) {
// LK.getSound('shoot').play();
// Optionally, reset other game state here if needed
lastPointCollided = {};
}
;
};
// Main update loop
game.update = function () {
if (!gameActive) return; // Block all gameplay updates until Başla is pressed
// --- Timer Update ---
// Calculate total elapsed seconds since the game started
var currentTotalSeconds = Math.floor((LK.ticks - gameStartTime) / 60);
// Update the text only if the number of seconds has changed
if (currentTotalSeconds !== elapsedSeconds) {
elapsedSeconds = currentTotalSeconds;
if (elapsedTimeText) {
// Check if the text object exists
elapsedTimeText.setText('Time: ' + elapsedSeconds + 's');
}
}
// --- Level Progression ---
if (LK.ticks - lastLevelUpTick > levelUpInterval) {
currentLevel++;
if (levelText) {
levelText.setText('Level: ' + currentLevel);
}
lastLevelUpTick = LK.ticks;
// Increase difficulty with the new level
obstacleSpeed += 1.0; // Make obstacles faster
// Decrease spawn intervals (more frequent spawns), but with a minimum cap
if (minObstacleInterval > 15) {
// Don't let obstacles spawn too frenetically
minObstacleInterval -= 2;
}
if (minPointInterval > 20) {
// Same for points/coins
minPointInterval -= 2;
}
// Optional: Cap the maximum obstacle speed to prevent it from becoming unplayably fast
if (obstacleSpeed > 40) {
obstacleSpeed = 40;
}
// You could add other effects or difficulty adjustments here when a new level is reached!
}
// --- Facekit control ---
// Use mouthCenter for X/Y, clamp to game area
var fx = facekit.mouthCenter && typeof facekit.mouthCenter.x === "number" ? facekit.mouthCenter.x : GAME_W / 2;
var fy = facekit.mouthCenter && typeof facekit.mouthCenter.y === "number" ? facekit.mouthCenter.y : GAME_H - 400;
// Optionally, if mouth is open, move faster (boost)
var boost = facekit.mouthOpen ? 1.7 : 1.0;
// Smooth movement (lerp)
lastFaceX += (fx - lastFaceX) * 0.25 * boost;
lastFaceY += (fy - lastFaceY) * 0.25 * boost;
// Clamp to bounds (keep inside screen)
var charW = faceChar.width;
var charH = faceChar.height;
var minX = charW / 2 + 40;
var maxX = GAME_W - charW / 2 - 40;
var minY = charH / 2 + 120;
var maxY = GAME_H - charH / 2 - 40;
faceChar.x = Math.max(minX, Math.min(maxX, lastFaceX));
faceChar.y = Math.max(minY, Math.min(maxY, lastFaceY));
// --- Spawn obstacles ---
// Update gun position to follow faceChar
if (gun && faceChar) {
gun.x = faceChar.x;
// Position the base of the gun (anchorY = 1.0) at the top edge of faceChar
gun.y = faceChar.y - faceChar.height / 2;
}
// --- Manage Bullets ---
for (var k = bullets.length - 1; k >= 0; k--) {
var bullet = bullets[k];
// Initialize lastY for tracking changes on Y if not already done (e.g. if not set at creation)
if (bullet.lastY === undefined) {
bullet.lastY = bullet.y;
}
// bullet.update(); // Bullet's own update method is called automatically by LK engine if bullet is added to game stage
// Off-Screen Detection (top of screen)
// A bullet's y is its center (anchorY: 0.5).
// It's off-screen if its center y is < -(bullet.visual.height / 2).
var bulletHeight = bullet.visual ? bullet.visual.height : 50; // Fallback height
var offScreenThreshold = -(bulletHeight / 2) - 10; // Add a small buffer beyond the edge
if (bullet.y < offScreenThreshold) {
// More robust check: if it *crossed* the boundary in this frame
if (bullet.lastY >= offScreenThreshold) {
bullet.destroy(); // Destroy can only be called from the main 'Game' class
bullets.splice(k, 1);
continue; // Skip further processing for this bullet
} else if (bullet.y < -GAME_H) {
// Failsafe: if it's way off screen (e.g. due to lag/teleport)
bullet.destroy();
bullets.splice(k, 1);
continue;
}
}
bullet.lastY = bullet.y; // Update last known Y position
// Bullet-Obstacle Collision
// Iterate through obstacles to check for collision with the current bullet
for (var obs_idx = obstacles.length - 1; obs_idx >= 0; obs_idx--) {
var obstacle = obstacles[obs_idx];
// Ensure the bullet still exists and is part of the game scene.
// This check is important because the bullet might have been destroyed by off-screen logic
// or by a previous collision in this same frame if multiple collisions were possible.
if (!bullet || !bullet.parent) {
// If bullet is no longer valid (e.g., already destroyed and removed from its parent container),
// then stop checking it against further obstacles.
break;
}
// Ensure the obstacle also still exists and is part of the game scene.
if (obstacle && obstacle.parent && bullet.intersects(obstacle)) {
// Collision detected!
// Destroy the obstacle
obstacle.destroy();
obstacles.splice(obs_idx, 1);
// Award points for destroying obstacle
score += 5;
LK.setScore(score);
if (scoreTxt) {
scoreTxt.setText(score);
}
// Destroy the bullet
bullet.destroy();
bullets.splice(k, 1); // k is the index from the outer bullets loop
// Since this bullet has been destroyed, break from iterating through more obstacles for it.
// The outer loop (for bullets) will correctly handle the modified bullets array due to the splice.
break;
}
}
// End of Bullet-Obstacle Collision logic for the current bullet
}
// --- Spawn obstacles ---
if (LK.ticks - lastObstacleTick > minObstacleInterval + Math.floor(Math.random() * 40)) {
var obs = new Obstacle();
// Random X, avoid leftmost 100px (menu)
var obsW = obs.width;
var obsX = 100 + obsW / 2 + Math.random() * (GAME_W - obsW - 200);
obs.x = obsX;
obs.y = -obs.height / 2;
obs.speed = obstacleSpeed + Math.random() * 3;
obstacles.push(obs);
game.addChild(obs);
lastObstacleTick = LK.ticks;
}
// --- Spawn points ---
if (LK.ticks - lastPointTick > minPointInterval + Math.floor(Math.random() * 60)) {
var pt = new CoinCollectible();
var ptW = pt.width;
var ptX = 100 + ptW / 2 + Math.random() * (GAME_W - ptW - 200);
pt.x = ptX;
pt.y = -pt.height / 2;
pt.speed = obstacleSpeed * 0.8 + Math.random() * 2;
points.push(pt);
game.addChild(pt);
lastPointTick = LK.ticks;
}
// --- Move obstacles, check collision ---
var collided = false;
for (var i = obstacles.length - 1; i >= 0; i--) {
var o = obstacles[i];
o.update();
// Off screen
if (o.y - o.height / 2 > GAME_H + 100) {
o.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (faceChar.intersects(o)) {
collided = true;
}
}
// --- Move points, check collection ---
for (var j = points.length - 1; j >= 0; j--) {
var p = points[j];
p.update();
// Off screen
if (p.y - p.height / 2 > GAME_H + 100) {
p.destroy();
points.splice(j, 1);
continue;
}
// Collision with player
var pointId = p._lkid || p._id || j;
if (!lastPointCollided[pointId]) {
if (faceChar.intersects(p)) {
// Collect point
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// Flash effect
LK.effects.flashObject(p, 0xffffff, 200);
p.destroy();
points.splice(j, 1);
lastPointCollided[pointId] = true;
continue;
}
}
}
// --- Collision state transitions ---
if (!lastCollided && collided) {
// Check for shield first
var usedShield = false;
if (typeof storage !== "undefined" && storage.shields && storage.shields > 0) {
storage.shields = storage.shields - 1;
usedShield = true;
LK.effects.flashScreen(0x00ffff, 600);
faceChar.flash();
// Don't trigger game over, just consume shield
}
// If no shield, check for extra life
else if (typeof storage !== "undefined" && storage.extraLives && storage.extraLives > 0) {
storage.extraLives = storage.extraLives - 1;
if (typeof updateLivesDisplay === "function") {
updateLivesDisplay();
} // Update hearts display
LK.effects.flashScreen(0x00ff00, 600);
faceChar.flash();
// Don't trigger game over, just consume extra life
}
// No shield or extra life, trigger game over
else {
LK.effects.flashScreen(0xff0000, 800);
faceChar.flash();
LK.showGameOver();
return;
}
}
lastCollided = collided;
// --- Difficulty scaling ---
if (LK.ticks % 300 === 0 && obstacleSpeed < 32) {
obstacleSpeed += 1.2;
if (minObstacleInterval > 20) {
minObstacleInterval -= 2;
}
if (minPointInterval > 30) {
minPointInterval -= 2;
}
}
};
// --- Instructions text removed ---
// --- Center score text ---
scoreTxt.x = 0;
scoreTxt.y = 20;
// --- Clean up on game over ---
game.on('destroy', function () {
// Save score to persistent storage before reset
if (typeof storage !== "undefined") {
storage.lastScore = score;
// Only add this run's score to totalPoints if score > 0
if (score > 0) {
var totalPoints = storage.totalPoints || 0;
// Convert 2 game points to 1 Coin
storage.totalPoints = totalPoints + Math.floor(score / 2);
}
if (typeof updatePersistentPointsDisplay === "function") {
updatePersistentPointsDisplay();
}
if (persistentPointsText) {
persistentPointsText.setText("Coin: 0");
}
}
// Destroy all active bullets
for (var i_bullet_cleanup = bullets.length - 1; i_bullet_cleanup >= 0; i_bullet_cleanup--) {
if (bullets[i_bullet_cleanup] && bullets[i_bullet_cleanup].destroy) {
bullets[i_bullet_cleanup].destroy();
}
}
bullets = [];
obstacles = [];
points = [];
lastPointCollided = {};
lastCollided = false;
lastFaceX = GAME_W / 2;
lastFaceY = GAME_H - 400;
if (faceChar) {
faceChar.x = GAME_W / 2;
faceChar.y = GAME_H - 400;
}
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
// Reset level variables
currentLevel = 1;
if (levelText) {
levelText.setText('Level: ' + currentLevel);
levelText.x = 250;
levelText.y = 20;
}
// lastLevelUpTick will be reset correctly when the game starts again via the main menu button.
// Setting to 0 here is a safe default if the game could restart without the menu.
lastLevelUpTick = 0;
elapsedSeconds = 0;
if (elapsedTimeText) {
elapsedTimeText.setText('Time: 0s');
}
if (gun) {
// gun instance is a child of game, so it will be destroyed automatically by LK.
// We just need to nullify the reference.
gun = null;
}
// Recreate gun for new game
gun = new Gun();
game.addChild(gun);
// Reset lives to 3 for new game
if (typeof storage !== "undefined") {
storage.extraLives = 3;
if (typeof updateLivesDisplay === "function") {
updateLivesDisplay();
} // Update hearts display
}
// Show shop after game over
showShop();
// Reset scoreTxt to show 0 for new game
if (typeof scoreTxt !== "undefined") {
scoreTxt.setText('0');
}
});
A tiny bomb. In-Game asset. 2d. High contrast. No shadows
Gold coin. In-Game asset. 2d. High contrast. No shadows
Mermi. In-Game asset. 2d. High contrast. No shadows
Kırmızı kalp. In-Game asset. 2d. High contrast. No shadows
Silah. In-Game asset. 2d. High contrast. No shadows
faceChar. In-Game asset. 2d. High contrast. No shadows