User prompt
Let the mage character in the 3rd room continuously send balls to us and let those balls take our lives. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
play and start ui on the game
User prompt
Name your game Brave Adventurer instead of Dungeon Dash
User prompt
Good luck text should be white
User prompt
Put an NPC next to the first room and have him say good luck to us before moving on to the second room.
User prompt
attack butonunu kaldır ve mouseumuz karakterimizin üstünde oldugunda hareket etsin ve click yaptığımızda saldırsın
User prompt
When our character moves to the 2nd room, there will be a different boss, so create different monster assets for each room.
User prompt
Let the heal button's image be a health pot and red
User prompt
When we click on the heal button, our health will be filled a little, but we have the right to refill our health 3 times.
User prompt
Make the background of the rooms not gray but dungeon style, pixel by pixel
User prompt
When you pass through the door, delete the health refill and add a heal pot button next to the attack button.
User prompt
iskeletleri öldürmesi çok zor
User prompt
There will be different monsters in each room, so it's a boss fight style.
User prompt
every room has a 1 monster
User prompt
still hero dying
User prompt
Our character dies when he gets close to the skeletons. Find a solution to this and do some research.
User prompt
hero dies immidatly
User prompt
Our character dies when he gets close to the skeletons. If the hero goes near the character, the skeleton freezes.
User prompt
fix skeleton collisions
User prompt
Our character dies directly without approaching the skeletons.
User prompt
Don't let the skeletons push us back
User prompt
When we go near the skeletons, they move too much and kill us immediately.
User prompt
Make the attack button theme nice and make it a bit bigger
User prompt
and let it be a button that is on all the time, not when you go near the skeletons
User prompt
Change the name of the kill button to attack and make it a bit more dungeon themed and round
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Chest class var Chest = Container.expand(function () { var self = Container.call(this); // Attach chest asset (brown box) var chestSprite = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5 }); self.width = chestSprite.width; self.height = chestSprite.height; self.opened = false; return self; }); // Door class (for next room) var Door = Container.expand(function () { var self = Container.call(this); // Attach door asset (purple box) var doorSprite = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5 }); self.width = doorSprite.width; self.height = doorSprite.height; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); // Attach hero asset (red box) var heroSprite = self.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); self.width = heroSprite.width; self.height = heroSprite.height; // Attach sword to hero var swordSprite = self.attachAsset('sword', { anchorX: 0.5, anchorY: 0.5 }); swordSprite.x = -heroSprite.width * 0.3; // Position sword to the left of hero swordSprite.y = 0; // Center vertically with hero swordSprite.rotation = -Math.PI / 4; // Angle the sword pointing left // Hero stats self.maxHealth = 3; self.health = self.maxHealth; self.invincible = false; self.invincibleTimer = 0; // Create health bar above character // Health bar border (white outline) self.healthBarBorder = LK.getAsset('trap', { width: 200, height: 40, anchorX: 0.5, anchorY: 0.5 }); self.healthBarBorder.tint = 0xFFFFFF; // White border self.healthBarBorder.x = 0; self.healthBarBorder.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarBorder); // Health bar background (dark) self.healthBarBackground = LK.getAsset('trap', { width: 180, height: 24, anchorX: 0.5, anchorY: 0.5 }); self.healthBarBackground.tint = 0x222222; // Dark gray background self.healthBarBackground.x = 0; self.healthBarBackground.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarBackground); // Health bar fill (green to red gradient) self.healthBarFill = LK.getAsset('trap', { width: 180, height: 24, anchorX: 0.0, anchorY: 0.5 }); self.healthBarFill.tint = 0x00FF00; // Green for full health self.healthBarFill.x = -90; // Left-aligned within background self.healthBarFill.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarFill); // Update health bar display self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; self.healthBarFill.scaleX = healthPercent; // Smooth color transition based on health percentage if (healthPercent > 0.75) { self.healthBarFill.tint = 0x00FF00; // Bright green } else if (healthPercent > 0.5) { self.healthBarFill.tint = 0x7FFF00; // Yellow-green } else if (healthPercent > 0.25) { self.healthBarFill.tint = 0xFFFF00; // Yellow } else if (healthPercent > 0.1) { self.healthBarFill.tint = 0xFF7F00; // Orange } else { self.healthBarFill.tint = 0xFF0000; // Red } // Add pulsing effect when health is critical if (healthPercent <= 0.25) { tween(self.healthBarFill, { alpha: 0.5 }, { duration: 500, loop: true, pingpong: true }); } else { tween.stop(self.healthBarFill, { alpha: true }); self.healthBarFill.alpha = 1.0; } }; // Initialize health bar self.updateHealthBar(); // Flash when hit self.flash = function () { tween(heroSprite, { tint: 0xffffff }, { duration: 100, onFinish: function onFinish() { tween(heroSprite, { tint: 0xd83318 }, { duration: 200 }); } }); }; // Take damage self.takeDamage = function () { if (self.invincible) return; self.health -= 1; self.flash(); self.invincible = true; self.invincibleTimer = 60; // 1 second at 60fps LK.effects.flashObject(self, 0xff0000, 300); self.updateHealthBar(); // Create blood flow effect for (var b = 0; b < 8; b++) { var bloodDrop = LK.getAsset('trap', { width: 12 + Math.random() * 8, height: 12 + Math.random() * 8, anchorX: 0.5, anchorY: 0.5 }); bloodDrop.tint = 0xAA0000; // Dark red blood color bloodDrop.x = self.x + (Math.random() - 0.5) * 60; // Random spread around hero bloodDrop.y = self.y + (Math.random() - 0.5) * 40; bloodDrop.alpha = 0.8 + Math.random() * 0.2; game.addChild(bloodDrop); // Animate blood drop falling and fading var fallDistance = 80 + Math.random() * 120; var fallDuration = 800 + Math.random() * 400; tween(bloodDrop, { y: bloodDrop.y + fallDistance, alpha: 0, scaleX: 0.3, scaleY: 0.3 }, { duration: fallDuration, easing: tween.easeIn, onFinish: function onFinish() { bloodDrop.destroy(); } }); } if (self.health <= 0) { LK.effects.flashScreen(0xff0000, 800); // Create dungeon-themed game over overlay var dungeonGameOverOverlay = new Container(); dungeonGameOverOverlay.zIndex = 10000; // ensure on top // Dark background var background = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); background.tint = 0x000000; background.alpha = 0.8; dungeonGameOverOverlay.addChild(background); // Main message var gameOverText = new Text2("You couldn't pass the dungeon...", { size: 120, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); gameOverText.anchor.set(0.5, 0.5); gameOverText.x = 2048 / 2; gameOverText.y = 1200; dungeonGameOverOverlay.addChild(gameOverText); // Subtitle var subtitleText = new Text2("The darkness consumed you in Room " + currentRoom, { size: 80, fill: 0x888888 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1400; dungeonGameOverOverlay.addChild(subtitleText); // Score display var finalScoreText = new Text2("Monsters Defeated: " + monstersDefeated, { size: 70, fill: 0xFFFF88 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 1600; dungeonGameOverOverlay.addChild(finalScoreText); game.addChild(dungeonGameOverOverlay); // Auto restart after 3 seconds LK.setTimeout(function () { LK.showGameOver(); }, 3000); } }; // Heal self.heal = function () { if (self.health < self.maxHealth) { self.health += 1; self.updateHealthBar(); LK.effects.flashObject(self, 0x83de44, 300); } }; // Called every tick self.update = function () { if (self.invincible) { self.invincibleTimer--; if (self.invincibleTimer <= 0) { self.invincible = false; } } }; return self; }); /**** * Asset Initialization ****/ // Hero: red box // Monster: green ellipse // Trap: yellow box // Treasure: blue ellipse // Door: purple box // Key class var Key = Container.expand(function () { var self = Container.call(this); // Attach key asset (gold ellipse) var keySprite = self.attachAsset('key', { anchorX: 0.5, anchorY: 0.5 }); self.width = keySprite.width; self.height = keySprite.height; return self; }); // Monster class var Monster = Container.expand(function () { var self = Container.call(this); // Attach monster asset (green ellipse) var monsterSprite = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Set larger hitbox for easier collision detection self.width = monsterSprite.width * 0.4; self.height = monsterSprite.height * 0.4; // Monster health system self.maxHealth = 1; self.health = self.maxHealth; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; // Red heart color heart.x = startX + h * heartSpacing; heart.y = -monsterSprite.height / 2 - 40; // Position above monster self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Initialize heart display self.updateHealthBar(); // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(monsterSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(monsterSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; self.speed = 1 + Math.random() * 1; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Called every tick self.update = function () { // Don't move if game is frozen if (gameFrozen) { return; } // Check if hero is close to this monster (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 200) { if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Tween plugin for animations // --- Start Screen Overlay --- var startScreenOverlay = new Container(); startScreenOverlay.zIndex = 10000; // ensure on top // Title var titleText = new Text2("Dungeon Dash", { size: 180, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 900; startScreenOverlay.addChild(titleText); // Subtitle var subtitleText = new Text2("Drag or tap to move. Collect treasures. Avoid monsters & traps!", { size: 70, fill: 0xB8B031 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1100; startScreenOverlay.addChild(subtitleText); // Play Button var playBtnWidth = 600; var playBtnHeight = 180; var playBtn = LK.getAsset('door', { width: playBtnWidth, height: playBtnHeight, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1500 }); startScreenOverlay.addChild(playBtn); var playBtnText = new Text2("PLAY", { size: 120, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); playBtnText.anchor.set(0.5, 0.5); playBtnText.x = 2048 / 2; playBtnText.y = 1500; startScreenOverlay.addChild(playBtnText); // Add overlay to game game.addChild(startScreenOverlay); // Block game input until started var gameStarted = false; // Start game handler function startGame() { if (gameStarted) return; gameStarted = true; startScreenOverlay.visible = false; } // Listen for tap/click on play button startScreenOverlay.down = function (x, y, obj) { // Check if tap is inside play button var local = playBtn.toLocal(startScreenOverlay.toGlobal({ x: x, y: y })); if (local.x > -playBtnWidth / 2 && local.x < playBtnWidth / 2 && local.y > -playBtnHeight / 2 && local.y < playBtnHeight / 2) { startGame(); } }; // Intercept all input until game started var oldGameDown = game.down; game.down = function (x, y, obj) { if (!gameStarted) { if (typeof startScreenOverlay.down === "function") startScreenOverlay.down(x, y, obj); return; } if (typeof oldGameDown === "function") oldGameDown(x, y, obj); }; var oldGameMove = game.move; game.move = function (x, y, obj) { if (!gameStarted) return; if (typeof oldGameMove === "function") oldGameMove(x, y, obj); }; var oldGameUp = game.up; game.up = function (x, y, obj) { if (!gameStarted) return; if (typeof oldGameUp === "function") oldGameUp(x, y, obj); }; // Room bounds (centered, with margin) var roomMargin = 120; var roomBounds = { x: roomMargin, y: roomMargin + 100, // leave top 100px for menu width: 2048 - roomMargin * 2, height: 2732 - roomMargin * 2 - 100 }; // Game state var hero; var monsters = []; var door; var keys = []; var chests = []; var hasKey = false; var draggingHero = false; var lastMoveX = 0, lastMoveY = 0; var currentRoom = 1; var maxRooms = 5; var monstersDefeated = 0; var roomCleared = false; var gameFrozen = false; var freezeTimer = 0; var FREEZE_DURATION = 120; // 2 seconds at 60fps // GUI elements var scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper to update health display - now updates the health bar on the character function updateHealthDisplay() { // This will be handled by the health bar on the character } // Helper to update score display function updateScoreDisplay() { var score = monstersDefeated * 5; scoreTxt.setText('Score: ' + score); } // Helper to clear room function clearRoom() { for (var i = 0; i < monsters.length; i++) monsters[i].destroy(); for (var i = 0; i < keys.length; i++) keys[i].destroy(); for (var i = 0; i < chests.length; i++) chests[i].destroy(); monsters = []; keys = []; chests = []; if (door) { door.destroy(); door = null; } roomCleared = false; hasKey = false; } // Helper to generate a room function generateRoom(roomNum) { clearRoom(); // Place hero at entrance (bottom center) hero.x = roomBounds.x + roomBounds.width / 2; hero.y = roomBounds.y + roomBounds.height - hero.height; // Helper function to check if position is safe from hero spawn area function isPositionSafeFromHeroSpawn(x, y, heroStartX, heroStartY, safeDistance) { var dx = x - heroStartX; var dy = y - heroStartY; var dist = Math.sqrt(dx * dx + dy * dy); return dist > safeDistance; } // Place monsters var monsterCount = 2 + roomNum; var heroStartX = roomBounds.x + roomBounds.width / 2; var heroStartY = roomBounds.y + roomBounds.height - hero.height; var heroSafeDistance = 400; // Increased safe distance around hero spawn for (var i = 0; i < monsterCount; i++) { var m = new Monster(); var validPosition = false; var attempts = 0; // Try to find a position that's not too close to hero spawn while (!validPosition && attempts < 50) { m.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200); m.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400); if (isPositionSafeFromHeroSpawn(m.x, m.y, heroStartX, heroStartY, heroSafeDistance)) { validPosition = true; } attempts++; } monsters.push(m); game.addChild(m); } // Key will come from chest after killing all monsters, so don't place it randomly // Place chest (always one per room) var c = new Chest(); var validPosition = false; var attempts = 0; while (!validPosition && attempts < 50) { c.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200); c.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400); if (isPositionSafeFromHeroSpawn(c.x, c.y, heroStartX, heroStartY, heroSafeDistance)) { validPosition = true; } attempts++; } chests.push(c); game.addChild(c); // Place door (top center) door = new Door(); door.x = roomBounds.x + roomBounds.width / 2; door.y = roomBounds.y + door.height / 2; game.addChild(door); door.visible = false; // Only show when room is cleared roomCleared = false; } // Create hero hero = new Hero(); game.addChild(hero); // Start first room generateRoom(currentRoom); updateScoreDisplay(); // Dragging logic game.down = function (x, y, obj) { // Only start drag if touch is on hero var local = hero.toLocal(game.toGlobal({ x: x, y: y })); if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) { draggingHero = true; lastMoveX = x; lastMoveY = y; } }; game.up = function (x, y, obj) { draggingHero = false; }; function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Walk-to destination variables var walkToActive = false; var walkToX = 0; var walkToY = 0; var walkToSpeed = 22; // Attack button for attacking monsters - round dungeon-themed button var killButton = LK.getAsset('chest', { width: 220, height: 220, anchorX: 0.5, anchorY: 0.5, x: 2048 - 150, y: 2732 - 150 }); killButton.tint = 0x8B4513; // Dark brown dungeon color game.addChild(killButton); var killButtonText = new Text2("ATTACK", { size: 50, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); killButtonText.anchor.set(0.5, 0.5); killButtonText.x = 2048 - 150; killButtonText.y = 2732 - 150; game.addChild(killButtonText); // Open chest text var openChestText = new Text2("OPEN CHEST", { size: 80, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); openChestText.anchor.set(0.5, 0.5); openChestText.x = 2048 / 2; openChestText.y = 2732 - 200; openChestText.visible = false; game.addChild(openChestText); // Sword swing state management var swordSwinging = false; var swordCooldown = 0; var SWORD_COOLDOWN_TIME = 30; // frames (0.5 seconds at 60fps) // Move handler game.move = function (x, y, obj) { if (draggingHero) { // Clamp hero inside room bounds var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2); var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2); hero.x = hx; hero.y = hy; walkToActive = false; // Cancel walk-to if dragging } }; // Tap-to-walk logic: on down, if not on hero, set walk-to destination var oldGameDown = game.down; game.down = function (x, y, obj) { // Only start drag if touch is on hero var local = hero.toLocal(game.toGlobal({ x: x, y: y })); // Check if kill button was tapped var killButtonLocal = killButton.toLocal(game.toGlobal({ x: x, y: y })); if (killButtonLocal.x > -killButton.width / 2 && killButtonLocal.x < killButton.width / 2 && killButtonLocal.y > -killButton.height / 2 && killButtonLocal.y < killButton.height / 2) { // Check if sword is available (not swinging and cooldown expired) if (!swordSwinging && swordCooldown <= 0) { // Get sword sprite from hero for animation var swordSprite = hero.children[1]; // Sword is the second child (index 1) // Stop any existing tweens on the sword tween.stop(swordSprite, { rotation: true }); // Set swinging state swordSwinging = true; swordCooldown = SWORD_COOLDOWN_TIME; // Create sword swing effect var originalRotation = swordSprite.rotation; tween(swordSprite, { rotation: originalRotation + Math.PI / 2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(swordSprite, { rotation: originalRotation }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { swordSwinging = false; } }); } }); // Kill button tapped - damage all monsters within 350px of hero for (var i = monsters.length - 1; i >= 0; i--) { var m = monsters[i]; var dx = hero.x - m.x; var dy = hero.y - m.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 350) { // Deal damage to monster m.takeDamage(); // Optional: flash hero for feedback LK.effects.flashObject(hero, 0xffffff, 120); // Check if monster is dead if (m.health <= 0) { // Remove monster m.destroy(); monsters.splice(i, 1); monstersDefeated++; updateScoreDisplay(); } // Unfreeze game when monster dies if (gameFrozen) { gameFrozen = false; freezeTimer = 0; // Remove freeze visual effects tween(hero, { tint: 0xffffff }, { duration: 300 }); for (var j = 0; j < monsters.length; j++) { tween(monsters[j], { tint: 0xffffff }, { duration: 300 }); } } } } } return; // Don't process other touch logic } if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) { draggingHero = true; lastMoveX = x; lastMoveY = y; walkToActive = false; } else { // Set walk-to destination if tap is inside room var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2); var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2); walkToX = hx; walkToY = hy; walkToActive = true; } if (typeof oldGameDown === "function") oldGameDown(x, y, obj); }; // In update, move hero toward walk-to destination if active var oldGameUpdate = game.update; game.update = function () { // Walk-to logic if (walkToActive && !draggingHero) { var dx = walkToX - hero.x; var dy = walkToY - hero.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > walkToSpeed) { hero.x += dx / dist * walkToSpeed; hero.y += dy / dist * walkToSpeed; } else { hero.x = walkToX; hero.y = walkToY; walkToActive = false; } } if (typeof oldGameUpdate === "function") oldGameUpdate(); }; // Main update loop game.update = function () { if (!gameStarted) return; // Handle freeze timer if (gameFrozen) { freezeTimer--; if (freezeTimer <= 0) { // Unfreeze game gameFrozen = false; // Remove freeze visual effects tween(hero, { tint: 0xffffff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { tween(monsters[m], { tint: 0xffffff }, { duration: 300 }); } } } // Update sword cooldown if (swordCooldown > 0) { swordCooldown--; } // Update hero (only if not frozen) if (!gameFrozen) { hero.update(); } // Update monsters for (var i = 0; i < monsters.length; i++) { monsters[i].update(); } // Check if hero is close to any monster to show/hide kill button var nearMonster = false; for (var i = 0; i < monsters.length; i++) { var m = monsters[i]; var dx = hero.x - m.x; var dy = hero.y - m.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 350) { nearMonster = true; break; } } // Show/hide kill button based on proximity killButton.visible = nearMonster; killButtonText.visible = nearMonster; // Check if hero is close to chest and all monsters are defeated var nearChest = false; if (monsters.length === 0) { for (var i = 0; i < chests.length; i++) { var c = chests[i]; if (!c.opened) { var dx = hero.x - c.x; var dy = hero.y - c.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { nearChest = true; break; } } } } // Show/hide open chest text based on proximity and game state openChestText.visible = nearChest; // Check collisions: hero vs monsters for (var i = monsters.length - 1; i >= 0; i--) { var m = monsters[i]; if (hero.intersects(m)) { hero.takeDamage(); // Knockback var dx = hero.x - m.x; var dy = hero.y - m.y; var dist = Math.sqrt(dx * dx + dy * dy) || 1; hero.x += dx / dist * 60; hero.y += dy / dist * 60; // Hero can only kill monsters with the kill button, not by collision } } // Check collisions: hero vs keys for (var i = keys.length - 1; i >= 0; i--) { var k = keys[i]; if (hero.intersects(k)) { k.destroy(); keys.splice(i, 1); hasKey = true; LK.effects.flashObject(hero, 0xFFD700, 300); } } // Check collisions: hero vs chests for (var i = 0; i < chests.length; i++) { var c = chests[i]; if (hero.intersects(c) && monsters.length === 0 && !c.opened) { c.opened = true; LK.effects.flashObject(c, 0x83de44, 600); updateScoreDisplay(); // Spawn key from chest after all monsters are defeated var k = new Key(); k.x = c.x; k.y = c.y; keys.push(k); game.addChild(k); } } // Room clear check - need to defeat all monsters first, then open chest, then collect key if (!roomCleared && monsters.length === 0 && chests.length > 0 && chests[0].opened && hasKey) { roomCleared = true; door.visible = true; LK.effects.flashObject(door, 0x83de44, 600); } // Check hero at door to next room if (roomCleared && door && hero.intersects(door)) { // Revive hero when passing through door if (hero.health < hero.maxHealth) { hero.health = hero.maxHealth; hero.updateHealthBar(); LK.effects.flashObject(hero, 0x00FF00, 500); // Green flash for healing } if (currentRoom < maxRooms) { currentRoom++; generateRoom(currentRoom); } else { // Win! LK.setScore(monstersDefeated * 5); LK.showYouWin(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Chest class
var Chest = Container.expand(function () {
var self = Container.call(this);
// Attach chest asset (brown box)
var chestSprite = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = chestSprite.width;
self.height = chestSprite.height;
self.opened = false;
return self;
});
// Door class (for next room)
var Door = Container.expand(function () {
var self = Container.call(this);
// Attach door asset (purple box)
var doorSprite = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = doorSprite.width;
self.height = doorSprite.height;
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
// Attach hero asset (red box)
var heroSprite = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = heroSprite.width;
self.height = heroSprite.height;
// Attach sword to hero
var swordSprite = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
swordSprite.x = -heroSprite.width * 0.3; // Position sword to the left of hero
swordSprite.y = 0; // Center vertically with hero
swordSprite.rotation = -Math.PI / 4; // Angle the sword pointing left
// Hero stats
self.maxHealth = 3;
self.health = self.maxHealth;
self.invincible = false;
self.invincibleTimer = 0;
// Create health bar above character
// Health bar border (white outline)
self.healthBarBorder = LK.getAsset('trap', {
width: 200,
height: 40,
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBorder.tint = 0xFFFFFF; // White border
self.healthBarBorder.x = 0;
self.healthBarBorder.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarBorder);
// Health bar background (dark)
self.healthBarBackground = LK.getAsset('trap', {
width: 180,
height: 24,
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBackground.tint = 0x222222; // Dark gray background
self.healthBarBackground.x = 0;
self.healthBarBackground.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarBackground);
// Health bar fill (green to red gradient)
self.healthBarFill = LK.getAsset('trap', {
width: 180,
height: 24,
anchorX: 0.0,
anchorY: 0.5
});
self.healthBarFill.tint = 0x00FF00; // Green for full health
self.healthBarFill.x = -90; // Left-aligned within background
self.healthBarFill.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarFill);
// Update health bar display
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.scaleX = healthPercent;
// Smooth color transition based on health percentage
if (healthPercent > 0.75) {
self.healthBarFill.tint = 0x00FF00; // Bright green
} else if (healthPercent > 0.5) {
self.healthBarFill.tint = 0x7FFF00; // Yellow-green
} else if (healthPercent > 0.25) {
self.healthBarFill.tint = 0xFFFF00; // Yellow
} else if (healthPercent > 0.1) {
self.healthBarFill.tint = 0xFF7F00; // Orange
} else {
self.healthBarFill.tint = 0xFF0000; // Red
}
// Add pulsing effect when health is critical
if (healthPercent <= 0.25) {
tween(self.healthBarFill, {
alpha: 0.5
}, {
duration: 500,
loop: true,
pingpong: true
});
} else {
tween.stop(self.healthBarFill, {
alpha: true
});
self.healthBarFill.alpha = 1.0;
}
};
// Initialize health bar
self.updateHealthBar();
// Flash when hit
self.flash = function () {
tween(heroSprite, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(heroSprite, {
tint: 0xd83318
}, {
duration: 200
});
}
});
};
// Take damage
self.takeDamage = function () {
if (self.invincible) return;
self.health -= 1;
self.flash();
self.invincible = true;
self.invincibleTimer = 60; // 1 second at 60fps
LK.effects.flashObject(self, 0xff0000, 300);
self.updateHealthBar();
// Create blood flow effect
for (var b = 0; b < 8; b++) {
var bloodDrop = LK.getAsset('trap', {
width: 12 + Math.random() * 8,
height: 12 + Math.random() * 8,
anchorX: 0.5,
anchorY: 0.5
});
bloodDrop.tint = 0xAA0000; // Dark red blood color
bloodDrop.x = self.x + (Math.random() - 0.5) * 60; // Random spread around hero
bloodDrop.y = self.y + (Math.random() - 0.5) * 40;
bloodDrop.alpha = 0.8 + Math.random() * 0.2;
game.addChild(bloodDrop);
// Animate blood drop falling and fading
var fallDistance = 80 + Math.random() * 120;
var fallDuration = 800 + Math.random() * 400;
tween(bloodDrop, {
y: bloodDrop.y + fallDistance,
alpha: 0,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: fallDuration,
easing: tween.easeIn,
onFinish: function onFinish() {
bloodDrop.destroy();
}
});
}
if (self.health <= 0) {
LK.effects.flashScreen(0xff0000, 800);
// Create dungeon-themed game over overlay
var dungeonGameOverOverlay = new Container();
dungeonGameOverOverlay.zIndex = 10000; // ensure on top
// Dark background
var background = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.tint = 0x000000;
background.alpha = 0.8;
dungeonGameOverOverlay.addChild(background);
// Main message
var gameOverText = new Text2("You couldn't pass the dungeon...", {
size: 120,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 1200;
dungeonGameOverOverlay.addChild(gameOverText);
// Subtitle
var subtitleText = new Text2("The darkness consumed you in Room " + currentRoom, {
size: 80,
fill: 0x888888
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1400;
dungeonGameOverOverlay.addChild(subtitleText);
// Score display
var finalScoreText = new Text2("Monsters Defeated: " + monstersDefeated, {
size: 70,
fill: 0xFFFF88
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 1600;
dungeonGameOverOverlay.addChild(finalScoreText);
game.addChild(dungeonGameOverOverlay);
// Auto restart after 3 seconds
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
}
};
// Heal
self.heal = function () {
if (self.health < self.maxHealth) {
self.health += 1;
self.updateHealthBar();
LK.effects.flashObject(self, 0x83de44, 300);
}
};
// Called every tick
self.update = function () {
if (self.invincible) {
self.invincibleTimer--;
if (self.invincibleTimer <= 0) {
self.invincible = false;
}
}
};
return self;
});
/****
* Asset Initialization
****/
// Hero: red box
// Monster: green ellipse
// Trap: yellow box
// Treasure: blue ellipse
// Door: purple box
// Key class
var Key = Container.expand(function () {
var self = Container.call(this);
// Attach key asset (gold ellipse)
var keySprite = self.attachAsset('key', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = keySprite.width;
self.height = keySprite.height;
return self;
});
// Monster class
var Monster = Container.expand(function () {
var self = Container.call(this);
// Attach monster asset (green ellipse)
var monsterSprite = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Set larger hitbox for easier collision detection
self.width = monsterSprite.width * 0.4;
self.height = monsterSprite.height * 0.4;
// Monster health system
self.maxHealth = 1;
self.health = self.maxHealth;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744; // Red heart color
heart.x = startX + h * heartSpacing;
heart.y = -monsterSprite.height / 2 - 40; // Position above monster
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Initialize heart display
self.updateHealthBar();
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(monsterSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(monsterSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
self.speed = 1 + Math.random() * 1;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Called every tick
self.update = function () {
// Don't move if game is frozen
if (gameFrozen) {
return;
}
// Check if hero is close to this monster (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 200) {
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Tween plugin for animations
// --- Start Screen Overlay ---
var startScreenOverlay = new Container();
startScreenOverlay.zIndex = 10000; // ensure on top
// Title
var titleText = new Text2("Dungeon Dash", {
size: 180,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 900;
startScreenOverlay.addChild(titleText);
// Subtitle
var subtitleText = new Text2("Drag or tap to move. Collect treasures. Avoid monsters & traps!", {
size: 70,
fill: 0xB8B031
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1100;
startScreenOverlay.addChild(subtitleText);
// Play Button
var playBtnWidth = 600;
var playBtnHeight = 180;
var playBtn = LK.getAsset('door', {
width: playBtnWidth,
height: playBtnHeight,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1500
});
startScreenOverlay.addChild(playBtn);
var playBtnText = new Text2("PLAY", {
size: 120,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
playBtnText.anchor.set(0.5, 0.5);
playBtnText.x = 2048 / 2;
playBtnText.y = 1500;
startScreenOverlay.addChild(playBtnText);
// Add overlay to game
game.addChild(startScreenOverlay);
// Block game input until started
var gameStarted = false;
// Start game handler
function startGame() {
if (gameStarted) return;
gameStarted = true;
startScreenOverlay.visible = false;
}
// Listen for tap/click on play button
startScreenOverlay.down = function (x, y, obj) {
// Check if tap is inside play button
var local = playBtn.toLocal(startScreenOverlay.toGlobal({
x: x,
y: y
}));
if (local.x > -playBtnWidth / 2 && local.x < playBtnWidth / 2 && local.y > -playBtnHeight / 2 && local.y < playBtnHeight / 2) {
startGame();
}
};
// Intercept all input until game started
var oldGameDown = game.down;
game.down = function (x, y, obj) {
if (!gameStarted) {
if (typeof startScreenOverlay.down === "function") startScreenOverlay.down(x, y, obj);
return;
}
if (typeof oldGameDown === "function") oldGameDown(x, y, obj);
};
var oldGameMove = game.move;
game.move = function (x, y, obj) {
if (!gameStarted) return;
if (typeof oldGameMove === "function") oldGameMove(x, y, obj);
};
var oldGameUp = game.up;
game.up = function (x, y, obj) {
if (!gameStarted) return;
if (typeof oldGameUp === "function") oldGameUp(x, y, obj);
};
// Room bounds (centered, with margin)
var roomMargin = 120;
var roomBounds = {
x: roomMargin,
y: roomMargin + 100,
// leave top 100px for menu
width: 2048 - roomMargin * 2,
height: 2732 - roomMargin * 2 - 100
};
// Game state
var hero;
var monsters = [];
var door;
var keys = [];
var chests = [];
var hasKey = false;
var draggingHero = false;
var lastMoveX = 0,
lastMoveY = 0;
var currentRoom = 1;
var maxRooms = 5;
var monstersDefeated = 0;
var roomCleared = false;
var gameFrozen = false;
var freezeTimer = 0;
var FREEZE_DURATION = 120; // 2 seconds at 60fps
// GUI elements
var scoreTxt = new Text2('Score: 0', {
size: 90,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper to update health display - now updates the health bar on the character
function updateHealthDisplay() {
// This will be handled by the health bar on the character
}
// Helper to update score display
function updateScoreDisplay() {
var score = monstersDefeated * 5;
scoreTxt.setText('Score: ' + score);
}
// Helper to clear room
function clearRoom() {
for (var i = 0; i < monsters.length; i++) monsters[i].destroy();
for (var i = 0; i < keys.length; i++) keys[i].destroy();
for (var i = 0; i < chests.length; i++) chests[i].destroy();
monsters = [];
keys = [];
chests = [];
if (door) {
door.destroy();
door = null;
}
roomCleared = false;
hasKey = false;
}
// Helper to generate a room
function generateRoom(roomNum) {
clearRoom();
// Place hero at entrance (bottom center)
hero.x = roomBounds.x + roomBounds.width / 2;
hero.y = roomBounds.y + roomBounds.height - hero.height;
// Helper function to check if position is safe from hero spawn area
function isPositionSafeFromHeroSpawn(x, y, heroStartX, heroStartY, safeDistance) {
var dx = x - heroStartX;
var dy = y - heroStartY;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist > safeDistance;
}
// Place monsters
var monsterCount = 2 + roomNum;
var heroStartX = roomBounds.x + roomBounds.width / 2;
var heroStartY = roomBounds.y + roomBounds.height - hero.height;
var heroSafeDistance = 400; // Increased safe distance around hero spawn
for (var i = 0; i < monsterCount; i++) {
var m = new Monster();
var validPosition = false;
var attempts = 0;
// Try to find a position that's not too close to hero spawn
while (!validPosition && attempts < 50) {
m.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200);
m.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400);
if (isPositionSafeFromHeroSpawn(m.x, m.y, heroStartX, heroStartY, heroSafeDistance)) {
validPosition = true;
}
attempts++;
}
monsters.push(m);
game.addChild(m);
}
// Key will come from chest after killing all monsters, so don't place it randomly
// Place chest (always one per room)
var c = new Chest();
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 50) {
c.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200);
c.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400);
if (isPositionSafeFromHeroSpawn(c.x, c.y, heroStartX, heroStartY, heroSafeDistance)) {
validPosition = true;
}
attempts++;
}
chests.push(c);
game.addChild(c);
// Place door (top center)
door = new Door();
door.x = roomBounds.x + roomBounds.width / 2;
door.y = roomBounds.y + door.height / 2;
game.addChild(door);
door.visible = false; // Only show when room is cleared
roomCleared = false;
}
// Create hero
hero = new Hero();
game.addChild(hero);
// Start first room
generateRoom(currentRoom);
updateScoreDisplay();
// Dragging logic
game.down = function (x, y, obj) {
// Only start drag if touch is on hero
var local = hero.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) {
draggingHero = true;
lastMoveX = x;
lastMoveY = y;
}
};
game.up = function (x, y, obj) {
draggingHero = false;
};
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Walk-to destination variables
var walkToActive = false;
var walkToX = 0;
var walkToY = 0;
var walkToSpeed = 22;
// Attack button for attacking monsters - round dungeon-themed button
var killButton = LK.getAsset('chest', {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 150,
y: 2732 - 150
});
killButton.tint = 0x8B4513; // Dark brown dungeon color
game.addChild(killButton);
var killButtonText = new Text2("ATTACK", {
size: 50,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
killButtonText.anchor.set(0.5, 0.5);
killButtonText.x = 2048 - 150;
killButtonText.y = 2732 - 150;
game.addChild(killButtonText);
// Open chest text
var openChestText = new Text2("OPEN CHEST", {
size: 80,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
openChestText.anchor.set(0.5, 0.5);
openChestText.x = 2048 / 2;
openChestText.y = 2732 - 200;
openChestText.visible = false;
game.addChild(openChestText);
// Sword swing state management
var swordSwinging = false;
var swordCooldown = 0;
var SWORD_COOLDOWN_TIME = 30; // frames (0.5 seconds at 60fps)
// Move handler
game.move = function (x, y, obj) {
if (draggingHero) {
// Clamp hero inside room bounds
var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2);
var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2);
hero.x = hx;
hero.y = hy;
walkToActive = false; // Cancel walk-to if dragging
}
};
// Tap-to-walk logic: on down, if not on hero, set walk-to destination
var oldGameDown = game.down;
game.down = function (x, y, obj) {
// Only start drag if touch is on hero
var local = hero.toLocal(game.toGlobal({
x: x,
y: y
}));
// Check if kill button was tapped
var killButtonLocal = killButton.toLocal(game.toGlobal({
x: x,
y: y
}));
if (killButtonLocal.x > -killButton.width / 2 && killButtonLocal.x < killButton.width / 2 && killButtonLocal.y > -killButton.height / 2 && killButtonLocal.y < killButton.height / 2) {
// Check if sword is available (not swinging and cooldown expired)
if (!swordSwinging && swordCooldown <= 0) {
// Get sword sprite from hero for animation
var swordSprite = hero.children[1]; // Sword is the second child (index 1)
// Stop any existing tweens on the sword
tween.stop(swordSprite, {
rotation: true
});
// Set swinging state
swordSwinging = true;
swordCooldown = SWORD_COOLDOWN_TIME;
// Create sword swing effect
var originalRotation = swordSprite.rotation;
tween(swordSprite, {
rotation: originalRotation + Math.PI / 2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(swordSprite, {
rotation: originalRotation
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
swordSwinging = false;
}
});
}
});
// Kill button tapped - damage all monsters within 350px of hero
for (var i = monsters.length - 1; i >= 0; i--) {
var m = monsters[i];
var dx = hero.x - m.x;
var dy = hero.y - m.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 350) {
// Deal damage to monster
m.takeDamage();
// Optional: flash hero for feedback
LK.effects.flashObject(hero, 0xffffff, 120);
// Check if monster is dead
if (m.health <= 0) {
// Remove monster
m.destroy();
monsters.splice(i, 1);
monstersDefeated++;
updateScoreDisplay();
}
// Unfreeze game when monster dies
if (gameFrozen) {
gameFrozen = false;
freezeTimer = 0;
// Remove freeze visual effects
tween(hero, {
tint: 0xffffff
}, {
duration: 300
});
for (var j = 0; j < monsters.length; j++) {
tween(monsters[j], {
tint: 0xffffff
}, {
duration: 300
});
}
}
}
}
}
return; // Don't process other touch logic
}
if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) {
draggingHero = true;
lastMoveX = x;
lastMoveY = y;
walkToActive = false;
} else {
// Set walk-to destination if tap is inside room
var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2);
var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2);
walkToX = hx;
walkToY = hy;
walkToActive = true;
}
if (typeof oldGameDown === "function") oldGameDown(x, y, obj);
};
// In update, move hero toward walk-to destination if active
var oldGameUpdate = game.update;
game.update = function () {
// Walk-to logic
if (walkToActive && !draggingHero) {
var dx = walkToX - hero.x;
var dy = walkToY - hero.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > walkToSpeed) {
hero.x += dx / dist * walkToSpeed;
hero.y += dy / dist * walkToSpeed;
} else {
hero.x = walkToX;
hero.y = walkToY;
walkToActive = false;
}
}
if (typeof oldGameUpdate === "function") oldGameUpdate();
};
// Main update loop
game.update = function () {
if (!gameStarted) return;
// Handle freeze timer
if (gameFrozen) {
freezeTimer--;
if (freezeTimer <= 0) {
// Unfreeze game
gameFrozen = false;
// Remove freeze visual effects
tween(hero, {
tint: 0xffffff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
tween(monsters[m], {
tint: 0xffffff
}, {
duration: 300
});
}
}
}
// Update sword cooldown
if (swordCooldown > 0) {
swordCooldown--;
}
// Update hero (only if not frozen)
if (!gameFrozen) {
hero.update();
}
// Update monsters
for (var i = 0; i < monsters.length; i++) {
monsters[i].update();
}
// Check if hero is close to any monster to show/hide kill button
var nearMonster = false;
for (var i = 0; i < monsters.length; i++) {
var m = monsters[i];
var dx = hero.x - m.x;
var dy = hero.y - m.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 350) {
nearMonster = true;
break;
}
}
// Show/hide kill button based on proximity
killButton.visible = nearMonster;
killButtonText.visible = nearMonster;
// Check if hero is close to chest and all monsters are defeated
var nearChest = false;
if (monsters.length === 0) {
for (var i = 0; i < chests.length; i++) {
var c = chests[i];
if (!c.opened) {
var dx = hero.x - c.x;
var dy = hero.y - c.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
nearChest = true;
break;
}
}
}
}
// Show/hide open chest text based on proximity and game state
openChestText.visible = nearChest;
// Check collisions: hero vs monsters
for (var i = monsters.length - 1; i >= 0; i--) {
var m = monsters[i];
if (hero.intersects(m)) {
hero.takeDamage();
// Knockback
var dx = hero.x - m.x;
var dy = hero.y - m.y;
var dist = Math.sqrt(dx * dx + dy * dy) || 1;
hero.x += dx / dist * 60;
hero.y += dy / dist * 60;
// Hero can only kill monsters with the kill button, not by collision
}
}
// Check collisions: hero vs keys
for (var i = keys.length - 1; i >= 0; i--) {
var k = keys[i];
if (hero.intersects(k)) {
k.destroy();
keys.splice(i, 1);
hasKey = true;
LK.effects.flashObject(hero, 0xFFD700, 300);
}
}
// Check collisions: hero vs chests
for (var i = 0; i < chests.length; i++) {
var c = chests[i];
if (hero.intersects(c) && monsters.length === 0 && !c.opened) {
c.opened = true;
LK.effects.flashObject(c, 0x83de44, 600);
updateScoreDisplay();
// Spawn key from chest after all monsters are defeated
var k = new Key();
k.x = c.x;
k.y = c.y;
keys.push(k);
game.addChild(k);
}
}
// Room clear check - need to defeat all monsters first, then open chest, then collect key
if (!roomCleared && monsters.length === 0 && chests.length > 0 && chests[0].opened && hasKey) {
roomCleared = true;
door.visible = true;
LK.effects.flashObject(door, 0x83de44, 600);
}
// Check hero at door to next room
if (roomCleared && door && hero.intersects(door)) {
// Revive hero when passing through door
if (hero.health < hero.maxHealth) {
hero.health = hero.maxHealth;
hero.updateHealthBar();
LK.effects.flashObject(hero, 0x00FF00, 500); // Green flash for healing
}
if (currentRoom < maxRooms) {
currentRoom++;
generateRoom(currentRoom);
} else {
// Win!
LK.setScore(monstersDefeated * 5);
LK.showYouWin();
}
}
};
dungeon door. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
png
dungeon hero sword. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon key. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon chest. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon demon. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon wizard. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a mage who throws fireballs. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
big dungeon snake. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a spider. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat