/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Clase Cannon para mostrar el cañón y animar disparo var Cannon = Container.expand(function () { var self = Container.call(this); // Asset del cañón (ahora usa la imagen personalizada) self.cannonAsset = self.attachAsset('cannon', { anchorX: 0.5, anchorY: 1 }); // Efecto de fogonazo al disparar self.flash = null; // Estado self.isVisible = true; self.isShooting = false; // Posición base (centrado horizontal, abajo) self.baseX = 2048 / 2; // Coloca el cañón pegado a la parte baja del juego (anchorY:1, así que baseY es exactamente el borde inferior) self.baseY = 2732; self.x = self.baseX; self.y = self.baseY; // Mostrar cañón self.show = function () { self.visible = true; self.isVisible = true; // Animar subida si estaba oculto tween(self, { y: self.baseY }, { duration: 350, easing: tween.cubicOut }); }; // Animar disparo (pequeño retroceso y fogonazo) self.shoot = function () { if (self.isShooting) { return; } self.isShooting = true; // Retroceso rápido tween(self, { y: self.baseY - 60 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { // Regresa a posición base tween(self, { y: self.baseY }, { duration: 120, easing: tween.cubicIn, onFinish: function onFinish() { self.isShooting = false; } }); } }); // Fogonazo visual if (!self.flash) { self.flash = self.attachAsset('cut_zone', { anchorX: 0.5, anchorY: 1, x: 0, y: -self.cannonAsset.height + 30, scaleX: 0.5, scaleY: 0.3, alpha: 0.0, color: 0xffffcc }); } self.flash.alpha = 0.7; self.flash.scaleX = 0.5; self.flash.scaleY = 0.3; tween(self.flash, { alpha: 0, scaleX: 1.2, scaleY: 0.7 }, { duration: 120, easing: tween.linear }); }; return self; }); // Clase para el objeto de cuenta regresiva "Go" var CountdownGo = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('Go', { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.asset.width / 2; self.isActive = true; self.state = 'countdown'; self.destroyCountdown = function () { self.isActive = false; self.visible = false; self.destroy(); }; return self; }); // Clase para el objeto de cuenta regresiva "1" var CountdownOne = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('1', { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.asset.width / 2; self.isActive = true; self.state = 'countdown'; self.destroyCountdown = function () { self.isActive = false; self.visible = false; self.destroy(); }; return self; }); // Clase para el objeto de cuenta regresiva "2" var CountdownTwo = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('2', { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.asset.width / 2; self.isActive = true; self.state = 'countdown'; self.destroyCountdown = function () { self.isActive = false; self.visible = false; self.destroy(); }; return self; }); var Fruit = Container.expand(function () { var self = Container.call(this); // Selección aleatoria de tipo de fruta var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum']; self.fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)]; // Asset de la fruta self.fruitAsset = self.attachAsset(self.fruitType, { anchorX: 0.5, anchorY: 0.5 }); if (self.fruitAsset) { self.fruitAsset.scaleX = 1; // Empieza muy pequeño self.fruitAsset.scaleY = 1; } else if (self.children && self.children.length > 0) { var lastChild = self.children[self.children.length - 1]; lastChild.scaleX = 0.1; lastChild.scaleY = 0.1; self.fruitAsset = lastChild; } self.radius = self.fruitAsset.width * self.fruitAsset.scaleX / 2 + 60; // Estado self.isCut = false; self.isActive = true; // Agregamos la velocidad de crecimiento de escala (puedes ajustar) self.scaleGrowSpeed = 0.1; // escala por frame // Método para actualizar la fruta cada frame (debe llamarse en el ciclo principal) self.update = function () { if (!self.isCut && self.fruitAsset) { // Incrementar escala hasta 0.5 (máximo) if (self.fruitAsset.scaleX < 0.5) { self.fruitAsset.scaleX = Math.min(0.5, self.fruitAsset.scaleX + self.scaleGrowSpeed); self.fruitAsset.scaleY = self.fruitAsset.scaleX; // Actualizar radius para hitbox self.radius = self.fruitAsset.width * self.fruitAsset.scaleX / 2 + 60; } } }; self.cut = function () { if (self.isCut) { return; } self.isCut = true; self.isActive = false; self.visible = false; }; return self; }); // Clase para la fruta cortada (dos mitades) var FruitCut = Container.expand(function () { var self = Container.call(this); // Recibe tipo de fruta, posición base, rotación y escala self.init = function (fruitType, x, y, rotation, scale) { if (typeof rotation === "undefined") { rotation = 0; } if (typeof scale === "undefined") { scale = 1; } // Para las frutas, usar assets de mitades cortadas específicos por tipo de fruta var leftAssetId = 'fruit_apple_cut_left'; var rightAssetId = 'fruit_apple_cut_right'; if (fruitType === 'fruit_apple') { leftAssetId = 'fruit_apple_cut_left'; rightAssetId = 'fruit_apple_cut_right'; } else if (fruitType === 'fruit_lemon') { leftAssetId = 'fruit_lemon_cut_left'; rightAssetId = 'fruit_lemon_cut_right'; } else if (fruitType === 'fruit_orange') { leftAssetId = 'fruit_orange_cut_left'; rightAssetId = 'fruit_orange_cut_right'; } else if (fruitType === 'fruit_plum') { leftAssetId = 'fruit_plum_cut_left'; rightAssetId = 'fruit_plum_cut_right'; } else if (fruitType === 'fruit_kiwi') { leftAssetId = 'fruit_kiwi_cut_left'; rightAssetId = 'fruit_kiwi_cut_right'; } // Mitad izquierda reflejada horizontalmente (flipX: 1) self.left = self.attachAsset(leftAssetId, { anchorX: 1, anchorY: 0.5, scaleX: scale, scaleY: scale, x: 0, y: 0, flipX: 1 }); // Mitad derecha normal self.right = self.attachAsset(rightAssetId, { anchorX: 0, anchorY: 0.5, scaleX: scale, scaleY: scale, x: 0, y: 0 }); self.x = x; self.y = y; self.left.rotation = rotation; self.right.rotation = rotation; self.rotation = rotation; }; // Animación de separación self.animate = function (_onFinish) { // Reduce size to 80% immediately tween(self.left, { x: -80, rotation: self.left.rotation + 0.7, scaleX: self.left.scaleX * 0.8, scaleY: self.left.scaleY * 0.8 }, { duration: 500, easing: tween.cubicOut }); tween(self.right, { x: 80, rotation: self.right.rotation - 0.7, scaleX: self.right.scaleX * 0.8, scaleY: self.right.scaleY * 0.8 }, { duration: 500, easing: tween.cubicOut, onFinish: function onFinish() { // Fade out both halves after movement/scaling tween(self.left, { alpha: 0 }, { duration: 350, easing: tween.linear }); tween(self.right, { alpha: 0 }, { duration: 350, easing: tween.linear, onFinish: function onFinish() { if (_onFinish) { _onFinish(); } } }); } }); }; return self; }); // Base Scene class for scene management var Scene = Container.expand(function () { var self = Container.call(this); self.isActive = false; self.activate = function () { self.isActive = true; self.visible = true; // Make sure overlay is visible when scene is activated if (self.overlay) { self.overlay.visible = true; } }; self.deactivate = function () { self.isActive = false; self.visible = false; // Make sure to hide overlay to prevent black background if (self.overlay) { self.overlay.visible = false; } }; return self; }); // Menu Scene var MenuScene = Scene.expand(function () { var self = Scene.call(this); // Full screen background (added first so it's behind everything) var menuBg = self.attachAsset('menu_background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); // Create decorative fruits container to ensure proper layering var fruitsContainer = new Container(); self.addChild(fruitsContainer); // Add Titulo asset at the top var titulo = self.attachAsset('Titulo', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1000 }); // Play button (will be added after fruits container) var playButton = self.attachAsset('button_play', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2000 }); // Button interaction playButton.interactive = true; playButton.down = function () { playButton.tint = 0xcccccc; }; playButton.up = function () { playButton.tint = 0xffffff; if (game.onPlayPressed) { game.onPlayPressed(); } }; // Decorative fruits var decorativeFruits = []; var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum']; var fruitScale = 1; // Normal size var fruitSpacing = 250; // Horizontal spacing between fruits var verticalSpacing = 200; // Vertical spacing between rows var baseY = 150; // Starting Y position for first row var fruitSpeed = 2; // Horizontal movement speed // Calculate how many rows we can fit var availableHeight = 2732 - 100; var numRows = Math.floor(availableHeight / verticalSpacing); // Decorative fruits var decorativeFruits = []; var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum']; var fruitScale = 1; // Normal size var fruitSpacing = 250; // Horizontal spacing between fruits var verticalSpacing = 200; // Vertical spacing between rows var baseY = 150; // Starting Y position for first row var fruitSpeed = 2; // Horizontal movement speed var resetX = -400; // X position where fruits reappear // Calculate how many rows we can fit var availableHeight = 2732 - 100; var numRows = Math.floor(availableHeight / verticalSpacing); // Create fruits and shadows for each row for (var row = 0; row < numRows; row++) { var y = baseY + row * verticalSpacing; var numFruits = Math.ceil((2048 + fruitSpacing * 2) / fruitSpacing); var rowOffset = row % 2 === 1 ? fruitSpacing / 2 : 0; var initialRotation = row % 2 === 1 ? Math.PI : 0; // 180° for odd rows for (var col = 0; col < numFruits; col++) { var fruitType = fruitTypes[(row + col) % fruitTypes.length]; var x = col * fruitSpacing - fruitSpacing + rowOffset; // Create shadow first (so it appears behind the fruit) var shadow = self.attachAsset(fruitType, { anchorX: 0.5, anchorY: 0.5, x: x + 15, y: y + 15, scaleX: fruitScale, scaleY: fruitScale, rotation: initialRotation, tint: 0x000000, alpha: 0.3 }); shadow.rotationSpeed = 0.02; shadow.isShadow = true; decorativeFruits.push(shadow); fruitsContainer.addChild(shadow); // Create actual fruit on top var fruit = self.attachAsset(fruitType, { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: fruitScale, scaleY: fruitScale, rotation: initialRotation }); fruit.rotationSpeed = 0.02; fruit.isShadow = false; decorativeFruits.push(fruit); fruitsContainer.addChild(fruit); } } self.activate = function () { self.isActive = true; self.visible = true; // Create black overlay for fade-in effect (larger than screen) var fadeOverlay = LK.getAsset('vertical_line', { anchorX: 0, anchorY: 0, x: -200, y: -200, width: 2448, height: 3132, color: 0x000000, alpha: 1 }); self.addChild(fadeOverlay); // Bring fade overlay to front to ensure it's above all other elements self.setChildIndex(fadeOverlay, self.children.length - 1); // Wait 1 second before starting the fade LK.setTimeout(function () { // Fade out the black overlay over 3 seconds tween(fadeOverlay, { alpha: 0 }, { duration: 3000, easing: tween.linear, onFinish: function onFinish() { fadeOverlay.destroy(); } }); }, 1000); // 1 second delay // Play menu music when menu activates LK.playMusic('menu_music'); }; // Update function for menu scene self.updateMenu = function () { for (var i = 0; i < decorativeFruits.length; i++) { var fruit = decorativeFruits[i]; fruit.x += fruitSpeed; fruit.rotation += fruit.rotationSpeed; if (fruit.x > 2048 + fruitSpacing) { if (fruit.isShadow) { // Aparece un poco más a la izquierda para compensar desfase fruit.x = resetX; } else { fruit.x = resetX; } } } }; // Add tutorial reset button in bottom-right corner var tutorialResetButton = self.attachAsset('tutorial_reset_button', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 80, // 80px from right edge y: 2732 - 80, // 80px from bottom edge scaleX: .6, scaleY: .6, alpha: 1.0 }); // Tutorial reset button interaction tutorialResetButton.interactive = true; tutorialResetButton.down = function () { tutorialResetButton.tint = 0xcccccc; }; tutorialResetButton.up = function () { tutorialResetButton.tint = 0xffffff; // Reset tutorial flag in storage storage.hasSeenIntro = false; // Visual feedback - flash the button LK.effects.flashObject(tutorialResetButton, 0x00ff00, 500); }; // Add hover effect self.move = function (x, y, obj) { // Convert coordinates to button's local space var globalPos = self.toGlobal({ x: x, y: y }); var localPos = playButton.toLocal(globalPos); var halfWidth = playButton.width / 2; var halfHeight = playButton.height / 2; if (localPos.x >= -halfWidth && localPos.x <= halfWidth && localPos.y >= -halfHeight && localPos.y <= halfHeight) { playButton.alpha = 0.8; // Make more opaque (less transparent) when hovering } else { playButton.alpha = 1; // Normal opacity (fully visible) } // Check hover for tutorial reset button var tutorialLocalPos = tutorialResetButton.toLocal(globalPos); var tutorialHalfWidth = tutorialResetButton.width * tutorialResetButton.scaleX / 2; var tutorialHalfHeight = tutorialResetButton.height * tutorialResetButton.scaleY / 2; if (tutorialLocalPos.x >= -tutorialHalfWidth && tutorialLocalPos.x <= tutorialHalfWidth && tutorialLocalPos.y >= -tutorialHalfHeight && tutorialLocalPos.y <= tutorialHalfHeight) { tutorialResetButton.alpha = 0.7; // More opaque when hovering } else { tutorialResetButton.alpha = 1.0; // Normal visibility } }; return self; }); // IntroScene - displays tutorial image before game starts var IntroScene = Scene.expand(function () { var self = Scene.call(this); // Tutorial background image - use full resolution self.tutorialImage = self.attachAsset('tutorial', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.0, scaleY: 1.0 }); // Tap anywhere to continue text var continueText = new Text2('TAP TO CONTINUE', { size: 80, fill: 0x000000 }); continueText.anchor.set(0.5, 0.5); continueText.x = 1024; continueText.y = 2530; self.addChild(continueText); // Fade text in and out animation self.animateText = function () { tween(continueText, { alpha: 0.3 }, { duration: 800, easing: tween.linear, onFinish: function onFinish() { tween(continueText, { alpha: 1 }, { duration: 800, easing: tween.linear, onFinish: function onFinish() { if (self.isActive) { self.animateText(); } } }); } }); }; self.activate = function () { self.isActive = true; self.visible = true; self.animateText(); }; self.deactivate = function () { self.isActive = false; self.visible = false; }; // Handle tap to continue self.down = function (x, y, obj) { if (self.isActive && game.onIntroComplete) { game.onIntroComplete(); } }; // Start deactivated self.deactivate(); return self; }); // Game Scene - wraps all the existing game logic var GameScene = Scene.expand(function () { var self = Scene.call(this); // All game variables var bottomBg, topBg, greenLine, destructionLine, scoreTxt; var comboImage = null; var comboFadeTimeout = null; var comboFadeTween = null; var horizontalLine, cannon; var frutasGeneradas = 0; var frutasFalladas = 0; var fruits = []; var fallingFruits = []; var cutFruits = []; var frutasFalladasTimeout = null; var fruitSpeed = 25; var comboCounter = 0; var cutZoneAsset, cutZoneRadius; var canCut = true; var lastTouchTick = -100; // Round system variables var roundState, roundBullets, roundFruitsQueue, roundFruitsActive; var roundBulletsFired, roundDelayTicks, currentStage; var roundDelayBetweenRounds, roundNextBulletTick, roundStartTick; var countdownObjectsFired, goShotTick, stage2Scheduled, stage3Scheduled; var roundJustReset, showCountdownInMiddleDone; self.activate = function () { self.isActive = true; self.visible = true; // Start with black overlay for fade-in effect var fadeOverlay = LK.getAsset('vertical_line', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, color: 0x000000, alpha: 1 }); self.addChild(fadeOverlay); // Bring fade overlay to front to ensure it's above all other elements self.setChildIndex(fadeOverlay, self.children.length - 1); // Fade out the black overlay tween(fadeOverlay, { alpha: 0 }, { duration: 800, easing: tween.linear, onFinish: function onFinish() { fadeOverlay.destroy(); } }); // Play background music when game starts if (Inicio) { // Add delay for subsequent plays LK.setTimeout(function () { LK.playMusic('bgmusic', { loop: false }); }, 300); // 2 second delay } else { // Play immediately for first time LK.setTimeout(function () { LK.playMusic('bgmusic', { loop: false }); }, 50); // 2 second delay } }; self.deactivate = function () { self.isActive = false; self.visible = false; }; // All game variables will be initialized here self.initGame = function () { // Fondo inferior café bottomBg = LK.getAsset('bottom_bg', { anchorX: 0, anchorY: 1, x: 0, y: 2732 }); self.addChild(bottomBg); // Fondo superior azul topBg = LK.getAsset('top_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); self.addChild(topBg); // Línea verde casi transparente greenLine = LK.getAsset('greenLine', { anchorX: 0, anchorY: 0.5, x: 25, y: 500, alpha: 0.2 }); self.addChild(greenLine); // Línea roja de destrucción destructionLine = LK.getAsset('vertical_line', { anchorX: 0, anchorY: 0.5, x: 0, y: -100, width: 2048, height: 10, color: 0xff0000, shape: 'box' }); self.addChild(destructionLine); // Score text (invisible) scoreTxt = new Text2('', { size: 1, fill: "#222", alpha: 0 }); scoreTxt.visible = false; // Línea negra horizontal horizontalLine = LK.getAsset('vertical_line', { anchorX: 0, anchorY: 0.5, x: 0, y: 2732 / 2 + 540, width: 2048, height: 120, color: 0x000000, shape: 'box' }); // Instancia el cañón cannon = new Cannon(); self.addChild(horizontalLine); self.addChild(cannon); cannon.show(); // Initialize cut zone cutZoneAsset = LK.getAsset('cut_zone', { anchorX: 0.5, anchorY: 0.5 }); cutZoneRadius = cutZoneAsset.width / 2; cutZoneAsset.destroy(); // Reset all game variables frutasGeneradas = 0; frutasFalladas = 0; fruits = []; fallingFruits = []; cutFruits = []; fruitSpeed = 25; comboCounter = 0; canCut = true; lastTouchTick = -100; roundState = undefined; countdownObjectsFired = undefined; goShotTick = undefined; stage2Scheduled = undefined; stage3Scheduled = undefined; roundJustReset = undefined; showCountdownInMiddleDone = undefined; // Reset score LK.setScore(0); updateScore(0); // Set game start delay if coming from menu or restart self.gameStartDelay = LK.ticks; // 0.2 seconds delay (12 ticks at 60fps) }; // Helper functions function updateScore(val) { LK.setScore(val); scoreTxt.setText(val); } function tryCutFruit(x, y) { if (!canCut) { return; } canCut = false; lastTouchTick = LK.ticks; for (var i = 0; i < fallingFruits.length; i++) { var fruit = fallingFruits[i]; if (!fruit.isActive) { continue; } if (fruit.state === 'cuttable') { var dx = x - fruit.x; var dy = y - fruit.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= fruit.radius * fruit.scaleX) { fruit.cut(); if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } LK.getSound('cut').play(); // Check if Y position is within combo zone (350-750) if (y >= 350 && y <= 750) { comboCounter++; if (comboCounter >= 13) { comboCounter = 1; // Reset to 1 after reaching 12 } } else { comboCounter = 0; } // Update combo image display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { // Cancel any ongoing tween on the previous combo image if (comboFadeTween) { comboFadeTween.stop(); comboFadeTween = null; } comboImage.destroy(); comboImage = null; } if (comboCounter > 0 && comboCounter <= 12) { comboImage = LK.getAsset('combo' + comboCounter, { anchorX: 1, anchorY: 0, alpha: 1, scaleX: 0.5, scaleY: 0.5 }); LK.gui.topRight.addChild(comboImage); comboImage.x = -20; // 20px from right edge comboImage.y = 20; // 20px from top // Scale up from 50% to 100% in 0.1 seconds tween(comboImage, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut }); // After 0.3 seconds (0.1s grow + 0.2s show), start fade out comboFadeTimeout = LK.setTimeout(function () { if (comboImage) { comboFadeTween = tween(comboImage, { alpha: 0 }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { if (comboImage) { comboImage.destroy(); comboImage = null; } comboFadeTween = null; } }); } comboFadeTimeout = null; }, 300); } // Animación de corte para todas las frutas var fruitCut = new FruitCut(); fruitCut.init(fruit.fruitType, fruit.x, fruit.y, fruit.rotation, fruit.scaleX); self.addChild(fruitCut); // Asegura que la línea negra esté sobre las frutas cortadas pero debajo del cañón if (horizontalLine && horizontalLine.parent) { horizontalLine.parent.removeChild(horizontalLine); self.addChild(horizontalLine); } // Asegura que el cañón siempre esté al frente if (cannon && cannon.parent) { cannon.parent.removeChild(cannon); self.addChild(cannon); } fruitCut.animate(function () { fruitCut.destroy(); }); cutFruits.push(fruitCut); frutasGeneradas++; updateScore(LK.getScore() + 1); return; } } } } self.down = function (x, y, obj) { if (self.isActive) { // Check if game start delay has passed if (self.gameStartDelay && LK.ticks < self.gameStartDelay) { return; } tryCutFruit(x, y); } }; self.updateGame = function () { if (!self.isActive) { return; } // Wait for game start delay before processing any game logic if (self.gameStartDelay && LK.ticks < self.gameStartDelay) { return; } // Permitir corte nuevamente tras 6 ticks (~100ms) if (!canCut && LK.ticks - lastTouchTick > 6) { canCut = true; } // --- SISTEMA DE BALAS POR RONDA (5 balas, 1s entre cada una, 7s delay antes de mostrar en pantalla superior) --- if (typeof roundState === "undefined") { // Estados posibles: 'waiting', 'spawning', 'waitingFruits' roundState = 'waiting'; roundBullets = 7; // 3 countdown objects + 4 fruits (stage 1) roundFruitsQueue = []; roundFruitsActive = []; roundBulletsFired = 0; roundDelayTicks = 0; currentStage = 1; // Track current stage (1 or 2) roundDelayBetweenRounds = 120; // 2 seconds between rounds } if (roundState === 'waiting') { // Limpiar arrays de frutas y mitades cortadas de la ronda anterior for (var i = fruits.length - 1; i >= 0; i--) { if (fruits[i]) { fruits[i].destroy(); } } fruits = []; for (var i = fallingFruits.length - 1; i >= 0; i--) { if (fallingFruits[i]) { fallingFruits[i].destroy(); } } fallingFruits = []; for (var i = cutFruits.length - 1; i >= 0; i--) { if (cutFruits[i]) { cutFruits[i].destroy(); } } cutFruits = []; // Limpiar arrays de control de ronda if (typeof roundFruitsQueue !== "undefined") { for (var i = roundFruitsQueue.length - 1; i >= 0; i--) { if (roundFruitsQueue[i] && roundFruitsQueue[i].destroy) { roundFruitsQueue[i].destroy(); } } } roundFruitsQueue = []; if (typeof roundFruitsActive !== "undefined") { for (var i = roundFruitsActive.length - 1; i >= 0; i--) { if (roundFruitsActive[i] && roundFruitsActive[i].destroy) { roundFruitsActive[i].destroy(); } } } roundFruitsActive = []; // Reset meta de ronda roundBulletsFired = 0; roundNextBulletTick = LK.ticks; roundDelayTicks = 0; // Set round bullets based on current stage if (currentStage === 1) { roundBullets = 7; // 3 countdown + 4 fruits // Schedule stage 2 to start 8 seconds after stage 1 begins if (typeof stage2Scheduled === "undefined" || !stage2Scheduled) { stage2Scheduled = true; LK.setTimeout(function () { // Start stage 2 currentStage = 2; roundState = 'waiting'; stage2Scheduled = false; // Schedule stage 3 to start 5 seconds after stage 2 begins if (typeof stage3Scheduled === "undefined" || !stage3Scheduled) { stage3Scheduled = true; LK.setTimeout(function () { currentStage = 3; roundState = 'waiting'; stage3Scheduled = false; }, 4000); // 5 seconds after stage 2 starts } }, 10300); // 8 seconds } } else if (currentStage === 2) { roundBullets = 4; // 3 fruits only, no countdown countdownObjectsFired = 4; // Skip countdown in stage 2 } else if (currentStage === 3) { // Reiniciar todas las variables para evitar errores en la generación de frutas frutasGeneradas = 0; frutasFalladas = 0; fruits = []; fallingFruits = []; cutFruits = []; roundFruitsQueue = []; roundFruitsActive = []; roundBullets = 4; // 4 frutas en la tercera etapa countdownObjectsFired = 4; // No countdown en la etapa 3 // Clear any existing combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } } // Esperar un frame antes de permitir el spawn para evitar generación múltiple if (typeof roundJustReset === "undefined" || !roundJustReset) { roundJustReset = true; return; } else { roundJustReset = false; roundState = 'spawning'; goShotTick = undefined; } } if (roundState === 'spawning') { // Espera inicial antes de cualquier disparo if (typeof roundStartTick === "undefined") { roundStartTick = LK.ticks; } var delay = 20; // ⏱️ Espera de 2 segundos (120 ticks) if (LK.ticks - roundStartTick < delay) { return; // ⏳ Aún esperando el inicio } // Disparar los objetos de cuenta regresiva "1", "2", "Go" en los primeros tres disparos if (typeof countdownObjectsFired === "undefined") { countdownObjectsFired = 0; } if (roundBulletsFired < roundBullets && LK.ticks >= roundNextBulletTick) { var margin = 180; var fruitX = margin + Math.random() * (2048 - 2 * margin); cannon.x = fruitX; cannon.show(); cannon.shoot(); // Dispara "1", "2", "Go" en los primeros tres disparos if (countdownObjectsFired === 0) { var obj1 = new Fruit(); obj1.fruitType = 'D1'; if (obj1.fruitAsset) { obj1.fruitAsset.destroy(); } obj1.fruitAsset = obj1.attachAsset('D1', { anchorX: 0.5, anchorY: 0.5 }); obj1.radius = obj1.fruitAsset.width / 2; obj1.x = fruitX; obj1.y = 2732 + obj1.radius - 120; obj1.startY = obj1.y; obj1.targetY = 2732 / 2 + 600; obj1.state = 'rising'; obj1.ticks = 0; obj1.scaleX = 0.1; obj1.scaleY = 0.1; obj1.roundMeta = { hasCrossed: false, crossedTick: null, showScheduled: false, showTimeout: null, destroyed: false }; fruits.push(obj1); roundFruitsQueue.push(obj1); self.addChild(obj1); // Mostrar "1" centrado y gigante con fade out var centerObj1 = new CountdownOne(); centerObj1.x = 2048 / 2; centerObj1.y = 2732 / 2.5; centerObj1.scaleX = centerObj1.scaleY = 1; // Gigante centerObj1.alpha = 1; self.addChild(centerObj1); // Fade out después de 0.5 segundos LK.setTimeout(function () { tween(centerObj1, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { centerObj1.destroy(); } }); }, 500); countdownObjectsFired++; } else if (countdownObjectsFired === 1) { var obj2 = new Fruit(); obj2.fruitType = 'D2'; if (obj2.fruitAsset) { obj2.fruitAsset.destroy(); } obj2.fruitAsset = obj2.attachAsset('D2', { anchorX: 0.5, anchorY: 0.5 }); obj2.radius = obj2.fruitAsset.width / 2; obj2.x = fruitX; obj2.y = 2732 + obj2.radius - 120; obj2.startY = obj2.y; obj2.targetY = 2732 / 2 + 600; obj2.state = 'rising'; obj2.ticks = 0; obj2.scaleX = 0.1; obj2.scaleY = 0.1; obj2.roundMeta = { hasCrossed: false, crossedTick: null, showScheduled: false, showTimeout: null, destroyed: false }; fruits.push(obj2); roundFruitsQueue.push(obj2); self.addChild(obj2); // Mostrar "2" centrado y gigante con fade out var centerObj2 = new CountdownTwo(); centerObj2.x = 2048 / 2; centerObj2.y = 2732 / 2.5; centerObj2.scaleX = centerObj2.scaleY = 1; // Gigante centerObj2.alpha = 1; self.addChild(centerObj2); // Fade out después de 0.5 segundos LK.setTimeout(function () { tween(centerObj2, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { centerObj2.destroy(); } }); }, 500); countdownObjectsFired++; } else if (countdownObjectsFired === 2) { var objGo = new Fruit(); objGo.fruitType = 'DGO'; if (objGo.fruitAsset) { objGo.fruitAsset.destroy(); } objGo.fruitAsset = objGo.attachAsset('DGO', { anchorX: 0.5, anchorY: 0.5 }); objGo.radius = objGo.fruitAsset.width / 2; objGo.x = fruitX; objGo.y = 2732 + objGo.radius - 120; objGo.startY = objGo.y; objGo.targetY = 2732 / 2 + 600; objGo.state = 'rising'; objGo.ticks = 0; objGo.scaleX = 0.1; objGo.scaleY = 0.1; objGo.roundMeta = { hasCrossed: false, crossedTick: null, showScheduled: false, showTimeout: null, destroyed: false }; fruits.push(objGo); roundFruitsQueue.push(objGo); self.addChild(objGo); // Mostrar "Go!" centrado y gigante con fade out var centerObjGo = new CountdownGo(); centerObjGo.x = 2048 / 2; centerObjGo.y = 2732 / 2.5; centerObjGo.scaleX = centerObjGo.scaleY = 1; // Gigante centerObjGo.alpha = 1; self.addChild(centerObjGo); // Fade out después de 0.5 segundos LK.setTimeout(function () { tween(centerObjGo, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { centerObjGo.destroy(); } }); }, 500); countdownObjectsFired++; // After Go!, wait 2 seconds before launching fruits if (typeof goShotTick === "undefined") { goShotTick = LK.ticks; // Schedule game end 18 seconds after Go! LK.setTimeout(function () { // Set Inicio to true when game ends for the first time if (!Inicio) { Inicio = true; } // Show stars based on fruits cut var starAsset; var starSound; if (LK.getScore() === 0) { starAsset = 'Estrellas0'; starSound = 'E1'; // Use E1 sound for 0 stars } else if (LK.getScore() <= 6) { starAsset = 'Estrellas1'; starSound = 'E1'; } else if (LK.getScore() <= 11) { starAsset = 'Estrellas2'; starSound = 'E2'; } else { starAsset = 'Estrellas3'; starSound = 'E3'; } // Create and display star var star = LK.getAsset(starAsset, { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, alpha: 0, scaleX: 0.5, scaleY: 0.5 }); self.addChild(star); // Play victory sound when stars appear LK.getSound('victory').play(); // Animate star appearance tween(star, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.cubicOut, onFinish: function onFinish() { // Keep star visible for a moment then show end scene LK.setTimeout(function () { // Stop music LK.stopMusic(); // Clean up combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } // Show end scene without deactivating game scene // This keeps the game visible in the background if (game.endScene) { game.endScene.activate(); game.currentScene = game.endScene; // Force end scene to be on top of everything game.endScene.bringToFront(); } }, 2000); } }); }, 18000); // 18 seconds } } else { // Wait 1.3 seconds (78 ticks) after Go! before launching fruits if (typeof goShotTick !== "undefined" && LK.ticks - goShotTick < 60) { return; } // Después de los tres primeros, dispara frutas normales var fruit = new Fruit(); fruit.x = fruitX; fruit.y = 2732 + fruit.radius - 300; fruit.startY = fruit.y; fruit.targetY = 2732 / 2 + 600; fruit.state = 'rising'; fruit.ticks = 0; fruit.roundMeta = { hasCrossed: false, crossedTick: null, showScheduled: false, showTimeout: null, destroyed: false }; fruits.push(fruit); roundFruitsQueue.push(fruit); self.addChild(fruit); } // Asegura que el cañón siempre esté al frente if (cannon && cannon.parent) { cannon.parent.removeChild(cannon); self.addChild(cannon); } roundBulletsFired++; // After Go!, wait 1.3 seconds before next bullet if (countdownObjectsFired === 3 && roundBulletsFired === 3) { roundNextBulletTick = LK.ticks + 60; // 1.3s wait after Go! } else if (roundBulletsFired < 3) { // Original timing for countdown objects (1, 2, Go!) roundNextBulletTick = LK.ticks + 30; // 0.5s between countdown objects } else { // Timing between fruits based on stage if (currentStage === 1) { roundNextBulletTick = LK.ticks + 60; // 1s entre frutas (stage 1) } else if (currentStage === 2) { roundNextBulletTick = LK.ticks + 30; // 0.5s entre frutas (stage 2) } else if (currentStage === 3) { roundNextBulletTick = LK.ticks + 30; } } } // When all bullets are fired, round is complete if (roundBulletsFired >= roundBullets) { roundState = 'complete'; } } if (roundState === 'complete') { // Round completed - do nothing, no more spawning } // Mover frutas que suben desde abajo hasta el centro for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; // Only destroy objects that are NOT rising (i.e., not going up from bottom) // This ensures fruits launched from bottom can reach the top before being destroyed if (fruit.state !== 'rising' && fruit.y < -100) { // Destroy cut zone circle if exists if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } // Mark as inactive fruit.isActive = false; // Mark as destroyed for round system if (fruit.roundMeta) { fruit.roundMeta.destroyed = true; } // Clear any scheduled timeouts if (fruit.roundMeta && fruit.roundMeta.showTimeout) { LK.clearTimeout(fruit.roundMeta.showTimeout); fruit.roundMeta.showTimeout = null; } // Remove from roundFruitsQueue if present if (typeof roundFruitsQueue !== "undefined") { for (var j = roundFruitsQueue.length - 1; j >= 0; j--) { if (roundFruitsQueue[j] === fruit) { roundFruitsQueue.splice(j, 1); break; } } } // Destroy the object completely fruit.destroy(); fruits.splice(i, 1); continue; // Skip rest of processing for this object } if (fruit.state === 'rising') { // Cambiar velocidad solo en etapa 2 var currentFruitSpeed = fruitSpeed; if (currentStage === 2) { currentFruitSpeed = 40; // velocidad más rápida en etapa 2 } fruit.y -= currentFruitSpeed; fruit.ticks++; // Escalado según altura (más arriba, más grande) var minScale = 1.0; var maxScale = 1.5; var y0 = 2732 + fruit.radius + 10; var y1 = 2732 / 2 + 420; var t = (fruit.y - y1) / (y0 - y1); if (t < 0) { t = 0; } if (t > 1) { t = 1; } var scale = minScale + (maxScale - minScale) * (1 - t); fruit.scaleX = fruit.scaleY = scale; // Rotación en la parte de abajo (solo mientras sube) if (typeof fruit.rotation === "undefined") { fruit.rotation = Math.random() * Math.PI * 2; } if (typeof fruit.rotationSpeed === "undefined") { fruit.rotationSpeed = (Math.random() - 0.5) * 0.08; } fruit.rotation += fruit.rotationSpeed; if (fruit.fruitAsset) { fruit.fruitAsset.rotation = fruit.rotation; } // Cuando cruza la altura objetivo (centro), programar aparición en pantalla superior tras un tiempo proporcional al número de frutas lanzadas if (!fruit.roundMeta.hasCrossed && fruit.y <= fruit.targetY) { fruit.roundMeta.hasCrossed = true; fruit.roundMeta.crossedTick = LK.ticks; fruit.visible = false; // Cambiar tiempo de espera solo en etapa 2 var appearDelayMs = 2700; // default 2.7s if (currentStage === 2) { appearDelayMs = 1100; // 1.5s en etapa 2 } if (currentStage === 3) { appearDelayMs = 1200; // 1.5s en etapa 2 } var isCountdownObject = fruit.fruitType === 'D1' || fruit.fruitType === 'D2' || fruit.fruitType === 'DGO'; if (!isCountdownObject) { fruit.roundMeta.showScheduled = true; fruit.roundMeta.showTimeout = LK.setTimeout(function (fruitRef, fruitsArr, iIdx) { return function () { if (fruitRef.roundMeta.destroyed) { return; } var fruitType = fruitRef.fruitType; var x = fruitRef.x; fruitRef.destroy(); for (var j = 0; j < fruitsArr.length; j++) { if (fruitsArr[j] === fruitRef) { fruitsArr.splice(j, 1); break; } } var fallingFruit = new Fruit(); if (typeof fruitType === "undefined" || !fruitType) { fruitType = fallingFruit.fruitType; } else { fallingFruit.fruitType = fruitType; } if (fallingFruit.fruitAsset) { fallingFruit.fruitAsset.destroy(); } fallingFruit.fruitAsset = fallingFruit.attachAsset(fallingFruit.fruitType, { anchorX: 0.5, anchorY: 0.5 }); fallingFruit.x = x; fallingFruit.y = 2732 / 2 + 420 + 100; fallingFruit.startY = fallingFruit.y; fallingFruit.lastY = 2732; // Initialize lastY to a safe value well below destruction line fallingFruit.state = 'cuttable'; fallingFruit.ticks = 0; fallingFruit.rotation = Math.random() * Math.PI * 2; fallingFruit.rotationSpeed = (Math.random() - 0.5) * 0.04; fallingFruit.roundMeta = { destroyed: false }; fallingFruits.push(fallingFruit); roundFruitsActive.push(fallingFruit); self.addChild(fallingFruit); if (horizontalLine && horizontalLine.parent) { horizontalLine.parent.removeChild(horizontalLine); self.addChild(horizontalLine); } if (cannon && cannon.parent) { cannon.parent.removeChild(cannon); self.addChild(cannon); } }; }(fruit, fruits, i), appearDelayMs); } } if (fruit.roundMeta.hasCrossed && fruit.roundMeta.showScheduled) { if (fruit.roundMeta.destroyed) { if (fruit.roundMeta.showTimeout) { LK.clearTimeout(fruit.roundMeta.showTimeout); fruit.roundMeta.showTimeout = null; } if (typeof roundFruitsQueue !== "undefined") { for (var j = roundFruitsQueue.length - 1; j >= 0; j--) { if (roundFruitsQueue[j] === fruit) { roundFruitsQueue.splice(j, 1); break; } } } fruits.splice(i, 1); } continue; } } } // Mover frutas que suben de nuevo y gestionar zona de corte visual // --- Asegura que la línea negra esté sobre los sprites de las frutas en cada frame --- if (horizontalLine && horizontalLine.parent) { horizontalLine.parent.removeChild(horizontalLine); self.addChild(horizontalLine); } // Asegura que la línea roja de destrucción esté visible encima de todo if (destructionLine && destructionLine.parent) { destructionLine.parent.removeChild(destructionLine); self.addChild(destructionLine); } // Volver a agregar la línea roja al final para que esté encima if (cannon && cannon.parent) { cannon.parent.removeChild(cannon); self.addChild(cannon); } if (destructionLine && destructionLine.parent) { destructionLine.parent.removeChild(destructionLine); self.addChild(destructionLine); } for (var i = fallingFruits.length - 1; i >= 0; i--) { var fruit = fallingFruits[i]; // Check if falling fruit passes above Y=800 for complete destruction // Only destroy fruits that are falling down (cuttable or flyingup states) if (fruit.y < -100 && (fruit.state === 'cuttable' || fruit.state === 'flyingup')) { // Destroy cut zone circle if exists if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } // Mark as inactive fruit.isActive = false; // Mark as destroyed for round system if (fruit.roundMeta) { fruit.roundMeta.destroyed = true; } // Remove from roundFruitsActive if present if (typeof roundFruitsActive !== "undefined") { for (var j = roundFruitsActive.length - 1; j >= 0; j--) { if (roundFruitsActive[j] === fruit) { roundFruitsActive.splice(j, 1); break; } } } // Count as failed if not cut if (!fruit.isCut) { frutasFalladas++; frutasGeneradas++; comboCounter = 0; // Play fruit miss sound LK.getSound('fruit_miss').play(); // Clear any existing combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } } // Destroy the fruit completely fruit.destroy(); fallingFruits.splice(i, 1); continue; // Skip rest of processing for this fruit } if (!fruit.isActive) { continue; } // Escalado según altura (más arriba, más grande) var minScale = 1.0; var maxScale = 1.25 * 1.3 * 1.3; //{45} // 60% más grande en la parte superior var y0 = 2732 / 2 + 420; var y1 = 420; var t = (fruit.y - y1) / (y0 - y1); if (t < 0) { t = 0; } if (t > 1) { t = 1; } var scale = minScale + (maxScale - minScale) * (1 - t); fruit.scaleX = fruit.scaleY = scale; // Rotación suave if (typeof fruit.rotationSpeed !== "undefined") { fruit.rotation += fruit.rotationSpeed; if (fruit.fruitAsset) { fruit.fruitAsset.rotation = fruit.rotation; } } // Movimiento hacia arriba mientras sea cortable o volando hacia arriba if (fruit.state === 'cuttable' || fruit.state === 'flyingup') { var topFruitSpeed = fruitSpeed; if (currentStage === 2) { topFruitSpeed = 30; // velocidad más alta en la segunda etapa } // Double speed if fruit has passed cutting zone if (fruit.doubledSpeed) { topFruitSpeed = topFruitSpeed * 2; } fruit.y -= topFruitSpeed; } // --- Animación progresiva del círculo de corte --- // El círculo aparece desde el inicio de la subida (cuttable), crece y se difumina progresivamente if (fruit.state === 'cuttable') { // Si no existe el círculo, crearlo if (!fruit.cutZoneCircle) { fruit.cutZoneCircle = LK.getAsset('cut_zone', { anchorX: 0.5, anchorY: 0.5, x: fruit.x, y: fruit.y, alpha: 0.0, scaleX: 0.1, scaleY: 0.1 }); self.addChild(fruit.cutZoneCircle); // Asegura que la línea negra esté sobre el círculo de corte pero debajo del cañón if (horizontalLine && horizontalLine.parent) { horizontalLine.parent.removeChild(horizontalLine); self.addChild(horizontalLine); } // Asegura que el cañón siempre esté al frente if (cannon && cannon.parent) { cannon.parent.removeChild(cannon); self.addChild(cannon); } } // Progresión del círculo: desde que inicia la subida hasta que llega cerca de la parte superior var appearT = (y0 - fruit.y) / (y0 - y1); if (appearT < 0) { appearT = 0; } if (appearT > 1) { appearT = 1; } var maxScaleZone = 0.85; // Más pequeño, para que quede dentro de la fruta var minScaleZone = 0.15; var maxAlpha = 0.38; // Más difuminado var minAlpha = 0.08; var scaleZone = minScaleZone + (maxScaleZone - minScaleZone) * appearT; var alphaZone = minAlpha + (maxAlpha - minAlpha) * appearT; fruit.cutZoneCircle.scaleX = fruit.cutZoneCircle.scaleY = scaleZone; fruit.cutZoneCircle.alpha = alphaZone; fruit.cutZoneCircle.x = fruit.x; fruit.cutZoneCircle.y = fruit.y; // Si la fruta sale por arriba, eliminar círculo y pasar a flyingup if (fruit.y < y1) { // Si el círculo de corte existe y la fruta no fue cortada, contar como fallada if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; if (fruit.isActive && !fruit.isCut) { frutasFalladas++; frutasGeneradas++; comboCounter = 0; // Play fruit miss sound LK.getSound('fruit_miss').play(); // Clear any existing combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } } } fruit.state = 'flyingup'; // Double the speed when fruit can no longer be cut fruit.doubledSpeed = true; } } else { // Si no está en estado cortable y existe el círculo, destruirlo if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } } // Si fue cortada, eliminar círculo visual inmediatamente pero mantener la fruta visible if (!fruit.isActive && fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } // Si está cayendo, animar caída if (fruit.state === 'dropping') { fruit.dropSpeed += 2.5; // gravedad fruit.y += fruit.dropSpeed; // Escalado decreciente al caer fruit.scaleX = fruit.scaleY = Math.max(0.7, fruit.scaleX - 0.01); if (fruit.cutZoneCircle) { fruit.cutZoneCircle.x = fruit.x; fruit.cutZoneCircle.y = fruit.y; } // Si sale de pantalla, marcar como inactivo pero mantener visible if (fruit.y > 2732 + fruit.radius + 20) { if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } fruit.isActive = false; } } // Si la fruta sigue subiendo después de la zona de corte y no fue cortada if (fruit.state === 'flyingup') { // Al entrar por primera vez en flyingup, inicia temporizador de espera if (typeof fruit.waitTicks === "undefined") { fruit.waitTicks = 0; } fruit.waitTicks++; // Mantener la fruta visible y estática durante 4 segundos (240 ticks) if (fruit.waitTicks < 240) { // Mantener posición (opcional: podrías hacer que siga subiendo lentamente) // fruit.y -= fruitSpeed * 0.1; } else { // Después de 7 segundos, hacer que suba y desaparezca // Continue moving at current speed (already doubled if applicable) // Escalado sigue creciendo un poco fruit.scaleX = fruit.scaleY = Math.min(maxScale, fruit.scaleX + 0.01); // Check if fruit passes destruction line at Y=800 for complete destruction if (fruit.y < -100) { // Destroy cut zone circle if exists if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } // Mark as inactive fruit.isActive = false; // Mark as destroyed for round system if (fruit.roundMeta) { fruit.roundMeta.destroyed = true; } // Remove from roundFruitsActive if present if (typeof roundFruitsActive !== "undefined") { for (var j = roundFruitsActive.length - 1; j >= 0; j--) { if (roundFruitsActive[j] === fruit) { roundFruitsActive.splice(j, 1); break; } } } // Count as failed if not cut if (!fruit.isCut) { frutasFalladas++; frutasGeneradas++; comboCounter = 0; // Play fruit miss sound LK.getSound('fruit_miss').play(); // Clear any existing combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } } // Destroy the fruit completely fruit.destroy(); fallingFruits.splice(i, 1); continue; // Skip rest of processing for this fruit } // Si sale de pantalla por arriba, marcar como inactiva y eliminar círculo de corte si existe if (fruit.y < -fruit.radius - 20) { if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } fruit.isActive = false; // Marcar como destruida para el sistema de ronda if (fruit.roundMeta) { fruit.roundMeta.destroyed = true; } // Eliminar de roundFruitsActive si está presente if (typeof roundFruitsActive !== "undefined") { for (var j = roundFruitsActive.length - 1; j >= 0; j--) { if (roundFruitsActive[j] === fruit) { roundFruitsActive.splice(j, 1); break; } } } // Aumentar frutasFalladas y frutasGeneradas solo si la fruta no fue cortada if (!fruit.isCut) { frutasFalladas++; frutasGeneradas++; comboCounter = 0; // Play fruit miss sound LK.getSound('fruit_miss').play(); // Clear any existing combo display if (comboFadeTimeout) { LK.clearTimeout(comboFadeTimeout); comboFadeTimeout = null; } if (comboImage) { comboImage.destroy(); comboImage = null; } } } } } // Si la fruta ya fue cortada o salió de pantalla, eliminar if (!fruit.isActive || fruit.y > 2732 + fruit.radius + 20 || fruit.y < -fruit.radius - 20) { if (fruit.cutZoneCircle) { fruit.cutZoneCircle.destroy(); fruit.cutZoneCircle = null; } // Marcar como destruida para el sistema de ronda if (fruit.roundMeta) { fruit.roundMeta.destroyed = true; } // Eliminar de roundFruitsActive si está presente if (typeof roundFruitsActive !== "undefined") { for (var j = roundFruitsActive.length - 1; j >= 0; j--) { if (roundFruitsActive[j] === fruit) { roundFruitsActive.splice(j, 1); break; } } } fruit.destroy(); fallingFruits.splice(i, 1); } } // Limpiar mitades cortadas que ya terminaron animación for (var i = cutFruits.length - 1; i >= 0; i--) { var fc = cutFruits[i]; if (!fc.left || !fc.right) { if (fc.destroy) { fc.destroy(); } cutFruits.splice(i, 1); } } // Variable para controlar el timeout (debe estar fuera de la función que chequea frutasFalladas) if (frutasFalladas >= 5 || frutasGeneradas >= 5 && frutasFalladas < 5) { // Mostrar los sprites 1, 2, Go en el centro con fade in/fade out rápido solo una vez, SOLO al inicio del juego if ((typeof showCountdownInMiddleDone === "undefined" || !showCountdownInMiddleDone) && LK.ticks < 120) { showCountdownInMiddleDone = true; var centerX = 2048 / 2; var centerY = 2732 / 2; var countdownSprites = [{ cls: CountdownOne, delay: 0 }, { cls: CountdownTwo, delay: 350 }, { cls: CountdownGo, delay: 700 }]; for (var idx = 0; idx < countdownSprites.length; idx++) { (function (idx) { var conf = countdownSprites[idx]; LK.setTimeout(function () { var obj = new conf.cls(); obj.x = centerX; obj.y = centerY; obj.alpha = 0; self.addChild(obj); // Fade in rápido tween(obj, { alpha: 1 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { // Fade out rápido tween(obj, { alpha: 0 }, { duration: 180, delay: 180, easing: tween.cubicIn, onFinish: function onFinish() { obj.destroy(); } }); } }); }, conf.delay); })(idx); } } return; } }; return self; }); // EndScene - con método reset para botones y visibilidad var EndScene = Scene.expand(function () { var self = Scene.call(this); self.overlay = self.attachAsset('vertical_line', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, width: 2048, height: 2732, alpha: 0.1, tint: 0x000000 }); self.restartButton = self.attachAsset('button_restart', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1200 }); self.menuButton = self.attachAsset('button_menu', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600 }); function setupButton(button, callback) { button.interactive = true; button.tint = 0xffffff; button.alpha = 1; button.down = function () { button.tint = 0xcccccc; }; button.up = function () { button.tint = 0xffffff; if (callback) { callback(); } }; } setupButton(self.restartButton, function () { if (game.onRestartPressed) { game.onRestartPressed(); } }); setupButton(self.menuButton, function () { if (game.onMenuPressed) { game.onMenuPressed(); } }); self.move = function (x, y, obj) { var globalPos = self.toGlobal({ x: x, y: y }); var lb = self.restartButton.toLocal(globalPos); var hw = self.restartButton.width / 2; var hh = self.restartButton.height / 2; self.restartButton.alpha = lb.x >= -hw && lb.x <= hw && lb.y >= -hh && lb.y <= hh ? 0.8 : 1; var lb2 = self.menuButton.toLocal(globalPos); var hw2 = self.menuButton.width / 2; var hh2 = self.menuButton.height / 2; self.menuButton.alpha = lb2.x >= -hw2 && lb2.x <= hw2 && lb2.y >= -hh2 && lb2.y <= hh2 ? 0.8 : 1; }; self.activate = function () { self.visible = true; self.restartButton.interactive = true; self.menuButton.interactive = true; self.restartButton.alpha = 1; self.menuButton.alpha = 1; self.restartButton.tint = 0xffffff; self.menuButton.tint = 0xffffff; // Always bring to front when activating self.bringToFront(); }; self.deactivate = function () { self.visible = false; self.restartButton.interactive = false; self.menuButton.interactive = false; }; // Subir esta escena a la cima del display list para que se vea encima de todo self.bringToFront = function () { if (self.parent) { self.parent.setChildIndex(self, self.parent.children.length - 1); } }; // Método para resetear los botones y visibilidad al mostrar la escena otra vez self.reset = function () { self.activate(); // Force multiple calls to ensure it's on top self.bringToFront(); LK.setTimeout(function () { self.bringToFront(); }, 10); }; // Empieza oculta self.deactivate(); return self; }); /**** * Initialize Game ****/ // -- Inicialización -- var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ var Inicio = false; // Track if this is the first game completion (local variable) // Preload critical menu assets to ensure they're ready var preloadedMenuBg = LK.getAsset('menu_background', {}); var preloadedTitulo = LK.getAsset('Titulo', {}); var menuScene = new MenuScene(); var introScene = new IntroScene(); var gameScene = new GameScene(); var endScene = new EndScene(); game.addChild(menuScene); game.addChild(introScene); game.addChild(gameScene); game.addChild(endScene); game.menuScene = menuScene; game.introScene = introScene; game.gameScene = gameScene; game.endScene = endScene; // Add a small delay before activating menu to ensure assets are loaded LK.setTimeout(function () { menuScene.activate(); }, 100); // Small delay to allow asset loading introScene.deactivate(); gameScene.deactivate(); endScene.deactivate(); var currentScene = menuScene; // -- Funciones de transición -- game.onPlayPressed = function () { // Stop menu music before transitioning LK.stopMusic(); menuScene.deactivate(); endScene.deactivate(); // Check if intro has been seen before if (storage.hasSeenIntro) { // Skip intro, go directly to game if (!gameScene.gameInitialized) { gameScene.initGame(); gameScene.gameInitialized = true; } gameScene.activate(); currentScene = gameScene; } else { // Show intro for first time introScene.activate(); currentScene = introScene; } }; game.onRestartPressed = function () { endScene.deactivate(); gameScene.deactivate(); introScene.deactivate(); game.removeChild(gameScene); gameScene = new GameScene(); game.addChild(gameScene); gameScene.initGame(); gameScene.gameInitialized = true; gameScene.activate(); currentScene = gameScene; LK.stopMusic(); }; game.onMenuPressed = function () { endScene.deactivate(); gameScene.deactivate(); introScene.deactivate(); LK.stopMusic(); menuScene.activate(); currentScene = menuScene; game.removeChild(gameScene); gameScene = new GameScene(); game.addChild(gameScene); gameScene.gameInitialized = false; }; game.onIntroComplete = function () { introScene.deactivate(); // Mark intro as seen in persistent storage storage.hasSeenIntro = true; if (!gameScene.gameInitialized) { gameScene.initGame(); gameScene.gameInitialized = true; } gameScene.activate(); currentScene = gameScene; }; // IMPORTANTE: cuando termines el juego llama esta función para mostrar los botones gameScene.onGameEnd = function () { gameScene.deactivate(); endScene.reset(); // <-- resetea y muestra el EndScene con botones activos y arriba currentScene = endScene; }; // -- Propagar movimiento -- game.move = function (x, y, obj) { if (currentScene && currentScene.move) { currentScene.move(x, y, obj); } }; game.update = function () { if (currentScene && currentScene.updateGame) { currentScene.updateGame(); } if (currentScene && currentScene.updateMenu) { currentScene.updateMenu(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Clase Cannon para mostrar el cañón y animar disparo
var Cannon = Container.expand(function () {
var self = Container.call(this);
// Asset del cañón (ahora usa la imagen personalizada)
self.cannonAsset = self.attachAsset('cannon', {
anchorX: 0.5,
anchorY: 1
});
// Efecto de fogonazo al disparar
self.flash = null;
// Estado
self.isVisible = true;
self.isShooting = false;
// Posición base (centrado horizontal, abajo)
self.baseX = 2048 / 2;
// Coloca el cañón pegado a la parte baja del juego (anchorY:1, así que baseY es exactamente el borde inferior)
self.baseY = 2732;
self.x = self.baseX;
self.y = self.baseY;
// Mostrar cañón
self.show = function () {
self.visible = true;
self.isVisible = true;
// Animar subida si estaba oculto
tween(self, {
y: self.baseY
}, {
duration: 350,
easing: tween.cubicOut
});
};
// Animar disparo (pequeño retroceso y fogonazo)
self.shoot = function () {
if (self.isShooting) {
return;
}
self.isShooting = true;
// Retroceso rápido
tween(self, {
y: self.baseY - 60
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Regresa a posición base
tween(self, {
y: self.baseY
}, {
duration: 120,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.isShooting = false;
}
});
}
});
// Fogonazo visual
if (!self.flash) {
self.flash = self.attachAsset('cut_zone', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -self.cannonAsset.height + 30,
scaleX: 0.5,
scaleY: 0.3,
alpha: 0.0,
color: 0xffffcc
});
}
self.flash.alpha = 0.7;
self.flash.scaleX = 0.5;
self.flash.scaleY = 0.3;
tween(self.flash, {
alpha: 0,
scaleX: 1.2,
scaleY: 0.7
}, {
duration: 120,
easing: tween.linear
});
};
return self;
});
// Clase para el objeto de cuenta regresiva "Go"
var CountdownGo = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('Go', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.asset.width / 2;
self.isActive = true;
self.state = 'countdown';
self.destroyCountdown = function () {
self.isActive = false;
self.visible = false;
self.destroy();
};
return self;
});
// Clase para el objeto de cuenta regresiva "1"
var CountdownOne = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('1', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.asset.width / 2;
self.isActive = true;
self.state = 'countdown';
self.destroyCountdown = function () {
self.isActive = false;
self.visible = false;
self.destroy();
};
return self;
});
// Clase para el objeto de cuenta regresiva "2"
var CountdownTwo = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('2', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.asset.width / 2;
self.isActive = true;
self.state = 'countdown';
self.destroyCountdown = function () {
self.isActive = false;
self.visible = false;
self.destroy();
};
return self;
});
var Fruit = Container.expand(function () {
var self = Container.call(this);
// Selección aleatoria de tipo de fruta
var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum'];
self.fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
// Asset de la fruta
self.fruitAsset = self.attachAsset(self.fruitType, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.fruitAsset) {
self.fruitAsset.scaleX = 1; // Empieza muy pequeño
self.fruitAsset.scaleY = 1;
} else if (self.children && self.children.length > 0) {
var lastChild = self.children[self.children.length - 1];
lastChild.scaleX = 0.1;
lastChild.scaleY = 0.1;
self.fruitAsset = lastChild;
}
self.radius = self.fruitAsset.width * self.fruitAsset.scaleX / 2 + 60;
// Estado
self.isCut = false;
self.isActive = true;
// Agregamos la velocidad de crecimiento de escala (puedes ajustar)
self.scaleGrowSpeed = 0.1; // escala por frame
// Método para actualizar la fruta cada frame (debe llamarse en el ciclo principal)
self.update = function () {
if (!self.isCut && self.fruitAsset) {
// Incrementar escala hasta 0.5 (máximo)
if (self.fruitAsset.scaleX < 0.5) {
self.fruitAsset.scaleX = Math.min(0.5, self.fruitAsset.scaleX + self.scaleGrowSpeed);
self.fruitAsset.scaleY = self.fruitAsset.scaleX;
// Actualizar radius para hitbox
self.radius = self.fruitAsset.width * self.fruitAsset.scaleX / 2 + 60;
}
}
};
self.cut = function () {
if (self.isCut) {
return;
}
self.isCut = true;
self.isActive = false;
self.visible = false;
};
return self;
});
// Clase para la fruta cortada (dos mitades)
var FruitCut = Container.expand(function () {
var self = Container.call(this);
// Recibe tipo de fruta, posición base, rotación y escala
self.init = function (fruitType, x, y, rotation, scale) {
if (typeof rotation === "undefined") {
rotation = 0;
}
if (typeof scale === "undefined") {
scale = 1;
}
// Para las frutas, usar assets de mitades cortadas específicos por tipo de fruta
var leftAssetId = 'fruit_apple_cut_left';
var rightAssetId = 'fruit_apple_cut_right';
if (fruitType === 'fruit_apple') {
leftAssetId = 'fruit_apple_cut_left';
rightAssetId = 'fruit_apple_cut_right';
} else if (fruitType === 'fruit_lemon') {
leftAssetId = 'fruit_lemon_cut_left';
rightAssetId = 'fruit_lemon_cut_right';
} else if (fruitType === 'fruit_orange') {
leftAssetId = 'fruit_orange_cut_left';
rightAssetId = 'fruit_orange_cut_right';
} else if (fruitType === 'fruit_plum') {
leftAssetId = 'fruit_plum_cut_left';
rightAssetId = 'fruit_plum_cut_right';
} else if (fruitType === 'fruit_kiwi') {
leftAssetId = 'fruit_kiwi_cut_left';
rightAssetId = 'fruit_kiwi_cut_right';
}
// Mitad izquierda reflejada horizontalmente (flipX: 1)
self.left = self.attachAsset(leftAssetId, {
anchorX: 1,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
x: 0,
y: 0,
flipX: 1
});
// Mitad derecha normal
self.right = self.attachAsset(rightAssetId, {
anchorX: 0,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
x: 0,
y: 0
});
self.x = x;
self.y = y;
self.left.rotation = rotation;
self.right.rotation = rotation;
self.rotation = rotation;
};
// Animación de separación
self.animate = function (_onFinish) {
// Reduce size to 80% immediately
tween(self.left, {
x: -80,
rotation: self.left.rotation + 0.7,
scaleX: self.left.scaleX * 0.8,
scaleY: self.left.scaleY * 0.8
}, {
duration: 500,
easing: tween.cubicOut
});
tween(self.right, {
x: 80,
rotation: self.right.rotation - 0.7,
scaleX: self.right.scaleX * 0.8,
scaleY: self.right.scaleY * 0.8
}, {
duration: 500,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Fade out both halves after movement/scaling
tween(self.left, {
alpha: 0
}, {
duration: 350,
easing: tween.linear
});
tween(self.right, {
alpha: 0
}, {
duration: 350,
easing: tween.linear,
onFinish: function onFinish() {
if (_onFinish) {
_onFinish();
}
}
});
}
});
};
return self;
});
// Base Scene class for scene management
var Scene = Container.expand(function () {
var self = Container.call(this);
self.isActive = false;
self.activate = function () {
self.isActive = true;
self.visible = true;
// Make sure overlay is visible when scene is activated
if (self.overlay) {
self.overlay.visible = true;
}
};
self.deactivate = function () {
self.isActive = false;
self.visible = false;
// Make sure to hide overlay to prevent black background
if (self.overlay) {
self.overlay.visible = false;
}
};
return self;
});
// Menu Scene
var MenuScene = Scene.expand(function () {
var self = Scene.call(this);
// Full screen background (added first so it's behind everything)
var menuBg = self.attachAsset('menu_background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Create decorative fruits container to ensure proper layering
var fruitsContainer = new Container();
self.addChild(fruitsContainer);
// Add Titulo asset at the top
var titulo = self.attachAsset('Titulo', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1000
});
// Play button (will be added after fruits container)
var playButton = self.attachAsset('button_play', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2000
});
// Button interaction
playButton.interactive = true;
playButton.down = function () {
playButton.tint = 0xcccccc;
};
playButton.up = function () {
playButton.tint = 0xffffff;
if (game.onPlayPressed) {
game.onPlayPressed();
}
};
// Decorative fruits
var decorativeFruits = [];
var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum'];
var fruitScale = 1; // Normal size
var fruitSpacing = 250; // Horizontal spacing between fruits
var verticalSpacing = 200; // Vertical spacing between rows
var baseY = 150; // Starting Y position for first row
var fruitSpeed = 2; // Horizontal movement speed
// Calculate how many rows we can fit
var availableHeight = 2732 - 100;
var numRows = Math.floor(availableHeight / verticalSpacing);
// Decorative fruits
var decorativeFruits = [];
var fruitTypes = ['fruit_apple', 'fruit_lemon', 'fruit_orange', 'fruit_kiwi', 'fruit_plum'];
var fruitScale = 1; // Normal size
var fruitSpacing = 250; // Horizontal spacing between fruits
var verticalSpacing = 200; // Vertical spacing between rows
var baseY = 150; // Starting Y position for first row
var fruitSpeed = 2; // Horizontal movement speed
var resetX = -400; // X position where fruits reappear
// Calculate how many rows we can fit
var availableHeight = 2732 - 100;
var numRows = Math.floor(availableHeight / verticalSpacing);
// Create fruits and shadows for each row
for (var row = 0; row < numRows; row++) {
var y = baseY + row * verticalSpacing;
var numFruits = Math.ceil((2048 + fruitSpacing * 2) / fruitSpacing);
var rowOffset = row % 2 === 1 ? fruitSpacing / 2 : 0;
var initialRotation = row % 2 === 1 ? Math.PI : 0; // 180° for odd rows
for (var col = 0; col < numFruits; col++) {
var fruitType = fruitTypes[(row + col) % fruitTypes.length];
var x = col * fruitSpacing - fruitSpacing + rowOffset;
// Create shadow first (so it appears behind the fruit)
var shadow = self.attachAsset(fruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: x + 15,
y: y + 15,
scaleX: fruitScale,
scaleY: fruitScale,
rotation: initialRotation,
tint: 0x000000,
alpha: 0.3
});
shadow.rotationSpeed = 0.02;
shadow.isShadow = true;
decorativeFruits.push(shadow);
fruitsContainer.addChild(shadow);
// Create actual fruit on top
var fruit = self.attachAsset(fruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: fruitScale,
scaleY: fruitScale,
rotation: initialRotation
});
fruit.rotationSpeed = 0.02;
fruit.isShadow = false;
decorativeFruits.push(fruit);
fruitsContainer.addChild(fruit);
}
}
self.activate = function () {
self.isActive = true;
self.visible = true;
// Create black overlay for fade-in effect (larger than screen)
var fadeOverlay = LK.getAsset('vertical_line', {
anchorX: 0,
anchorY: 0,
x: -200,
y: -200,
width: 2448,
height: 3132,
color: 0x000000,
alpha: 1
});
self.addChild(fadeOverlay);
// Bring fade overlay to front to ensure it's above all other elements
self.setChildIndex(fadeOverlay, self.children.length - 1);
// Wait 1 second before starting the fade
LK.setTimeout(function () {
// Fade out the black overlay over 3 seconds
tween(fadeOverlay, {
alpha: 0
}, {
duration: 3000,
easing: tween.linear,
onFinish: function onFinish() {
fadeOverlay.destroy();
}
});
}, 1000); // 1 second delay
// Play menu music when menu activates
LK.playMusic('menu_music');
};
// Update function for menu scene
self.updateMenu = function () {
for (var i = 0; i < decorativeFruits.length; i++) {
var fruit = decorativeFruits[i];
fruit.x += fruitSpeed;
fruit.rotation += fruit.rotationSpeed;
if (fruit.x > 2048 + fruitSpacing) {
if (fruit.isShadow) {
// Aparece un poco más a la izquierda para compensar desfase
fruit.x = resetX;
} else {
fruit.x = resetX;
}
}
}
};
// Add tutorial reset button in bottom-right corner
var tutorialResetButton = self.attachAsset('tutorial_reset_button', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 80,
// 80px from right edge
y: 2732 - 80,
// 80px from bottom edge
scaleX: .6,
scaleY: .6,
alpha: 1.0
});
// Tutorial reset button interaction
tutorialResetButton.interactive = true;
tutorialResetButton.down = function () {
tutorialResetButton.tint = 0xcccccc;
};
tutorialResetButton.up = function () {
tutorialResetButton.tint = 0xffffff;
// Reset tutorial flag in storage
storage.hasSeenIntro = false;
// Visual feedback - flash the button
LK.effects.flashObject(tutorialResetButton, 0x00ff00, 500);
};
// Add hover effect
self.move = function (x, y, obj) {
// Convert coordinates to button's local space
var globalPos = self.toGlobal({
x: x,
y: y
});
var localPos = playButton.toLocal(globalPos);
var halfWidth = playButton.width / 2;
var halfHeight = playButton.height / 2;
if (localPos.x >= -halfWidth && localPos.x <= halfWidth && localPos.y >= -halfHeight && localPos.y <= halfHeight) {
playButton.alpha = 0.8; // Make more opaque (less transparent) when hovering
} else {
playButton.alpha = 1; // Normal opacity (fully visible)
}
// Check hover for tutorial reset button
var tutorialLocalPos = tutorialResetButton.toLocal(globalPos);
var tutorialHalfWidth = tutorialResetButton.width * tutorialResetButton.scaleX / 2;
var tutorialHalfHeight = tutorialResetButton.height * tutorialResetButton.scaleY / 2;
if (tutorialLocalPos.x >= -tutorialHalfWidth && tutorialLocalPos.x <= tutorialHalfWidth && tutorialLocalPos.y >= -tutorialHalfHeight && tutorialLocalPos.y <= tutorialHalfHeight) {
tutorialResetButton.alpha = 0.7; // More opaque when hovering
} else {
tutorialResetButton.alpha = 1.0; // Normal visibility
}
};
return self;
});
// IntroScene - displays tutorial image before game starts
var IntroScene = Scene.expand(function () {
var self = Scene.call(this);
// Tutorial background image - use full resolution
self.tutorialImage = self.attachAsset('tutorial', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.0,
scaleY: 1.0
});
// Tap anywhere to continue text
var continueText = new Text2('TAP TO CONTINUE', {
size: 80,
fill: 0x000000
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 1024;
continueText.y = 2530;
self.addChild(continueText);
// Fade text in and out animation
self.animateText = function () {
tween(continueText, {
alpha: 0.3
}, {
duration: 800,
easing: tween.linear,
onFinish: function onFinish() {
tween(continueText, {
alpha: 1
}, {
duration: 800,
easing: tween.linear,
onFinish: function onFinish() {
if (self.isActive) {
self.animateText();
}
}
});
}
});
};
self.activate = function () {
self.isActive = true;
self.visible = true;
self.animateText();
};
self.deactivate = function () {
self.isActive = false;
self.visible = false;
};
// Handle tap to continue
self.down = function (x, y, obj) {
if (self.isActive && game.onIntroComplete) {
game.onIntroComplete();
}
};
// Start deactivated
self.deactivate();
return self;
});
// Game Scene - wraps all the existing game logic
var GameScene = Scene.expand(function () {
var self = Scene.call(this);
// All game variables
var bottomBg, topBg, greenLine, destructionLine, scoreTxt;
var comboImage = null;
var comboFadeTimeout = null;
var comboFadeTween = null;
var horizontalLine, cannon;
var frutasGeneradas = 0;
var frutasFalladas = 0;
var fruits = [];
var fallingFruits = [];
var cutFruits = [];
var frutasFalladasTimeout = null;
var fruitSpeed = 25;
var comboCounter = 0;
var cutZoneAsset, cutZoneRadius;
var canCut = true;
var lastTouchTick = -100;
// Round system variables
var roundState, roundBullets, roundFruitsQueue, roundFruitsActive;
var roundBulletsFired, roundDelayTicks, currentStage;
var roundDelayBetweenRounds, roundNextBulletTick, roundStartTick;
var countdownObjectsFired, goShotTick, stage2Scheduled, stage3Scheduled;
var roundJustReset, showCountdownInMiddleDone;
self.activate = function () {
self.isActive = true;
self.visible = true;
// Start with black overlay for fade-in effect
var fadeOverlay = LK.getAsset('vertical_line', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
color: 0x000000,
alpha: 1
});
self.addChild(fadeOverlay);
// Bring fade overlay to front to ensure it's above all other elements
self.setChildIndex(fadeOverlay, self.children.length - 1);
// Fade out the black overlay
tween(fadeOverlay, {
alpha: 0
}, {
duration: 800,
easing: tween.linear,
onFinish: function onFinish() {
fadeOverlay.destroy();
}
});
// Play background music when game starts
if (Inicio) {
// Add delay for subsequent plays
LK.setTimeout(function () {
LK.playMusic('bgmusic', {
loop: false
});
}, 300); // 2 second delay
} else {
// Play immediately for first time
LK.setTimeout(function () {
LK.playMusic('bgmusic', {
loop: false
});
}, 50); // 2 second delay
}
};
self.deactivate = function () {
self.isActive = false;
self.visible = false;
};
// All game variables will be initialized here
self.initGame = function () {
// Fondo inferior café
bottomBg = LK.getAsset('bottom_bg', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
});
self.addChild(bottomBg);
// Fondo superior azul
topBg = LK.getAsset('top_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
self.addChild(topBg);
// Línea verde casi transparente
greenLine = LK.getAsset('greenLine', {
anchorX: 0,
anchorY: 0.5,
x: 25,
y: 500,
alpha: 0.2
});
self.addChild(greenLine);
// Línea roja de destrucción
destructionLine = LK.getAsset('vertical_line', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: -100,
width: 2048,
height: 10,
color: 0xff0000,
shape: 'box'
});
self.addChild(destructionLine);
// Score text (invisible)
scoreTxt = new Text2('', {
size: 1,
fill: "#222",
alpha: 0
});
scoreTxt.visible = false;
// Línea negra horizontal
horizontalLine = LK.getAsset('vertical_line', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 2732 / 2 + 540,
width: 2048,
height: 120,
color: 0x000000,
shape: 'box'
});
// Instancia el cañón
cannon = new Cannon();
self.addChild(horizontalLine);
self.addChild(cannon);
cannon.show();
// Initialize cut zone
cutZoneAsset = LK.getAsset('cut_zone', {
anchorX: 0.5,
anchorY: 0.5
});
cutZoneRadius = cutZoneAsset.width / 2;
cutZoneAsset.destroy();
// Reset all game variables
frutasGeneradas = 0;
frutasFalladas = 0;
fruits = [];
fallingFruits = [];
cutFruits = [];
fruitSpeed = 25;
comboCounter = 0;
canCut = true;
lastTouchTick = -100;
roundState = undefined;
countdownObjectsFired = undefined;
goShotTick = undefined;
stage2Scheduled = undefined;
stage3Scheduled = undefined;
roundJustReset = undefined;
showCountdownInMiddleDone = undefined;
// Reset score
LK.setScore(0);
updateScore(0);
// Set game start delay if coming from menu or restart
self.gameStartDelay = LK.ticks; // 0.2 seconds delay (12 ticks at 60fps)
};
// Helper functions
function updateScore(val) {
LK.setScore(val);
scoreTxt.setText(val);
}
function tryCutFruit(x, y) {
if (!canCut) {
return;
}
canCut = false;
lastTouchTick = LK.ticks;
for (var i = 0; i < fallingFruits.length; i++) {
var fruit = fallingFruits[i];
if (!fruit.isActive) {
continue;
}
if (fruit.state === 'cuttable') {
var dx = x - fruit.x;
var dy = y - fruit.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= fruit.radius * fruit.scaleX) {
fruit.cut();
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
LK.getSound('cut').play();
// Check if Y position is within combo zone (350-750)
if (y >= 350 && y <= 750) {
comboCounter++;
if (comboCounter >= 13) {
comboCounter = 1; // Reset to 1 after reaching 12
}
} else {
comboCounter = 0;
}
// Update combo image display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
// Cancel any ongoing tween on the previous combo image
if (comboFadeTween) {
comboFadeTween.stop();
comboFadeTween = null;
}
comboImage.destroy();
comboImage = null;
}
if (comboCounter > 0 && comboCounter <= 12) {
comboImage = LK.getAsset('combo' + comboCounter, {
anchorX: 1,
anchorY: 0,
alpha: 1,
scaleX: 0.5,
scaleY: 0.5
});
LK.gui.topRight.addChild(comboImage);
comboImage.x = -20; // 20px from right edge
comboImage.y = 20; // 20px from top
// Scale up from 50% to 100% in 0.1 seconds
tween(comboImage, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut
});
// After 0.3 seconds (0.1s grow + 0.2s show), start fade out
comboFadeTimeout = LK.setTimeout(function () {
if (comboImage) {
comboFadeTween = tween(comboImage, {
alpha: 0
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
comboFadeTween = null;
}
});
}
comboFadeTimeout = null;
}, 300);
}
// Animación de corte para todas las frutas
var fruitCut = new FruitCut();
fruitCut.init(fruit.fruitType, fruit.x, fruit.y, fruit.rotation, fruit.scaleX);
self.addChild(fruitCut);
// Asegura que la línea negra esté sobre las frutas cortadas pero debajo del cañón
if (horizontalLine && horizontalLine.parent) {
horizontalLine.parent.removeChild(horizontalLine);
self.addChild(horizontalLine);
}
// Asegura que el cañón siempre esté al frente
if (cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
self.addChild(cannon);
}
fruitCut.animate(function () {
fruitCut.destroy();
});
cutFruits.push(fruitCut);
frutasGeneradas++;
updateScore(LK.getScore() + 1);
return;
}
}
}
}
self.down = function (x, y, obj) {
if (self.isActive) {
// Check if game start delay has passed
if (self.gameStartDelay && LK.ticks < self.gameStartDelay) {
return;
}
tryCutFruit(x, y);
}
};
self.updateGame = function () {
if (!self.isActive) {
return;
}
// Wait for game start delay before processing any game logic
if (self.gameStartDelay && LK.ticks < self.gameStartDelay) {
return;
}
// Permitir corte nuevamente tras 6 ticks (~100ms)
if (!canCut && LK.ticks - lastTouchTick > 6) {
canCut = true;
}
// --- SISTEMA DE BALAS POR RONDA (5 balas, 1s entre cada una, 7s delay antes de mostrar en pantalla superior) ---
if (typeof roundState === "undefined") {
// Estados posibles: 'waiting', 'spawning', 'waitingFruits'
roundState = 'waiting';
roundBullets = 7; // 3 countdown objects + 4 fruits (stage 1)
roundFruitsQueue = [];
roundFruitsActive = [];
roundBulletsFired = 0;
roundDelayTicks = 0;
currentStage = 1; // Track current stage (1 or 2)
roundDelayBetweenRounds = 120; // 2 seconds between rounds
}
if (roundState === 'waiting') {
// Limpiar arrays de frutas y mitades cortadas de la ronda anterior
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i]) {
fruits[i].destroy();
}
}
fruits = [];
for (var i = fallingFruits.length - 1; i >= 0; i--) {
if (fallingFruits[i]) {
fallingFruits[i].destroy();
}
}
fallingFruits = [];
for (var i = cutFruits.length - 1; i >= 0; i--) {
if (cutFruits[i]) {
cutFruits[i].destroy();
}
}
cutFruits = [];
// Limpiar arrays de control de ronda
if (typeof roundFruitsQueue !== "undefined") {
for (var i = roundFruitsQueue.length - 1; i >= 0; i--) {
if (roundFruitsQueue[i] && roundFruitsQueue[i].destroy) {
roundFruitsQueue[i].destroy();
}
}
}
roundFruitsQueue = [];
if (typeof roundFruitsActive !== "undefined") {
for (var i = roundFruitsActive.length - 1; i >= 0; i--) {
if (roundFruitsActive[i] && roundFruitsActive[i].destroy) {
roundFruitsActive[i].destroy();
}
}
}
roundFruitsActive = [];
// Reset meta de ronda
roundBulletsFired = 0;
roundNextBulletTick = LK.ticks;
roundDelayTicks = 0;
// Set round bullets based on current stage
if (currentStage === 1) {
roundBullets = 7; // 3 countdown + 4 fruits
// Schedule stage 2 to start 8 seconds after stage 1 begins
if (typeof stage2Scheduled === "undefined" || !stage2Scheduled) {
stage2Scheduled = true;
LK.setTimeout(function () {
// Start stage 2
currentStage = 2;
roundState = 'waiting';
stage2Scheduled = false;
// Schedule stage 3 to start 5 seconds after stage 2 begins
if (typeof stage3Scheduled === "undefined" || !stage3Scheduled) {
stage3Scheduled = true;
LK.setTimeout(function () {
currentStage = 3;
roundState = 'waiting';
stage3Scheduled = false;
}, 4000); // 5 seconds after stage 2 starts
}
}, 10300); // 8 seconds
}
} else if (currentStage === 2) {
roundBullets = 4; // 3 fruits only, no countdown
countdownObjectsFired = 4; // Skip countdown in stage 2
} else if (currentStage === 3) {
// Reiniciar todas las variables para evitar errores en la generación de frutas
frutasGeneradas = 0;
frutasFalladas = 0;
fruits = [];
fallingFruits = [];
cutFruits = [];
roundFruitsQueue = [];
roundFruitsActive = [];
roundBullets = 4; // 4 frutas en la tercera etapa
countdownObjectsFired = 4; // No countdown en la etapa 3
// Clear any existing combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
}
// Esperar un frame antes de permitir el spawn para evitar generación múltiple
if (typeof roundJustReset === "undefined" || !roundJustReset) {
roundJustReset = true;
return;
} else {
roundJustReset = false;
roundState = 'spawning';
goShotTick = undefined;
}
}
if (roundState === 'spawning') {
// Espera inicial antes de cualquier disparo
if (typeof roundStartTick === "undefined") {
roundStartTick = LK.ticks;
}
var delay = 20; // ⏱️ Espera de 2 segundos (120 ticks)
if (LK.ticks - roundStartTick < delay) {
return; // ⏳ Aún esperando el inicio
}
// Disparar los objetos de cuenta regresiva "1", "2", "Go" en los primeros tres disparos
if (typeof countdownObjectsFired === "undefined") {
countdownObjectsFired = 0;
}
if (roundBulletsFired < roundBullets && LK.ticks >= roundNextBulletTick) {
var margin = 180;
var fruitX = margin + Math.random() * (2048 - 2 * margin);
cannon.x = fruitX;
cannon.show();
cannon.shoot();
// Dispara "1", "2", "Go" en los primeros tres disparos
if (countdownObjectsFired === 0) {
var obj1 = new Fruit();
obj1.fruitType = 'D1';
if (obj1.fruitAsset) {
obj1.fruitAsset.destroy();
}
obj1.fruitAsset = obj1.attachAsset('D1', {
anchorX: 0.5,
anchorY: 0.5
});
obj1.radius = obj1.fruitAsset.width / 2;
obj1.x = fruitX;
obj1.y = 2732 + obj1.radius - 120;
obj1.startY = obj1.y;
obj1.targetY = 2732 / 2 + 600;
obj1.state = 'rising';
obj1.ticks = 0;
obj1.scaleX = 0.1;
obj1.scaleY = 0.1;
obj1.roundMeta = {
hasCrossed: false,
crossedTick: null,
showScheduled: false,
showTimeout: null,
destroyed: false
};
fruits.push(obj1);
roundFruitsQueue.push(obj1);
self.addChild(obj1);
// Mostrar "1" centrado y gigante con fade out
var centerObj1 = new CountdownOne();
centerObj1.x = 2048 / 2;
centerObj1.y = 2732 / 2.5;
centerObj1.scaleX = centerObj1.scaleY = 1; // Gigante
centerObj1.alpha = 1;
self.addChild(centerObj1);
// Fade out después de 0.5 segundos
LK.setTimeout(function () {
tween(centerObj1, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
centerObj1.destroy();
}
});
}, 500);
countdownObjectsFired++;
} else if (countdownObjectsFired === 1) {
var obj2 = new Fruit();
obj2.fruitType = 'D2';
if (obj2.fruitAsset) {
obj2.fruitAsset.destroy();
}
obj2.fruitAsset = obj2.attachAsset('D2', {
anchorX: 0.5,
anchorY: 0.5
});
obj2.radius = obj2.fruitAsset.width / 2;
obj2.x = fruitX;
obj2.y = 2732 + obj2.radius - 120;
obj2.startY = obj2.y;
obj2.targetY = 2732 / 2 + 600;
obj2.state = 'rising';
obj2.ticks = 0;
obj2.scaleX = 0.1;
obj2.scaleY = 0.1;
obj2.roundMeta = {
hasCrossed: false,
crossedTick: null,
showScheduled: false,
showTimeout: null,
destroyed: false
};
fruits.push(obj2);
roundFruitsQueue.push(obj2);
self.addChild(obj2);
// Mostrar "2" centrado y gigante con fade out
var centerObj2 = new CountdownTwo();
centerObj2.x = 2048 / 2;
centerObj2.y = 2732 / 2.5;
centerObj2.scaleX = centerObj2.scaleY = 1; // Gigante
centerObj2.alpha = 1;
self.addChild(centerObj2);
// Fade out después de 0.5 segundos
LK.setTimeout(function () {
tween(centerObj2, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
centerObj2.destroy();
}
});
}, 500);
countdownObjectsFired++;
} else if (countdownObjectsFired === 2) {
var objGo = new Fruit();
objGo.fruitType = 'DGO';
if (objGo.fruitAsset) {
objGo.fruitAsset.destroy();
}
objGo.fruitAsset = objGo.attachAsset('DGO', {
anchorX: 0.5,
anchorY: 0.5
});
objGo.radius = objGo.fruitAsset.width / 2;
objGo.x = fruitX;
objGo.y = 2732 + objGo.radius - 120;
objGo.startY = objGo.y;
objGo.targetY = 2732 / 2 + 600;
objGo.state = 'rising';
objGo.ticks = 0;
objGo.scaleX = 0.1;
objGo.scaleY = 0.1;
objGo.roundMeta = {
hasCrossed: false,
crossedTick: null,
showScheduled: false,
showTimeout: null,
destroyed: false
};
fruits.push(objGo);
roundFruitsQueue.push(objGo);
self.addChild(objGo);
// Mostrar "Go!" centrado y gigante con fade out
var centerObjGo = new CountdownGo();
centerObjGo.x = 2048 / 2;
centerObjGo.y = 2732 / 2.5;
centerObjGo.scaleX = centerObjGo.scaleY = 1; // Gigante
centerObjGo.alpha = 1;
self.addChild(centerObjGo);
// Fade out después de 0.5 segundos
LK.setTimeout(function () {
tween(centerObjGo, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
centerObjGo.destroy();
}
});
}, 500);
countdownObjectsFired++;
// After Go!, wait 2 seconds before launching fruits
if (typeof goShotTick === "undefined") {
goShotTick = LK.ticks;
// Schedule game end 18 seconds after Go!
LK.setTimeout(function () {
// Set Inicio to true when game ends for the first time
if (!Inicio) {
Inicio = true;
}
// Show stars based on fruits cut
var starAsset;
var starSound;
if (LK.getScore() === 0) {
starAsset = 'Estrellas0';
starSound = 'E1'; // Use E1 sound for 0 stars
} else if (LK.getScore() <= 6) {
starAsset = 'Estrellas1';
starSound = 'E1';
} else if (LK.getScore() <= 11) {
starAsset = 'Estrellas2';
starSound = 'E2';
} else {
starAsset = 'Estrellas3';
starSound = 'E3';
}
// Create and display star
var star = LK.getAsset(starAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(star);
// Play victory sound when stars appear
LK.getSound('victory').play();
// Animate star appearance
tween(star, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Keep star visible for a moment then show end scene
LK.setTimeout(function () {
// Stop music
LK.stopMusic();
// Clean up combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
// Show end scene without deactivating game scene
// This keeps the game visible in the background
if (game.endScene) {
game.endScene.activate();
game.currentScene = game.endScene;
// Force end scene to be on top of everything
game.endScene.bringToFront();
}
}, 2000);
}
});
}, 18000); // 18 seconds
}
} else {
// Wait 1.3 seconds (78 ticks) after Go! before launching fruits
if (typeof goShotTick !== "undefined" && LK.ticks - goShotTick < 60) {
return;
}
// Después de los tres primeros, dispara frutas normales
var fruit = new Fruit();
fruit.x = fruitX;
fruit.y = 2732 + fruit.radius - 300;
fruit.startY = fruit.y;
fruit.targetY = 2732 / 2 + 600;
fruit.state = 'rising';
fruit.ticks = 0;
fruit.roundMeta = {
hasCrossed: false,
crossedTick: null,
showScheduled: false,
showTimeout: null,
destroyed: false
};
fruits.push(fruit);
roundFruitsQueue.push(fruit);
self.addChild(fruit);
}
// Asegura que el cañón siempre esté al frente
if (cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
self.addChild(cannon);
}
roundBulletsFired++;
// After Go!, wait 1.3 seconds before next bullet
if (countdownObjectsFired === 3 && roundBulletsFired === 3) {
roundNextBulletTick = LK.ticks + 60; // 1.3s wait after Go!
} else if (roundBulletsFired < 3) {
// Original timing for countdown objects (1, 2, Go!)
roundNextBulletTick = LK.ticks + 30; // 0.5s between countdown objects
} else {
// Timing between fruits based on stage
if (currentStage === 1) {
roundNextBulletTick = LK.ticks + 60; // 1s entre frutas (stage 1)
} else if (currentStage === 2) {
roundNextBulletTick = LK.ticks + 30; // 0.5s entre frutas (stage 2)
} else if (currentStage === 3) {
roundNextBulletTick = LK.ticks + 30;
}
}
}
// When all bullets are fired, round is complete
if (roundBulletsFired >= roundBullets) {
roundState = 'complete';
}
}
if (roundState === 'complete') {
// Round completed - do nothing, no more spawning
}
// Mover frutas que suben desde abajo hasta el centro
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
// Only destroy objects that are NOT rising (i.e., not going up from bottom)
// This ensures fruits launched from bottom can reach the top before being destroyed
if (fruit.state !== 'rising' && fruit.y < -100) {
// Destroy cut zone circle if exists
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
// Mark as inactive
fruit.isActive = false;
// Mark as destroyed for round system
if (fruit.roundMeta) {
fruit.roundMeta.destroyed = true;
}
// Clear any scheduled timeouts
if (fruit.roundMeta && fruit.roundMeta.showTimeout) {
LK.clearTimeout(fruit.roundMeta.showTimeout);
fruit.roundMeta.showTimeout = null;
}
// Remove from roundFruitsQueue if present
if (typeof roundFruitsQueue !== "undefined") {
for (var j = roundFruitsQueue.length - 1; j >= 0; j--) {
if (roundFruitsQueue[j] === fruit) {
roundFruitsQueue.splice(j, 1);
break;
}
}
}
// Destroy the object completely
fruit.destroy();
fruits.splice(i, 1);
continue; // Skip rest of processing for this object
}
if (fruit.state === 'rising') {
// Cambiar velocidad solo en etapa 2
var currentFruitSpeed = fruitSpeed;
if (currentStage === 2) {
currentFruitSpeed = 40; // velocidad más rápida en etapa 2
}
fruit.y -= currentFruitSpeed;
fruit.ticks++;
// Escalado según altura (más arriba, más grande)
var minScale = 1.0;
var maxScale = 1.5;
var y0 = 2732 + fruit.radius + 10;
var y1 = 2732 / 2 + 420;
var t = (fruit.y - y1) / (y0 - y1);
if (t < 0) {
t = 0;
}
if (t > 1) {
t = 1;
}
var scale = minScale + (maxScale - minScale) * (1 - t);
fruit.scaleX = fruit.scaleY = scale;
// Rotación en la parte de abajo (solo mientras sube)
if (typeof fruit.rotation === "undefined") {
fruit.rotation = Math.random() * Math.PI * 2;
}
if (typeof fruit.rotationSpeed === "undefined") {
fruit.rotationSpeed = (Math.random() - 0.5) * 0.08;
}
fruit.rotation += fruit.rotationSpeed;
if (fruit.fruitAsset) {
fruit.fruitAsset.rotation = fruit.rotation;
}
// Cuando cruza la altura objetivo (centro), programar aparición en pantalla superior tras un tiempo proporcional al número de frutas lanzadas
if (!fruit.roundMeta.hasCrossed && fruit.y <= fruit.targetY) {
fruit.roundMeta.hasCrossed = true;
fruit.roundMeta.crossedTick = LK.ticks;
fruit.visible = false;
// Cambiar tiempo de espera solo en etapa 2
var appearDelayMs = 2700; // default 2.7s
if (currentStage === 2) {
appearDelayMs = 1100; // 1.5s en etapa 2
}
if (currentStage === 3) {
appearDelayMs = 1200; // 1.5s en etapa 2
}
var isCountdownObject = fruit.fruitType === 'D1' || fruit.fruitType === 'D2' || fruit.fruitType === 'DGO';
if (!isCountdownObject) {
fruit.roundMeta.showScheduled = true;
fruit.roundMeta.showTimeout = LK.setTimeout(function (fruitRef, fruitsArr, iIdx) {
return function () {
if (fruitRef.roundMeta.destroyed) {
return;
}
var fruitType = fruitRef.fruitType;
var x = fruitRef.x;
fruitRef.destroy();
for (var j = 0; j < fruitsArr.length; j++) {
if (fruitsArr[j] === fruitRef) {
fruitsArr.splice(j, 1);
break;
}
}
var fallingFruit = new Fruit();
if (typeof fruitType === "undefined" || !fruitType) {
fruitType = fallingFruit.fruitType;
} else {
fallingFruit.fruitType = fruitType;
}
if (fallingFruit.fruitAsset) {
fallingFruit.fruitAsset.destroy();
}
fallingFruit.fruitAsset = fallingFruit.attachAsset(fallingFruit.fruitType, {
anchorX: 0.5,
anchorY: 0.5
});
fallingFruit.x = x;
fallingFruit.y = 2732 / 2 + 420 + 100;
fallingFruit.startY = fallingFruit.y;
fallingFruit.lastY = 2732; // Initialize lastY to a safe value well below destruction line
fallingFruit.state = 'cuttable';
fallingFruit.ticks = 0;
fallingFruit.rotation = Math.random() * Math.PI * 2;
fallingFruit.rotationSpeed = (Math.random() - 0.5) * 0.04;
fallingFruit.roundMeta = {
destroyed: false
};
fallingFruits.push(fallingFruit);
roundFruitsActive.push(fallingFruit);
self.addChild(fallingFruit);
if (horizontalLine && horizontalLine.parent) {
horizontalLine.parent.removeChild(horizontalLine);
self.addChild(horizontalLine);
}
if (cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
self.addChild(cannon);
}
};
}(fruit, fruits, i), appearDelayMs);
}
}
if (fruit.roundMeta.hasCrossed && fruit.roundMeta.showScheduled) {
if (fruit.roundMeta.destroyed) {
if (fruit.roundMeta.showTimeout) {
LK.clearTimeout(fruit.roundMeta.showTimeout);
fruit.roundMeta.showTimeout = null;
}
if (typeof roundFruitsQueue !== "undefined") {
for (var j = roundFruitsQueue.length - 1; j >= 0; j--) {
if (roundFruitsQueue[j] === fruit) {
roundFruitsQueue.splice(j, 1);
break;
}
}
}
fruits.splice(i, 1);
}
continue;
}
}
}
// Mover frutas que suben de nuevo y gestionar zona de corte visual
// --- Asegura que la línea negra esté sobre los sprites de las frutas en cada frame ---
if (horizontalLine && horizontalLine.parent) {
horizontalLine.parent.removeChild(horizontalLine);
self.addChild(horizontalLine);
}
// Asegura que la línea roja de destrucción esté visible encima de todo
if (destructionLine && destructionLine.parent) {
destructionLine.parent.removeChild(destructionLine);
self.addChild(destructionLine);
}
// Volver a agregar la línea roja al final para que esté encima
if (cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
self.addChild(cannon);
}
if (destructionLine && destructionLine.parent) {
destructionLine.parent.removeChild(destructionLine);
self.addChild(destructionLine);
}
for (var i = fallingFruits.length - 1; i >= 0; i--) {
var fruit = fallingFruits[i];
// Check if falling fruit passes above Y=800 for complete destruction
// Only destroy fruits that are falling down (cuttable or flyingup states)
if (fruit.y < -100 && (fruit.state === 'cuttable' || fruit.state === 'flyingup')) {
// Destroy cut zone circle if exists
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
// Mark as inactive
fruit.isActive = false;
// Mark as destroyed for round system
if (fruit.roundMeta) {
fruit.roundMeta.destroyed = true;
}
// Remove from roundFruitsActive if present
if (typeof roundFruitsActive !== "undefined") {
for (var j = roundFruitsActive.length - 1; j >= 0; j--) {
if (roundFruitsActive[j] === fruit) {
roundFruitsActive.splice(j, 1);
break;
}
}
}
// Count as failed if not cut
if (!fruit.isCut) {
frutasFalladas++;
frutasGeneradas++;
comboCounter = 0;
// Play fruit miss sound
LK.getSound('fruit_miss').play();
// Clear any existing combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
}
// Destroy the fruit completely
fruit.destroy();
fallingFruits.splice(i, 1);
continue; // Skip rest of processing for this fruit
}
if (!fruit.isActive) {
continue;
}
// Escalado según altura (más arriba, más grande)
var minScale = 1.0;
var maxScale = 1.25 * 1.3 * 1.3; //{45} // 60% más grande en la parte superior
var y0 = 2732 / 2 + 420;
var y1 = 420;
var t = (fruit.y - y1) / (y0 - y1);
if (t < 0) {
t = 0;
}
if (t > 1) {
t = 1;
}
var scale = minScale + (maxScale - minScale) * (1 - t);
fruit.scaleX = fruit.scaleY = scale;
// Rotación suave
if (typeof fruit.rotationSpeed !== "undefined") {
fruit.rotation += fruit.rotationSpeed;
if (fruit.fruitAsset) {
fruit.fruitAsset.rotation = fruit.rotation;
}
}
// Movimiento hacia arriba mientras sea cortable o volando hacia arriba
if (fruit.state === 'cuttable' || fruit.state === 'flyingup') {
var topFruitSpeed = fruitSpeed;
if (currentStage === 2) {
topFruitSpeed = 30; // velocidad más alta en la segunda etapa
}
// Double speed if fruit has passed cutting zone
if (fruit.doubledSpeed) {
topFruitSpeed = topFruitSpeed * 2;
}
fruit.y -= topFruitSpeed;
}
// --- Animación progresiva del círculo de corte ---
// El círculo aparece desde el inicio de la subida (cuttable), crece y se difumina progresivamente
if (fruit.state === 'cuttable') {
// Si no existe el círculo, crearlo
if (!fruit.cutZoneCircle) {
fruit.cutZoneCircle = LK.getAsset('cut_zone', {
anchorX: 0.5,
anchorY: 0.5,
x: fruit.x,
y: fruit.y,
alpha: 0.0,
scaleX: 0.1,
scaleY: 0.1
});
self.addChild(fruit.cutZoneCircle);
// Asegura que la línea negra esté sobre el círculo de corte pero debajo del cañón
if (horizontalLine && horizontalLine.parent) {
horizontalLine.parent.removeChild(horizontalLine);
self.addChild(horizontalLine);
}
// Asegura que el cañón siempre esté al frente
if (cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
self.addChild(cannon);
}
}
// Progresión del círculo: desde que inicia la subida hasta que llega cerca de la parte superior
var appearT = (y0 - fruit.y) / (y0 - y1);
if (appearT < 0) {
appearT = 0;
}
if (appearT > 1) {
appearT = 1;
}
var maxScaleZone = 0.85; // Más pequeño, para que quede dentro de la fruta
var minScaleZone = 0.15;
var maxAlpha = 0.38; // Más difuminado
var minAlpha = 0.08;
var scaleZone = minScaleZone + (maxScaleZone - minScaleZone) * appearT;
var alphaZone = minAlpha + (maxAlpha - minAlpha) * appearT;
fruit.cutZoneCircle.scaleX = fruit.cutZoneCircle.scaleY = scaleZone;
fruit.cutZoneCircle.alpha = alphaZone;
fruit.cutZoneCircle.x = fruit.x;
fruit.cutZoneCircle.y = fruit.y;
// Si la fruta sale por arriba, eliminar círculo y pasar a flyingup
if (fruit.y < y1) {
// Si el círculo de corte existe y la fruta no fue cortada, contar como fallada
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
if (fruit.isActive && !fruit.isCut) {
frutasFalladas++;
frutasGeneradas++;
comboCounter = 0;
// Play fruit miss sound
LK.getSound('fruit_miss').play();
// Clear any existing combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
}
}
fruit.state = 'flyingup';
// Double the speed when fruit can no longer be cut
fruit.doubledSpeed = true;
}
} else {
// Si no está en estado cortable y existe el círculo, destruirlo
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
}
// Si fue cortada, eliminar círculo visual inmediatamente pero mantener la fruta visible
if (!fruit.isActive && fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
// Si está cayendo, animar caída
if (fruit.state === 'dropping') {
fruit.dropSpeed += 2.5; // gravedad
fruit.y += fruit.dropSpeed;
// Escalado decreciente al caer
fruit.scaleX = fruit.scaleY = Math.max(0.7, fruit.scaleX - 0.01);
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.x = fruit.x;
fruit.cutZoneCircle.y = fruit.y;
}
// Si sale de pantalla, marcar como inactivo pero mantener visible
if (fruit.y > 2732 + fruit.radius + 20) {
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
fruit.isActive = false;
}
}
// Si la fruta sigue subiendo después de la zona de corte y no fue cortada
if (fruit.state === 'flyingup') {
// Al entrar por primera vez en flyingup, inicia temporizador de espera
if (typeof fruit.waitTicks === "undefined") {
fruit.waitTicks = 0;
}
fruit.waitTicks++;
// Mantener la fruta visible y estática durante 4 segundos (240 ticks)
if (fruit.waitTicks < 240) {
// Mantener posición (opcional: podrías hacer que siga subiendo lentamente)
// fruit.y -= fruitSpeed * 0.1;
} else {
// Después de 7 segundos, hacer que suba y desaparezca
// Continue moving at current speed (already doubled if applicable)
// Escalado sigue creciendo un poco
fruit.scaleX = fruit.scaleY = Math.min(maxScale, fruit.scaleX + 0.01);
// Check if fruit passes destruction line at Y=800 for complete destruction
if (fruit.y < -100) {
// Destroy cut zone circle if exists
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
// Mark as inactive
fruit.isActive = false;
// Mark as destroyed for round system
if (fruit.roundMeta) {
fruit.roundMeta.destroyed = true;
}
// Remove from roundFruitsActive if present
if (typeof roundFruitsActive !== "undefined") {
for (var j = roundFruitsActive.length - 1; j >= 0; j--) {
if (roundFruitsActive[j] === fruit) {
roundFruitsActive.splice(j, 1);
break;
}
}
}
// Count as failed if not cut
if (!fruit.isCut) {
frutasFalladas++;
frutasGeneradas++;
comboCounter = 0;
// Play fruit miss sound
LK.getSound('fruit_miss').play();
// Clear any existing combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
}
// Destroy the fruit completely
fruit.destroy();
fallingFruits.splice(i, 1);
continue; // Skip rest of processing for this fruit
}
// Si sale de pantalla por arriba, marcar como inactiva y eliminar círculo de corte si existe
if (fruit.y < -fruit.radius - 20) {
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
fruit.isActive = false;
// Marcar como destruida para el sistema de ronda
if (fruit.roundMeta) {
fruit.roundMeta.destroyed = true;
}
// Eliminar de roundFruitsActive si está presente
if (typeof roundFruitsActive !== "undefined") {
for (var j = roundFruitsActive.length - 1; j >= 0; j--) {
if (roundFruitsActive[j] === fruit) {
roundFruitsActive.splice(j, 1);
break;
}
}
}
// Aumentar frutasFalladas y frutasGeneradas solo si la fruta no fue cortada
if (!fruit.isCut) {
frutasFalladas++;
frutasGeneradas++;
comboCounter = 0;
// Play fruit miss sound
LK.getSound('fruit_miss').play();
// Clear any existing combo display
if (comboFadeTimeout) {
LK.clearTimeout(comboFadeTimeout);
comboFadeTimeout = null;
}
if (comboImage) {
comboImage.destroy();
comboImage = null;
}
}
}
}
}
// Si la fruta ya fue cortada o salió de pantalla, eliminar
if (!fruit.isActive || fruit.y > 2732 + fruit.radius + 20 || fruit.y < -fruit.radius - 20) {
if (fruit.cutZoneCircle) {
fruit.cutZoneCircle.destroy();
fruit.cutZoneCircle = null;
}
// Marcar como destruida para el sistema de ronda
if (fruit.roundMeta) {
fruit.roundMeta.destroyed = true;
}
// Eliminar de roundFruitsActive si está presente
if (typeof roundFruitsActive !== "undefined") {
for (var j = roundFruitsActive.length - 1; j >= 0; j--) {
if (roundFruitsActive[j] === fruit) {
roundFruitsActive.splice(j, 1);
break;
}
}
}
fruit.destroy();
fallingFruits.splice(i, 1);
}
}
// Limpiar mitades cortadas que ya terminaron animación
for (var i = cutFruits.length - 1; i >= 0; i--) {
var fc = cutFruits[i];
if (!fc.left || !fc.right) {
if (fc.destroy) {
fc.destroy();
}
cutFruits.splice(i, 1);
}
}
// Variable para controlar el timeout (debe estar fuera de la función que chequea frutasFalladas)
if (frutasFalladas >= 5 || frutasGeneradas >= 5 && frutasFalladas < 5) {
// Mostrar los sprites 1, 2, Go en el centro con fade in/fade out rápido solo una vez, SOLO al inicio del juego
if ((typeof showCountdownInMiddleDone === "undefined" || !showCountdownInMiddleDone) && LK.ticks < 120) {
showCountdownInMiddleDone = true;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var countdownSprites = [{
cls: CountdownOne,
delay: 0
}, {
cls: CountdownTwo,
delay: 350
}, {
cls: CountdownGo,
delay: 700
}];
for (var idx = 0; idx < countdownSprites.length; idx++) {
(function (idx) {
var conf = countdownSprites[idx];
LK.setTimeout(function () {
var obj = new conf.cls();
obj.x = centerX;
obj.y = centerY;
obj.alpha = 0;
self.addChild(obj);
// Fade in rápido
tween(obj, {
alpha: 1
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Fade out rápido
tween(obj, {
alpha: 0
}, {
duration: 180,
delay: 180,
easing: tween.cubicIn,
onFinish: function onFinish() {
obj.destroy();
}
});
}
});
}, conf.delay);
})(idx);
}
}
return;
}
};
return self;
});
// EndScene - con método reset para botones y visibilidad
var EndScene = Scene.expand(function () {
var self = Scene.call(this);
self.overlay = self.attachAsset('vertical_line', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
width: 2048,
height: 2732,
alpha: 0.1,
tint: 0x000000
});
self.restartButton = self.attachAsset('button_restart', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200
});
self.menuButton = self.attachAsset('button_menu', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600
});
function setupButton(button, callback) {
button.interactive = true;
button.tint = 0xffffff;
button.alpha = 1;
button.down = function () {
button.tint = 0xcccccc;
};
button.up = function () {
button.tint = 0xffffff;
if (callback) {
callback();
}
};
}
setupButton(self.restartButton, function () {
if (game.onRestartPressed) {
game.onRestartPressed();
}
});
setupButton(self.menuButton, function () {
if (game.onMenuPressed) {
game.onMenuPressed();
}
});
self.move = function (x, y, obj) {
var globalPos = self.toGlobal({
x: x,
y: y
});
var lb = self.restartButton.toLocal(globalPos);
var hw = self.restartButton.width / 2;
var hh = self.restartButton.height / 2;
self.restartButton.alpha = lb.x >= -hw && lb.x <= hw && lb.y >= -hh && lb.y <= hh ? 0.8 : 1;
var lb2 = self.menuButton.toLocal(globalPos);
var hw2 = self.menuButton.width / 2;
var hh2 = self.menuButton.height / 2;
self.menuButton.alpha = lb2.x >= -hw2 && lb2.x <= hw2 && lb2.y >= -hh2 && lb2.y <= hh2 ? 0.8 : 1;
};
self.activate = function () {
self.visible = true;
self.restartButton.interactive = true;
self.menuButton.interactive = true;
self.restartButton.alpha = 1;
self.menuButton.alpha = 1;
self.restartButton.tint = 0xffffff;
self.menuButton.tint = 0xffffff;
// Always bring to front when activating
self.bringToFront();
};
self.deactivate = function () {
self.visible = false;
self.restartButton.interactive = false;
self.menuButton.interactive = false;
};
// Subir esta escena a la cima del display list para que se vea encima de todo
self.bringToFront = function () {
if (self.parent) {
self.parent.setChildIndex(self, self.parent.children.length - 1);
}
};
// Método para resetear los botones y visibilidad al mostrar la escena otra vez
self.reset = function () {
self.activate();
// Force multiple calls to ensure it's on top
self.bringToFront();
LK.setTimeout(function () {
self.bringToFront();
}, 10);
};
// Empieza oculta
self.deactivate();
return self;
});
/****
* Initialize Game
****/
// -- Inicialización --
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
var Inicio = false; // Track if this is the first game completion (local variable)
// Preload critical menu assets to ensure they're ready
var preloadedMenuBg = LK.getAsset('menu_background', {});
var preloadedTitulo = LK.getAsset('Titulo', {});
var menuScene = new MenuScene();
var introScene = new IntroScene();
var gameScene = new GameScene();
var endScene = new EndScene();
game.addChild(menuScene);
game.addChild(introScene);
game.addChild(gameScene);
game.addChild(endScene);
game.menuScene = menuScene;
game.introScene = introScene;
game.gameScene = gameScene;
game.endScene = endScene;
// Add a small delay before activating menu to ensure assets are loaded
LK.setTimeout(function () {
menuScene.activate();
}, 100); // Small delay to allow asset loading
introScene.deactivate();
gameScene.deactivate();
endScene.deactivate();
var currentScene = menuScene;
// -- Funciones de transición --
game.onPlayPressed = function () {
// Stop menu music before transitioning
LK.stopMusic();
menuScene.deactivate();
endScene.deactivate();
// Check if intro has been seen before
if (storage.hasSeenIntro) {
// Skip intro, go directly to game
if (!gameScene.gameInitialized) {
gameScene.initGame();
gameScene.gameInitialized = true;
}
gameScene.activate();
currentScene = gameScene;
} else {
// Show intro for first time
introScene.activate();
currentScene = introScene;
}
};
game.onRestartPressed = function () {
endScene.deactivate();
gameScene.deactivate();
introScene.deactivate();
game.removeChild(gameScene);
gameScene = new GameScene();
game.addChild(gameScene);
gameScene.initGame();
gameScene.gameInitialized = true;
gameScene.activate();
currentScene = gameScene;
LK.stopMusic();
};
game.onMenuPressed = function () {
endScene.deactivate();
gameScene.deactivate();
introScene.deactivate();
LK.stopMusic();
menuScene.activate();
currentScene = menuScene;
game.removeChild(gameScene);
gameScene = new GameScene();
game.addChild(gameScene);
gameScene.gameInitialized = false;
};
game.onIntroComplete = function () {
introScene.deactivate();
// Mark intro as seen in persistent storage
storage.hasSeenIntro = true;
if (!gameScene.gameInitialized) {
gameScene.initGame();
gameScene.gameInitialized = true;
}
gameScene.activate();
currentScene = gameScene;
};
// IMPORTANTE: cuando termines el juego llama esta función para mostrar los botones
gameScene.onGameEnd = function () {
gameScene.deactivate();
endScene.reset(); // <-- resetea y muestra el EndScene con botones activos y arriba
currentScene = endScene;
};
// -- Propagar movimiento --
game.move = function (x, y, obj) {
if (currentScene && currentScene.move) {
currentScene.move(x, y, obj);
}
};
game.update = function () {
if (currentScene && currentScene.updateGame) {
currentScene.updateGame();
}
if (currentScene && currentScene.updateMenu) {
currentScene.updateMenu();
}
};
Kiwi Fruta con ojos lindos. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Limon Circular Fruta con ojos lindos. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Naranja Circular Fruta con ojos lindos. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Manzana Fruta con ojos lindos. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Ciruela Fruta con ojos lindos. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Manzana Fruta cortada a la mitad. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Kiwi Fruta cortado por la mitad. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Limon Circular Cortado por la mitad. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Naranja Circular Cortada por la mitad. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Ciruela Fruta cortada por la mitad. In-Game asset. 2d. High contrast. No shadows. Cartoon.
Agrega una rueda en la parte trasera del cañon.
En lugar del numero 2 un numero 1
Letars GO! dentro del circulo.
Red cicle thiner but maintan the image size
En lugar del numero 1 un numero 2
Puedes hacer varias imagenes reemplazando el numero 2 por los numeros 3, 4, 5, 6, 7 y 8? Solo debe haber un numero en cada imagen.
En lugar del numero 3 un numero 4
En lugar del numero 4 un numero 5
En lugar del numero 5 un numero 6
En lugar del numero 6 un numero 7
En lugar del numero 1 un numero 8
En lugar del numero 1 un numero 9
En lugar del numero 9 un numero 10
En lugar del numero 1 un numero 11
Boton de juego que diga "START". In-Game asset. 2d. High contrast. No shadows
Boton de juego que diga "RESTART". In-Game asset. 2d. High contrast. No shadows
Boton de juego Azul que diga "MENU". In-Game asset. 2d. High contrast. No shadows
Un fondo colorido y brillante estilo caricatura. La escena es un bosque abierto alegre con colores pastel vibrantes, formas suaves y redondeadas, y un cielo azul claro con nubes esponjosas. El estilo es kawaii, juguetón y fantástico, con líneas suaves y una atmósfera feliz y amigable, perfecto para una introducción divertida y cute de un juego. In-Game asset. 2d. High contrast. No shadows
"Beat the Fruit" titulo para el juego, muestra las letras en grande blancas con un borde negro y sin fondo, con algunas frutitas felices junto a las letras. In-Game asset. 2d. High contrast. No shadows
Barra verde horizontal estilo caricatura.. In-Game asset. 2d. High contrast. No shadows