/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var DarknessMask = Container.expand(function () { var self = Container.call(this); // Create a dark overlay that covers the entire screen var darkness = LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, alpha: 0.85, tint: 0x000000 }); // Scale to cover the entire screen darkness.width = 2048; darkness.height = 2732; self.addChild(darkness); // Create a visible circle around player (light effect) self.lightRadius = 250; // Reduced visibility radius for more tension self.adjustLightRadius = function (playerNoiseLevel) { // Light shrinks when making noise (harder to see when noisy) var targetRadius = 250 - playerNoiseLevel * 80; // Smooth transition with tween tween(self, { lightRadius: targetRadius }, { duration: 500 }); }; self.updatePosition = function (playerX, playerY) { self.x = 2048 / 2; self.y = 2732 / 2; // Create a mask using a circle asset at player's position if (!self.mask) { // Create a circle mask using a shape asset instead of Graphics self.mask = self.addChild(LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: self.lightRadius * 2, height: self.lightRadius * 2, tint: 0xFFFFFF })); } // Update mask position self.mask.x = playerX - self.x; self.mask.y = playerY - self.y; // Ensure mask size matches the desired light radius self.mask.width = self.lightRadius * 2; self.mask.height = self.lightRadius * 2; }; return self; }); var Exit = Container.expand(function () { var self = Container.call(this); var exitSprite = self.attachAsset('exit', { anchorX: 0.5, anchorY: 0.5 }); // Initially exits are inactive exitSprite.alpha = 0.3; self.activate = function () { exitSprite.alpha = 1; // Make the exit slightly pulse to draw attention function pulse() { tween(exitSprite, { scaleX: 1.2, scaleY: 1.2 }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { tween(exitSprite, { scaleX: 1, scaleY: 1 }, { duration: 700, easing: tween.easeInOut, onFinish: pulse }); } }); } pulse(); }; return self; }); var Locker = Container.expand(function () { var self = Container.call(this); var lockerSprite = self.attachAsset('locker', { anchorX: 0.5, anchorY: 0.5 }); self.isOccupied = false; return self; }); var Monster = Container.expand(function () { var self = Container.call(this); var monsterSprite = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.detectionRadius = 400; self.hasSight = false; self.targetX = 0; self.targetY = 0; self.isChasing = false; self.wanderTimer = 0; self.wanderDirection = { x: 0, y: 0 }; self.setHasSight = function (value) { self.hasSight = value; if (value) { tween(monsterSprite, { tint: 0xFF00FF }, { duration: 1000 }); self.speed = 3; // Show "HE IS NOT BLIND!!" message when monster gains sight if (instructionText) { instructionText.setText('HE IS NOT BLIND!!'); instructionText.style.fill = "#FF0000"; // Flash the screen to indicate danger LK.effects.flashScreen(0xFF0000, 500); } } }; self.startChasing = function (x, y) { self.isChasing = true; self.targetX = x; self.targetY = y; LK.getSound('monster_detect').play(); // Increase speed temporarily during initial chase self.speed = self.speed * 1.5; LK.setTimeout(function () { if (self.isChasing) { self.speed = self.speed / 1.5; } }, 3000); // Monster growls and bangs chest function bangChest() { tween(monsterSprite, { scaleX: 1.3, scaleY: 1.3, tint: 0xFF3300 }, { duration: 200, onFinish: function onFinish() { tween(monsterSprite, { scaleX: 1.0, scaleY: 1.0, tint: 0xFF0000 }, { duration: 200, onFinish: function onFinish() { if (self.isChasing && LK.ticks % 2 === 0) { bangChest(); } } }); } }); } bangChest(); }; self.stopChasing = function () { self.isChasing = false; }; self.wander = function () { if (LK.ticks % 120 === 0 || self.wanderTimer <= 0) { // Pick a random direction var angle = Math.random() * Math.PI * 2; self.wanderDirection = { x: Math.cos(angle), y: Math.sin(angle) }; self.wanderTimer = 60 + Math.random() * 120; // Sometimes patrol near a locker if (Math.random() < 0.3 && lockers && lockers.length > 0) { // Target a random locker var randomLocker = lockers[Math.floor(Math.random() * lockers.length)]; self.targetX = randomLocker.x; self.targetY = randomLocker.y; // Move toward locker for a while LK.setTimeout(function () { // Reset after approaching self.targetX = 0; self.targetY = 0; }, 3000); } } // If targeting a specific location during wandering if (self.targetX !== 0 && self.targetY !== 0) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 150) { // Move toward target at reduced speed self.x += dx / dist * (self.speed * 0.3); self.y += dy / dist * (self.speed * 0.3); } else { // Wander normally when close to target self.x += self.wanderDirection.x * (self.speed * 0.5); self.y += self.wanderDirection.y * (self.speed * 0.5); } } else { // Normal wandering self.x += self.wanderDirection.x * (self.speed * 0.5); self.y += self.wanderDirection.y * (self.speed * 0.5); } // Boundary checks if (self.x < 100) self.x = 100; if (self.x > 2048 - 100) self.x = 2048 - 100; if (self.y < 100) self.y = 100; if (self.y > 2732 - 100) self.y = 2732 - 100; self.wanderTimer--; }; self.moveTowardsTarget = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { // Basic pathfinding - try to avoid obstacles var obstacleAhead = false; // Check if there's a direct obstacle in movement path for (var i = 0; walls && i < walls.length; i++) { // Check only walls directly in path var wallDx = walls[i].x - self.x; var wallDy = walls[i].y - self.y; var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy); // Check if wall is within immediate path and roughly in the same direction if (wallDist < 100 && (wallDx * dx + wallDy * dy) / (wallDist * distance) > 0.7) { obstacleAhead = true; break; } } if (obstacleAhead && Math.random() < 0.5) { // Add slight random variation to movement to navigate around obstacles var angle = Math.atan2(dy, dx) + (Math.random() * 0.8 - 0.4); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } else { // Direct movement toward target self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Occasionally increase speed in bursts when chasing if (LK.ticks % 180 === 0 && Math.random() < 0.3) { var burstSpeed = self.speed * 1.5; var originalSpeed = self.speed; self.speed = burstSpeed; LK.setTimeout(function () { self.speed = originalSpeed; }, 500); } } } }; self.update = function () { if (self.isChasing) { self.moveTowardsTarget(); } else { self.wander(); } }; return self; }); var Notebook = Container.expand(function () { var self = Container.call(this); var notebookSprite = self.attachAsset('notebook', { anchorX: 0.5, anchorY: 0.5 }); // Make the notebook slightly pulse to draw attention function pulse() { tween(notebookSprite, { scaleX: 1.1, scaleY: 1.1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(notebookSprite, { scaleX: 1, scaleY: 1 }, { duration: 1000, easing: tween.easeInOut, onFinish: pulse }); } }); } pulse(); return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerSprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 4; self.isHidden = false; self.noiseLevel = 0; self.notebooksCollected = 0; self.makeNoise = function (amount) { self.noiseLevel = Math.min(1, self.noiseLevel + amount); // Noise decreases over time LK.setTimeout(function () { self.noiseLevel = Math.max(0, self.noiseLevel - 0.1); }, 1000); }; self.hide = function () { if (!self.isHidden) { self.isHidden = true; // Fade out effect tween(playerSprite, { alpha: 0.3, scaleX: 0.8, scaleY: 0.8 }, { duration: 300 }); LK.getSound('hide').play(); // Heavy breathing effect when monster is nearby if (monster && monster.isChasing) { // Trembling effect while hiding from monster var _tremble = function tremble() { if (self.isHidden && monster.isChasing) { var trembleAmt = 0.05; playerSprite.x = Math.random() * trembleAmt * 2 - trembleAmt; playerSprite.y = Math.random() * trembleAmt * 2 - trembleAmt; LK.setTimeout(_tremble, 100); } else { playerSprite.x = 0; playerSprite.y = 0; } }; _tremble(); } } }; self.unhide = function () { if (self.isHidden) { self.isHidden = false; playerSprite.alpha = 1; } }; self.update = function () { // Player visibility logic is handled in the main game update if (self.isMoving && !self.isHidden) { if (LK.ticks % 20 === 0) { LK.getSound('footstep').play(); self.makeNoise(0.15); // Add subtle visual feedback for footsteps tween(playerSprite, { scaleX: 0.95, scaleY: 1.05, y: 5 }, { duration: 150, onFinish: function onFinish() { tween(playerSprite, { scaleX: 1, scaleY: 1, y: 0 }, { duration: 150 }); } }); } // Create footstep effect if (LK.ticks % 30 === 0 && self.noiseLevel > 0.4) { // Add visual ripple for footsteps when being noisy var footstepEffect = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, alpha: 0.3, width: 20, height: 20, tint: 0xFFFFFF }); self.addChild(footstepEffect); // Animate and remove tween(footstepEffect, { width: 60, height: 60, alpha: 0 }, { duration: 500, onFinish: function onFinish() { self.removeChild(footstepEffect); } }); } } }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x050505 }); /**** * Game Code ****/ // Game variables var player; var monster; var notebooks = []; var lockers = []; var exits = []; var walls = []; var notebooksRequired = 7; var lastTouchedLocker = null; var gamePhase = "collecting"; // collecting, escaping // UI elements var notebookCounter; var noiseIndicator; var instructionText; // Create the UI function setupUI() { // Notebook counter notebookCounter = new Text2('0/' + notebooksRequired, { size: 80, fill: 0xFFD700 }); notebookCounter.anchor.set(1, 0); LK.gui.topRight.addChild(notebookCounter); // Noise indicator noiseIndicator = new Text2('Noise: ●', { size: 60, fill: 0x00FF00 }); noiseIndicator.anchor.set(0, 0); LK.gui.topRight.addChild(noiseIndicator); noiseIndicator.y = 100; // Instruction text instructionText = new Text2('Find and collect 7 notebooks to escape!', { size: 50, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 0); LK.gui.top.addChild(instructionText); instructionText.y = 20; } // Generate a random position that isn't too close to existing objects function getRandomPosition(minDistance) { var x, y; var validPosition = false; while (!validPosition) { x = 150 + Math.random() * (2048 - 300); y = 150 + Math.random() * (2732 - 300); validPosition = true; // Check distance from player if (player) { var dxPlayer = x - player.x; var dyPlayer = y - player.y; var distPlayer = Math.sqrt(dxPlayer * dxPlayer + dyPlayer * dyPlayer); if (distPlayer < minDistance) { validPosition = false; continue; } } // Check distance from other notebooks for (var i = 0; i < notebooks.length; i++) { var dxNote = x - notebooks[i].x; var dyNote = y - notebooks[i].y; var distNote = Math.sqrt(dxNote * dxNote + dyNote * dyNote); if (distNote < minDistance) { validPosition = false; break; } } // Check distance from lockers for (var j = 0; j < lockers.length; j++) { var dxLocker = x - lockers[j].x; var dyLocker = y - lockers[j].y; var distLocker = Math.sqrt(dxLocker * dxLocker + dyLocker * dyLocker); if (distLocker < minDistance) { validPosition = false; break; } } } return { x: x, y: y }; } // Generate the game level function setupLevel() { // Create player player = new Player(); player.x = 2048 / 2; player.y = 2732 / 2; game.addChild(player); // Add darkness overlay with light around player var darknessMask = new DarknessMask(); darknessMask.updatePosition(player.x, player.y); game.addChild(darknessMask); // Create school environment with walls function createSchoolWalls() { // Create outer walls (school boundaries) // Top wall for (var x = 0; x < 2048; x += 50) { var topWall = new Wall(); topWall.x = x; topWall.y = 25; walls.push(topWall); game.addChild(topWall); } // Bottom wall for (var x = 0; x < 2048; x += 50) { var bottomWall = new Wall(); bottomWall.x = x; bottomWall.y = 2732 - 25; walls.push(bottomWall); game.addChild(bottomWall); } // Left wall for (var y = 0; y < 2732; y += 50) { var leftWall = new Wall(); leftWall.x = 25; leftWall.y = y; walls.push(leftWall); game.addChild(leftWall); } // Right wall for (var y = 0; y < 2732; y += 50) { var rightWall = new Wall(); rightWall.x = 2048 - 25; rightWall.y = y; walls.push(rightWall); game.addChild(rightWall); } // Create internal school hallways and rooms // Create random corridors var corridorCount = 8; for (var i = 0; i < corridorCount; i++) { var isHorizontal = Math.random() > 0.5; var startX = 100 + Math.random() * (2048 - 200); var startY = 100 + Math.random() * (2732 - 200); var length = 300 + Math.random() * 500; if (isHorizontal) { for (var x = 0; x < length; x += 100) { if (startX + x < 2048 - 100) { // Add walls on sides of corridor var topWall = new Wall(); topWall.x = startX + x; topWall.y = startY - 50; walls.push(topWall); game.addChild(topWall); var bottomWall = new Wall(); bottomWall.x = startX + x; bottomWall.y = startY + 50; walls.push(bottomWall); game.addChild(bottomWall); } } } else { for (var y = 0; y < length; y += 100) { if (startY + y < 2732 - 100) { // Add walls on sides of corridor var leftWall = new Wall(); leftWall.x = startX - 50; leftWall.y = startY + y; walls.push(leftWall); game.addChild(leftWall); var rightWall = new Wall(); rightWall.x = startX + 50; rightWall.y = startY + y; walls.push(rightWall); game.addChild(rightWall); } } } } } createSchoolWalls(); // Create monster monster = new Monster(); monster.x = 300; monster.y = 300; game.addChild(monster); // Create notebooks for (var i = 0; i < notebooksRequired; i++) { var pos = getRandomPosition(300); var notebook = new Notebook(); notebook.x = pos.x; notebook.y = pos.y; notebooks.push(notebook); game.addChild(notebook); } // Create lockers var lockerCount = 12; for (var j = 0; j < lockerCount; j++) { var lockerPos = getRandomPosition(250); var locker = new Locker(); locker.x = lockerPos.x; locker.y = lockerPos.y; lockers.push(locker); game.addChild(locker); } // Create exits (initially inactive) var exitPositions = [{ x: 100, y: 100 }, { x: 2048 - 100, y: 100 }, { x: 2048 / 2, y: 100 }, { x: 100, y: 2732 - 100 }, { x: 2048 - 100, y: 2732 - 100 }, { x: 2048 / 2, y: 2732 - 100 }]; for (var k = 0; k < exitPositions.length; k++) { var exit = new Exit(); exit.x = exitPositions[k].x; exit.y = exitPositions[k].y; exits.push(exit); game.addChild(exit); } } // Start the game function startGame() { setupUI(); setupLevel(); // Play ambient music LK.playMusic('ambient'); // Show tutorial message var tutorialText = new Text2('Drag to move. Find notebooks.\nFlumbo Jumbo is blind but can hear you.\nMove carefully to stay quiet.\nHide in lockers when detected!', { size: 40, fill: 0xFFFFFF }); tutorialText.anchor.set(0.5, 0.5); tutorialText.x = 2048 / 2; tutorialText.y = 2732 / 2; game.addChild(tutorialText); // Fade out tutorial after a delay LK.setTimeout(function () { tween(tutorialText, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { game.removeChild(tutorialText); } }); }, 5000); } // Input handling var isDragging = false; var lastTouchX = 0; var lastTouchY = 0; game.down = function (x, y, obj) { isDragging = true; lastTouchX = x; lastTouchY = y; }; game.up = function (x, y, obj) { isDragging = false; player.isMoving = false; }; game.move = function (x, y, obj) { if (isDragging && !player.isHidden) { // Calculate direction and distance var dx = x - lastTouchX; var dy = y - lastTouchY; var distance = Math.sqrt(dx * dx + dy * dy); // Only move if there's significant movement if (distance > 5) { player.isMoving = true; // Calculate speed based on noise level - more careful = less noise var speedMultiplier = distance > 30 ? 0.6 : 0.4; player.x += dx * speedMultiplier; player.y += dy * speedMultiplier; // If moving fast, generate more noise if (distance > 30) { player.makeNoise(0.01); } // Keep player in bounds if (player.x < 50) player.x = 50; if (player.x > 2048 - 50) player.x = 2048 - 50; if (player.y < 50) player.y = 50; if (player.y > 2732 - 50) player.y = 2732 - 50; // Update last position lastTouchX = x; lastTouchY = y; } else { player.isMoving = false; } } }; // Update game state game.update = function () { if (!player) { startGame(); return; } // Update noise indicator if (player.noiseLevel < 0.3) { noiseIndicator.setText('Noise: ●'); noiseIndicator.style.fill = "#00FF00"; } else if (player.noiseLevel < 0.7) { noiseIndicator.setText('Noise: ●●'); noiseIndicator.style.fill = "#FFFF00"; } else { noiseIndicator.setText('Noise: ●●●'); noiseIndicator.style.fill = "#FF0000"; } // Check for notebook collection for (var i = notebooks.length - 1; i >= 0; i--) { var notebook = notebooks[i]; if (player.intersects(notebook) && !player.isHidden) { // Remove the notebook notebook.destroy(); notebooks.splice(i, 1); // Update player stats player.notebooksCollected++; notebookCounter.setText(player.notebooksCollected + '/' + notebooksRequired); // Play sound LK.getSound('pickup').play(); // Make some noise when collecting player.makeNoise(0.3); // Visual effect for notebook collection var collectEffect = LK.getAsset('notebook', { anchorX: 0.5, anchorY: 0.5, x: notebook.x, y: notebook.y, alpha: 0.8, scaleX: 1, scaleY: 1 }); game.addChild(collectEffect); // Animation for notebook collection tween(collectEffect, { scaleX: 2, scaleY: 2, alpha: 0, y: collectEffect.y - 100 }, { duration: 800, onFinish: function onFinish() { game.removeChild(collectEffect); } }); // Check if all notebooks are collected if (player.notebooksCollected >= notebooksRequired) { gamePhase = "escaping"; instructionText.setText('All notebooks collected! Find an exit before Flumbo catches you!'); monster.setHasSight(true); // Dramatic effect when monster gains sight LK.effects.flashScreen(0xFF0000, 800); // Only activate the last exit (6th one) as instructed // The other exits should be visible but closed for (var e = 0; e < exits.length; e++) { if (e === exits.length - 1) { // Activate only the last exit exits[e].activate(); // Make a subtle pulsing effect to hint at correct exit LK.setTimeout(function () { tween(exits[exits.length - 1].children[0], { alpha: 0.7 }, { duration: 500, onFinish: function onFinish() { tween(exits[exits.length - 1].children[0], { alpha: 1 }, { duration: 500, onFinish: function onFinish() { if (gamePhase === "escaping") { this.onFinish(); } } }); } }); }, 1000); } else { // Make other exits visible but not usable var exitSprite = exits[e].children[0]; exitSprite.alpha = 0.5; exitSprite.tint = 0x880000; // Red tint to indicate closed } } } } } // Check for hiding in lockers var nearLocker = false; for (var j = 0; j < lockers.length; j++) { var locker = lockers[j]; var dxLocker = player.x - locker.x; var dyLocker = player.y - locker.y; var distLocker = Math.sqrt(dxLocker * dxLocker + dyLocker * dyLocker); if (distLocker < 100 && !locker.isOccupied) { nearLocker = true; lastTouchedLocker = locker; // Double tap or tap when monster is chasing to hide if (isDragging && (monster.isChasing || LK.ticks % 30 === 0)) { player.hide(); player.x = locker.x; player.y = locker.y; locker.isOccupied = true; break; } } } // Allow player to exit locker when not being chased if (player.isHidden && !monster.isChasing && isDragging) { player.unhide(); if (lastTouchedLocker) { lastTouchedLocker.isOccupied = false; } } // Check for reaching an exit in escape phase if (gamePhase === "escaping") { for (var k = 0; k < exits.length; k++) { var exit = exits[k]; if (player.intersects(exit) && !player.isHidden) { // Check if this is the last exit (the only open one) if (k === exits.length - 1) { // Play escape cutscene player.isHidden = true; // Prevent further interaction monster.isChasing = true; monster.targetX = player.x; monster.targetY = player.y; // Player slides through the exit tween(player, { x: exit.x, y: exit.y, alpha: 0.5, scaleX: 0.5, scaleY: 0.5 }, { duration: 1000, onFinish: function onFinish() { // Monster gets crushed by the door - dramatic chase sequence tween(monster, { x: exit.x - 50, y: exit.y }, { duration: 600, onFinish: function onFinish() { // Monster tries to reach player in last desperate attempt tween(monster.children[0], { scaleX: 1.3, scaleY: 1.1, tint: 0xFF0000 }, { duration: 300, onFinish: function onFinish() { // Door closes on monster LK.effects.flashScreen(0xFF0000, 500); // Play monster detection sound for impact LK.getSound('monster_detect').play(); // Flatten monster to show crushing tween(monster.children[0], { scaleY: 0.2, scaleX: 1.8, alpha: 0.5, tint: 0x660000 }, { duration: 700, onFinish: function onFinish() { // Show "soft place center" message instructionText.setText("You've escaped to the soft place center! Chapter complete."); instructionText.style.fill = "#00FF00"; // Create final escape visual effect var light = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: exit.x, y: exit.y, alpha: 0, width: 50, height: 50, tint: 0xFFFFFF }); game.addChild(light); tween(light, { alpha: 0.8, width: 2048, height: 2048 }, { duration: 1500, onFinish: function onFinish() { LK.setTimeout(function () { LK.showYouWin(); }, 500); } }); } }); } }); } }); } }); return; } else { // Closed exit - monster gets angrier LK.effects.flashScreen(0xFF0000, 300); monster.speed += 0.5; monster.startChasing(player.x, player.y); } } } } // Monster detection var dxMonster = monster.x - player.x; var dyMonster = monster.y - player.y; var distMonster = Math.sqrt(dxMonster * dxMonster + dyMonster * dyMonster); // Proximity warning - monster is getting close if (!player.isHidden && distMonster < monster.detectionRadius * 1.5 && !monster.isChasing) { // Visual proximity indicator if (LK.ticks % 60 === 0) { LK.effects.flashScreen(0x330000, 300); // Increase player fear based on proximity var proximityFear = 1 - distMonster / (monster.detectionRadius * 1.5); player.makeNoise(proximityFear * 0.1); // Fear increases noise level slightly // Heartbeat effect - the closer the monster, the faster the heartbeat var heartbeatIntensity = 1 - distMonster / (monster.detectionRadius * 1.5); // Pulse the player slightly to simulate heartbeat tween(player, { scaleX: 1 + heartbeatIntensity * 0.1, scaleY: 1 + heartbeatIntensity * 0.1 }, { duration: 300 - heartbeatIntensity * 200, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1 }, { duration: 300 - heartbeatIntensity * 200 }); } }); } } // Monster detects player based on noise if (!player.isHidden && distMonster < monster.detectionRadius * player.noiseLevel) { monster.startChasing(player.x, player.y); // Show warning message as instructed instructionText.setText('RUN AND GO FIND A LOCKER!!'); instructionText.style.fill = "#FF0000"; } // Monster can see player in the final phase else if (gamePhase === "escaping" && monster.hasSight && !player.isHidden && distMonster < monster.detectionRadius * 1.5) { monster.startChasing(player.x, player.y); } // Update monster's target position if already chasing else if (monster.isChasing && !player.isHidden) { monster.targetX = player.x; monster.targetY = player.y; } // Stop chasing if player is hidden else if (player.isHidden && monster.isChasing) { LK.setTimeout(function () { monster.stopChasing(); }, 1500); } // Stop chasing if lost track for too long else if (monster.isChasing && LK.ticks % 180 === 0) { monster.stopChasing(); } // Check for collisions with walls function checkWallCollisions() { // Check player-wall collisions for (var i = 0; i < walls.length; i++) { var wall = walls[i]; if (player.intersects(wall) && !player.isHidden) { // Calculate push back direction var dx = player.x - wall.x; var dy = player.y - wall.y; var dist = Math.sqrt(dx * dx + dy * dy); // Push player away from wall if (dist > 0) { // Calculate minimal push needed var overlapX = (player.width + wall.width) / 2 - Math.abs(dx); var overlapY = (player.height + wall.height) / 2 - Math.abs(dy); // Push in the direction of minimum overlap if (overlapX < overlapY) { player.x += dx / dist * overlapX * 1.1; } else { player.y += dy / dist * overlapY * 1.1; } } } } // Check monster-wall collisions (less strict, monster can navigate around walls better) if (monster.isChasing) { for (var i = 0; i < walls.length; i++) { var wall = walls[i]; if (monster.intersects(wall)) { // Less strict collision for monster - allows some navigation through gaps var dx = monster.x - wall.x; var dy = monster.y - wall.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { monster.x += dx / dist * 2; monster.y += dy / dist * 2; } } } } } checkWallCollisions(); // Update darkness mask to follow player for (var i = 0; i < game.children.length; i++) { if (game.children[i] instanceof DarknessMask) { game.children[i].updatePosition(player.x, player.y); // Also adjust light radius based on player noise game.children[i].adjustLightRadius(player.noiseLevel); break; } } // Check for player caught by monster if (!player.isHidden && player.intersects(monster)) { // Flash the screen red LK.effects.flashScreen(0xFF0000, 500); // Game over LK.showGameOver(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var DarknessMask = Container.expand(function () {
var self = Container.call(this);
// Create a dark overlay that covers the entire screen
var darkness = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
alpha: 0.85,
tint: 0x000000
});
// Scale to cover the entire screen
darkness.width = 2048;
darkness.height = 2732;
self.addChild(darkness);
// Create a visible circle around player (light effect)
self.lightRadius = 250; // Reduced visibility radius for more tension
self.adjustLightRadius = function (playerNoiseLevel) {
// Light shrinks when making noise (harder to see when noisy)
var targetRadius = 250 - playerNoiseLevel * 80;
// Smooth transition with tween
tween(self, {
lightRadius: targetRadius
}, {
duration: 500
});
};
self.updatePosition = function (playerX, playerY) {
self.x = 2048 / 2;
self.y = 2732 / 2;
// Create a mask using a circle asset at player's position
if (!self.mask) {
// Create a circle mask using a shape asset instead of Graphics
self.mask = self.addChild(LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: self.lightRadius * 2,
height: self.lightRadius * 2,
tint: 0xFFFFFF
}));
}
// Update mask position
self.mask.x = playerX - self.x;
self.mask.y = playerY - self.y;
// Ensure mask size matches the desired light radius
self.mask.width = self.lightRadius * 2;
self.mask.height = self.lightRadius * 2;
};
return self;
});
var Exit = Container.expand(function () {
var self = Container.call(this);
var exitSprite = self.attachAsset('exit', {
anchorX: 0.5,
anchorY: 0.5
});
// Initially exits are inactive
exitSprite.alpha = 0.3;
self.activate = function () {
exitSprite.alpha = 1;
// Make the exit slightly pulse to draw attention
function pulse() {
tween(exitSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(exitSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
};
return self;
});
var Locker = Container.expand(function () {
var self = Container.call(this);
var lockerSprite = self.attachAsset('locker', {
anchorX: 0.5,
anchorY: 0.5
});
self.isOccupied = false;
return self;
});
var Monster = Container.expand(function () {
var self = Container.call(this);
var monsterSprite = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
self.detectionRadius = 400;
self.hasSight = false;
self.targetX = 0;
self.targetY = 0;
self.isChasing = false;
self.wanderTimer = 0;
self.wanderDirection = {
x: 0,
y: 0
};
self.setHasSight = function (value) {
self.hasSight = value;
if (value) {
tween(monsterSprite, {
tint: 0xFF00FF
}, {
duration: 1000
});
self.speed = 3;
// Show "HE IS NOT BLIND!!" message when monster gains sight
if (instructionText) {
instructionText.setText('HE IS NOT BLIND!!');
instructionText.style.fill = "#FF0000";
// Flash the screen to indicate danger
LK.effects.flashScreen(0xFF0000, 500);
}
}
};
self.startChasing = function (x, y) {
self.isChasing = true;
self.targetX = x;
self.targetY = y;
LK.getSound('monster_detect').play();
// Increase speed temporarily during initial chase
self.speed = self.speed * 1.5;
LK.setTimeout(function () {
if (self.isChasing) {
self.speed = self.speed / 1.5;
}
}, 3000);
// Monster growls and bangs chest
function bangChest() {
tween(monsterSprite, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFF3300
}, {
duration: 200,
onFinish: function onFinish() {
tween(monsterSprite, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFF0000
}, {
duration: 200,
onFinish: function onFinish() {
if (self.isChasing && LK.ticks % 2 === 0) {
bangChest();
}
}
});
}
});
}
bangChest();
};
self.stopChasing = function () {
self.isChasing = false;
};
self.wander = function () {
if (LK.ticks % 120 === 0 || self.wanderTimer <= 0) {
// Pick a random direction
var angle = Math.random() * Math.PI * 2;
self.wanderDirection = {
x: Math.cos(angle),
y: Math.sin(angle)
};
self.wanderTimer = 60 + Math.random() * 120;
// Sometimes patrol near a locker
if (Math.random() < 0.3 && lockers && lockers.length > 0) {
// Target a random locker
var randomLocker = lockers[Math.floor(Math.random() * lockers.length)];
self.targetX = randomLocker.x;
self.targetY = randomLocker.y;
// Move toward locker for a while
LK.setTimeout(function () {
// Reset after approaching
self.targetX = 0;
self.targetY = 0;
}, 3000);
}
}
// If targeting a specific location during wandering
if (self.targetX !== 0 && self.targetY !== 0) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 150) {
// Move toward target at reduced speed
self.x += dx / dist * (self.speed * 0.3);
self.y += dy / dist * (self.speed * 0.3);
} else {
// Wander normally when close to target
self.x += self.wanderDirection.x * (self.speed * 0.5);
self.y += self.wanderDirection.y * (self.speed * 0.5);
}
} else {
// Normal wandering
self.x += self.wanderDirection.x * (self.speed * 0.5);
self.y += self.wanderDirection.y * (self.speed * 0.5);
}
// Boundary checks
if (self.x < 100) self.x = 100;
if (self.x > 2048 - 100) self.x = 2048 - 100;
if (self.y < 100) self.y = 100;
if (self.y > 2732 - 100) self.y = 2732 - 100;
self.wanderTimer--;
};
self.moveTowardsTarget = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Basic pathfinding - try to avoid obstacles
var obstacleAhead = false;
// Check if there's a direct obstacle in movement path
for (var i = 0; walls && i < walls.length; i++) {
// Check only walls directly in path
var wallDx = walls[i].x - self.x;
var wallDy = walls[i].y - self.y;
var wallDist = Math.sqrt(wallDx * wallDx + wallDy * wallDy);
// Check if wall is within immediate path and roughly in the same direction
if (wallDist < 100 && (wallDx * dx + wallDy * dy) / (wallDist * distance) > 0.7) {
obstacleAhead = true;
break;
}
}
if (obstacleAhead && Math.random() < 0.5) {
// Add slight random variation to movement to navigate around obstacles
var angle = Math.atan2(dy, dx) + (Math.random() * 0.8 - 0.4);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
} else {
// Direct movement toward target
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Occasionally increase speed in bursts when chasing
if (LK.ticks % 180 === 0 && Math.random() < 0.3) {
var burstSpeed = self.speed * 1.5;
var originalSpeed = self.speed;
self.speed = burstSpeed;
LK.setTimeout(function () {
self.speed = originalSpeed;
}, 500);
}
}
}
};
self.update = function () {
if (self.isChasing) {
self.moveTowardsTarget();
} else {
self.wander();
}
};
return self;
});
var Notebook = Container.expand(function () {
var self = Container.call(this);
var notebookSprite = self.attachAsset('notebook', {
anchorX: 0.5,
anchorY: 0.5
});
// Make the notebook slightly pulse to draw attention
function pulse() {
tween(notebookSprite, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(notebookSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: pulse
});
}
});
}
pulse();
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.isHidden = false;
self.noiseLevel = 0;
self.notebooksCollected = 0;
self.makeNoise = function (amount) {
self.noiseLevel = Math.min(1, self.noiseLevel + amount);
// Noise decreases over time
LK.setTimeout(function () {
self.noiseLevel = Math.max(0, self.noiseLevel - 0.1);
}, 1000);
};
self.hide = function () {
if (!self.isHidden) {
self.isHidden = true;
// Fade out effect
tween(playerSprite, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300
});
LK.getSound('hide').play();
// Heavy breathing effect when monster is nearby
if (monster && monster.isChasing) {
// Trembling effect while hiding from monster
var _tremble = function tremble() {
if (self.isHidden && monster.isChasing) {
var trembleAmt = 0.05;
playerSprite.x = Math.random() * trembleAmt * 2 - trembleAmt;
playerSprite.y = Math.random() * trembleAmt * 2 - trembleAmt;
LK.setTimeout(_tremble, 100);
} else {
playerSprite.x = 0;
playerSprite.y = 0;
}
};
_tremble();
}
}
};
self.unhide = function () {
if (self.isHidden) {
self.isHidden = false;
playerSprite.alpha = 1;
}
};
self.update = function () {
// Player visibility logic is handled in the main game update
if (self.isMoving && !self.isHidden) {
if (LK.ticks % 20 === 0) {
LK.getSound('footstep').play();
self.makeNoise(0.15);
// Add subtle visual feedback for footsteps
tween(playerSprite, {
scaleX: 0.95,
scaleY: 1.05,
y: 5
}, {
duration: 150,
onFinish: function onFinish() {
tween(playerSprite, {
scaleX: 1,
scaleY: 1,
y: 0
}, {
duration: 150
});
}
});
}
// Create footstep effect
if (LK.ticks % 30 === 0 && self.noiseLevel > 0.4) {
// Add visual ripple for footsteps when being noisy
var footstepEffect = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.3,
width: 20,
height: 20,
tint: 0xFFFFFF
});
self.addChild(footstepEffect);
// Animate and remove
tween(footstepEffect, {
width: 60,
height: 60,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.removeChild(footstepEffect);
}
});
}
}
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x050505
});
/****
* Game Code
****/
// Game variables
var player;
var monster;
var notebooks = [];
var lockers = [];
var exits = [];
var walls = [];
var notebooksRequired = 7;
var lastTouchedLocker = null;
var gamePhase = "collecting"; // collecting, escaping
// UI elements
var notebookCounter;
var noiseIndicator;
var instructionText;
// Create the UI
function setupUI() {
// Notebook counter
notebookCounter = new Text2('0/' + notebooksRequired, {
size: 80,
fill: 0xFFD700
});
notebookCounter.anchor.set(1, 0);
LK.gui.topRight.addChild(notebookCounter);
// Noise indicator
noiseIndicator = new Text2('Noise: ●', {
size: 60,
fill: 0x00FF00
});
noiseIndicator.anchor.set(0, 0);
LK.gui.topRight.addChild(noiseIndicator);
noiseIndicator.y = 100;
// Instruction text
instructionText = new Text2('Find and collect 7 notebooks to escape!', {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0);
LK.gui.top.addChild(instructionText);
instructionText.y = 20;
}
// Generate a random position that isn't too close to existing objects
function getRandomPosition(minDistance) {
var x, y;
var validPosition = false;
while (!validPosition) {
x = 150 + Math.random() * (2048 - 300);
y = 150 + Math.random() * (2732 - 300);
validPosition = true;
// Check distance from player
if (player) {
var dxPlayer = x - player.x;
var dyPlayer = y - player.y;
var distPlayer = Math.sqrt(dxPlayer * dxPlayer + dyPlayer * dyPlayer);
if (distPlayer < minDistance) {
validPosition = false;
continue;
}
}
// Check distance from other notebooks
for (var i = 0; i < notebooks.length; i++) {
var dxNote = x - notebooks[i].x;
var dyNote = y - notebooks[i].y;
var distNote = Math.sqrt(dxNote * dxNote + dyNote * dyNote);
if (distNote < minDistance) {
validPosition = false;
break;
}
}
// Check distance from lockers
for (var j = 0; j < lockers.length; j++) {
var dxLocker = x - lockers[j].x;
var dyLocker = y - lockers[j].y;
var distLocker = Math.sqrt(dxLocker * dxLocker + dyLocker * dyLocker);
if (distLocker < minDistance) {
validPosition = false;
break;
}
}
}
return {
x: x,
y: y
};
}
// Generate the game level
function setupLevel() {
// Create player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// Add darkness overlay with light around player
var darknessMask = new DarknessMask();
darknessMask.updatePosition(player.x, player.y);
game.addChild(darknessMask);
// Create school environment with walls
function createSchoolWalls() {
// Create outer walls (school boundaries)
// Top wall
for (var x = 0; x < 2048; x += 50) {
var topWall = new Wall();
topWall.x = x;
topWall.y = 25;
walls.push(topWall);
game.addChild(topWall);
}
// Bottom wall
for (var x = 0; x < 2048; x += 50) {
var bottomWall = new Wall();
bottomWall.x = x;
bottomWall.y = 2732 - 25;
walls.push(bottomWall);
game.addChild(bottomWall);
}
// Left wall
for (var y = 0; y < 2732; y += 50) {
var leftWall = new Wall();
leftWall.x = 25;
leftWall.y = y;
walls.push(leftWall);
game.addChild(leftWall);
}
// Right wall
for (var y = 0; y < 2732; y += 50) {
var rightWall = new Wall();
rightWall.x = 2048 - 25;
rightWall.y = y;
walls.push(rightWall);
game.addChild(rightWall);
}
// Create internal school hallways and rooms
// Create random corridors
var corridorCount = 8;
for (var i = 0; i < corridorCount; i++) {
var isHorizontal = Math.random() > 0.5;
var startX = 100 + Math.random() * (2048 - 200);
var startY = 100 + Math.random() * (2732 - 200);
var length = 300 + Math.random() * 500;
if (isHorizontal) {
for (var x = 0; x < length; x += 100) {
if (startX + x < 2048 - 100) {
// Add walls on sides of corridor
var topWall = new Wall();
topWall.x = startX + x;
topWall.y = startY - 50;
walls.push(topWall);
game.addChild(topWall);
var bottomWall = new Wall();
bottomWall.x = startX + x;
bottomWall.y = startY + 50;
walls.push(bottomWall);
game.addChild(bottomWall);
}
}
} else {
for (var y = 0; y < length; y += 100) {
if (startY + y < 2732 - 100) {
// Add walls on sides of corridor
var leftWall = new Wall();
leftWall.x = startX - 50;
leftWall.y = startY + y;
walls.push(leftWall);
game.addChild(leftWall);
var rightWall = new Wall();
rightWall.x = startX + 50;
rightWall.y = startY + y;
walls.push(rightWall);
game.addChild(rightWall);
}
}
}
}
}
createSchoolWalls();
// Create monster
monster = new Monster();
monster.x = 300;
monster.y = 300;
game.addChild(monster);
// Create notebooks
for (var i = 0; i < notebooksRequired; i++) {
var pos = getRandomPosition(300);
var notebook = new Notebook();
notebook.x = pos.x;
notebook.y = pos.y;
notebooks.push(notebook);
game.addChild(notebook);
}
// Create lockers
var lockerCount = 12;
for (var j = 0; j < lockerCount; j++) {
var lockerPos = getRandomPosition(250);
var locker = new Locker();
locker.x = lockerPos.x;
locker.y = lockerPos.y;
lockers.push(locker);
game.addChild(locker);
}
// Create exits (initially inactive)
var exitPositions = [{
x: 100,
y: 100
}, {
x: 2048 - 100,
y: 100
}, {
x: 2048 / 2,
y: 100
}, {
x: 100,
y: 2732 - 100
}, {
x: 2048 - 100,
y: 2732 - 100
}, {
x: 2048 / 2,
y: 2732 - 100
}];
for (var k = 0; k < exitPositions.length; k++) {
var exit = new Exit();
exit.x = exitPositions[k].x;
exit.y = exitPositions[k].y;
exits.push(exit);
game.addChild(exit);
}
}
// Start the game
function startGame() {
setupUI();
setupLevel();
// Play ambient music
LK.playMusic('ambient');
// Show tutorial message
var tutorialText = new Text2('Drag to move. Find notebooks.\nFlumbo Jumbo is blind but can hear you.\nMove carefully to stay quiet.\nHide in lockers when detected!', {
size: 40,
fill: 0xFFFFFF
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 2048 / 2;
tutorialText.y = 2732 / 2;
game.addChild(tutorialText);
// Fade out tutorial after a delay
LK.setTimeout(function () {
tween(tutorialText, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(tutorialText);
}
});
}, 5000);
}
// Input handling
var isDragging = false;
var lastTouchX = 0;
var lastTouchY = 0;
game.down = function (x, y, obj) {
isDragging = true;
lastTouchX = x;
lastTouchY = y;
};
game.up = function (x, y, obj) {
isDragging = false;
player.isMoving = false;
};
game.move = function (x, y, obj) {
if (isDragging && !player.isHidden) {
// Calculate direction and distance
var dx = x - lastTouchX;
var dy = y - lastTouchY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only move if there's significant movement
if (distance > 5) {
player.isMoving = true;
// Calculate speed based on noise level - more careful = less noise
var speedMultiplier = distance > 30 ? 0.6 : 0.4;
player.x += dx * speedMultiplier;
player.y += dy * speedMultiplier;
// If moving fast, generate more noise
if (distance > 30) {
player.makeNoise(0.01);
}
// Keep player in bounds
if (player.x < 50) player.x = 50;
if (player.x > 2048 - 50) player.x = 2048 - 50;
if (player.y < 50) player.y = 50;
if (player.y > 2732 - 50) player.y = 2732 - 50;
// Update last position
lastTouchX = x;
lastTouchY = y;
} else {
player.isMoving = false;
}
}
};
// Update game state
game.update = function () {
if (!player) {
startGame();
return;
}
// Update noise indicator
if (player.noiseLevel < 0.3) {
noiseIndicator.setText('Noise: ●');
noiseIndicator.style.fill = "#00FF00";
} else if (player.noiseLevel < 0.7) {
noiseIndicator.setText('Noise: ●●');
noiseIndicator.style.fill = "#FFFF00";
} else {
noiseIndicator.setText('Noise: ●●●');
noiseIndicator.style.fill = "#FF0000";
}
// Check for notebook collection
for (var i = notebooks.length - 1; i >= 0; i--) {
var notebook = notebooks[i];
if (player.intersects(notebook) && !player.isHidden) {
// Remove the notebook
notebook.destroy();
notebooks.splice(i, 1);
// Update player stats
player.notebooksCollected++;
notebookCounter.setText(player.notebooksCollected + '/' + notebooksRequired);
// Play sound
LK.getSound('pickup').play();
// Make some noise when collecting
player.makeNoise(0.3);
// Visual effect for notebook collection
var collectEffect = LK.getAsset('notebook', {
anchorX: 0.5,
anchorY: 0.5,
x: notebook.x,
y: notebook.y,
alpha: 0.8,
scaleX: 1,
scaleY: 1
});
game.addChild(collectEffect);
// Animation for notebook collection
tween(collectEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0,
y: collectEffect.y - 100
}, {
duration: 800,
onFinish: function onFinish() {
game.removeChild(collectEffect);
}
});
// Check if all notebooks are collected
if (player.notebooksCollected >= notebooksRequired) {
gamePhase = "escaping";
instructionText.setText('All notebooks collected! Find an exit before Flumbo catches you!');
monster.setHasSight(true);
// Dramatic effect when monster gains sight
LK.effects.flashScreen(0xFF0000, 800);
// Only activate the last exit (6th one) as instructed
// The other exits should be visible but closed
for (var e = 0; e < exits.length; e++) {
if (e === exits.length - 1) {
// Activate only the last exit
exits[e].activate();
// Make a subtle pulsing effect to hint at correct exit
LK.setTimeout(function () {
tween(exits[exits.length - 1].children[0], {
alpha: 0.7
}, {
duration: 500,
onFinish: function onFinish() {
tween(exits[exits.length - 1].children[0], {
alpha: 1
}, {
duration: 500,
onFinish: function onFinish() {
if (gamePhase === "escaping") {
this.onFinish();
}
}
});
}
});
}, 1000);
} else {
// Make other exits visible but not usable
var exitSprite = exits[e].children[0];
exitSprite.alpha = 0.5;
exitSprite.tint = 0x880000; // Red tint to indicate closed
}
}
}
}
}
// Check for hiding in lockers
var nearLocker = false;
for (var j = 0; j < lockers.length; j++) {
var locker = lockers[j];
var dxLocker = player.x - locker.x;
var dyLocker = player.y - locker.y;
var distLocker = Math.sqrt(dxLocker * dxLocker + dyLocker * dyLocker);
if (distLocker < 100 && !locker.isOccupied) {
nearLocker = true;
lastTouchedLocker = locker;
// Double tap or tap when monster is chasing to hide
if (isDragging && (monster.isChasing || LK.ticks % 30 === 0)) {
player.hide();
player.x = locker.x;
player.y = locker.y;
locker.isOccupied = true;
break;
}
}
}
// Allow player to exit locker when not being chased
if (player.isHidden && !monster.isChasing && isDragging) {
player.unhide();
if (lastTouchedLocker) {
lastTouchedLocker.isOccupied = false;
}
}
// Check for reaching an exit in escape phase
if (gamePhase === "escaping") {
for (var k = 0; k < exits.length; k++) {
var exit = exits[k];
if (player.intersects(exit) && !player.isHidden) {
// Check if this is the last exit (the only open one)
if (k === exits.length - 1) {
// Play escape cutscene
player.isHidden = true; // Prevent further interaction
monster.isChasing = true;
monster.targetX = player.x;
monster.targetY = player.y;
// Player slides through the exit
tween(player, {
x: exit.x,
y: exit.y,
alpha: 0.5,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000,
onFinish: function onFinish() {
// Monster gets crushed by the door - dramatic chase sequence
tween(monster, {
x: exit.x - 50,
y: exit.y
}, {
duration: 600,
onFinish: function onFinish() {
// Monster tries to reach player in last desperate attempt
tween(monster.children[0], {
scaleX: 1.3,
scaleY: 1.1,
tint: 0xFF0000
}, {
duration: 300,
onFinish: function onFinish() {
// Door closes on monster
LK.effects.flashScreen(0xFF0000, 500);
// Play monster detection sound for impact
LK.getSound('monster_detect').play();
// Flatten monster to show crushing
tween(monster.children[0], {
scaleY: 0.2,
scaleX: 1.8,
alpha: 0.5,
tint: 0x660000
}, {
duration: 700,
onFinish: function onFinish() {
// Show "soft place center" message
instructionText.setText("You've escaped to the soft place center! Chapter complete.");
instructionText.style.fill = "#00FF00";
// Create final escape visual effect
var light = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: exit.x,
y: exit.y,
alpha: 0,
width: 50,
height: 50,
tint: 0xFFFFFF
});
game.addChild(light);
tween(light, {
alpha: 0.8,
width: 2048,
height: 2048
}, {
duration: 1500,
onFinish: function onFinish() {
LK.setTimeout(function () {
LK.showYouWin();
}, 500);
}
});
}
});
}
});
}
});
}
});
return;
} else {
// Closed exit - monster gets angrier
LK.effects.flashScreen(0xFF0000, 300);
monster.speed += 0.5;
monster.startChasing(player.x, player.y);
}
}
}
}
// Monster detection
var dxMonster = monster.x - player.x;
var dyMonster = monster.y - player.y;
var distMonster = Math.sqrt(dxMonster * dxMonster + dyMonster * dyMonster);
// Proximity warning - monster is getting close
if (!player.isHidden && distMonster < monster.detectionRadius * 1.5 && !monster.isChasing) {
// Visual proximity indicator
if (LK.ticks % 60 === 0) {
LK.effects.flashScreen(0x330000, 300);
// Increase player fear based on proximity
var proximityFear = 1 - distMonster / (monster.detectionRadius * 1.5);
player.makeNoise(proximityFear * 0.1); // Fear increases noise level slightly
// Heartbeat effect - the closer the monster, the faster the heartbeat
var heartbeatIntensity = 1 - distMonster / (monster.detectionRadius * 1.5);
// Pulse the player slightly to simulate heartbeat
tween(player, {
scaleX: 1 + heartbeatIntensity * 0.1,
scaleY: 1 + heartbeatIntensity * 0.1
}, {
duration: 300 - heartbeatIntensity * 200,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1
}, {
duration: 300 - heartbeatIntensity * 200
});
}
});
}
}
// Monster detects player based on noise
if (!player.isHidden && distMonster < monster.detectionRadius * player.noiseLevel) {
monster.startChasing(player.x, player.y);
// Show warning message as instructed
instructionText.setText('RUN AND GO FIND A LOCKER!!');
instructionText.style.fill = "#FF0000";
}
// Monster can see player in the final phase
else if (gamePhase === "escaping" && monster.hasSight && !player.isHidden && distMonster < monster.detectionRadius * 1.5) {
monster.startChasing(player.x, player.y);
}
// Update monster's target position if already chasing
else if (monster.isChasing && !player.isHidden) {
monster.targetX = player.x;
monster.targetY = player.y;
}
// Stop chasing if player is hidden
else if (player.isHidden && monster.isChasing) {
LK.setTimeout(function () {
monster.stopChasing();
}, 1500);
}
// Stop chasing if lost track for too long
else if (monster.isChasing && LK.ticks % 180 === 0) {
monster.stopChasing();
}
// Check for collisions with walls
function checkWallCollisions() {
// Check player-wall collisions
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (player.intersects(wall) && !player.isHidden) {
// Calculate push back direction
var dx = player.x - wall.x;
var dy = player.y - wall.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Push player away from wall
if (dist > 0) {
// Calculate minimal push needed
var overlapX = (player.width + wall.width) / 2 - Math.abs(dx);
var overlapY = (player.height + wall.height) / 2 - Math.abs(dy);
// Push in the direction of minimum overlap
if (overlapX < overlapY) {
player.x += dx / dist * overlapX * 1.1;
} else {
player.y += dy / dist * overlapY * 1.1;
}
}
}
}
// Check monster-wall collisions (less strict, monster can navigate around walls better)
if (monster.isChasing) {
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (monster.intersects(wall)) {
// Less strict collision for monster - allows some navigation through gaps
var dx = monster.x - wall.x;
var dy = monster.y - wall.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
monster.x += dx / dist * 2;
monster.y += dy / dist * 2;
}
}
}
}
}
checkWallCollisions();
// Update darkness mask to follow player
for (var i = 0; i < game.children.length; i++) {
if (game.children[i] instanceof DarknessMask) {
game.children[i].updatePosition(player.x, player.y);
// Also adjust light radius based on player noise
game.children[i].adjustLightRadius(player.noiseLevel);
break;
}
}
// Check for player caught by monster
if (!player.isHidden && player.intersects(monster)) {
// Flash the screen red
LK.effects.flashScreen(0xFF0000, 500);
// Game over
LK.showGameOver();
}
};
Make a mascot evil creature. In-Game asset. 2d. High contrast. No shadows
A dirty notebook like it was in a abandoned school. In-Game asset. 2d. High contrast. No shadows
Make a scary dark locker that is blue and make it like it was in a abandoned school. In-Game asset. 2d. High contrast. No shadows
Make a wall like it was in a abandoned school. In-Game asset. 2d. High contrast. No shadows
Make a door with a white wall like it was in an abandoned school on top of the door is a glowing red exit sign. In-Game asset. 2d. High contrast. No shadows
Make a centercircle in an abandoned school. In-Game asset. 2d. High contrast. No shadows