/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { currentLevel: 1, unlockedLevels: 1 }); /**** * Classes ****/ var DimensionSwitch = Container.expand(function () { var self = Container.call(this); // Create switch graphic self.graphic = self.attachAsset('dimensionSwitch', { anchorX: 0.5, anchorY: 0.5 }); // Create pulsing animation self.pulseAnimation = function () { tween(self.graphic, { scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(self.graphic, { scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeInOut, onFinish: self.pulseAnimation }); } }); }; // Start pulsing self.pulseAnimation(); return self; }); var Goal = Container.expand(function () { var self = Container.call(this); // Create goal graphic self.graphic = self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5 }); // Animate the goal self.animateGoal = function () { tween(self, { rotation: Math.PI * 2 }, { duration: 3000, onFinish: function onFinish() { self.rotation = 0; self.animateGoal(); } }); }; // Start animation self.animateGoal(); return self; }); var Hazard = Container.expand(function () { var self = Container.call(this); // Create hazard graphic self.graphic = self.attachAsset('hazard', { anchorX: 0.5, anchorY: 0.5 }); return self; }); var LevelButton = Container.expand(function (levelNum) { var self = Container.call(this); self.levelNum = levelNum; // Create button background self.background = self.attachAsset('levelSelectButton', { anchorX: 0.5, anchorY: 0.5 }); // Create level text self.text = new Text2(levelNum.toString(), { size: 50, fill: 0xFFFFFF }); self.text.anchor.set(0.5, 0.5); self.addChild(self.text); // Interactive events self.down = function (x, y, obj) { if (storage.unlockedLevels >= self.levelNum) { tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); } }; self.up = function (x, y, obj) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (storage.unlockedLevels >= self.levelNum) { storage.currentLevel = self.levelNum; startGame(); } } }); }; // Update appearance based on level unlock status self.updateAppearance = function () { if (storage.unlockedLevels >= self.levelNum) { self.background.alpha = 1; self.text.alpha = 1; } else { self.background.alpha = 0.5; self.text.setText("🔒"); } }; self.updateAppearance(); return self; }); var Platform = Container.expand(function (type) { var self = Container.call(this); // Platform properties self.type = type || 'light'; // 'light' or 'shadow' // Create platform graphic self.graphic = self.attachAsset(self.type === 'light' ? 'lightPlatform' : 'shadowPlatform', { anchorX: 0.5, anchorY: 0.5 }); // Check if platform is solid in current dimension self.isSolid = function (isDimensionLight) { return self.type === 'light' && isDimensionLight || self.type === 'shadow' && !isDimensionLight; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); // Player properties self.velocityX = 0; self.velocityY = 0; self.speed = 10; self.jumpForce = -24; self.gravity = 0.3; // Reduced gravity for slower falling self.isGrounded = false; self.isDimensionLight = true; self.isDead = false; self.isGliding = false; self.collectedTrophies = 0; self.totalTrophies = 0; // Create light and shadow versions of player self.lightForm = self.attachAsset('playerLight', { anchorX: 0.5, anchorY: 0.5 }); self.shadowForm = self.attachAsset('playerShadow', { anchorX: 0.5, anchorY: 0.5, visible: false }); // Swap dimensions self.swapDimension = function () { if (self.isDead) { return; } self.isDimensionLight = !self.isDimensionLight; self.lightForm.visible = self.isDimensionLight; self.shadowForm.visible = !self.isDimensionLight; LK.getSound('swap').play(); // Visual effect for dimension swap LK.effects.flashObject(self, self.isDimensionLight ? 0xFFFFFF : 0x000000, 300); }; // Jump action self.jump = function () { if (self.isGrounded && !self.isDead) { self.velocityY = self.jumpForce; self.isGrounded = false; LK.getSound('jump').play(); } }; // Move left self.moveLeft = function () { if (!self.isDead) { self.velocityX = -self.speed; } }; // Move right self.moveRight = function () { if (!self.isDead) { self.velocityX = self.speed; } }; // Stop horizontal movement self.stopMoving = function () { self.velocityX = 0; }; // Kill player self.die = function () { if (!self.isDead) { self.isDead = true; LK.getSound('death').play(); tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { // Let the game handle the reset restartLevel(); } }); } }; // Physics update self.update = function () { if (self.isDead) { return; } // Track if player is gliding var isGliding = Math.abs(self.velocityX) > 3 && !self.isGrounded && self.velocityY > 0; // Apply gravity with gliding effect if (isGliding) { // Reduced gravity when gliding self.velocityY += self.gravity * 0.3; // Cap fall speed while gliding if (self.velocityY > 5) { self.velocityY = 5; } } else { // Normal gravity self.velocityY += self.gravity; } // Apply velocities self.x += self.velocityX; self.y += self.velocityY; // Simple friction if (Math.abs(self.velocityX) > 0.1) { // Less friction while gliding self.velocityX *= isGliding ? 0.98 : 0.9; } else { self.velocityX = 0; } // Visual effect for gliding if (isGliding && !self.isGliding) { self.isGliding = true; // Stretch horizontally while gliding tween(self, { scaleX: 1.3, scaleY: 0.8 }, { duration: 300 }); } else if (!isGliding && self.isGliding) { self.isGliding = false; // Return to normal shape tween(self, { scaleX: 1, scaleY: 1 }, { duration: 300 }); } // Screen bounds if (self.x < 30) { self.x = 30; } else if (self.x > 2048 - 30) { self.x = 2048 - 30; } if (self.y > 2732) { self.die(); } }; return self; }); var Trophy = Container.expand(function () { var self = Container.call(this); // Create trophy graphic - using goal asset with different color self.graphic = self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); // Apply gold color tint self.graphic.tint = 0xFFD700; // Hover animation self.animate = function () { tween(self, { y: self.y - 10 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { y: self.y + 10 }, { duration: 1000, easing: tween.easeInOut, onFinish: self.animate }); } }); }; // Start animation self.animate(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Game states var STATE_MENU = 'menu'; var STATE_GAME = 'game'; var gameState = STATE_MENU; // Game elements var player; var platforms = []; var hazards = []; var dimensionSwitches = []; var trophies = []; var goal; var levelButtons = []; var touchControls = {}; var background; // Game logic variables var totalLevels = 5; var currentLevel = storage.currentLevel || 1; var isSwapEnabled = true; var swapCooldown = 0; var levelComplete = false; // Level designs var levels = [ // Level 1: Simple introduction { playerStart: { x: 300, y: 300 }, platforms: [{ x: 300, y: 400, type: 'light', width: 1 }, { x: 600, y: 500, type: 'shadow', width: 1 }, { x: 900, y: 600, type: 'light', width: 1 }, { x: 1200, y: 700, type: 'shadow', width: 1 }, { x: 1500, y: 800, type: 'light', width: 1 }], dimensionSwitches: [{ x: 600, y: 520 }, { x: 1200, y: 920 }], hazards: [], goal: { x: 1500, y: 1120 } }, // Level 2: Platforms and hazards { playerStart: { x: 200, y: 300 }, platforms: [{ x: 200, y: 400, type: 'light', width: 1 }, { x: 500, y: 500, type: 'shadow', width: 1 }, { x: 800, y: 600, type: 'light', width: 1 }, { x: 1100, y: 700, type: 'shadow', width: 1 }, { x: 1400, y: 800, type: 'light', width: 1 }, { x: 1700, y: 900, type: 'shadow', width: 1 }], dimensionSwitches: [{ x: 500, y: 420 }, { x: 1100, y: 620 }], hazards: [{ x: 800, y: 500 }, { x: 1400, y: 700 }], goal: { x: 1700, y: 820 } }, // Level 3: More complex puzzle { playerStart: { x: 150, y: 300 }, platforms: [{ x: 150, y: 400, type: 'light', width: 1 }, { x: 450, y: 400, type: 'shadow', width: 1 }, { x: 750, y: 500, type: 'light', width: 1 }, { x: 1050, y: 500, type: 'shadow', width: 1 }, { x: 1350, y: 600, type: 'light', width: 1 }, { x: 1650, y: 600, type: 'shadow', width: 1 }, { x: 1650, y: 800, type: 'light', width: 1 }, { x: 1350, y: 800, type: 'shadow', width: 1 }, { x: 1050, y: 1000, type: 'light', width: 1 }, { x: 750, y: 1000, type: 'shadow', width: 1 }], dimensionSwitches: [{ x: 450, y: 320 }, { x: 1050, y: 420 }, { x: 1650, y: 520 }, { x: 1050, y: 920 }], hazards: [{ x: 750, y: 400 }, { x: 1350, y: 500 }, { x: 1350, y: 900 }], goal: { x: 750, y: 920 } }, // Level 4: Advanced obstacles { playerStart: { x: 200, y: 200 }, platforms: [{ x: 200, y: 300, type: 'light', width: 1 }, { x: 500, y: 300, type: 'shadow', width: 1 }, { x: 800, y: 400, type: 'light', width: 1 }, { x: 1100, y: 400, type: 'shadow', width: 1 }, { x: 1400, y: 500, type: 'light', width: 1 }, { x: 1700, y: 500, type: 'shadow', width: 1 }, { x: 1400, y: 700, type: 'shadow', width: 1 }, { x: 1100, y: 700, type: 'light', width: 1 }, { x: 800, y: 900, type: 'shadow', width: 1 }, { x: 500, y: 900, type: 'light', width: 1 }, { x: 200, y: 1100, type: 'shadow', width: 1 }, { x: 500, y: 1100, type: 'light', width: 1 }], dimensionSwitches: [{ x: 500, y: 220 }, { x: 1100, y: 320 }, { x: 1400, y: 620 }, { x: 800, y: 820 }, { x: 200, y: 1020 }], hazards: [{ x: 800, y: 300 }, { x: 1400, y: 400 }, { x: 1100, y: 600 }, { x: 500, y: 800 }], goal: { x: 500, y: 1020 } }, // Level 5: Final challenge { playerStart: { x: 150, y: 200 }, platforms: [{ x: 150, y: 300, type: 'light', width: 1 }, { x: 450, y: 400, type: 'shadow', width: 1 }, { x: 750, y: 500, type: 'light', width: 1 }, { x: 1050, y: 600, type: 'shadow', width: 1 }, { x: 1350, y: 700, type: 'light', width: 1 }, { x: 1650, y: 800, type: 'shadow', width: 1 }, { x: 1350, y: 1000, type: 'shadow', width: 1 }, { x: 1050, y: 1200, type: 'light', width: 1 }, { x: 750, y: 1400, type: 'shadow', width: 1 }, { x: 450, y: 1600, type: 'light', width: 1 }, { x: 750, y: 1800, type: 'shadow', width: 1 }, { x: 1050, y: 2000, type: 'light', width: 1 }, { x: 1350, y: 2200, type: 'shadow', width: 1 }, { x: 1650, y: 2400, type: 'light', width: 1 }], dimensionSwitches: [{ x: 450, y: 320 }, { x: 1050, y: 520 }, { x: 1650, y: 720 }, { x: 1050, y: 1120 }, { x: 450, y: 1520 }, { x: 1050, y: 1920 }, { x: 1650, y: 2320 }], hazards: [{ x: 750, y: 400 }, { x: 1350, y: 600 }, { x: 1350, y: 900 }, { x: 750, y: 1300 }, { x: 750, y: 1700 }, { x: 1350, y: 2100 }], trophies: [{ x: 750, y: 450 }, { x: 1350, y: 650 }, { x: 750, y: 1350 }, { x: 1050, y: 1950 }], goal: { x: 1650, y: 2320 } }]; // Initialize menu function setupMenu() { gameState = STATE_MENU; clearLevel(); // Create title text var titleText = new Text2("SHADOW SWAP", { size: 100, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 400; game.addChild(titleText); // Create level selection text var levelSelectText = new Text2("SELECT LEVEL", { size: 60, fill: 0xFFFFFF }); levelSelectText.anchor.set(0.5, 0.5); levelSelectText.x = 2048 / 2; levelSelectText.y = 550; game.addChild(levelSelectText); // Create level selection buttons for (var i = 0; i < totalLevels; i++) { var button = new LevelButton(i + 1); button.x = 2048 / 2 + (i - 2) * 250; button.y = 700; levelButtons.push(button); game.addChild(button); } // Create instruction text var instructionText = new Text2("CONTROLS:\n" + "- TAP LEFT/RIGHT SIDES TO MOVE\n" + "- TAP CENTER TO JUMP\n" + "- SWIPE UP TO SWAP DIMENSIONS\n" + "- MOVE HORIZONTALLY TO GLIDE\n\n" + "GOAL:\n" + "- NAVIGATE THROUGH PLATFORMS\n" + "- LIGHT PLATFORMS ARE SOLID IN LIGHT DIMENSION\n" + "- SHADOW PLATFORMS ARE SOLID IN SHADOW DIMENSION\n" + "- COLLECT ALL GOLDEN TROPHIES\n" + "- REACH THE GREEN GOAL TO COMPLETE LEVEL", { size: 40, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 0); instructionText.x = 2048 / 2; instructionText.y = 900; game.addChild(instructionText); } // Start game with the selected level function startGame() { gameState = STATE_GAME; clearLevel(); setupLevel(storage.currentLevel); LK.playMusic('gameBgm'); } // Clear all game elements function clearLevel() { // Remove all game objects if (player) { game.removeChild(player); player = null; } platforms.forEach(function (platform) { game.removeChild(platform); }); platforms = []; hazards.forEach(function (hazard) { game.removeChild(hazard); }); hazards = []; dimensionSwitches.forEach(function (dimensionSwitch) { game.removeChild(dimensionSwitch); }); dimensionSwitches = []; trophies.forEach(function (trophy) { game.removeChild(trophy); }); trophies = []; if (goal) { game.removeChild(goal); goal = null; } levelButtons.forEach(function (button) { game.removeChild(button); }); levelButtons = []; // Remove any other UI elements for (var key in touchControls) { if (touchControls[key]) { game.removeChild(touchControls[key]); } } touchControls = {}; // Reset game state levelComplete = false; isSwapEnabled = true; swapCooldown = 0; // Clear any existing children while (game.children.length > 0) { game.removeChild(game.children[0]); } } // Set up a level function setupLevel(levelNum) { currentLevel = levelNum; var levelData = levels[levelNum - 1] || levels[0]; // Create background background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 }); background.x = 2048 / 2; background.y = 2732 / 2; game.addChild(background); // Create level text var levelText = new Text2("LEVEL " + currentLevel, { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); levelText.x = 2048 / 2; levelText.y = 50; game.addChild(levelText); // Create dimension indicator var dimensionText = new Text2("DIMENSION: LIGHT", { size: 40, fill: 0xFFFFFF }); dimensionText.anchor.set(0.5, 0); dimensionText.x = 2048 / 2; dimensionText.y = 120; game.addChild(dimensionText); touchControls.dimensionText = dimensionText; // Create trophy counter text var trophyText = new Text2("TROPHIES: 0/0", { size: 40, fill: 0xFFD700 }); trophyText.anchor.set(0.5, 0); trophyText.x = 2048 / 2; trophyText.y = 180; game.addChild(trophyText); touchControls.trophyText = trophyText; // Create player player = new Player(); player.x = levelData.playerStart.x; player.y = levelData.playerStart.y; game.addChild(player); // Create platforms levelData.platforms.forEach(function (platformData) { var platform = new Platform(platformData.type); platform.x = platformData.x; platform.y = platformData.y; // Make all platforms 1.5x wider for easier landing platform.graphic.scaleX = platformData.width ? platformData.width * 1.5 : 1.5; platforms.push(platform); game.addChild(platform); }); // Create dimension switches levelData.dimensionSwitches.forEach(function (switchData) { var dimensionSwitch = new DimensionSwitch(); dimensionSwitch.x = switchData.x; dimensionSwitch.y = switchData.y; dimensionSwitches.push(dimensionSwitch); game.addChild(dimensionSwitch); }); // Create hazards (limit the number of hazards per level to make it easier) var hazardCount = 0; levelData.hazards.forEach(function (hazardData) { // Only create 2/3 of the hazards to make game easier hazardCount++; if (hazardCount % 3 !== 0) { // Skip every third hazard var hazard = new Hazard(); hazard.x = hazardData.x; hazard.y = hazardData.y; hazards.push(hazard); game.addChild(hazard); } }); // Create trophies if (levelData.trophies) { player.totalTrophies = levelData.trophies.length; player.collectedTrophies = 0; levelData.trophies.forEach(function (trophyData) { var trophy = new Trophy(); trophy.x = trophyData.x; trophy.y = trophyData.y; trophy.collected = false; trophies.push(trophy); game.addChild(trophy); }); // Update trophy counter if (touchControls.trophyText) { touchControls.trophyText.setText("TROPHIES: " + player.collectedTrophies + "/" + player.totalTrophies); } } // Create goal goal = new Goal(); goal.x = levelData.goal.x; goal.y = levelData.goal.y; game.addChild(goal); // Touch controls areas (invisible) var leftControl = LK.getAsset('lightPlatform', { anchorX: 0, anchorY: 0, alpha: 0 }); leftControl.width = 2048 / 3; leftControl.height = 2732; leftControl.x = 0; leftControl.y = 0; game.addChild(leftControl); touchControls.left = leftControl; var rightControl = LK.getAsset('lightPlatform', { anchorX: 0, anchorY: 0, alpha: 0 }); rightControl.width = 2048 / 3; rightControl.height = 2732; rightControl.x = 2048 * 2 / 3; rightControl.y = 0; game.addChild(rightControl); touchControls.right = rightControl; var centerControl = LK.getAsset('lightPlatform', { anchorX: 0, anchorY: 0, alpha: 0 }); centerControl.width = 2048 / 3; centerControl.height = 2732; centerControl.x = 2048 / 3; centerControl.y = 0; game.addChild(centerControl); touchControls.center = centerControl; } // Restart current level function restartLevel() { clearLevel(); setupLevel(currentLevel); } // Complete level and move to next function completeLevel() { if (!levelComplete) { // For the final level, require all trophies to be collected before completing if (currentLevel === totalLevels && player.collectedTrophies < player.totalTrophies) { // Show message that trophies are required var missingTrophiesText = new Text2("COLLECT ALL TROPHIES FIRST!", { size: 60, fill: 0xFFD700 }); missingTrophiesText.anchor.set(0.5, 0.5); missingTrophiesText.x = 2048 / 2; missingTrophiesText.y = 2732 / 2; game.addChild(missingTrophiesText); // Flash and remove after 2 seconds tween(missingTrophiesText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { game.removeChild(missingTrophiesText); } }); return; } levelComplete = true; LK.getSound('win').play(); // Update unlocked levels if (currentLevel >= storage.unlockedLevels) { storage.unlockedLevels = currentLevel + 1; if (storage.unlockedLevels > totalLevels) { storage.unlockedLevels = totalLevels; } } // Show win animation tween(player, { scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { LK.showYouWin(); // Move to next level if (currentLevel < totalLevels) { storage.currentLevel = currentLevel + 1; } else { // If all levels completed, go back to menu setupMenu(); } } }); } } // Check collisions between player and platforms function checkPlatformCollisions() { var wasGrounded = player.isGrounded; player.isGrounded = false; platforms.forEach(function (platform) { if (platform.isSolid(player.isDimensionLight) && player.intersects(platform)) { // Calculate collision sides var playerBottom = player.y + player.lightForm.height / 2; var playerTop = player.y - player.lightForm.height / 2; var playerLeft = player.x - player.lightForm.width / 2; var playerRight = player.x + player.lightForm.width / 2; var platformBottom = platform.y + platform.graphic.height / 2; var platformTop = platform.y - platform.graphic.height / 2; var platformLeft = platform.x - platform.graphic.width / 2; var platformRight = platform.x + platform.graphic.width / 2; // Calculate penetration depths var fromTop = playerBottom - platformTop; var fromBottom = platformBottom - playerTop; var fromLeft = playerRight - platformLeft; var fromRight = platformRight - playerLeft; // Find minimum penetration var minPenetration = Math.min(fromTop, fromBottom, fromLeft, fromRight); // Resolve collision based on minimum penetration if (minPenetration === fromTop && player.velocityY > 0) { player.y = platformTop - player.lightForm.height / 2; player.velocityY = 0; player.isGrounded = true; } else if (minPenetration === fromBottom && player.velocityY < 0) { player.y = platformBottom + player.lightForm.height / 2; player.velocityY = 0; } else if (minPenetration === fromLeft && player.velocityX > 0) { player.x = platformLeft - player.lightForm.width / 2; player.velocityX = 0; } else if (minPenetration === fromRight && player.velocityX < 0) { player.x = platformRight + player.lightForm.width / 2; player.velocityX = 0; } } }); // Landing effect if (!wasGrounded && player.isGrounded) { tween(player, { scaleX: 1.2, scaleY: 0.8 }, { duration: 100, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); } } // Check other game object interactions function checkInteractions() { // Check dimension switches dimensionSwitches.forEach(function (dimensionSwitch) { if (player.intersects(dimensionSwitch) && isSwapEnabled) { player.swapDimension(); isSwapEnabled = false; swapCooldown = 45; // 0.75-second cooldown at 60 FPS // Update dimension text if (touchControls.dimensionText) { touchControls.dimensionText.setText("DIMENSION: " + (player.isDimensionLight ? "LIGHT" : "SHADOW")); } // Animate the switch tween(dimensionSwitch, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, onFinish: function onFinish() { tween(dimensionSwitch, { scaleX: 1, scaleY: 1 }, { duration: 200 }); } }); } }); // Check hazards hazards.forEach(function (hazard) { if (player.intersects(hazard)) { player.die(); } }); // Check trophies trophies.forEach(function (trophy, index) { if (!trophy.collected && player.intersects(trophy)) { // Collect trophy trophy.collected = true; player.collectedTrophies++; // Update trophy counter if (touchControls.trophyText) { touchControls.trophyText.setText("TROPHIES: " + player.collectedTrophies + "/" + player.totalTrophies); } // Trophy collection animation tween(trophy, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 300, onFinish: function onFinish() { game.removeChild(trophy); } }); } }); // Check goal if (player.intersects(goal)) { completeLevel(); } // Swap cooldown if (!isSwapEnabled) { swapCooldown--; if (swapCooldown <= 0) { isSwapEnabled = true; } } } // Touch controls var touchStart = { x: 0, y: 0 }; var isTouching = false; var movingLeft = false; var movingRight = false; game.down = function (x, y, obj) { if (gameState === STATE_GAME) { touchStart.x = x; touchStart.y = y; isTouching = true; // Check which control area was touched if (x < 2048 / 3) { // Left side - move left movingLeft = true; player.moveLeft(); } else if (x > 2048 * 2 / 3) { // Right side - move right movingRight = true; player.moveRight(); } else { // Center - jump player.jump(); } } }; game.move = function (x, y, obj) { if (gameState === STATE_GAME && isTouching) { // Check for swipe up (dimension swap) - reduced distance required if (y < touchStart.y - 70) { if (isSwapEnabled) { player.swapDimension(); isSwapEnabled = false; swapCooldown = 45; // Update dimension text if (touchControls.dimensionText) { touchControls.dimensionText.setText("DIMENSION: " + (player.isDimensionLight ? "LIGHT" : "SHADOW")); } } // Reset touch to prevent multiple swipes isTouching = false; } } }; game.up = function (x, y, obj) { if (gameState === STATE_GAME) { isTouching = false; // Stop horizontal movement if this was a movement control if (movingLeft || movingRight) { player.stopMoving(); movingLeft = false; movingRight = false; } } }; // Main game update loop game.update = function () { if (gameState === STATE_GAME && !levelComplete && player && !player.isDead) { // Update player physics player.update(); // Check collisions and interactions checkPlatformCollisions(); checkInteractions(); // Update platform visuals based on current dimension platforms.forEach(function (platform) { // Make solid platforms more visible (higher alpha) in current dimension if (platform.isSolid(player.isDimensionLight)) { platform.graphic.alpha = 1.0; } else { platform.graphic.alpha = 0.5; } }); } }; // Initialize the game with the menu setupMenu();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
currentLevel: 1,
unlockedLevels: 1
});
/****
* Classes
****/
var DimensionSwitch = Container.expand(function () {
var self = Container.call(this);
// Create switch graphic
self.graphic = self.attachAsset('dimensionSwitch', {
anchorX: 0.5,
anchorY: 0.5
});
// Create pulsing animation
self.pulseAnimation = function () {
tween(self.graphic, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self.graphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: self.pulseAnimation
});
}
});
};
// Start pulsing
self.pulseAnimation();
return self;
});
var Goal = Container.expand(function () {
var self = Container.call(this);
// Create goal graphic
self.graphic = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5
});
// Animate the goal
self.animateGoal = function () {
tween(self, {
rotation: Math.PI * 2
}, {
duration: 3000,
onFinish: function onFinish() {
self.rotation = 0;
self.animateGoal();
}
});
};
// Start animation
self.animateGoal();
return self;
});
var Hazard = Container.expand(function () {
var self = Container.call(this);
// Create hazard graphic
self.graphic = self.attachAsset('hazard', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var LevelButton = Container.expand(function (levelNum) {
var self = Container.call(this);
self.levelNum = levelNum;
// Create button background
self.background = self.attachAsset('levelSelectButton', {
anchorX: 0.5,
anchorY: 0.5
});
// Create level text
self.text = new Text2(levelNum.toString(), {
size: 50,
fill: 0xFFFFFF
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
// Interactive events
self.down = function (x, y, obj) {
if (storage.unlockedLevels >= self.levelNum) {
tween(self, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
}
};
self.up = function (x, y, obj) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (storage.unlockedLevels >= self.levelNum) {
storage.currentLevel = self.levelNum;
startGame();
}
}
});
};
// Update appearance based on level unlock status
self.updateAppearance = function () {
if (storage.unlockedLevels >= self.levelNum) {
self.background.alpha = 1;
self.text.alpha = 1;
} else {
self.background.alpha = 0.5;
self.text.setText("🔒");
}
};
self.updateAppearance();
return self;
});
var Platform = Container.expand(function (type) {
var self = Container.call(this);
// Platform properties
self.type = type || 'light'; // 'light' or 'shadow'
// Create platform graphic
self.graphic = self.attachAsset(self.type === 'light' ? 'lightPlatform' : 'shadowPlatform', {
anchorX: 0.5,
anchorY: 0.5
});
// Check if platform is solid in current dimension
self.isSolid = function (isDimensionLight) {
return self.type === 'light' && isDimensionLight || self.type === 'shadow' && !isDimensionLight;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Player properties
self.velocityX = 0;
self.velocityY = 0;
self.speed = 10;
self.jumpForce = -24;
self.gravity = 0.3; // Reduced gravity for slower falling
self.isGrounded = false;
self.isDimensionLight = true;
self.isDead = false;
self.isGliding = false;
self.collectedTrophies = 0;
self.totalTrophies = 0;
// Create light and shadow versions of player
self.lightForm = self.attachAsset('playerLight', {
anchorX: 0.5,
anchorY: 0.5
});
self.shadowForm = self.attachAsset('playerShadow', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
// Swap dimensions
self.swapDimension = function () {
if (self.isDead) {
return;
}
self.isDimensionLight = !self.isDimensionLight;
self.lightForm.visible = self.isDimensionLight;
self.shadowForm.visible = !self.isDimensionLight;
LK.getSound('swap').play();
// Visual effect for dimension swap
LK.effects.flashObject(self, self.isDimensionLight ? 0xFFFFFF : 0x000000, 300);
};
// Jump action
self.jump = function () {
if (self.isGrounded && !self.isDead) {
self.velocityY = self.jumpForce;
self.isGrounded = false;
LK.getSound('jump').play();
}
};
// Move left
self.moveLeft = function () {
if (!self.isDead) {
self.velocityX = -self.speed;
}
};
// Move right
self.moveRight = function () {
if (!self.isDead) {
self.velocityX = self.speed;
}
};
// Stop horizontal movement
self.stopMoving = function () {
self.velocityX = 0;
};
// Kill player
self.die = function () {
if (!self.isDead) {
self.isDead = true;
LK.getSound('death').play();
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
// Let the game handle the reset
restartLevel();
}
});
}
};
// Physics update
self.update = function () {
if (self.isDead) {
return;
}
// Track if player is gliding
var isGliding = Math.abs(self.velocityX) > 3 && !self.isGrounded && self.velocityY > 0;
// Apply gravity with gliding effect
if (isGliding) {
// Reduced gravity when gliding
self.velocityY += self.gravity * 0.3;
// Cap fall speed while gliding
if (self.velocityY > 5) {
self.velocityY = 5;
}
} else {
// Normal gravity
self.velocityY += self.gravity;
}
// Apply velocities
self.x += self.velocityX;
self.y += self.velocityY;
// Simple friction
if (Math.abs(self.velocityX) > 0.1) {
// Less friction while gliding
self.velocityX *= isGliding ? 0.98 : 0.9;
} else {
self.velocityX = 0;
}
// Visual effect for gliding
if (isGliding && !self.isGliding) {
self.isGliding = true;
// Stretch horizontally while gliding
tween(self, {
scaleX: 1.3,
scaleY: 0.8
}, {
duration: 300
});
} else if (!isGliding && self.isGliding) {
self.isGliding = false;
// Return to normal shape
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
// Screen bounds
if (self.x < 30) {
self.x = 30;
} else if (self.x > 2048 - 30) {
self.x = 2048 - 30;
}
if (self.y > 2732) {
self.die();
}
};
return self;
});
var Trophy = Container.expand(function () {
var self = Container.call(this);
// Create trophy graphic - using goal asset with different color
self.graphic = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Apply gold color tint
self.graphic.tint = 0xFFD700;
// Hover animation
self.animate = function () {
tween(self, {
y: self.y - 10
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 10
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: self.animate
});
}
});
};
// Start animation
self.animate();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game states
var STATE_MENU = 'menu';
var STATE_GAME = 'game';
var gameState = STATE_MENU;
// Game elements
var player;
var platforms = [];
var hazards = [];
var dimensionSwitches = [];
var trophies = [];
var goal;
var levelButtons = [];
var touchControls = {};
var background;
// Game logic variables
var totalLevels = 5;
var currentLevel = storage.currentLevel || 1;
var isSwapEnabled = true;
var swapCooldown = 0;
var levelComplete = false;
// Level designs
var levels = [
// Level 1: Simple introduction
{
playerStart: {
x: 300,
y: 300
},
platforms: [{
x: 300,
y: 400,
type: 'light',
width: 1
}, {
x: 600,
y: 500,
type: 'shadow',
width: 1
}, {
x: 900,
y: 600,
type: 'light',
width: 1
}, {
x: 1200,
y: 700,
type: 'shadow',
width: 1
}, {
x: 1500,
y: 800,
type: 'light',
width: 1
}],
dimensionSwitches: [{
x: 600,
y: 520
}, {
x: 1200,
y: 920
}],
hazards: [],
goal: {
x: 1500,
y: 1120
}
},
// Level 2: Platforms and hazards
{
playerStart: {
x: 200,
y: 300
},
platforms: [{
x: 200,
y: 400,
type: 'light',
width: 1
}, {
x: 500,
y: 500,
type: 'shadow',
width: 1
}, {
x: 800,
y: 600,
type: 'light',
width: 1
}, {
x: 1100,
y: 700,
type: 'shadow',
width: 1
}, {
x: 1400,
y: 800,
type: 'light',
width: 1
}, {
x: 1700,
y: 900,
type: 'shadow',
width: 1
}],
dimensionSwitches: [{
x: 500,
y: 420
}, {
x: 1100,
y: 620
}],
hazards: [{
x: 800,
y: 500
}, {
x: 1400,
y: 700
}],
goal: {
x: 1700,
y: 820
}
},
// Level 3: More complex puzzle
{
playerStart: {
x: 150,
y: 300
},
platforms: [{
x: 150,
y: 400,
type: 'light',
width: 1
}, {
x: 450,
y: 400,
type: 'shadow',
width: 1
}, {
x: 750,
y: 500,
type: 'light',
width: 1
}, {
x: 1050,
y: 500,
type: 'shadow',
width: 1
}, {
x: 1350,
y: 600,
type: 'light',
width: 1
}, {
x: 1650,
y: 600,
type: 'shadow',
width: 1
}, {
x: 1650,
y: 800,
type: 'light',
width: 1
}, {
x: 1350,
y: 800,
type: 'shadow',
width: 1
}, {
x: 1050,
y: 1000,
type: 'light',
width: 1
}, {
x: 750,
y: 1000,
type: 'shadow',
width: 1
}],
dimensionSwitches: [{
x: 450,
y: 320
}, {
x: 1050,
y: 420
}, {
x: 1650,
y: 520
}, {
x: 1050,
y: 920
}],
hazards: [{
x: 750,
y: 400
}, {
x: 1350,
y: 500
}, {
x: 1350,
y: 900
}],
goal: {
x: 750,
y: 920
}
},
// Level 4: Advanced obstacles
{
playerStart: {
x: 200,
y: 200
},
platforms: [{
x: 200,
y: 300,
type: 'light',
width: 1
}, {
x: 500,
y: 300,
type: 'shadow',
width: 1
}, {
x: 800,
y: 400,
type: 'light',
width: 1
}, {
x: 1100,
y: 400,
type: 'shadow',
width: 1
}, {
x: 1400,
y: 500,
type: 'light',
width: 1
}, {
x: 1700,
y: 500,
type: 'shadow',
width: 1
}, {
x: 1400,
y: 700,
type: 'shadow',
width: 1
}, {
x: 1100,
y: 700,
type: 'light',
width: 1
}, {
x: 800,
y: 900,
type: 'shadow',
width: 1
}, {
x: 500,
y: 900,
type: 'light',
width: 1
}, {
x: 200,
y: 1100,
type: 'shadow',
width: 1
}, {
x: 500,
y: 1100,
type: 'light',
width: 1
}],
dimensionSwitches: [{
x: 500,
y: 220
}, {
x: 1100,
y: 320
}, {
x: 1400,
y: 620
}, {
x: 800,
y: 820
}, {
x: 200,
y: 1020
}],
hazards: [{
x: 800,
y: 300
}, {
x: 1400,
y: 400
}, {
x: 1100,
y: 600
}, {
x: 500,
y: 800
}],
goal: {
x: 500,
y: 1020
}
},
// Level 5: Final challenge
{
playerStart: {
x: 150,
y: 200
},
platforms: [{
x: 150,
y: 300,
type: 'light',
width: 1
}, {
x: 450,
y: 400,
type: 'shadow',
width: 1
}, {
x: 750,
y: 500,
type: 'light',
width: 1
}, {
x: 1050,
y: 600,
type: 'shadow',
width: 1
}, {
x: 1350,
y: 700,
type: 'light',
width: 1
}, {
x: 1650,
y: 800,
type: 'shadow',
width: 1
}, {
x: 1350,
y: 1000,
type: 'shadow',
width: 1
}, {
x: 1050,
y: 1200,
type: 'light',
width: 1
}, {
x: 750,
y: 1400,
type: 'shadow',
width: 1
}, {
x: 450,
y: 1600,
type: 'light',
width: 1
}, {
x: 750,
y: 1800,
type: 'shadow',
width: 1
}, {
x: 1050,
y: 2000,
type: 'light',
width: 1
}, {
x: 1350,
y: 2200,
type: 'shadow',
width: 1
}, {
x: 1650,
y: 2400,
type: 'light',
width: 1
}],
dimensionSwitches: [{
x: 450,
y: 320
}, {
x: 1050,
y: 520
}, {
x: 1650,
y: 720
}, {
x: 1050,
y: 1120
}, {
x: 450,
y: 1520
}, {
x: 1050,
y: 1920
}, {
x: 1650,
y: 2320
}],
hazards: [{
x: 750,
y: 400
}, {
x: 1350,
y: 600
}, {
x: 1350,
y: 900
}, {
x: 750,
y: 1300
}, {
x: 750,
y: 1700
}, {
x: 1350,
y: 2100
}],
trophies: [{
x: 750,
y: 450
}, {
x: 1350,
y: 650
}, {
x: 750,
y: 1350
}, {
x: 1050,
y: 1950
}],
goal: {
x: 1650,
y: 2320
}
}];
// Initialize menu
function setupMenu() {
gameState = STATE_MENU;
clearLevel();
// Create title text
var titleText = new Text2("SHADOW SWAP", {
size: 100,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 400;
game.addChild(titleText);
// Create level selection text
var levelSelectText = new Text2("SELECT LEVEL", {
size: 60,
fill: 0xFFFFFF
});
levelSelectText.anchor.set(0.5, 0.5);
levelSelectText.x = 2048 / 2;
levelSelectText.y = 550;
game.addChild(levelSelectText);
// Create level selection buttons
for (var i = 0; i < totalLevels; i++) {
var button = new LevelButton(i + 1);
button.x = 2048 / 2 + (i - 2) * 250;
button.y = 700;
levelButtons.push(button);
game.addChild(button);
}
// Create instruction text
var instructionText = new Text2("CONTROLS:\n" + "- TAP LEFT/RIGHT SIDES TO MOVE\n" + "- TAP CENTER TO JUMP\n" + "- SWIPE UP TO SWAP DIMENSIONS\n" + "- MOVE HORIZONTALLY TO GLIDE\n\n" + "GOAL:\n" + "- NAVIGATE THROUGH PLATFORMS\n" + "- LIGHT PLATFORMS ARE SOLID IN LIGHT DIMENSION\n" + "- SHADOW PLATFORMS ARE SOLID IN SHADOW DIMENSION\n" + "- COLLECT ALL GOLDEN TROPHIES\n" + "- REACH THE GREEN GOAL TO COMPLETE LEVEL", {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0);
instructionText.x = 2048 / 2;
instructionText.y = 900;
game.addChild(instructionText);
}
// Start game with the selected level
function startGame() {
gameState = STATE_GAME;
clearLevel();
setupLevel(storage.currentLevel);
LK.playMusic('gameBgm');
}
// Clear all game elements
function clearLevel() {
// Remove all game objects
if (player) {
game.removeChild(player);
player = null;
}
platforms.forEach(function (platform) {
game.removeChild(platform);
});
platforms = [];
hazards.forEach(function (hazard) {
game.removeChild(hazard);
});
hazards = [];
dimensionSwitches.forEach(function (dimensionSwitch) {
game.removeChild(dimensionSwitch);
});
dimensionSwitches = [];
trophies.forEach(function (trophy) {
game.removeChild(trophy);
});
trophies = [];
if (goal) {
game.removeChild(goal);
goal = null;
}
levelButtons.forEach(function (button) {
game.removeChild(button);
});
levelButtons = [];
// Remove any other UI elements
for (var key in touchControls) {
if (touchControls[key]) {
game.removeChild(touchControls[key]);
}
}
touchControls = {};
// Reset game state
levelComplete = false;
isSwapEnabled = true;
swapCooldown = 0;
// Clear any existing children
while (game.children.length > 0) {
game.removeChild(game.children[0]);
}
}
// Set up a level
function setupLevel(levelNum) {
currentLevel = levelNum;
var levelData = levels[levelNum - 1] || levels[0];
// Create background
background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
background.x = 2048 / 2;
background.y = 2732 / 2;
game.addChild(background);
// Create level text
var levelText = new Text2("LEVEL " + currentLevel, {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.x = 2048 / 2;
levelText.y = 50;
game.addChild(levelText);
// Create dimension indicator
var dimensionText = new Text2("DIMENSION: LIGHT", {
size: 40,
fill: 0xFFFFFF
});
dimensionText.anchor.set(0.5, 0);
dimensionText.x = 2048 / 2;
dimensionText.y = 120;
game.addChild(dimensionText);
touchControls.dimensionText = dimensionText;
// Create trophy counter text
var trophyText = new Text2("TROPHIES: 0/0", {
size: 40,
fill: 0xFFD700
});
trophyText.anchor.set(0.5, 0);
trophyText.x = 2048 / 2;
trophyText.y = 180;
game.addChild(trophyText);
touchControls.trophyText = trophyText;
// Create player
player = new Player();
player.x = levelData.playerStart.x;
player.y = levelData.playerStart.y;
game.addChild(player);
// Create platforms
levelData.platforms.forEach(function (platformData) {
var platform = new Platform(platformData.type);
platform.x = platformData.x;
platform.y = platformData.y;
// Make all platforms 1.5x wider for easier landing
platform.graphic.scaleX = platformData.width ? platformData.width * 1.5 : 1.5;
platforms.push(platform);
game.addChild(platform);
});
// Create dimension switches
levelData.dimensionSwitches.forEach(function (switchData) {
var dimensionSwitch = new DimensionSwitch();
dimensionSwitch.x = switchData.x;
dimensionSwitch.y = switchData.y;
dimensionSwitches.push(dimensionSwitch);
game.addChild(dimensionSwitch);
});
// Create hazards (limit the number of hazards per level to make it easier)
var hazardCount = 0;
levelData.hazards.forEach(function (hazardData) {
// Only create 2/3 of the hazards to make game easier
hazardCount++;
if (hazardCount % 3 !== 0) {
// Skip every third hazard
var hazard = new Hazard();
hazard.x = hazardData.x;
hazard.y = hazardData.y;
hazards.push(hazard);
game.addChild(hazard);
}
});
// Create trophies
if (levelData.trophies) {
player.totalTrophies = levelData.trophies.length;
player.collectedTrophies = 0;
levelData.trophies.forEach(function (trophyData) {
var trophy = new Trophy();
trophy.x = trophyData.x;
trophy.y = trophyData.y;
trophy.collected = false;
trophies.push(trophy);
game.addChild(trophy);
});
// Update trophy counter
if (touchControls.trophyText) {
touchControls.trophyText.setText("TROPHIES: " + player.collectedTrophies + "/" + player.totalTrophies);
}
}
// Create goal
goal = new Goal();
goal.x = levelData.goal.x;
goal.y = levelData.goal.y;
game.addChild(goal);
// Touch controls areas (invisible)
var leftControl = LK.getAsset('lightPlatform', {
anchorX: 0,
anchorY: 0,
alpha: 0
});
leftControl.width = 2048 / 3;
leftControl.height = 2732;
leftControl.x = 0;
leftControl.y = 0;
game.addChild(leftControl);
touchControls.left = leftControl;
var rightControl = LK.getAsset('lightPlatform', {
anchorX: 0,
anchorY: 0,
alpha: 0
});
rightControl.width = 2048 / 3;
rightControl.height = 2732;
rightControl.x = 2048 * 2 / 3;
rightControl.y = 0;
game.addChild(rightControl);
touchControls.right = rightControl;
var centerControl = LK.getAsset('lightPlatform', {
anchorX: 0,
anchorY: 0,
alpha: 0
});
centerControl.width = 2048 / 3;
centerControl.height = 2732;
centerControl.x = 2048 / 3;
centerControl.y = 0;
game.addChild(centerControl);
touchControls.center = centerControl;
}
// Restart current level
function restartLevel() {
clearLevel();
setupLevel(currentLevel);
}
// Complete level and move to next
function completeLevel() {
if (!levelComplete) {
// For the final level, require all trophies to be collected before completing
if (currentLevel === totalLevels && player.collectedTrophies < player.totalTrophies) {
// Show message that trophies are required
var missingTrophiesText = new Text2("COLLECT ALL TROPHIES FIRST!", {
size: 60,
fill: 0xFFD700
});
missingTrophiesText.anchor.set(0.5, 0.5);
missingTrophiesText.x = 2048 / 2;
missingTrophiesText.y = 2732 / 2;
game.addChild(missingTrophiesText);
// Flash and remove after 2 seconds
tween(missingTrophiesText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
game.removeChild(missingTrophiesText);
}
});
return;
}
levelComplete = true;
LK.getSound('win').play();
// Update unlocked levels
if (currentLevel >= storage.unlockedLevels) {
storage.unlockedLevels = currentLevel + 1;
if (storage.unlockedLevels > totalLevels) {
storage.unlockedLevels = totalLevels;
}
}
// Show win animation
tween(player, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
LK.showYouWin();
// Move to next level
if (currentLevel < totalLevels) {
storage.currentLevel = currentLevel + 1;
} else {
// If all levels completed, go back to menu
setupMenu();
}
}
});
}
}
// Check collisions between player and platforms
function checkPlatformCollisions() {
var wasGrounded = player.isGrounded;
player.isGrounded = false;
platforms.forEach(function (platform) {
if (platform.isSolid(player.isDimensionLight) && player.intersects(platform)) {
// Calculate collision sides
var playerBottom = player.y + player.lightForm.height / 2;
var playerTop = player.y - player.lightForm.height / 2;
var playerLeft = player.x - player.lightForm.width / 2;
var playerRight = player.x + player.lightForm.width / 2;
var platformBottom = platform.y + platform.graphic.height / 2;
var platformTop = platform.y - platform.graphic.height / 2;
var platformLeft = platform.x - platform.graphic.width / 2;
var platformRight = platform.x + platform.graphic.width / 2;
// Calculate penetration depths
var fromTop = playerBottom - platformTop;
var fromBottom = platformBottom - playerTop;
var fromLeft = playerRight - platformLeft;
var fromRight = platformRight - playerLeft;
// Find minimum penetration
var minPenetration = Math.min(fromTop, fromBottom, fromLeft, fromRight);
// Resolve collision based on minimum penetration
if (minPenetration === fromTop && player.velocityY > 0) {
player.y = platformTop - player.lightForm.height / 2;
player.velocityY = 0;
player.isGrounded = true;
} else if (minPenetration === fromBottom && player.velocityY < 0) {
player.y = platformBottom + player.lightForm.height / 2;
player.velocityY = 0;
} else if (minPenetration === fromLeft && player.velocityX > 0) {
player.x = platformLeft - player.lightForm.width / 2;
player.velocityX = 0;
} else if (minPenetration === fromRight && player.velocityX < 0) {
player.x = platformRight + player.lightForm.width / 2;
player.velocityX = 0;
}
}
});
// Landing effect
if (!wasGrounded && player.isGrounded) {
tween(player, {
scaleX: 1.2,
scaleY: 0.8
}, {
duration: 100,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
}
}
// Check other game object interactions
function checkInteractions() {
// Check dimension switches
dimensionSwitches.forEach(function (dimensionSwitch) {
if (player.intersects(dimensionSwitch) && isSwapEnabled) {
player.swapDimension();
isSwapEnabled = false;
swapCooldown = 45; // 0.75-second cooldown at 60 FPS
// Update dimension text
if (touchControls.dimensionText) {
touchControls.dimensionText.setText("DIMENSION: " + (player.isDimensionLight ? "LIGHT" : "SHADOW"));
}
// Animate the switch
tween(dimensionSwitch, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
onFinish: function onFinish() {
tween(dimensionSwitch, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
});
// Check hazards
hazards.forEach(function (hazard) {
if (player.intersects(hazard)) {
player.die();
}
});
// Check trophies
trophies.forEach(function (trophy, index) {
if (!trophy.collected && player.intersects(trophy)) {
// Collect trophy
trophy.collected = true;
player.collectedTrophies++;
// Update trophy counter
if (touchControls.trophyText) {
touchControls.trophyText.setText("TROPHIES: " + player.collectedTrophies + "/" + player.totalTrophies);
}
// Trophy collection animation
tween(trophy, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
game.removeChild(trophy);
}
});
}
});
// Check goal
if (player.intersects(goal)) {
completeLevel();
}
// Swap cooldown
if (!isSwapEnabled) {
swapCooldown--;
if (swapCooldown <= 0) {
isSwapEnabled = true;
}
}
}
// Touch controls
var touchStart = {
x: 0,
y: 0
};
var isTouching = false;
var movingLeft = false;
var movingRight = false;
game.down = function (x, y, obj) {
if (gameState === STATE_GAME) {
touchStart.x = x;
touchStart.y = y;
isTouching = true;
// Check which control area was touched
if (x < 2048 / 3) {
// Left side - move left
movingLeft = true;
player.moveLeft();
} else if (x > 2048 * 2 / 3) {
// Right side - move right
movingRight = true;
player.moveRight();
} else {
// Center - jump
player.jump();
}
}
};
game.move = function (x, y, obj) {
if (gameState === STATE_GAME && isTouching) {
// Check for swipe up (dimension swap) - reduced distance required
if (y < touchStart.y - 70) {
if (isSwapEnabled) {
player.swapDimension();
isSwapEnabled = false;
swapCooldown = 45;
// Update dimension text
if (touchControls.dimensionText) {
touchControls.dimensionText.setText("DIMENSION: " + (player.isDimensionLight ? "LIGHT" : "SHADOW"));
}
}
// Reset touch to prevent multiple swipes
isTouching = false;
}
}
};
game.up = function (x, y, obj) {
if (gameState === STATE_GAME) {
isTouching = false;
// Stop horizontal movement if this was a movement control
if (movingLeft || movingRight) {
player.stopMoving();
movingLeft = false;
movingRight = false;
}
}
};
// Main game update loop
game.update = function () {
if (gameState === STATE_GAME && !levelComplete && player && !player.isDead) {
// Update player physics
player.update();
// Check collisions and interactions
checkPlatformCollisions();
checkInteractions();
// Update platform visuals based on current dimension
platforms.forEach(function (platform) {
// Make solid platforms more visible (higher alpha) in current dimension
if (platform.isSolid(player.isDimensionLight)) {
platform.graphic.alpha = 1.0;
} else {
platform.graphic.alpha = 0.5;
}
});
}
};
// Initialize the game with the menu
setupMenu();
Switch. In-Game asset. 2d. High contrast. No shadows
Trophy. In-Game asset. 2d. High contrast. No shadows
Hazard. In-Game asset. 2d. High contrast. No shadows
A shadow ninja. In-Game asset. 2d. High contrast. No shadows
A shadow ninja. In-Game asset. 2d. High contrast. shadow
Shadow platform. In-Game asset. 2d. High contrast. No shadows