/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // --- MovingPlatform Class --- var MovingPlatform = Container.expand(function () { var self = Container.call(this); // Default size self.width = 400; self.height = 40; // Use a distinct color for moving platforms var movingColor = 0xffe066; var platAsset = self.attachAsset('platform', { width: self.width, height: self.height, color: movingColor, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Movement bounds self.minX = 0; self.maxX = 0; self.speed = 6 + Math.random() * 4; // random speed self.direction = Math.random() < 0.5 ? 1 : -1; // Set platform size self.setSize = function (w, h) { self.width = w; self.height = h; platAsset.width = w; platAsset.height = h; }; // Set movement bounds self.setMoveBounds = function (minX, maxX) { self.minX = minX; self.maxX = maxX; }; // Update method for moving self.update = function () { if (self.minX === self.maxX) { return; } if (self.lastX === undefined) { self.lastX = self.x; } self.x += self.speed * self.direction; if (self.x < self.minX) { self.x = self.minX; self.direction *= -1; } else if (self.x > self.maxX) { self.x = self.maxX; self.direction *= -1; } self.lastX = self.x; }; return self; }); // Do not generate platforms or add player until after character is selected and game is started // All gameplay setup is now handled in resetGame, which is only called after character selection // --- Platform Class --- var Platform = Container.expand(function () { var self = Container.call(this); // Default size self.width = 400; self.height = 40; // Platform visual var platAsset = self.attachAsset('platform', { width: self.width, height: self.height, color: PLATFORM_COLOR, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Set platform size self.setSize = function (w, h) { self.width = w; self.height = h; platAsset.width = w; platAsset.height = h; }; return self; }); // --- Player Class --- var Player = Container.expand(function () { var self = Container.call(this); // Use selected character asset, fallback to 'player' var charId = selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player'; var asset = self.attachAsset(charId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = 50; // for collision, matches asset size self.vx = 0; self.vy = 0; self.ax = 0; // acceleration x self.ay = 0; // acceleration y self.lastVx = 0; self.lastVy = 0; self.lastAx = 0; self.lastAy = 0; self.isJumping = false; // Move left self.moveLeft = function () { self.vx = -playerMoveSpeed; }; // Move right self.moveRight = function () { self.vx = playerMoveSpeed; }; // Stop movement self.stopMove = function () { self.vx = 0; }; // Jump self.jump = function () { self.vy = -playerJumpVelocity; self.isJumping = true; LK.getSound('jump').play(); }; // Update method for velocity/acceleration tracking and dynamic triggers self.update = function () { // Calculate acceleration self.ax = self.vx - self.lastVx; self.ay = self.vy - self.lastVy; // Example: Trigger effect if horizontal velocity changes rapidly if (self.lastVx !== undefined && Math.abs(self.vx - self.lastVx) > 10) { // Hız çizgisi efekti veya başka bir efekt tetiklenebilir // Örnek: LK.effects.flashObject(self, 0x00ffff, 200); } // Example: Trigger effect if vertical acceleration exceeds threshold (e.g. falling fast) if (self.lastAy !== undefined && Math.abs(self.ay) > 20) { // Tehlike uyarısı veya başka bir efekt tetiklenebilir // Örnek: LK.effects.flashScreen(0xffe066, 100); } // Store last values for next update self.lastVx = self.vx; self.lastVy = self.vy; self.lastAx = self.ax; self.lastAy = self.ay; }; return self; }); // --- Powerup Class --- var Powerup = Container.expand(function () { var self = Container.call(this); // Use the 'powerup' asset, centered var asset = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.radius = 50; // for collision, matches asset size self.collected = false; self.visible = true; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Constants --- // analog image asset for analog control var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLATFORM_MIN_WIDTH = 300; var PLATFORM_MAX_WIDTH = 600; var PLATFORM_HEIGHT = 40; var PLATFORM_MIN_GAP = 250; var PLATFORM_MAX_GAP = 600; var PLATFORM_SIDE_MARGIN = 100; var PLATFORM_COLOR = 0x4ecdc4; var PLAYER_START_X = GAME_WIDTH / 2; var PLAYER_START_Y = GAME_HEIGHT - 400; var playerMoveSpeed = 18; var playerJumpVelocity = 75; var gravity = 3.2; var maxFallSpeed = 60; var scrollThreshold = GAME_HEIGHT / 2; var platformScrollSpeed = 0; // will be set dynamically // --- State --- var platforms = []; var player; var score = 0; var scoreTxt; var highestY = PLAYER_START_Y; var dragDir = 0; // -1: left, 1: right, 0: none var isTouching = false; var lastTouchX = 0; var lastTouchY = 0; var gameOver = false; // --- Trail State --- var playerTrail = []; var maxTrailLength = 16; // Number of trail dots var trailDotAlphaStart = 0.35; // Starting alpha for the oldest dot var trailDotAlphaEnd = 0.8; // Ending alpha for the newest dot // --- Powerup State --- var powerups = []; var jumpBoostActive = false; var jumpBoostTimeout = null; // --- Helper: Spawn Powerup --- function spawnPowerup(x, y) { var p = new Powerup(); p.x = x; p.y = y - 80; // float above platform p.visible = true; // ensure powerup is visible when spawned powerups.push(p); game.addChild(p); return p; } // --- GUI --- scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Helper: Platform Generation --- function randomPlatformWidth() { return PLATFORM_MIN_WIDTH + Math.floor(Math.random() * (PLATFORM_MAX_WIDTH - PLATFORM_MIN_WIDTH)); } function randomPlatformX(width) { var minX = PLATFORM_SIDE_MARGIN + width / 2; var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2; return minX + Math.random() * (maxX - minX); } function randomPlatformGap() { return PLATFORM_MIN_GAP + Math.random() * (PLATFORM_MAX_GAP - PLATFORM_MIN_GAP); } // --- Helper: Platform Creation --- function createPlatform(x, y, width, type) { // type: "moving" or undefined for normal var plat; if (type === "moving" || type === undefined && Math.random() < 0.25 && y < PLAYER_START_Y + 100) { plat = new MovingPlatform(); plat.setSize(width, PLATFORM_HEIGHT); plat.x = x; plat.y = y; // Set movement bounds so it doesn't go off screen var minX = PLATFORM_SIDE_MARGIN + width / 2; var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2; plat.setMoveBounds(minX, maxX); } else { plat = new Platform(); plat.setSize(width, PLATFORM_HEIGHT); plat.x = x; plat.y = y; } platforms.push(plat); game.addChild(plat); // Powerup: spawn with 10% chance on top of platform if (Math.random() < 0.10) { spawnPowerup(x, y - PLATFORM_HEIGHT / 2); } return plat; } // --- Helper: Remove platform --- function removePlatform(plat) { var idx = platforms.indexOf(plat); if (idx !== -1) { platforms.splice(idx, 1); } plat.destroy(); } // --- Helper: Collision Detection (AABB) --- function playerOnPlatform(player, plat) { // Only check if player is falling if (player.vy < 0) { return false; } // Player bottom var px = player.x; var py = player.y + player.radius * 0.8; // Platform bounds var left = plat.x - plat.width / 2; var right = plat.x + plat.width / 2; var top = plat.y - plat.height / 2; var bottom = plat.y + plat.height / 2; // Require player's feet to be above the platform top in the previous frame and now inside platform if (player.lastY !== undefined) { var lastPy = player.lastY + player.radius * 0.8; // Only allow landing if last frame was above platform and now inside if (lastPy <= top && // last frame above platform py > top && py < bottom && // now inside platform vertical bounds px >= left && px <= right // inclusive horizontal bounds for small platforms ) { return true; } } // For very small platforms, allow a little tolerance (±1px) to ensure collision is detected if (plat.width < 40) { if (player.lastY !== undefined && lastPy <= top && py > top && py < bottom && px >= left - 1 && px <= right + 1) { return true; } } return false; } // --- Helper: Generate Initial Platforms --- function generateInitialPlatforms() { var y = PLAYER_START_Y + 200; // First platform: wide, centered, at bottom createPlatform(GAME_WIDTH / 2, y, 600); // Remove circular platform creation, just continue with normal platforms y -= 350; // Generate upwards, but with fewer platforms (reduce density) // Ensure all platforms are reachable by limiting vertical gap to jump height var platformCount = 0; var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // s = v^2/(2g) var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP); // 90% of max jump height for margin // --- Calculate reachable circle for the player --- var jumpRadius = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // max jump height var jumpCircleRadius = jumpRadius * 0.9; // 90% for margin var circleCenterX = GAME_WIDTH / 2; var circleCenterY = PLAYER_START_Y; // Place at least one platform inside the jump circle and above the player var mustHavePlatformY = PLAYER_START_Y - jumpCircleRadius * 0.7; // 70% up the circle var mustHavePlatformX = circleCenterX + (Math.random() - 0.5) * jumpCircleRadius * 0.7; // random X inside circle var mustHavePlatformWidth = randomPlatformWidth(); createPlatform(mustHavePlatformX, mustHavePlatformY, mustHavePlatformWidth); // Now fill the rest of the platforms as before, but avoid overlapping the must-have platform while (y > 400 && platformCount < 7) { // Always use a gap that is not more than safeGap, so player can always reach var gap = Math.min(randomPlatformGap(), safeGap); y -= gap + 80; // slightly increase gap for fewer platforms var width = randomPlatformWidth(); var x = randomPlatformX(width); // Avoid placing a platform too close to the must-have platform if (Math.abs(y - mustHavePlatformY) < 100) { continue; } createPlatform(x, y, width); platformCount++; } } // --- Helper: Generate New Platforms Above --- function generatePlatformsIfNeeded() { // Find highest platform var highestPlatY = GAME_HEIGHT; for (var i = 0; i < platforms.length; i++) { if (platforms[i].y < highestPlatY) { highestPlatY = platforms[i].y; } } // Limit to 12 platforms on screen // Restore to original logic: just fill up with normal platforms, no must-have guarantee while (highestPlatY > -200 && platforms.length < 12) { var width = randomPlatformWidth(); var x = randomPlatformX(width); var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity); var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP); var gap = Math.min(randomPlatformGap(), safeGap); highestPlatY -= gap; // Ensure no two platforms have the same Y coordinate (within 1px tolerance) var yUnique = true; for (var j = 0; j < platforms.length; j++) { if (Math.abs(platforms[j].y - highestPlatY) < 1) { yUnique = false; break; } } if (yUnique) { createPlatform(x, highestPlatY, width); } else { // If not unique, try a slightly different gap highestPlatY -= 10 + Math.random() * 20; } } } // --- Helper: Remove Offscreen Platforms --- function removeOffscreenPlatforms() { for (var i = platforms.length - 1; i >= 0; i--) { if (platforms[i].y > GAME_HEIGHT + 200) { removePlatform(platforms[i]); } } } // --- Helper: Update Score --- function updateScore() { // Score is highestY reached (inverted, since y decreases as we go up) var newScore = Math.max(0, Math.floor((PLAYER_START_Y - highestY) / 10)); if (newScore > score) { score = newScore; scoreTxt.setText(score); // Play sound at every 1000 and multiples of 1000 if (score > 0 && score % 1000 === 0) { LK.getSound('jump').play(); } } } // --- Character Selection --- var characterOptions = [{ id: 'player', color: 0xf67280, label: 'Kırmızı' }, { id: 'player2', color: 0x4ecdc4, label: 'Mavi' }, { id: 'player3', color: 0xffe066, label: 'Sarı' }]; var selectedCharacter = null; var characterSelectNodes = []; var characterSelectActive = false; // --- Language State --- var LANG_TR = 'tr'; var LANG_EN = 'en'; var currentLang = LANG_EN; // Default language is English // --- Language Strings --- var langStrings = { tr: { title: 'Zıpla!', info: 'En yükseğe zıpla, güçlendiricileri topla!', start: 'Başla', lang_tr: 'Türkçe', lang_en: 'İngilizce' }, en: { title: 'Jump!', info: 'Jump as high as you can, collect powerups!', start: 'Start', lang_tr: 'Turkish', lang_en: 'English' } }; // --- Start Menu --- var startMenuNodes = []; var startMenuActive = true; var langBtnTr, langBtnEn; function showStartMenu() { startMenuActive = true; // Remove any previous menu nodes for (var i = 0; i < startMenuNodes.length; i++) { if (startMenuNodes[i].parent) { startMenuNodes[i].parent.removeChild(startMenuNodes[i]); } } startMenuNodes = []; // Add a white background covering the whole screen using a dedicated menu background asset var menuBg = LK.getAsset('background', { width: GAME_WIDTH, height: GAME_HEIGHT, anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(menuBg); startMenuNodes.push(menuBg); // --- Language Toggle Buttons (Top Right) --- // Language buttons: stack vertically, each with a colored background for visibility var langBtnY = 80; var langBtnSpacingY = 130; var langBtnSize = 110; // Türkçe button background var langBgTr = LK.getAsset('platform', { width: 220, height: 90, color: currentLang === LANG_TR ? 0xffe066 : 0xfaf3dd, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH - 140, y: langBtnY }); game.addChild(langBgTr); startMenuNodes.push(langBgTr); langBtnTr = new Text2(langStrings.tr.lang_tr, { size: 54, fill: currentLang === LANG_TR ? "#ff9900" : "#22223B" }); langBtnTr.anchor.set(0.5, 0.5); langBtnTr.x = GAME_WIDTH - 140; langBtnTr.y = langBtnY; langBtnTr.interactive = true; langBtnTr.buttonMode = true; langBtnTr._lang = LANG_TR; game.addChild(langBtnTr); startMenuNodes.push(langBtnTr); // English button background var langBgEn = LK.getAsset('platform', { width: 220, height: 90, color: currentLang === LANG_EN ? 0xffe066 : 0xfaf3dd, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH - 140, y: langBtnY + langBtnSpacingY }); game.addChild(langBgEn); startMenuNodes.push(langBgEn); langBtnEn = new Text2(langStrings.tr.lang_en, { size: 54, fill: currentLang === LANG_EN ? "#ff9900" : "#22223B" }); langBtnEn.anchor.set(0.5, 0.5); langBtnEn.x = GAME_WIDTH - 140; langBtnEn.y = langBtnY + langBtnSpacingY; langBtnEn.interactive = true; langBtnEn.buttonMode = true; langBtnEn._lang = LANG_EN; game.addChild(langBtnEn); startMenuNodes.push(langBtnEn); // --- Animated Game Title --- var titleY = 320; var title = new Text2(langStrings[currentLang].title, { size: 220, fill: 0xF67280, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); title.anchor.set(0.5, 0.5); title.x = GAME_WIDTH / 2; title.y = titleY; game.addChild(title); startMenuNodes.push(title); // Animate title with a gentle up-down floating effect tween(title, { y: titleY + 40 }, { duration: 1200, yoyo: true, repeat: Infinity, easing: tween.easeInOutSine }); // --- Info Text --- var infoTxt = new Text2(langStrings[currentLang].info, { size: 70, fill: 0x22223B }); infoTxt.anchor.set(0.5, 0.5); infoTxt.x = GAME_WIDTH / 2; infoTxt.y = titleY + 160; game.addChild(infoTxt); startMenuNodes.push(infoTxt); // --- Character Selection with Visual Highlight --- var charY = GAME_HEIGHT * 0.75; var charSpacing = 320; var charStartX = GAME_WIDTH / 2 - (characterOptions.length - 1) / 2 * charSpacing; for (var i = 0; i < characterOptions.length; i++) { var opt = characterOptions[i]; // Add a highlight ring behind the character if selected var highlight = LK.getAsset('platform', { width: 200, height: 200, color: 0xfaf3dd, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: charStartX + i * charSpacing, y: charY }); highlight.visible = selectedCharacter && selectedCharacter.id === opt.id; highlight._charIndex = i; game.addChild(highlight); startMenuNodes.push(highlight); var node = LK.getAsset(opt.id, { width: 160, height: 160, color: opt.color, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: charStartX + i * charSpacing, y: charY }); node.interactive = true; node.buttonMode = true; // Add colored background behind character node for visibility (instead of label) var charBg = LK.getAsset('platform', { width: 180, height: 180, color: opt.color, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5, x: charStartX + i * charSpacing, y: charY }); game.addChild(charBg); startMenuNodes.push(charBg); node._charIndex = i; game.addChild(node); startMenuNodes.push(node); } // --- Başla Button with Glow Effect --- var btnWidth = 600; var btnHeight = 180; var btnY = charY - 350; // Başla button background (removed image asset, use only colored ellipse asset) var btnBg = LK.getAsset('baslaBtnBg', { width: btnWidth + 40, height: btnHeight + 40, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: btnY }); game.addChild(btnBg); startMenuNodes.push(btnBg); var btn = LK.getAsset('baslaBtnBg', { width: btnWidth, height: btnHeight, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: btnY }); btn.interactive = true; btn.buttonMode = true; // Add a glowing effect to Başla button (pulsing scale) tween(btn, { scaleX: 1.05, scaleY: 1.05 }, { duration: 900, yoyo: true, repeat: Infinity, easing: tween.easeInOutSine }); // Add an asset inside the Başla button (e.g. a star icon, using 'powerup' asset as example) var btnInnerAsset = LK.getAsset('powerup', { width: 90, height: 90, anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: btnY }); game.addChild(btnInnerAsset); startMenuNodes.push(btnInnerAsset); // Başla button label (will be updated on language change) var btnLabel = new Text2(langStrings[currentLang].start, { size: 110, fill: "#fff" }); btnLabel.anchor.set(0.5, 0.5); btnLabel.x = GAME_WIDTH / 2; btnLabel.y = btnY; btn._btnLabel = btnLabel; // reference for dynamic update game.addChild(btn); game.addChild(btnLabel); startMenuNodes.push(btn, btnLabel); // --- Language Button Event Handlers --- langBtnTr.down = function () { if (currentLang !== LANG_TR) { currentLang = LANG_TR; showStartMenu(); } }; langBtnEn.down = function () { if (currentLang !== LANG_EN) { currentLang = LANG_EN; showStartMenu(); } }; // Update Başla button label if present (for language switch) if (btn && btn._btnLabel) { btn._btnLabel.setText(langStrings[currentLang].start); } } function hideStartMenu() { for (var i = 0; i < startMenuNodes.length; i++) { if (startMenuNodes[i].parent) { startMenuNodes[i].parent.removeChild(startMenuNodes[i]); } } startMenuNodes = []; startMenuActive = false; } // Show character selection UI // character select is now part of start menu, so this is not needed function showCharacterSelect() {} // character select is now part of start menu, so this is not needed function hideCharacterSelect() {} // Listen for down event to select character or start game // --- Game Setup --- function resetGame() { // Remove old platforms for (var i = 0; i < platforms.length; i++) { platforms[i].destroy(); } platforms = []; // Remove old powerups for (var i = 0; i < powerups.length; i++) { powerups[i].destroy(); } powerups = []; // Remove player if exists if (player) { player.destroy(); } // Reset state score = 0; scoreTxt.setText(score); highestY = PLAYER_START_Y; dragDir = 0; isTouching = false; lastTouchX = 0; lastTouchY = 0; gameOver = false; // Reset trail for (var i = 0; i < playerTrail.length; i++) { if (playerTrail[i].parent) { playerTrail[i].parent.removeChild(playerTrail[i]); } } playerTrail = []; jumpBoostActive = false; if (jumpBoostTimeout) { LK.clearTimeout(jumpBoostTimeout); jumpBoostTimeout = null; } // Reset platform count for powerup spawn createPlatform.platformCount = 0; // Generate platforms generateInitialPlatforms(); // Create player player = new Player(); player.x = PLAYER_START_X; player.y = PLAYER_START_Y; player.vx = 0; player.vy = 0; player.isJumping = false; game.addChild(player); // (No circular platform creation here; handled only in generateInitialPlatforms) } // --- Analog Button for Left/Right Movement --- var analogBase = null, analogKnob = null; var analogActive = false; var analogStartX = 0, analogStartY = 0; var analogDir = 0; // -1: left, 1: right, 0: none var analogRadius = 180 * 1.2; // 1.2x larger var analogKnobRadius = 70 * 1.2; // 1.2x larger var analogCenterX = 0; var analogCenterY = 0; // Helper to create analog at a given position function showAnalogAt(x, y) { // Clamp analog so it doesn't go off screen (and not in top left 100x100) var minX = 100 + analogRadius; var maxX = GAME_WIDTH - analogRadius; var minY = analogRadius; var maxY = GAME_HEIGHT - analogRadius; analogCenterX = Math.max(minX, Math.min(maxX, x)); analogCenterY = Math.max(minY, Math.min(maxY, y)); // Remove previous analog if exists if (analogBase && analogBase.parent) { analogBase.parent.removeChild(analogBase); } if (analogKnob && analogKnob.parent) { analogKnob.parent.removeChild(analogKnob); } analogBase = LK.getAsset('analog', { width: analogRadius * 2, height: analogRadius * 2, anchorX: 0.5, anchorY: 0.5, x: analogCenterX, y: analogCenterY }); analogBase.alpha = 0.35; analogBase.interactive = true; analogBase.buttonMode = true; game.addChild(analogBase); analogKnob = LK.getAsset('analog', { width: analogKnobRadius * 2, height: analogKnobRadius * 2, anchorX: 0.5, anchorY: 0.5, x: analogCenterX, y: analogCenterY }); analogKnob.alpha = 0.7; analogKnob.interactive = true; analogKnob.buttonMode = true; game.addChild(analogKnob); // Attach handlers analogBase.down = analogKnob.down = function (x, y, obj) { if (startMenuActive || characterSelectActive || gameOver) { return; } analogActive = true; analogStartX = x; analogStartY = y; analogKnob.x = x; analogKnob.y = y; updateAnalogDir(x, y); }; } // Helper to update analog direction and player movement function updateAnalogDir(knobX, knobY) { var dx = knobX - analogCenterX; // Only consider horizontal movement if (dx < -30) { analogDir = -1; player.moveLeft(); } else if (dx > 30) { analogDir = 1; player.moveRight(); } else { analogDir = 0; player.stopMove(); } } // Analog input handlers game.down = function (x, y, obj) { // Block all gameplay input if start menu or character select is active if (startMenuActive || characterSelectActive) { // If start menu is active, handle menu logic if (startMenuActive) { var charSelected = false; // Check if a character was tapped for (var i = 0; i < startMenuNodes.length; i++) { var node = startMenuNodes[i]; if (node._charIndex !== undefined) { var dx = x - node.x; var dy = y - node.y; if (dx * dx + dy * dy < 80 * 80) { selectedCharacter = characterOptions[node._charIndex]; charSelected = true; // Visually highlight the selected character by updating highlight rings for (var j = 0; j < startMenuNodes.length; j++) { var n = startMenuNodes[j]; if (n._charIndex !== undefined && n.width === 200 && n.height === 200) { n.visible = n._charIndex === node._charIndex; } } } } } // Check if Başla button pressed (always last two nodes) var btn = startMenuNodes[startMenuNodes.length - 2]; var btnLabel = startMenuNodes[startMenuNodes.length - 1]; var dx = x - btn.x; var dy = y - btn.y; var baslaPressed = dx * dx / (btn.width * btn.width * 0.25) + dy * dy / (btn.height * btn.height * 0.25) < 1; if (baslaPressed && selectedCharacter) { // Show highlight for selected character before starting for (var j = 0; j < startMenuNodes.length; j++) { var n = startMenuNodes[j]; if (n._charIndex !== undefined && n.width === 200 && n.height === 200) { n.visible = selectedCharacter && characterOptions[n._charIndex].id === selectedCharacter.id; } } // Prevent multiple presses if (btn._pressed) { return; } btn._pressed = true; // Animate Başla button to scale up to 1.1x, then back to 1.0x, then start game after animation tween(btn, { scaleX: 1.1, scaleY: 1.1 }, { duration: 120, easing: tween.easeOutBack, onFinish: function onFinish() { tween(btn, { scaleX: 1.0, scaleY: 1.0 }, { duration: 80, easing: tween.easeInBack, onFinish: function onFinish() { hideStartMenu(); resetGame(); btn._pressed = false; } }); } }); return; } return; } return; } if (gameOver) { return; } // Always remove any existing analog before creating a new one if (analogBase && analogBase.parent) { analogBase.parent.removeChild(analogBase); } if (analogKnob && analogKnob.parent) { analogKnob.parent.removeChild(analogKnob); } analogBase = null; analogKnob = null; analogActive = false; // Show analog at touch location showAnalogAt(x, y); analogActive = true; analogStartX = x; analogStartY = y; analogKnob.x = x; analogKnob.y = y; updateAnalogDir(x, y); }; game.move = function (x, y, obj) { if (startMenuActive || characterSelectActive || gameOver) { return; } if (!analogActive || !analogBase || !analogKnob) { return; } // Clamp knob within analog base var dx = x - analogCenterX; var dy = y - analogCenterY; var dist = Math.sqrt(dx * dx + dy * dy); var maxDist = analogRadius - analogKnobRadius; if (dist > maxDist) { dx = dx * maxDist / dist; dy = dy * maxDist / dist; } analogKnob.x = analogCenterX + dx; analogKnob.y = analogCenterY + dy; updateAnalogDir(analogKnob.x, analogKnob.y); }; game.up = function (x, y, obj) { if (startMenuActive || characterSelectActive || gameOver) { return; } analogActive = false; analogDir = 0; player.stopMove(); // Hide analog after release if (analogBase && analogBase.parent) { analogBase.parent.removeChild(analogBase); } if (analogKnob && analogKnob.parent) { analogKnob.parent.removeChild(analogKnob); } analogBase = null; analogKnob = null; }; // --- Input Handling (Touch/Drag) --- // This input handler is now only used for gameplay, not for menu/character select // --- Main Game Loop --- game.update = function () { // Block all game updates if start menu or character select is active if (startMenuActive || characterSelectActive) { return; } if (gameOver) { return; } // --- Player Physics --- // Call player update for velocity/acceleration tracking and dynamic triggers if (typeof player.update === "function") { player.update(); } // Show player only when jumping if (player.isJumping && !player.parent) { game.addChild(player); } else if (!player.isJumping && player.parent) { player.parent.removeChild(player); } // --- Player Trail Effect --- // Add current player position to trail if (!gameOver && player.parent) { playerTrail.push({ x: player.x, y: player.y }); if (playerTrail.length > maxTrailLength) { playerTrail.shift(); } } // Remove old trail dots if any for (var i = 0; i < playerTrail.length; i++) { if (playerTrail[i].node && playerTrail[i].node.parent) { playerTrail[i].node.parent.removeChild(playerTrail[i].node); playerTrail[i].node = null; } } // Draw trail dots (skip if not enough points) for (var i = 0; i < playerTrail.length; i++) { var t = i / (playerTrail.length - 1); var alpha = trailDotAlphaStart + (trailDotAlphaEnd - trailDotAlphaStart) * t; var dot = LK.getAsset(selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player', { width: 60, height: 60, anchorX: 0.5, anchorY: 0.5, x: playerTrail[i].x, y: playerTrail[i].y }); dot.alpha = alpha; // Place behind player game.addChildAt(dot, 0); playerTrail[i].node = dot; } // Horizontal movement player.x += player.vx; // Wrap player horizontally: if player goes off right, appear at left; if off left, appear at right if (player.x > GAME_WIDTH + player.radius) { player.x = -player.radius; } if (player.x < -player.radius) { player.x = GAME_WIDTH + player.radius; } // Track lastY for platform collision logic player.lastY = player.y; // Vertical movement player.y += player.vy; player.vy += gravity; if (player.vy > maxFallSpeed) { player.vy = maxFallSpeed; } // --- Update moving platforms --- for (var i = 0; i < platforms.length; i++) { if (typeof platforms[i].update === "function" && platforms[i] instanceof MovingPlatform) { platforms[i].update(); } } // --- Platform Collision --- var landed = false; for (var i = 0; i < platforms.length; i++) { if (playerOnPlatform(player, platforms[i])) { // Only allow landing if falling if (player.vy >= 0) { player.y = platforms[i].y - platforms[i].height / 2 - player.radius * 0.8; player.jump(); landed = true; break; } } } // --- Scrolling --- // If player is above scroll threshold, move everything down if (player.y < scrollThreshold) { var dy = scrollThreshold - player.y; player.y = scrollThreshold; // Move all platforms down for (var i = 0; i < platforms.length; i++) { platforms[i].y += dy; } // Move all powerups down (so they behave like platforms) for (var i = 0; i < powerups.length; i++) { powerups[i].y += dy; } // Move backgrounds down bg1.y += dy; bg2.y += dy; // If a background moves completely below the screen, move it above the other for seamless repeat if (bg1.y >= GAME_HEIGHT) { bg1.y = bg2.y - BG_HEIGHT; } if (bg2.y >= GAME_HEIGHT) { bg2.y = bg1.y - BG_HEIGHT; } // Track highestY highestY -= dy; } else { // Track highestY if (player.y < highestY) { highestY = player.y; } // Also update backgrounds to follow player if not scrolling // (keeps backgrounds in sync if player falls) // This ensures backgrounds always cover the screen if (bg1.y > 0 && bg2.y > 0) { // If both are above, move the one further down above the other if (bg1.y > bg2.y) { bg1.y = bg2.y - BG_HEIGHT; } else { bg2.y = bg1.y - BG_HEIGHT; } } if (bg1.y < -BG_HEIGHT) { bg1.y = bg2.y + BG_HEIGHT; } if (bg2.y < -BG_HEIGHT) { bg2.y = bg1.y + BG_HEIGHT; } } // --- Remove Offscreen Platforms & Generate New Ones --- removeOffscreenPlatforms(); generatePlatformsIfNeeded(); // --- Powerup Collision --- for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; if (!p.collected) { // Simple circle collision var dx = player.x - p.x; var dy = player.y - p.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < player.radius + p.radius) { // Collect powerup p.collected = true; p.visible = false; // Activate jump boost jumpBoostActive = true; playerJumpVelocity = 90; // 1.5x original (60) // Clear previous timeout if any if (jumpBoostTimeout) { LK.clearTimeout(jumpBoostTimeout); } jumpBoostTimeout = LK.setTimeout(function () { jumpBoostActive = false; playerJumpVelocity = 60; jumpBoostTimeout = null; }, 3000); } } } // --- Remove collected/offscreen powerups --- for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; if (p.collected || p.y > GAME_HEIGHT + 200) { p.destroy(); powerups.splice(i, 1); } } // --- Update Score --- updateScore(); // --- Game Over: Fell below screen --- if (player.y > GAME_HEIGHT + 200) { gameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } }; // --- Start Game --- // Only show start menu at very beginning, do not start/reset game until after character is selected showStartMenu(); // --- Repeating Background Setup --- var BG_HEIGHT = GAME_HEIGHT; var BG_WIDTH = GAME_WIDTH; // Create two background assets for seamless vertical repeat var bg1 = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: BG_WIDTH, height: BG_HEIGHT }); var bg2 = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: -BG_HEIGHT, width: BG_WIDTH, height: BG_HEIGHT }); game.addChildAt(bg1, 0); game.addChildAt(bg2, 0); // --- Background Repeat Logic --- // Move this logic into game.update;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- MovingPlatform Class ---
var MovingPlatform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Use a distinct color for moving platforms
var movingColor = 0xffe066;
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: movingColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Movement bounds
self.minX = 0;
self.maxX = 0;
self.speed = 6 + Math.random() * 4; // random speed
self.direction = Math.random() < 0.5 ? 1 : -1;
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
// Set movement bounds
self.setMoveBounds = function (minX, maxX) {
self.minX = minX;
self.maxX = maxX;
};
// Update method for moving
self.update = function () {
if (self.minX === self.maxX) {
return;
}
if (self.lastX === undefined) {
self.lastX = self.x;
}
self.x += self.speed * self.direction;
if (self.x < self.minX) {
self.x = self.minX;
self.direction *= -1;
} else if (self.x > self.maxX) {
self.x = self.maxX;
self.direction *= -1;
}
self.lastX = self.x;
};
return self;
});
// Do not generate platforms or add player until after character is selected and game is started
// All gameplay setup is now handled in resetGame, which is only called after character selection
// --- Platform Class ---
var Platform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Platform visual
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: PLATFORM_COLOR,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
return self;
});
// --- Player Class ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Use selected character asset, fallback to 'player'
var charId = selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player';
var asset = self.attachAsset(charId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.vx = 0;
self.vy = 0;
self.ax = 0; // acceleration x
self.ay = 0; // acceleration y
self.lastVx = 0;
self.lastVy = 0;
self.lastAx = 0;
self.lastAy = 0;
self.isJumping = false;
// Move left
self.moveLeft = function () {
self.vx = -playerMoveSpeed;
};
// Move right
self.moveRight = function () {
self.vx = playerMoveSpeed;
};
// Stop movement
self.stopMove = function () {
self.vx = 0;
};
// Jump
self.jump = function () {
self.vy = -playerJumpVelocity;
self.isJumping = true;
LK.getSound('jump').play();
};
// Update method for velocity/acceleration tracking and dynamic triggers
self.update = function () {
// Calculate acceleration
self.ax = self.vx - self.lastVx;
self.ay = self.vy - self.lastVy;
// Example: Trigger effect if horizontal velocity changes rapidly
if (self.lastVx !== undefined && Math.abs(self.vx - self.lastVx) > 10) {
// Hız çizgisi efekti veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashObject(self, 0x00ffff, 200);
}
// Example: Trigger effect if vertical acceleration exceeds threshold (e.g. falling fast)
if (self.lastAy !== undefined && Math.abs(self.ay) > 20) {
// Tehlike uyarısı veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashScreen(0xffe066, 100);
}
// Store last values for next update
self.lastVx = self.vx;
self.lastVy = self.vy;
self.lastAx = self.ax;
self.lastAy = self.ay;
};
return self;
});
// --- Powerup Class ---
var Powerup = Container.expand(function () {
var self = Container.call(this);
// Use the 'powerup' asset, centered
var asset = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.collected = false;
self.visible = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Constants ---
// analog image asset for analog control
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLATFORM_MIN_WIDTH = 300;
var PLATFORM_MAX_WIDTH = 600;
var PLATFORM_HEIGHT = 40;
var PLATFORM_MIN_GAP = 250;
var PLATFORM_MAX_GAP = 600;
var PLATFORM_SIDE_MARGIN = 100;
var PLATFORM_COLOR = 0x4ecdc4;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 400;
var playerMoveSpeed = 18;
var playerJumpVelocity = 75;
var gravity = 3.2;
var maxFallSpeed = 60;
var scrollThreshold = GAME_HEIGHT / 2;
var platformScrollSpeed = 0; // will be set dynamically
// --- State ---
var platforms = [];
var player;
var score = 0;
var scoreTxt;
var highestY = PLAYER_START_Y;
var dragDir = 0; // -1: left, 1: right, 0: none
var isTouching = false;
var lastTouchX = 0;
var lastTouchY = 0;
var gameOver = false;
// --- Trail State ---
var playerTrail = [];
var maxTrailLength = 16; // Number of trail dots
var trailDotAlphaStart = 0.35; // Starting alpha for the oldest dot
var trailDotAlphaEnd = 0.8; // Ending alpha for the newest dot
// --- Powerup State ---
var powerups = [];
var jumpBoostActive = false;
var jumpBoostTimeout = null;
// --- Helper: Spawn Powerup ---
function spawnPowerup(x, y) {
var p = new Powerup();
p.x = x;
p.y = y - 80; // float above platform
p.visible = true; // ensure powerup is visible when spawned
powerups.push(p);
game.addChild(p);
return p;
}
// --- GUI ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper: Platform Generation ---
function randomPlatformWidth() {
return PLATFORM_MIN_WIDTH + Math.floor(Math.random() * (PLATFORM_MAX_WIDTH - PLATFORM_MIN_WIDTH));
}
function randomPlatformX(width) {
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
return minX + Math.random() * (maxX - minX);
}
function randomPlatformGap() {
return PLATFORM_MIN_GAP + Math.random() * (PLATFORM_MAX_GAP - PLATFORM_MIN_GAP);
}
// --- Helper: Platform Creation ---
function createPlatform(x, y, width, type) {
// type: "moving" or undefined for normal
var plat;
if (type === "moving" || type === undefined && Math.random() < 0.25 && y < PLAYER_START_Y + 100) {
plat = new MovingPlatform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
// Set movement bounds so it doesn't go off screen
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
plat.setMoveBounds(minX, maxX);
} else {
plat = new Platform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
}
platforms.push(plat);
game.addChild(plat);
// Powerup: spawn with 10% chance on top of platform
if (Math.random() < 0.10) {
spawnPowerup(x, y - PLATFORM_HEIGHT / 2);
}
return plat;
}
// --- Helper: Remove platform ---
function removePlatform(plat) {
var idx = platforms.indexOf(plat);
if (idx !== -1) {
platforms.splice(idx, 1);
}
plat.destroy();
}
// --- Helper: Collision Detection (AABB) ---
function playerOnPlatform(player, plat) {
// Only check if player is falling
if (player.vy < 0) {
return false;
}
// Player bottom
var px = player.x;
var py = player.y + player.radius * 0.8;
// Platform bounds
var left = plat.x - plat.width / 2;
var right = plat.x + plat.width / 2;
var top = plat.y - plat.height / 2;
var bottom = plat.y + plat.height / 2;
// Require player's feet to be above the platform top in the previous frame and now inside platform
if (player.lastY !== undefined) {
var lastPy = player.lastY + player.radius * 0.8;
// Only allow landing if last frame was above platform and now inside
if (lastPy <= top &&
// last frame above platform
py > top && py < bottom &&
// now inside platform vertical bounds
px >= left && px <= right // inclusive horizontal bounds for small platforms
) {
return true;
}
}
// For very small platforms, allow a little tolerance (±1px) to ensure collision is detected
if (plat.width < 40) {
if (player.lastY !== undefined && lastPy <= top && py > top && py < bottom && px >= left - 1 && px <= right + 1) {
return true;
}
}
return false;
}
// --- Helper: Generate Initial Platforms ---
function generateInitialPlatforms() {
var y = PLAYER_START_Y + 200;
// First platform: wide, centered, at bottom
createPlatform(GAME_WIDTH / 2, y, 600);
// Remove circular platform creation, just continue with normal platforms
y -= 350;
// Generate upwards, but with fewer platforms (reduce density)
// Ensure all platforms are reachable by limiting vertical gap to jump height
var platformCount = 0;
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // s = v^2/(2g)
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP); // 90% of max jump height for margin
// --- Calculate reachable circle for the player ---
var jumpRadius = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // max jump height
var jumpCircleRadius = jumpRadius * 0.9; // 90% for margin
var circleCenterX = GAME_WIDTH / 2;
var circleCenterY = PLAYER_START_Y;
// Place at least one platform inside the jump circle and above the player
var mustHavePlatformY = PLAYER_START_Y - jumpCircleRadius * 0.7; // 70% up the circle
var mustHavePlatformX = circleCenterX + (Math.random() - 0.5) * jumpCircleRadius * 0.7; // random X inside circle
var mustHavePlatformWidth = randomPlatformWidth();
createPlatform(mustHavePlatformX, mustHavePlatformY, mustHavePlatformWidth);
// Now fill the rest of the platforms as before, but avoid overlapping the must-have platform
while (y > 400 && platformCount < 7) {
// Always use a gap that is not more than safeGap, so player can always reach
var gap = Math.min(randomPlatformGap(), safeGap);
y -= gap + 80; // slightly increase gap for fewer platforms
var width = randomPlatformWidth();
var x = randomPlatformX(width);
// Avoid placing a platform too close to the must-have platform
if (Math.abs(y - mustHavePlatformY) < 100) {
continue;
}
createPlatform(x, y, width);
platformCount++;
}
}
// --- Helper: Generate New Platforms Above ---
function generatePlatformsIfNeeded() {
// Find highest platform
var highestPlatY = GAME_HEIGHT;
for (var i = 0; i < platforms.length; i++) {
if (platforms[i].y < highestPlatY) {
highestPlatY = platforms[i].y;
}
}
// Limit to 12 platforms on screen
// Restore to original logic: just fill up with normal platforms, no must-have guarantee
while (highestPlatY > -200 && platforms.length < 12) {
var width = randomPlatformWidth();
var x = randomPlatformX(width);
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity);
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP);
var gap = Math.min(randomPlatformGap(), safeGap);
highestPlatY -= gap;
// Ensure no two platforms have the same Y coordinate (within 1px tolerance)
var yUnique = true;
for (var j = 0; j < platforms.length; j++) {
if (Math.abs(platforms[j].y - highestPlatY) < 1) {
yUnique = false;
break;
}
}
if (yUnique) {
createPlatform(x, highestPlatY, width);
} else {
// If not unique, try a slightly different gap
highestPlatY -= 10 + Math.random() * 20;
}
}
}
// --- Helper: Remove Offscreen Platforms ---
function removeOffscreenPlatforms() {
for (var i = platforms.length - 1; i >= 0; i--) {
if (platforms[i].y > GAME_HEIGHT + 200) {
removePlatform(platforms[i]);
}
}
}
// --- Helper: Update Score ---
function updateScore() {
// Score is highestY reached (inverted, since y decreases as we go up)
var newScore = Math.max(0, Math.floor((PLAYER_START_Y - highestY) / 10));
if (newScore > score) {
score = newScore;
scoreTxt.setText(score);
// Play sound at every 1000 and multiples of 1000
if (score > 0 && score % 1000 === 0) {
LK.getSound('jump').play();
}
}
}
// --- Character Selection ---
var characterOptions = [{
id: 'player',
color: 0xf67280,
label: 'Kırmızı'
}, {
id: 'player2',
color: 0x4ecdc4,
label: 'Mavi'
}, {
id: 'player3',
color: 0xffe066,
label: 'Sarı'
}];
var selectedCharacter = null;
var characterSelectNodes = [];
var characterSelectActive = false;
// --- Language State ---
var LANG_TR = 'tr';
var LANG_EN = 'en';
var currentLang = LANG_EN; // Default language is English
// --- Language Strings ---
var langStrings = {
tr: {
title: 'Zıpla!',
info: 'En yükseğe zıpla, güçlendiricileri topla!',
start: 'Başla',
lang_tr: 'Türkçe',
lang_en: 'İngilizce'
},
en: {
title: 'Jump!',
info: 'Jump as high as you can, collect powerups!',
start: 'Start',
lang_tr: 'Turkish',
lang_en: 'English'
}
};
// --- Start Menu ---
var startMenuNodes = [];
var startMenuActive = true;
var langBtnTr, langBtnEn;
function showStartMenu() {
startMenuActive = true;
// Remove any previous menu nodes
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
// Add a white background covering the whole screen using a dedicated menu background asset
var menuBg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBg);
startMenuNodes.push(menuBg);
// --- Language Toggle Buttons (Top Right) ---
// Language buttons: stack vertically, each with a colored background for visibility
var langBtnY = 80;
var langBtnSpacingY = 130;
var langBtnSize = 110;
// Türkçe button background
var langBgTr = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_TR ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY
});
game.addChild(langBgTr);
startMenuNodes.push(langBgTr);
langBtnTr = new Text2(langStrings.tr.lang_tr, {
size: 54,
fill: currentLang === LANG_TR ? "#ff9900" : "#22223B"
});
langBtnTr.anchor.set(0.5, 0.5);
langBtnTr.x = GAME_WIDTH - 140;
langBtnTr.y = langBtnY;
langBtnTr.interactive = true;
langBtnTr.buttonMode = true;
langBtnTr._lang = LANG_TR;
game.addChild(langBtnTr);
startMenuNodes.push(langBtnTr);
// English button background
var langBgEn = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_EN ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY + langBtnSpacingY
});
game.addChild(langBgEn);
startMenuNodes.push(langBgEn);
langBtnEn = new Text2(langStrings.tr.lang_en, {
size: 54,
fill: currentLang === LANG_EN ? "#ff9900" : "#22223B"
});
langBtnEn.anchor.set(0.5, 0.5);
langBtnEn.x = GAME_WIDTH - 140;
langBtnEn.y = langBtnY + langBtnSpacingY;
langBtnEn.interactive = true;
langBtnEn.buttonMode = true;
langBtnEn._lang = LANG_EN;
game.addChild(langBtnEn);
startMenuNodes.push(langBtnEn);
// --- Animated Game Title ---
var titleY = 320;
var title = new Text2(langStrings[currentLang].title, {
size: 220,
fill: 0xF67280,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.x = GAME_WIDTH / 2;
title.y = titleY;
game.addChild(title);
startMenuNodes.push(title);
// Animate title with a gentle up-down floating effect
tween(title, {
y: titleY + 40
}, {
duration: 1200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// --- Info Text ---
var infoTxt = new Text2(langStrings[currentLang].info, {
size: 70,
fill: 0x22223B
});
infoTxt.anchor.set(0.5, 0.5);
infoTxt.x = GAME_WIDTH / 2;
infoTxt.y = titleY + 160;
game.addChild(infoTxt);
startMenuNodes.push(infoTxt);
// --- Character Selection with Visual Highlight ---
var charY = GAME_HEIGHT * 0.75;
var charSpacing = 320;
var charStartX = GAME_WIDTH / 2 - (characterOptions.length - 1) / 2 * charSpacing;
for (var i = 0; i < characterOptions.length; i++) {
var opt = characterOptions[i];
// Add a highlight ring behind the character if selected
var highlight = LK.getAsset('platform', {
width: 200,
height: 200,
color: 0xfaf3dd,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
highlight.visible = selectedCharacter && selectedCharacter.id === opt.id;
highlight._charIndex = i;
game.addChild(highlight);
startMenuNodes.push(highlight);
var node = LK.getAsset(opt.id, {
width: 160,
height: 160,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
node.interactive = true;
node.buttonMode = true;
// Add colored background behind character node for visibility (instead of label)
var charBg = LK.getAsset('platform', {
width: 180,
height: 180,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
game.addChild(charBg);
startMenuNodes.push(charBg);
node._charIndex = i;
game.addChild(node);
startMenuNodes.push(node);
}
// --- Başla Button with Glow Effect ---
var btnWidth = 600;
var btnHeight = 180;
var btnY = charY - 350;
// Başla button background (removed image asset, use only colored ellipse asset)
var btnBg = LK.getAsset('baslaBtnBg', {
width: btnWidth + 40,
height: btnHeight + 40,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnBg);
startMenuNodes.push(btnBg);
var btn = LK.getAsset('baslaBtnBg', {
width: btnWidth,
height: btnHeight,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
btn.interactive = true;
btn.buttonMode = true;
// Add a glowing effect to Başla button (pulsing scale)
tween(btn, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 900,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// Add an asset inside the Başla button (e.g. a star icon, using 'powerup' asset as example)
var btnInnerAsset = LK.getAsset('powerup', {
width: 90,
height: 90,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnInnerAsset);
startMenuNodes.push(btnInnerAsset);
// Başla button label (will be updated on language change)
var btnLabel = new Text2(langStrings[currentLang].start, {
size: 110,
fill: "#fff"
});
btnLabel.anchor.set(0.5, 0.5);
btnLabel.x = GAME_WIDTH / 2;
btnLabel.y = btnY;
btn._btnLabel = btnLabel; // reference for dynamic update
game.addChild(btn);
game.addChild(btnLabel);
startMenuNodes.push(btn, btnLabel);
// --- Language Button Event Handlers ---
langBtnTr.down = function () {
if (currentLang !== LANG_TR) {
currentLang = LANG_TR;
showStartMenu();
}
};
langBtnEn.down = function () {
if (currentLang !== LANG_EN) {
currentLang = LANG_EN;
showStartMenu();
}
};
// Update Başla button label if present (for language switch)
if (btn && btn._btnLabel) {
btn._btnLabel.setText(langStrings[currentLang].start);
}
}
function hideStartMenu() {
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
startMenuActive = false;
}
// Show character selection UI
// character select is now part of start menu, so this is not needed
function showCharacterSelect() {}
// character select is now part of start menu, so this is not needed
function hideCharacterSelect() {}
// Listen for down event to select character or start game
// --- Game Setup ---
function resetGame() {
// Remove old platforms
for (var i = 0; i < platforms.length; i++) {
platforms[i].destroy();
}
platforms = [];
// Remove old powerups
for (var i = 0; i < powerups.length; i++) {
powerups[i].destroy();
}
powerups = [];
// Remove player if exists
if (player) {
player.destroy();
}
// Reset state
score = 0;
scoreTxt.setText(score);
highestY = PLAYER_START_Y;
dragDir = 0;
isTouching = false;
lastTouchX = 0;
lastTouchY = 0;
gameOver = false;
// Reset trail
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].parent) {
playerTrail[i].parent.removeChild(playerTrail[i]);
}
}
playerTrail = [];
jumpBoostActive = false;
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
jumpBoostTimeout = null;
}
// Reset platform count for powerup spawn
createPlatform.platformCount = 0;
// Generate platforms
generateInitialPlatforms();
// Create player
player = new Player();
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
player.vx = 0;
player.vy = 0;
player.isJumping = false;
game.addChild(player);
// (No circular platform creation here; handled only in generateInitialPlatforms)
}
// --- Analog Button for Left/Right Movement ---
var analogBase = null,
analogKnob = null;
var analogActive = false;
var analogStartX = 0,
analogStartY = 0;
var analogDir = 0; // -1: left, 1: right, 0: none
var analogRadius = 180 * 1.2; // 1.2x larger
var analogKnobRadius = 70 * 1.2; // 1.2x larger
var analogCenterX = 0;
var analogCenterY = 0;
// Helper to create analog at a given position
function showAnalogAt(x, y) {
// Clamp analog so it doesn't go off screen (and not in top left 100x100)
var minX = 100 + analogRadius;
var maxX = GAME_WIDTH - analogRadius;
var minY = analogRadius;
var maxY = GAME_HEIGHT - analogRadius;
analogCenterX = Math.max(minX, Math.min(maxX, x));
analogCenterY = Math.max(minY, Math.min(maxY, y));
// Remove previous analog if exists
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = LK.getAsset('analog', {
width: analogRadius * 2,
height: analogRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogBase.alpha = 0.35;
analogBase.interactive = true;
analogBase.buttonMode = true;
game.addChild(analogBase);
analogKnob = LK.getAsset('analog', {
width: analogKnobRadius * 2,
height: analogKnobRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogKnob.alpha = 0.7;
analogKnob.interactive = true;
analogKnob.buttonMode = true;
game.addChild(analogKnob);
// Attach handlers
analogBase.down = analogKnob.down = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
}
// Helper to update analog direction and player movement
function updateAnalogDir(knobX, knobY) {
var dx = knobX - analogCenterX;
// Only consider horizontal movement
if (dx < -30) {
analogDir = -1;
player.moveLeft();
} else if (dx > 30) {
analogDir = 1;
player.moveRight();
} else {
analogDir = 0;
player.stopMove();
}
}
// Analog input handlers
game.down = function (x, y, obj) {
// Block all gameplay input if start menu or character select is active
if (startMenuActive || characterSelectActive) {
// If start menu is active, handle menu logic
if (startMenuActive) {
var charSelected = false;
// Check if a character was tapped
for (var i = 0; i < startMenuNodes.length; i++) {
var node = startMenuNodes[i];
if (node._charIndex !== undefined) {
var dx = x - node.x;
var dy = y - node.y;
if (dx * dx + dy * dy < 80 * 80) {
selectedCharacter = characterOptions[node._charIndex];
charSelected = true;
// Visually highlight the selected character by updating highlight rings
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = n._charIndex === node._charIndex;
}
}
}
}
}
// Check if Başla button pressed (always last two nodes)
var btn = startMenuNodes[startMenuNodes.length - 2];
var btnLabel = startMenuNodes[startMenuNodes.length - 1];
var dx = x - btn.x;
var dy = y - btn.y;
var baslaPressed = dx * dx / (btn.width * btn.width * 0.25) + dy * dy / (btn.height * btn.height * 0.25) < 1;
if (baslaPressed && selectedCharacter) {
// Show highlight for selected character before starting
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = selectedCharacter && characterOptions[n._charIndex].id === selectedCharacter.id;
}
}
// Prevent multiple presses
if (btn._pressed) {
return;
}
btn._pressed = true;
// Animate Başla button to scale up to 1.1x, then back to 1.0x, then start game after animation
tween(btn, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 120,
easing: tween.easeOutBack,
onFinish: function onFinish() {
tween(btn, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 80,
easing: tween.easeInBack,
onFinish: function onFinish() {
hideStartMenu();
resetGame();
btn._pressed = false;
}
});
}
});
return;
}
return;
}
return;
}
if (gameOver) {
return;
}
// Always remove any existing analog before creating a new one
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
analogActive = false;
// Show analog at touch location
showAnalogAt(x, y);
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
game.move = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
if (!analogActive || !analogBase || !analogKnob) {
return;
}
// Clamp knob within analog base
var dx = x - analogCenterX;
var dy = y - analogCenterY;
var dist = Math.sqrt(dx * dx + dy * dy);
var maxDist = analogRadius - analogKnobRadius;
if (dist > maxDist) {
dx = dx * maxDist / dist;
dy = dy * maxDist / dist;
}
analogKnob.x = analogCenterX + dx;
analogKnob.y = analogCenterY + dy;
updateAnalogDir(analogKnob.x, analogKnob.y);
};
game.up = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = false;
analogDir = 0;
player.stopMove();
// Hide analog after release
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
};
// --- Input Handling (Touch/Drag) ---
// This input handler is now only used for gameplay, not for menu/character select
// --- Main Game Loop ---
game.update = function () {
// Block all game updates if start menu or character select is active
if (startMenuActive || characterSelectActive) {
return;
}
if (gameOver) {
return;
}
// --- Player Physics ---
// Call player update for velocity/acceleration tracking and dynamic triggers
if (typeof player.update === "function") {
player.update();
}
// Show player only when jumping
if (player.isJumping && !player.parent) {
game.addChild(player);
} else if (!player.isJumping && player.parent) {
player.parent.removeChild(player);
}
// --- Player Trail Effect ---
// Add current player position to trail
if (!gameOver && player.parent) {
playerTrail.push({
x: player.x,
y: player.y
});
if (playerTrail.length > maxTrailLength) {
playerTrail.shift();
}
}
// Remove old trail dots if any
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].node && playerTrail[i].node.parent) {
playerTrail[i].node.parent.removeChild(playerTrail[i].node);
playerTrail[i].node = null;
}
}
// Draw trail dots (skip if not enough points)
for (var i = 0; i < playerTrail.length; i++) {
var t = i / (playerTrail.length - 1);
var alpha = trailDotAlphaStart + (trailDotAlphaEnd - trailDotAlphaStart) * t;
var dot = LK.getAsset(selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: playerTrail[i].x,
y: playerTrail[i].y
});
dot.alpha = alpha;
// Place behind player
game.addChildAt(dot, 0);
playerTrail[i].node = dot;
}
// Horizontal movement
player.x += player.vx;
// Wrap player horizontally: if player goes off right, appear at left; if off left, appear at right
if (player.x > GAME_WIDTH + player.radius) {
player.x = -player.radius;
}
if (player.x < -player.radius) {
player.x = GAME_WIDTH + player.radius;
}
// Track lastY for platform collision logic
player.lastY = player.y;
// Vertical movement
player.y += player.vy;
player.vy += gravity;
if (player.vy > maxFallSpeed) {
player.vy = maxFallSpeed;
}
// --- Update moving platforms ---
for (var i = 0; i < platforms.length; i++) {
if (typeof platforms[i].update === "function" && platforms[i] instanceof MovingPlatform) {
platforms[i].update();
}
}
// --- Platform Collision ---
var landed = false;
for (var i = 0; i < platforms.length; i++) {
if (playerOnPlatform(player, platforms[i])) {
// Only allow landing if falling
if (player.vy >= 0) {
player.y = platforms[i].y - platforms[i].height / 2 - player.radius * 0.8;
player.jump();
landed = true;
break;
}
}
}
// --- Scrolling ---
// If player is above scroll threshold, move everything down
if (player.y < scrollThreshold) {
var dy = scrollThreshold - player.y;
player.y = scrollThreshold;
// Move all platforms down
for (var i = 0; i < platforms.length; i++) {
platforms[i].y += dy;
}
// Move all powerups down (so they behave like platforms)
for (var i = 0; i < powerups.length; i++) {
powerups[i].y += dy;
}
// Move backgrounds down
bg1.y += dy;
bg2.y += dy;
// If a background moves completely below the screen, move it above the other for seamless repeat
if (bg1.y >= GAME_HEIGHT) {
bg1.y = bg2.y - BG_HEIGHT;
}
if (bg2.y >= GAME_HEIGHT) {
bg2.y = bg1.y - BG_HEIGHT;
}
// Track highestY
highestY -= dy;
} else {
// Track highestY
if (player.y < highestY) {
highestY = player.y;
}
// Also update backgrounds to follow player if not scrolling
// (keeps backgrounds in sync if player falls)
// This ensures backgrounds always cover the screen
if (bg1.y > 0 && bg2.y > 0) {
// If both are above, move the one further down above the other
if (bg1.y > bg2.y) {
bg1.y = bg2.y - BG_HEIGHT;
} else {
bg2.y = bg1.y - BG_HEIGHT;
}
}
if (bg1.y < -BG_HEIGHT) {
bg1.y = bg2.y + BG_HEIGHT;
}
if (bg2.y < -BG_HEIGHT) {
bg2.y = bg1.y + BG_HEIGHT;
}
}
// --- Remove Offscreen Platforms & Generate New Ones ---
removeOffscreenPlatforms();
generatePlatformsIfNeeded();
// --- Powerup Collision ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (!p.collected) {
// Simple circle collision
var dx = player.x - p.x;
var dy = player.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.radius + p.radius) {
// Collect powerup
p.collected = true;
p.visible = false;
// Activate jump boost
jumpBoostActive = true;
playerJumpVelocity = 90; // 1.5x original (60)
// Clear previous timeout if any
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
}
jumpBoostTimeout = LK.setTimeout(function () {
jumpBoostActive = false;
playerJumpVelocity = 60;
jumpBoostTimeout = null;
}, 3000);
}
}
}
// --- Remove collected/offscreen powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (p.collected || p.y > GAME_HEIGHT + 200) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Update Score ---
updateScore();
// --- Game Over: Fell below screen ---
if (player.y > GAME_HEIGHT + 200) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
};
// --- Start Game ---
// Only show start menu at very beginning, do not start/reset game until after character is selected
showStartMenu();
// --- Repeating Background Setup ---
var BG_HEIGHT = GAME_HEIGHT;
var BG_WIDTH = GAME_WIDTH;
// Create two background assets for seamless vertical repeat
var bg1 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: BG_WIDTH,
height: BG_HEIGHT
});
var bg2 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: -BG_HEIGHT,
width: BG_WIDTH,
height: BG_HEIGHT
});
game.addChildAt(bg1, 0);
game.addChildAt(bg2, 0);
// --- Background Repeat Logic ---
// Move this logic into game.update;
Cartoon bir astronot. In-Game asset. 2d. High contrast. No shadows
Cartoon bir astronot. In-Game asset. 2d. High contrast. No shadows
İskender kebap. In-Game asset. 2d. High contrast. No shadows
Nebula uzay arkaplan nebula oranı 3/10 olsun. In-Game asset. 2d. High contrast. No shadows
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Jump Up: Sonsuz Platform Macerası" and with the description astronot in the moon"Karakterin otomatik zıpladığı, sağ-sol yönlendirme ile platformlarda yükseldiğin sonsuz bir parkur oyunu. Platformlar rastgele ve benzersiz şekilde oluşur.". No text on banner!