User prompt
Karakterle beraber hareket etmesin poweruplar
User prompt
Ekrana powerup ları görsel olarak ekle
User prompt
Powerupları ekle plartformların üstünde olsun %10 oranla çıksın
User prompt
Geri al
User prompt
Powerup oluşumunu plartformların üstünde rasgele çıkabilir hale getir 10 plartformda 1 olsun
User prompt
Powerup class ını ekrana ekle ve karakter ona dokunursa 1.5 x jump olsun 3 saniyeline
User prompt
Plartform şeklini elips yap
User prompt
Plartformlar köşeleri yumuşak olsun
User prompt
Her zıplama için bir ses ekle
User prompt
Değişiklikleri geri al
User prompt
Maximum zıplama mesafesini hesapla ve asla zıplama imkansız plartform oluşturma
Code edit (1 edits merged)
Please save this source code
User prompt
Jump Up: Sonsuz Platform Macerası
Initial prompt
Bir platform bazlı parkur oyunu istiyorum yukarı doğru otomatik zıplayan karaktere sağ sol diye yön verelim ve plartformlar random seed le oluşturulsun y ekseni ilerledikçe oluşmaya devam etsin oluşan plartformlar aynı olmasın gene random olsun
/**** * 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!