User prompt
Make it so you can glide ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make you sell the trophies on the last platform
User prompt
Make it so the platforms are closer
User prompt
Make it so it’s a little easier
User prompt
Make it so you fall slower
User prompt
Make it easier
Code edit (1 edits merged)
Please save this source code
User prompt
Shadow Swap: Dimension Shifter
Initial prompt
Genre: Puzzle Platformer Concept: You play as a shadow who can “swap places” with light! The world is filled with puzzles where light and shadow behave like solid platforms or traps. You must swap between shadow and light form to navigate tricky levels, avoid enemies, and activate switches. Key Features: • Light = visible, passable platforms. • Shadow = invisible paths and walls only shadows can use. • Swapping at the right time is the key to survival. • Boss levels where you race against moving light sources.
/**** * 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