User prompt
Update with: self.slideHitboxHeight = 200; // Make it taller self.slideHitboxYOffset = 150; // Move it much lower // Modify the getBounds method to account for sliding self.getBounds = function() { var bounds = { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2 }; if (self.isSliding) { // During slide, move hitbox much lower bounds.top = (self.y - self.slideHitboxHeight / 2) + self.slideHitboxYOffset; bounds.bottom = (self.y + self.slideHitboxHeight / 2) + self.slideHitboxYOffset; } else { bounds.top = self.y - self.hitboxHeight / 2; bounds.bottom = self.y + self.hitboxHeight / 2; } return bounds; };
User prompt
Update with: // Modify the getBounds method to account for sliding self.getBounds = function() { var bounds = { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2 }; if (self.isSliding) { // During slide, move hitbox down bounds.top = (self.y - self.slideHitboxHeight / 2) + self.slideHitboxYOffset; bounds.bottom = (self.y + self.slideHitboxHeight / 2) + self.slideHitboxYOffset; } else { bounds.top = self.y - self.hitboxHeight / 2; bounds.bottom = self.y + self.hitboxHeight / 2; } return bounds; };
User prompt
Update with: // In Player class properties, adjust slide hitbox self.slideHitboxHeight = 150; // Keep this height self.slideHitboxYOffset = 75; // Offset up from bottom of regular hitbox // Modify the getBounds method to account for sliding self.getBounds = function() { var bounds = { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2 }; if (self.isSliding) { // During slide, align hitbox with bottom of regular hitbox bounds.bottom = self.y + self.normalHitboxHeight / 2; bounds.top = bounds.bottom - self.slideHitboxHeight; } else { bounds.top = self.y - self.hitboxHeight / 2; bounds.bottom = self.y + self.hitboxHeight / 2; } return bounds; };
User prompt
Update with: CollectibleBehavior.standardUpdate = function(self) { if (self.collected) { return; } // Apply physics self.velocityY += 0.5; // gravity self.x += self.velocityX * gameSpeedMultiplier; self.y += self.velocityY; // Check for platform collision with bounce if (self.velocityY > 0) { GameUtils.checkPlatformCollision(self, 80, true); } // Check if off screen if (self.x < -50 || self.x > GAME_WIDTH + 50 || self.y > GAME_HEIGHT) { self.destroy(); return; } // Player collection detection - use slide hitbox height if sliding var playerBounds = { left: player.x - player.hitboxWidth / 2, right: player.x + player.hitboxWidth / 2, top: player.y - player.hitboxHeight / 2, bottom: player.y + player.hitboxHeight / 2 }; var itemBounds = { left: self.x - 25, right: self.x + 25, top: self.y - 25, bottom: self.y + 25 }; if (GameUtils.checkCollision(playerBounds, itemBounds)) { self.collect(); } };
User prompt
Update with: self.jump = function() { if (self.isSliding) { return; // Can't jump while sliding } if (self.isOnGround) { self.isJumping = true; self.isOnGround = false; self.velocityY = -self.jumpHeight; self.jumpState = "start"; self.jumpStartTime = Date.now(); LK.getSound('playerjump').play(); self.currentPlatform = null; } else if (self.isJumping && self.velocityY < 10) { // Small double-jump to reach higher platforms self.velocityY = -self.jumpHeight * 0.7; self.jumpStartTime = Date.now(); } };
User prompt
Update as needed with: self.updateGoblinNormalState = function() { // Move left with speed multiplier self.x -= self.speed * gameSpeedMultiplier; // ... rest of goblin update code };
User prompt
Update as needed with: // In Enemy class update methods self.updateEyeballNormalState = function() { self.x -= self.speed * gameSpeedMultiplier; // Modify speed here // ... rest of eyeball update code };
User prompt
Update as needed with: // Update collectible movement CollectibleBehavior.standardUpdate = function(self) { if (self.collected) { return; } // Apply physics self.velocityY += 0.5; // gravity self.x += self.velocityX * gameSpeedMultiplier; self.y += self.velocityY; // ... rest of update code };
User prompt
Update as needed with: Enemy.prototype.update = function() { // ... existing enemy update code self.x -= self.speed * gameSpeedMultiplier; // ... rest of update code };
User prompt
Update with: Enemy.prototype.update = function() { // ... existing enemy update code self.x -= self.speed * gameSpeedMultiplier; // ... rest of update code };
User prompt
Update with: Platform.prototype.update = function() { self.x -= PLATFORM_SPEED * gameSpeedMultiplier; if (self.x < -500) { self.destroy(); } };
User prompt
Update torch update with: child.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier;
User prompt
Update as needed with: function updateBackgrounds() { // Background layer (slowest) bg1.x -= PLATFORM_SPEED * 0.3 * gameSpeedMultiplier; bg2.x -= PLATFORM_SPEED * 0.3 * gameSpeedMultiplier; // ... rest of background updates mg1.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier; mg2.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier; // ... foreground updates fg1.x -= PLATFORM_SPEED * gameSpeedMultiplier; fg2.x -= PLATFORM_SPEED * gameSpeedMultiplier;
User prompt
Update with: if (self.slideTimer <= 0) { self.isSliding = false; self.hitboxHeight = self.normalHitboxHeight; self.lastSlideTime = Date.now(); // Reset speed multiplier instead of base speed gameSpeedMultiplier = 1.0; }
User prompt
Update with: self.slide = function() { var currentTime = Date.now(); if (!self.isSliding && self.isOnGround && !self.isJumping && !self.isAttacking && currentTime - self.lastSlideTime > self.slideCooldown) { self.isSliding = true; self.slideTimer = self.slideDuration; self.hitboxHeight = self.slideHitboxHeight; self.animationCounter = 0; // Adjust game speed multiplier instead of base speed gameSpeedMultiplier = self.slideSpeedMultiplier; } };
Code edit (1 edits merged)
Please save this source code
User prompt
Update as needed with: game.up = function(x, y, obj) { if (!gameStarted) { // ... existing title screen code return; } touchEndX = x; touchEndY = y; var deltaY = touchStartY - touchEndY; var deltaX = touchEndX - touchStartX; var currentTime = Date.now(); if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (deltaX > 50) { // Right swipe - trigger slide player.slide(); } } else if (deltaY > 50) { // Vertical swipe - existing jump code if (currentTime - lastJumpTime > JUMP_COOLDOWN) { player.jump(); lastJumpTime = currentTime; } } else { // Tap - existing attack code player.attack(); } };
Code edit (5 edits merged)
Please save this source code
User prompt
Update player with: if (self.isSliding) { self.updateSlideAnimation(); } else if (self.isAttacking) { // ... existing animation checks }
Code edit (1 edits merged)
Please save this source code
User prompt
When doing the score popup for the health potion, instead of using the heart icon, use the heart asset with the same red tint from the health display.
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: ScorePopup is not defined' in or related to this line: 'if (child instanceof ScorePopup) {' Line Number: 1763
Code edit (12 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.initGoblinSprites is not a function' in or related to this line: 'self.initGoblinSprites();' Line Number: 190
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Refactored classes - keeping original Container.expand pattern var Coin = Container.expand(function (type) { var self = Container.call(this); // Set type and create sprite self.type = type || 'coin'; self.sprite = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5, tint: 0xFFFFFF }); // Initialize physics properties CollectibleBehavior.initPhysics(self); // Get value based on type self.getValue = function () { switch (self.type) { case 'diamond': return 10; case 'emerald': return 5; case 'ruby': return 3; default: return 1; } }; // Collection functionality self.collect = function () { if (!self.collected) { self.collected = true; var value = self.getValue(); scoreManager.addScore(value, self.x, self.y); LK.getSound('coincollect').play(); self.destroy(); } }; // Standard update method using shared behavior self.update = function () { CollectibleBehavior.standardUpdate(self); }; self.checkPlatformCollision = function () { return GameUtils.checkPlatformCollision(self, 80, true) != null; }; return self; }); // Enemy class with refactored animation management var Enemy = Container.expand(function (type) { var self = Container.call(this); // Enemy properties self.type = type || 'basic'; self.speed = 7; self.isOnGround = true; self.velocityY = 0; self.currentPlatform = null; self.groundY = GAME_HEIGHT / 1.5; self.isHit = false; self.isDying = false; self.deathTimer = 0; self.throwBackSpeed = 15; self.throwBackDistance = 0; self.maxThrowBack = 200; // Hitbox properties self.hitboxWidth = 200; self.hitboxHeight = self.type === 'eyeball' ? 90 : 260; // Animation properties self.sprites = []; self.animationCounter = 0; self.animationSpeed = 0.08; // Initialize based on enemy type if (self.type === 'eyeball') { // Eyeball-specific properties self.isFlying = true; self.flyingHeight = 0; self.verticalSpeed = 2; self.maxVerticalSpeed = 4; self.homingDelay = 80; self.homingTimer = 0; self.flyFrame = 0; // Animation frames self.flyAnimation = ['eyefly1', 'eyefly2', 'eyefly3', 'eyefly4', 'eyefly5', 'eyefly6', 'eyefly7', 'eyefly8']; self.hitAnimation = ['eyedie1', 'eyedie2']; self.dieAnimation = ['eyedie3', 'eyedie4', 'eyedie5']; // Initialize animation sprites self.initEyeballSprites(); } else if (self.type === 'goblin') { // Goblin-specific properties self.runFrame = 0; // Animation frames self.runAnimation = ['goblinrun1', 'goblinrun2', 'goblinrun3', 'goblinrun4', 'goblinrun5', 'goblinrun6', 'goblinrun7', 'goblinrun8']; self.hitAnimation = ['goblinhit1']; self.dieAnimation = ['goblindie1', 'goblindie2', 'goblindie3', 'goblindie4']; // Initialize animation sprites self.initGoblinSprites(); } else { // Basic enemy var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); } // Initialize animation sprites for eyeball self.initEyeballSprites = function () { // Add fly animations for (var i = 0; i < self.flyAnimation.length; i++) { var sprite = self.attachAsset(self.flyAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } // Add hit animations for (var i = 0; i < self.hitAnimation.length; i++) { var sprite = self.attachAsset(self.hitAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Add die animations for (var i = 0; i < self.dieAnimation.length; i++) { var sprite = self.attachAsset(self.dieAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } }; // Initialize animation sprites for goblin self.initGoblinSprites = function () { // Add run animations for (var i = 0; i < self.runAnimation.length; i++) { var sprite = self.attachAsset(self.runAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } // Add hit animations for (var i = 0; i < self.hitAnimation.length; i++) { var sprite = self.attachAsset(self.hitAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Add die animations for (var i = 0; i < self.dieAnimation.length; i++) { var sprite = self.attachAsset(self.dieAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } }; // Hide all animation sprites self.hideAllSprites = function () { for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } }; // Update eyeball hit/die animation self.updateEyeballDamageState = function () { if (self.isHit) { var hitOffset = self.flyAnimation.length; self.throwBackDistance += Math.abs(self.throwBackSpeed); self.x += self.throwBackSpeed; self.throwBackSpeed *= 0.95; // Show hit animation var hitFrame = Math.floor(self.hitTimer / 100) % 2; self.sprites[hitOffset + hitFrame].alpha = 1; self.hitTimer--; if (self.hitTimer <= 0) { self.isHit = false; self.isDying = true; self.deathTimer = 180; self.deathFrame = 0; } } else if (self.isDying) { var dieOffset = self.flyAnimation.length + self.hitAnimation.length; // Always check for ground collision during fall if (!self.isOnGround) { self.velocityY += 0.5; self.y += self.velocityY; } // Progress through death frames if (self.deathTimer > 120) { self.sprites[dieOffset].alpha = 1; // eyedie3 } else if (self.deathTimer > 60) { self.sprites[dieOffset + 1].alpha = 1; // eyedie4 } else { self.sprites[dieOffset + 2].alpha = 1; // eyedie5 } self.deathTimer--; if (self.deathTimer <= 0) { self.alpha -= 0.05; if (self.alpha <= 0) { self.destroy(); } } } }; // Update eyeball normal movement self.updateEyeballNormalState = function () { self.x -= self.speed; // Only start homing after delay if (self.homingTimer >= self.homingDelay) { // Home toward player var deltaY = player.y - self.y; self.velocityY += deltaY > 0 ? 0.2 : -0.2; self.velocityY = Math.max(-self.maxVerticalSpeed, Math.min(self.maxVerticalSpeed, self.velocityY)); } else { // Before homing, maintain height with slight wave motion self.velocityY = Math.sin(self.homingTimer * 0.05) * 2; self.homingTimer++; } self.y += self.velocityY; // Animate self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.flyFrame = (self.flyFrame + 1) % self.flyAnimation.length; } self.sprites[self.flyFrame].alpha = 1; }; // Update goblin hit animation self.updateGoblinHitState = function () { // Handle throw back motion self.x += self.throwBackSpeed; self.throwBackDistance += Math.abs(self.throwBackSpeed); self.throwBackSpeed *= 0.95; // Show hit animation var hitOffset = self.runAnimation.length; self.sprites[hitOffset].alpha = 1; // Decrease hit timer self.hitTimer--; // Once hit timer expires, start death animation if (self.hitTimer <= 0) { self.isHit = false; self.isDying = true; self.deathTimer = 60; self.deathFrame = 0; } }; // Update goblin dying animation self.updateGoblinDyingState = function () { // Continue throw back during death self.x += self.throwBackSpeed; // After halfway through death animation, match platform speed if (self.deathFrame >= 2) { self.x -= PLATFORM_SPEED; } self.throwBackSpeed *= 0.95; // Handle death animation var dieOffset = self.runAnimation.length + self.hitAnimation.length; // Progress frame every 15 frames if (self.deathTimer % 15 === 0 && self.deathFrame < self.dieAnimation.length - 1) { self.deathFrame++; } self.sprites[dieOffset + self.deathFrame].alpha = 1; // Count down death timer self.deathTimer--; // After timer expires, fade out if (self.deathTimer <= 0) { self.alpha -= 0.1; if (self.alpha <= 0) { self.destroy(); } } }; // Update goblin normal movement self.updateGoblinNormalState = function () { // Move left self.x -= self.speed; // Platform and gravity code if (!self.isOnGround) { self.velocityY += 0.7; self.y += self.velocityY; self.checkPlatformCollision(); } // Ensure goblin stays on platform if (self.currentPlatform) { var stillOnPlatform = self.x >= self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x <= self.currentPlatform.x + PLATFORM_HALF_WIDTH; if (!stillOnPlatform) { var foundAnotherPlatform = false; // Check for another platform for (var i = 0; i < platforms.length; i++) { var otherPlatform = platforms[i]; if (otherPlatform === self.currentPlatform) { continue; } if (self.x >= otherPlatform.x - PLATFORM_HALF_WIDTH && self.x <= otherPlatform.x + PLATFORM_HALF_WIDTH && Math.abs(self.y - (otherPlatform.y - ENEMY_PLATFORM_OFFSET)) < 5) { self.currentPlatform = otherPlatform; foundAnotherPlatform = true; break; } } // Start falling if no other platform found if (!foundAnotherPlatform) { self.isOnGround = false; self.currentPlatform = null; if (self.velocityY === 0) { self.velocityY = 0.1; } } } } // Animate running self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.runFrame = (self.runFrame + 1) % self.runAnimation.length; } self.sprites[self.runFrame].alpha = 1; }; // Check platform collision self.checkPlatformCollision = function () { var onAnyPlatform = false; for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var leftEdge = platform.x - PLATFORM_HALF_WIDTH; var rightEdge = platform.x + PLATFORM_HALF_WIDTH; // Check if we're on a platform (exact position check) if (self.x >= leftEdge && self.x <= rightEdge) { if (Math.abs(self.y - (platform.y - ENEMY_PLATFORM_OFFSET)) < 5) { onAnyPlatform = true; self.currentPlatform = platform; self.y = platform.y - ENEMY_PLATFORM_OFFSET; self.isOnGround = true; self.velocityY = 0; return true; } // Check for passing through platform during fall if (self.velocityY > 0 && self.y < platform.y - ENEMY_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - ENEMY_PLATFORM_OFFSET) { self.y = platform.y - ENEMY_PLATFORM_OFFSET; self.velocityY = 0; self.isOnGround = true; self.currentPlatform = platform; return true; } } } if (!onAnyPlatform) { self.isOnGround = false; self.currentPlatform = null; } return false; }; // Get collision bounds self.getBounds = function () { return { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2, top: self.y - self.hitboxHeight / 2, bottom: self.y + self.hitboxHeight / 2 }; }; // Hit handling self.hit = function () { if (!self.isHit && !self.isDying) { self.isHit = true; self.throwBackSpeed = 25; self.throwBackDistance = 0; self.hitTimer = 35; // Add particle effect var particleOffset = self.type === 'eyeball' ? 175 : 250; particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x); } }; // Main update method self.update = function () { // Hide all sprites first self.hideAllSprites(); // Handle different enemy types and states if (self.type === 'eyeball') { if (self.isHit || self.isDying) { self.updateEyeballDamageState(); } else { self.updateEyeballNormalState(); } } else { // Goblin logic if (self.isHit) { self.updateGoblinHitState(); } else if (self.isDying) { self.updateGoblinDyingState(); } else { self.updateGoblinNormalState(); } } // Destroy if off screen if (self.x < -50 || self.y > GAME_HEIGHT) { self.destroy(); } }; return self; }); var HealthPotion = Container.expand(function () { var self = Container.call(this); // Create sprite self.sprite = self.attachAsset('healthpotion', { anchorX: 0.5, anchorY: 0.8 }); // Initialize physics properties CollectibleBehavior.initPhysics(self); // Collection functionality self.collect = function () { if (!self.collected && player.currentHealth < player.maxHealth) { self.collected = true; player.currentHealth++; player.heartContainer.updateHealth(player.currentHealth); player.heartContainer.alpha = 1; player.heartVisibilityTimer = player.heartVisibilityDuration; // Create heart popup var popup = new Text2('❤️', { size: 80, fill: 0xFF0000, anchorX: 0.5, anchorY: 0.5 }); popup.x = self.x; popup.y = self.y - 30; popup.velocityY = -3; popup.lifespan = 45; popup.update = function () { this.y += this.velocityY; this.lifespan--; if (this.lifespan < 15) { this.alpha -= 0.07; } if (this.alpha <= 0 || this.lifespan <= 0) { this.destroy(); } }; game.addChild(popup); LK.getSound('potion').play(); self.destroy(); } }; // Standard update using shared behavior self.update = function () { CollectibleBehavior.standardUpdate(self); }; self.checkPlatformCollision = function () { return GameUtils.checkPlatformCollision(self, 80, true) != null; }; return self; }); var HeartContainer = Container.expand(function () { var self = Container.call(this); self.maxHealth = 3; self.currentHealth = 3; self.hearts = []; // Calculate total width of heart display var heartSpacing = 80; var totalWidth = (self.maxHealth - 1) * heartSpacing; // Initialize hearts with centered positioning for (var i = 0; i < self.maxHealth; i++) { var heart = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5, tint: 0xCC0000 }); heart.x = i * heartSpacing - totalWidth / 2; heart.y = 0; self.hearts.push(heart); } // Start invisible self.alpha = 0; self.updateHealth = function (newHealth) { self.currentHealth = newHealth; // Update heart display for (var i = 0; i < self.maxHealth; i++) { if (i < newHealth) { // Full heart self.hearts[i].tint = 0xFF0000; self.hearts[i].alpha = 1; } else { // Empty heart self.hearts[i].tint = 0x000000; self.hearts[i].alpha = 0.5; } } }; return self; }); var Jar = Container.expand(function () { var self = Container.call(this); // Attach jar sprite self.sprite = self.attachAsset('jar', { anchorX: 0.5, anchorY: 0.5, tint: 0xC0C0C0 }); // Initialize as breakable BreakableBehavior.initBreakable(self); // Break functionality self["break"] = function () { BreakableBehavior.standardBreak(self, JarPiece, 4, function (jar) { // Spawn health potion with low chance if (Math.random() < 0.05) { var potion = new HealthPotion(); potion.x = jar.x; potion.y = jar.y; potion.velocityX = GameUtils.randomRange(4, 12); potion.velocityY = -GameUtils.randomRange(12, 22); game.addChild(potion); coins.push(potion); } // Spawn coins var coinCount = Math.floor(GameUtils.randomRange(1, 9)); for (var i = 0; i < coinCount; i++) { var coin = new Coin(); coin.x = jar.x; coin.y = jar.y; coin.velocityX = GameUtils.randomRange(4, 12); coin.velocityY = -GameUtils.randomRange(12, 22); game.addChild(coin); coins.push(coin); } LK.getSound('jarbreak').play(); }); }; return self; }); var JarPiece = Container.expand(function (pieceNum) { var self = Container.call(this); // Attach piece sprite self.sprite = self.attachAsset('jarpiece' + pieceNum, { anchorX: 0.5, anchorY: 0.5, tint: 0xC0C0C0 }); // Initialize as piece PieceBehavior.initPiece(self); // Update method self.update = function () { PieceBehavior.standardUpdate(self); }; self.checkPlatformCollision = function () { return GameUtils.checkPlatformCollision(self, 80, true) != null; }; return self; }); var ParticlePool = Container.expand(function (maxParticles) { var self = Container.call(this); self.particles = []; self.activeParticles = []; self.redTints = [0xff0000, 0xff3333, 0xcc0000]; for (var i = 0; i < maxParticles; i++) { var particle = self.attachAsset('pixel', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); particle.alpha = 0; particle.velocityX = 0; particle.velocityY = 0; particle.lifespan = 0; particle.fadeSpeed = 0; self.particles.push(particle); } self.emitFromHit = function (x, y, playerX) { var directionX = x - playerX; var directionSign = Math.sign(directionX); for (var i = 0; i < 20; i++) { if (self.particles.length === 0) { break; } var particle = self.particles.pop(); self.activeParticles.push(particle); particle.x = x; particle.y = y; particle.alpha = 1; particle.tint = self.redTints[Math.floor(Math.random() * self.redTints.length)]; // Set scale var particleSize = Math.random() * 0.2 + 0.2; particle.scaleX = particleSize; particle.scaleY = particleSize; var angle = Math.random() * Math.PI / 2 - Math.PI / 4; var speed = Math.random() * 5 + 10; particle.velocityX = Math.cos(angle) * speed * directionSign; particle.velocityY = Math.sin(angle) * speed; particle.lifespan = 100; particle.fadeSpeed = 1 / 60; } }; self.update = function () { for (var i = self.activeParticles.length - 1; i >= 0; i--) { var particle = self.activeParticles[i]; particle.x += particle.velocityX; particle.y += particle.velocityY; particle.alpha -= particle.fadeSpeed; particle.lifespan--; if (particle.lifespan <= 0 || particle.alpha <= 0) { particle.alpha = 0; self.activeParticles.splice(i, 1); self.particles.push(particle); } } }; return self; }); var Platform = Container.expand(function () { var self = Container.call(this); var platformGraphics = self.attachAsset('platform', { anchorX: 0.5, anchorY: 0.5 }); self.speed = PLATFORM_SPEED; self.passed = false; self.update = function () { self.x -= self.speed; if (self.x < -500) { self.destroy(); } }; return self; }); // Player class with refactored animation management var Player = Container.expand(function () { var self = Container.call(this); // Animation properties self.runAnimation = ['playerrun1', 'playerrun2', 'playerrun3', 'playerrun4', 'playerrun5', 'playerrun6']; self.jumpAnimation = ['playerjump1', 'playerjump2', 'playerjump3']; self.attackAnimation = ['playerattack1', 'playerattack2', 'playerattack3', 'playerattack4', 'playerattack5']; // Animation states self.isAttacking = false; self.attackFrame = 0; self.runFrame = 0; self.animationSpeed = 0.08; self.attackAnimationSpeed = 0.15; self.animationCounter = 0; self.sprites = []; // Physics properties self.groundY = GAME_HEIGHT * 0.9; self.hitboxWidth = 150; self.hitboxHeight = 300; self.attackHitboxWidth = 200; self.attackHitboxHeight = 400; self.attackHitboxOffset = 50; // Platform collision properties self.isOnGround = true; self.currentPlatform = null; // Health properties self.heartContainer = heartContainer; self.maxHealth = 3; self.currentHealth = 3; self.isInvulnerable = false; self.invulnerabilityDuration = 90; self.invulnerabilityTimer = 0; self.heartVisibilityTimer = 0; self.heartVisibilityDuration = 120; // Movement properties self.speed = 5; self.jumpHeight = 40; self.isJumping = false; self.velocityY = 0; self.jumpState = "none"; self.jumpStartTime = 0; // Initialize animation sprites self.initAnimations = function () { // Run animations for (var i = 0; i < self.runAnimation.length; i++) { var sprite = self.attachAsset(self.runAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = i === 0 ? 1 : 0; self.sprites.push(sprite); } // Jump animations for (var i = 0; i < self.jumpAnimation.length; i++) { var sprite = self.attachAsset(self.jumpAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Attack animations for (var i = 0; i < self.attackAnimation.length; i++) { var sprite = self.attachAsset(self.attackAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } }; // Call initialization self.initAnimations(); // Get collision bounds self.getBounds = function () { return { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2, top: self.y - self.hitboxHeight / 2, bottom: self.y + self.hitboxHeight / 2 }; }; // Get attack hitbox self.getAttackBounds = function () { if (!self.isAttacking) { return null; } return { left: self.x + (self.attackHitboxOffset - self.attackHitboxWidth / 2), right: self.x + (self.attackHitboxOffset + self.attackHitboxWidth / 2), top: self.y - self.attackHitboxHeight / 2, bottom: self.y + self.attackHitboxHeight / 2 }; }; // Update heart container self.updateHeartContainer = function () { self.heartContainer.x = self.x + 80; self.heartContainer.y = self.y - 200; // Handle heart visibility if (self.heartVisibilityTimer > 0) { self.heartVisibilityTimer--; if (self.heartVisibilityTimer <= 0) { self.heartContainer.alpha = 0; } } }; // Update invulnerability state self.updateInvulnerability = function () { if (self.isInvulnerable) { self.invulnerabilityTimer--; // Flash only the player, not the hearts self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1; if (self.invulnerabilityTimer <= 0) { self.isInvulnerable = false; self.alpha = 1; } } }; // Handle platform collision self.handlePlatformCollision = function () { // Already checks in jumpstate if (!self.isOnGround && !self.isJumping) { self.velocityY += 0.7; self.y += self.velocityY; self.checkPlatformCollision(); } // Check if on any platform var onAnyPlatform = false; for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var leftEdge = platform.x - PLATFORM_HALF_WIDTH; var rightEdge = platform.x + PLATFORM_HALF_WIDTH; // Check if player is within horizontal bounds of platform if (self.x >= leftEdge && self.x <= rightEdge) { // If we're at platform height and not jumping if (Math.abs(self.y - (platform.y - PLAYER_PLATFORM_OFFSET)) < 5 && !self.isJumping) { onAnyPlatform = true; self.currentPlatform = platform; self.y = platform.y - PLAYER_PLATFORM_OFFSET; self.isOnGround = true; self.velocityY = 0; break; } } } // Handle falling state if (!onAnyPlatform && !self.isJumping) { self.isOnGround = false; self.currentPlatform = null; // Apply gravity if (self.velocityY === 0) { self.velocityY = 0.1; } self.velocityY += 0.1; self.y += self.velocityY; // Ground collision check comes AFTER falling movement if (self.y >= self.groundY) { // Reached the bottom of the screen - game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } } // If on a platform, check if still above it if (self.currentPlatform) { var stillOnPlatform = self.x > self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x < self.currentPlatform.x + PLATFORM_HALF_WIDTH; if (!stillOnPlatform) { var foundAnotherPlatform = false; for (var i = 0; i < platforms.length; i++) { var otherPlatform = platforms[i]; if (otherPlatform === self.currentPlatform) { continue; } if (self.x > otherPlatform.x - PLATFORM_HALF_WIDTH && self.x < otherPlatform.x + PLATFORM_HALF_WIDTH) { // Found another platform self.currentPlatform = otherPlatform; foundAnotherPlatform = true; break; } } // Fall if no other platform found if (!foundAnotherPlatform) { self.isOnGround = false; self.currentPlatform = null; self.velocityY = 0.1; } } } }; // Update attack animation self.updateAttackAnimation = function () { var attackOffset = self.runAnimation.length + self.jumpAnimation.length; self.animationCounter += self.attackAnimationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.attackFrame++; if (self.attackFrame >= self.attackAnimation.length) { self.isAttacking = false; self.attackFrame = 0; } } self.sprites[attackOffset + self.attackFrame].alpha = 1; }; // Update jump animation self.updateJumpAnimation = function () { var jumpOffset = self.runAnimation.length; var currentTime = Date.now(); self.y += self.velocityY; self.velocityY += 0.7; // Check for landing on platforms while falling self.checkPlatformCollision(); // End jump if hitting the ground if (self.y >= self.groundY && !self.currentPlatform) { self.y = self.groundY; self.isJumping = false; self.velocityY = 0; self.jumpState = "none"; self.isOnGround = true; } // Show appropriate jump frame if (currentTime - self.jumpStartTime < 100) { self.sprites[jumpOffset + 0].alpha = 1; } else if (self.velocityY < 0) { self.sprites[jumpOffset + 1].alpha = 1; } else if (self.velocityY > 0) { self.sprites[jumpOffset + 2].alpha = 1; } }; // Update run animation self.updateRunAnimation = function () { self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.runFrame = (self.runFrame + 1) % self.runAnimation.length; } self.sprites[self.runFrame].alpha = 1; }; // Check platform collision self.checkPlatformCollision = function () { for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; // Check if player is above the platform and falling if (self.velocityY > 0 && self.y < platform.y - PLAYER_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - PLAYER_PLATFORM_OFFSET && self.x > platform.x - PLATFORM_HALF_WIDTH && self.x < platform.x + PLATFORM_HALF_WIDTH) { // Land on the platform self.y = platform.y - PLAYER_PLATFORM_OFFSET; self.velocityY = 0; self.isJumping = false; self.isOnGround = true; self.currentPlatform = platform; return; } } }; // Hide all sprites self.hideAllSprites = function () { for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } }; // Jump method self.jump = function () { if (self.isOnGround) { self.isJumping = true; self.isOnGround = false; self.velocityY = -self.jumpHeight; self.jumpState = "start"; self.jumpStartTime = Date.now(); LK.getSound('playerjump').play(); self.currentPlatform = null; } else if (self.isJumping && self.velocityY < 10) { // Small double-jump to reach higher platforms self.velocityY = -self.jumpHeight * 0.7; self.jumpStartTime = Date.now(); } }; // Attack method self.attack = function () { if (!self.isAttacking) { self.isAttacking = true; self.attackFrame = 0; self.animationCounter = 0; LK.getSound('swordslash').play(); } }; // Take damage method self.takeDamage = function () { if (!self.isInvulnerable) { self.currentHealth--; self.heartContainer.updateHealth(self.currentHealth); // Show hearts and set visibility timer self.heartContainer.alpha = 1; self.heartVisibilityTimer = self.heartVisibilityDuration; // Visual feedback self.isInvulnerable = true; self.invulnerabilityTimer = self.invulnerabilityDuration; // Flash player red self.tint = 0xFF0000; tween(self, { tint: 0xFFFFFF }, { duration: 500, easing: tween.easeOut }); if (self.currentHealth <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } } }; // Main update method self.update = function () { // Update heart container position self.updateHeartContainer(); // Hide all sprites self.hideAllSprites(); // Handle platform collision and falling self.handlePlatformCollision(); // Handle invulnerability self.updateInvulnerability(); // Update animations based on state if (self.isAttacking) { self.updateAttackAnimation(); } else if (self.isJumping || !self.isOnGround) { self.updateJumpAnimation(); } else if (self.isOnGround) { self.updateRunAnimation(); } // Check for falling off screen if (self.y > GAME_HEIGHT) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } }; return self; }); var ScorePopup = Container.expand(function (x, y, amount) { var self = Container.call(this); // Create the text self.text = new Text2('+' + amount, { size: 80, fill: 0xFFFFFF, anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.text); self.x = x; self.y = y; self.velocityY = -3; self.lifespan = 45; self.update = function () { self.y += self.velocityY; self.lifespan--; if (self.lifespan < 15) { self.alpha -= 0.07; } if (self.alpha <= 0 || self.lifespan <= 0) { self.destroy(); } }; return self; }); /**** * Constants ****/ var Torch = Container.expand(function () { var self = Container.call(this); // Create base torch sprite self.base = self.attachAsset('torch', { anchorX: 0.5, anchorY: 1 }); // Create flame sprite self.flame = self.attachAsset('torchflame', { anchorX: 0.5, anchorY: 1, y: -180 }); // Create aura sprite self.aura = self.attachAsset('torchaura', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, y: -250 }); // Animation properties self.flameTime = Math.random() * Math.PI * 2; self.auraTime = Math.random() * Math.PI * 2; self.flameSpeed = 0.05; self.auraSpeed = 0.03; // Update animation self.update = function () { // Animate flame scale self.flameTime += self.flameSpeed; var flameScale = 1 + Math.sin(self.flameTime) * 0.2; self.flame.scaleY = flameScale; // Random flip chance for flame if (Math.random() < 0.02) { self.flame.scaleX *= -1; } // Animate aura alpha self.auraTime += self.auraSpeed; var auraAlpha = 0.3 + Math.sin(self.auraTime) * 0.15; self.aura.alpha = auraAlpha; }; return self; }); var TreasureChest = Container.expand(function () { var self = Container.call(this); // Attach chest sprite self.sprite = self.attachAsset('treasurechest', { anchorX: 0.5, anchorY: 0.5, tint: 0xC0C0C0 }); // Initialize as breakable BreakableBehavior.initBreakable(self); // Break functionality self["break"] = function () { BreakableBehavior.standardBreak(self, TreasureChestPiece, 4, function (chest) { // Spawn health potion with medium chance if (Math.random() < 0.25) { var potion = new HealthPotion(); potion.x = chest.x; potion.y = chest.y; potion.velocityX = GameUtils.randomRange(2, 12); potion.velocityY = -GameUtils.randomRange(14, 26); game.addChild(potion); coins.push(potion); } // Spawn valuable items var totalItems = Math.floor(GameUtils.randomRange(3, 9)); for (var i = 0; i < totalItems; i++) { // Random chance for different gems var rand = Math.random(); var item; if (rand < 0.05) { // 5% diamond item = new Coin('diamond'); } else if (rand < 0.15) { // 10% emerald item = new Coin('emerald'); } else if (rand < 0.30) { // 15% ruby item = new Coin('ruby'); } else { // 70% gold coin item = new Coin('coin'); } item.x = chest.x; item.y = chest.y; item.velocityX = GameUtils.randomRange(2, 12); item.velocityY = -GameUtils.randomRange(14, 26); game.addChild(item); coins.push(item); } LK.getSound('woodbreak').play(); }); }; return self; }); var TreasureChestPiece = Container.expand(function (pieceNum) { var self = Container.call(this); // Attach piece sprite self.sprite = self.attachAsset('treasurechestpiece' + pieceNum, { anchorX: 0.5, anchorY: 0.5, tint: 0xC0C0C0 }); // Initialize as piece PieceBehavior.initPiece(self); // Update method self.update = function () { PieceBehavior.standardUpdate(self); }; self.checkPlatformCollision = function () { return GameUtils.checkPlatformCollision(self, 80, true) != null; }; return self; }); /**** * Initialize Game ****/ /**** * Game Initialization ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Base collectible behavior for items that can be collected /**** * Game Management ****/ var CollectibleBehavior = { initPhysics: function initPhysics(self) { self.velocityX = 0; self.velocityY = 0; self.collected = false; self.bounceCount = 0; self.maxBounces = 2; }, standardUpdate: function standardUpdate(self) { if (self.collected) { return; } // Apply physics self.velocityY += 0.5; // gravity self.x += self.velocityX; self.y += self.velocityY; // Check for platform collision with bounce if (self.velocityY > 0) { GameUtils.checkPlatformCollision(self, 80, true); } // Check if off screen if (self.x < -50 || self.x > GAME_WIDTH + 50 || self.y > GAME_HEIGHT) { self.destroy(); return; } // Player collection detection var playerBounds = player.getBounds(); var itemBounds = { left: self.x - 25, right: self.x + 25, top: self.y - 25, bottom: self.y + 25 }; if (GameUtils.checkCollision(playerBounds, itemBounds)) { self.collect(); } } }; // Base behavior for breakable objects var BreakableBehavior = { initBreakable: function initBreakable(self) { self.isBreaking = false; self.currentPlatform = null; }, standardBreak: function standardBreak(self, pieceClass, pieceCount, itemSpawnCallback) { if (self.isBreaking) { return; } self.isBreaking = true; // Spawn pieces for (var i = 1; i <= pieceCount; i++) { var piece = new pieceClass(i); piece.x = self.x; piece.y = self.y; piece.velocityX = GameUtils.randomRange(-6, 6); piece.velocityY = -GameUtils.randomRange(6, 12); piece.rotationSpeed = GameUtils.randomRange(-0.1, 0.1); game.addChild(piece); } // Call the custom item spawn callback if (itemSpawnCallback) { itemSpawnCallback(self); } self.destroy(); } }; // Base behavior for pieces of broken objects var PieceBehavior = { initPiece: function initPiece(self) { self.velocityX = 0; self.velocityY = 0; self.rotationSpeed = 0; self.fadeSpeed = 0.02; self.bounceCount = 0; self.maxBounces = 2; }, standardUpdate: function standardUpdate(self) { // Apply physics self.velocityY += 0.5; // gravity self.x += self.velocityX; self.y += self.velocityY; self.rotation += self.rotationSpeed; // Check for platform collision var platformCollision = GameUtils.checkPlatformCollision(self, 80, self.bounceCount < self.maxBounces); if (platformCollision && self.bounceCount >= self.maxBounces) { // Start fading after max bounces self.velocityY = 0; self.velocityX = -PLATFORM_SPEED; self.alpha -= self.fadeSpeed; if (self.alpha <= 0) { self.destroy(); } } // Destroy if off screen if (self.x < -50 || self.y > GAME_HEIGHT) { self.destroy(); } } }; /**** * Constants ****/ var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLATFORM_WIDTH = 1000; var PLATFORM_HALF_WIDTH = PLATFORM_WIDTH / 2; var PLATFORM_OVERLAP = 50; var PLATFORM_SPEED = 5; var ENEMY_PLATFORM_OFFSET = 225; var PLAYER_PLATFORM_OFFSET = 250; var MIN_PLATFORMS_IN_SEQUENCE = 2; var MAX_PLATFORMS_IN_SEQUENCE = 5; var JUMP_COOLDOWN = 250; var MOVE_THRESHOLD = 40; /**** * Utilities ****/ var GameUtils = { // Check for collision between two rectangular bounds checkCollision: function checkCollision(bounds1, bounds2) { return bounds1.left < bounds2.right && bounds1.right > bounds2.left && bounds1.top < bounds2.bottom && bounds1.bottom > bounds2.top; }, // Check platform collision with standard offset checkPlatformCollision: function checkPlatformCollision(obj, offsetY, bounceOnCollision) { for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; if (Math.abs(obj.y - (platform.y - offsetY)) < 10 && obj.x > platform.x - PLATFORM_HALF_WIDTH && obj.x < platform.x + PLATFORM_HALF_WIDTH) { obj.y = platform.y - offsetY; if (bounceOnCollision && obj.bounceCount < obj.maxBounces) { LK.getSound('coinbounce').play(); var impactSpeed = Math.abs(obj.velocityY); obj.velocityY = -(impactSpeed * 0.5); obj.velocityX *= 0.8; obj.bounceCount++; return true; } else if (bounceOnCollision) { obj.velocityY = 0; obj.velocityX = -PLATFORM_SPEED; } return platform; } } return null; }, // Get random value within range randomRange: function randomRange(min, max) { return Math.random() * (max - min) + min; }, // Check if position is clear for spawning canSpawnAtPosition: function canSpawnAtPosition(x) { var safeDistance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200; for (var i = 0; i < collectibles.length; i++) { if (Math.abs(collectibles[i].x - x) < safeDistance) { return false; } } return true; } }; var ScoreManager = function ScoreManager() { var self = {}; // Initialize score and UI self.score = 0; self.scoreText = new Text2('0', { size: 160, fill: 0xFFFFFF, anchorX: 1, anchorY: 0.5 }); self.coinIcon = LK.getAsset('coin', { anchorX: 0, anchorY: 0.5, scaleX: 2, scaleY: 2 }); // Initialize container self.container = new Container(); self.container.addChild(self.scoreText); self.container.addChild(self.coinIcon); // Position container self.container.x = GAME_WIDTH - 100; self.container.y = 100; // Position elements self.scoreText.x = -100; self.scoreText.y = -100; self.coinIcon.x = 0; self.coinIcon.y = 0; // Getter and setter for score self.getScore = function () { return self.score; }; self.setScore = function (newScore) { self.score = newScore; self.scoreText.setText(newScore); // Adjust position based on digit count if (newScore >= 10 && newScore < 100) { self.scoreText.x = -200; } else if (newScore >= 100 && newScore < 1000) { self.scoreText.x = -300; } else if (newScore >= 1000) { self.scoreText.x = -400; } else { self.scoreText.x = -100; } }; // Add score and display popup self.addScore = function (amount, x, y) { self.setScore(self.score + amount); LK.setScore(self.score); // Create score popup if position is provided if (x !== undefined && y !== undefined) { var popup = new ScorePopup(x, y - 30, amount); game.addChild(popup); } }; return self; }; /**** * Game Variables ****/ // Containers var backgroundContainer = game.addChild(new Container()); var midgroundContainer = game.addChild(new Container()); var foregroundContainer = game.addChild(new Container()); var scoreManager = new ScoreManager(); game.addChild(scoreManager.container); var scoreContainer = game.addChild(new Container()); // Game state var gameStarted = false; var titleScreen; var playButton; // Platform management var platforms = []; var platformSpawnCounter = 0; var platformsUntilNextChange = 5; var lowPlatformHeight = GAME_HEIGHT / 1.5 + PLAYER_PLATFORM_OFFSET; var highPlatformHeight = lowPlatformHeight - 600; var currentPlatformHeight = lowPlatformHeight; var lastPlatformHeight = lowPlatformHeight; var lastPlatformX = 0; // Touch controls var touchStartX = 0; var touchStartY = 0; var touchEndX = 0; var touchEndY = 0; var lastMoveY = 0; var lastJumpTime = 0; // Game objects var jars = []; var coins = []; var collectibles = []; var jarSpawnCounter = 0; var jarSpawnInterval = 20; var chestSpawnCounter = 0; var chestSpawnInterval = 100; var enemies = []; var enemySpawnInterval = 100; var goblinSpawnCounter = 0; var goblinSpawnInterval = 100; var eyeballSpawnCounter = 150; var eyeballSpawnInterval = 500; var particleSystem; var heartContainer = new HeartContainer(); var player; // Background elements var bg1 = backgroundContainer.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 1 })); var bg2 = backgroundContainer.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 1 })); bg1.y = GAME_HEIGHT; bg2.y = GAME_HEIGHT; bg2.x = GAME_WIDTH; // Midground elements var mg1 = midgroundContainer.addChild(LK.getAsset('midground', { anchorX: 0, anchorY: 1 })); var mg2 = midgroundContainer.addChild(LK.getAsset('midground', { anchorX: 0, anchorY: 1 })); mg1.y = GAME_HEIGHT; mg2.y = GAME_HEIGHT; mg2.x = GAME_WIDTH; // Foreground elements var fg1 = foregroundContainer.addChild(LK.getAsset('foreground', { anchorX: 0, anchorY: 1 })); var fg2 = foregroundContainer.addChild(LK.getAsset('foreground', { anchorX: 0, anchorY: 1 })); fg1.y = GAME_HEIGHT * 1.25; fg2.y = GAME_HEIGHT * 1.25; fg1.x = 0; fg2.x = GAME_WIDTH; /**** * Game Functions ****/ // Create the title screen function createTitleScreen() { titleScreen = new Container(); game.addChild(titleScreen); // Add title image with fade-in var titleImage = titleScreen.attachAsset('title', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); titleImage.x = GAME_WIDTH / 2; titleImage.y = GAME_HEIGHT / 2.7; // Fade in title tween(titleImage, { alpha: 1 }, { duration: 1000, easing: tween.easeIn }); // Add play button playButton = titleScreen.attachAsset('playbutton', { anchorX: 0.5, anchorY: 0.5 }); playButton.x = GAME_WIDTH / 2; playButton.y = GAME_HEIGHT / 1.3; // Add flashing animation function flashPlayButton() { tween(playButton, { alpha: 0 }, { duration: 250, easing: tween.linear, onFinish: function onFinish() { tween(playButton, { alpha: 1 }, { duration: 250, easing: tween.linear }); } }); } // Flash every 2 seconds LK.setInterval(flashPlayButton, 2000); // Initialize torch decorations initializeTorches(); } // Place torches in the scene function initializeTorches() { // Create torches for the two background sections var torch1 = new Torch(); torch1.x = 25; torch1.y = GAME_HEIGHT * 0.7; midgroundContainer.addChild(torch1); var torch2 = new Torch(); torch2.x = GAME_WIDTH + 25; torch2.y = GAME_HEIGHT * 0.7; midgroundContainer.addChild(torch2); } // Initialize game elements function initializeGame() { // Create player player = game.addChild(new Player()); player.x = GAME_WIDTH / 4.5; player.y = GAME_HEIGHT / 1.5; game.addChild(heartContainer); // Create initial platforms at the low level for (var i = 0; i < 5; i++) { var platform = new Platform(); if (i === 0) { // First platform centered on player platform.x = player.x; } else { // Position with slight overlap platform.x = lastPlatformX + PLATFORM_WIDTH - PLATFORM_OVERLAP; } platform.y = lowPlatformHeight; platforms.push(platform); game.addChild(platform); lastPlatformX = platform.x; } lastPlatformHeight = lowPlatformHeight; player.isOnGround = true; player.currentPlatform = platforms[0]; } // Start the game function startGame() { gameStarted = true; titleScreen.destroy(); initializeGame(); // Initialize particle system particleSystem = new ParticlePool(100); game.addChild(particleSystem); // Show health player.heartContainer.alpha = 1; player.heartVisibilityTimer = 120; // Play background music LK.playMusic('backgroundmusic1', { fade: { start: 0, end: 0.7, duration: 1000 } }); } // Update background layers function updateBackgrounds() { // Background layer (slowest) bg1.x -= PLATFORM_SPEED * 0.3; bg2.x -= PLATFORM_SPEED * 0.3; if (bg1.x <= -GAME_WIDTH) { bg1.x = bg2.x + GAME_WIDTH; } if (bg2.x <= -GAME_WIDTH) { bg2.x = bg1.x + GAME_WIDTH; } // Midground layer mg1.x -= PLATFORM_SPEED * 0.6; mg2.x -= PLATFORM_SPEED * 0.6; if (mg1.x <= -GAME_WIDTH) { mg1.x = mg2.x + GAME_WIDTH; } if (mg2.x <= -GAME_WIDTH) { mg2.x = mg1.x + GAME_WIDTH; } // Foreground layer (fastest) fg1.x -= PLATFORM_SPEED; fg2.x -= PLATFORM_SPEED; if (fg1.x <= -GAME_WIDTH) { fg1.x = fg2.x + GAME_WIDTH; } if (fg2.x <= -GAME_WIDTH) { fg2.x = fg1.x + GAME_WIDTH; } // Update torches for (var i = 0; i < midgroundContainer.children.length; i++) { var child = midgroundContainer.children[i]; if (child instanceof Torch) { child.update(); child.x -= PLATFORM_SPEED * 0.6; if (child.x <= -GAME_WIDTH) { child.x = child.x + GAME_WIDTH * 2; } } } } // Update and spawn platforms function updatePlatforms() { // Check if we need to spawn a new sequence of platforms var lastPlatform = platforms[platforms.length - 1]; if (lastPlatform && lastPlatform.x < GAME_WIDTH + 500) { // Time to spawn a new sequence if (platformsUntilNextChange <= 0) { // Switch height currentPlatformHeight = currentPlatformHeight === lowPlatformHeight ? highPlatformHeight : lowPlatformHeight; // Generate new random sequence length platformsUntilNextChange = Math.floor(Math.random() * (MAX_PLATFORMS_IN_SEQUENCE - MIN_PLATFORMS_IN_SEQUENCE + 1)) + MIN_PLATFORMS_IN_SEQUENCE; // Make ground sequences longer if (currentPlatformHeight === lowPlatformHeight) { platformsUntilNextChange += 5; } } // Spawn a single platform in the sequence var platform = new Platform(); platform.x = lastPlatform.x + (PLATFORM_WIDTH - PLATFORM_OVERLAP); platform.y = currentPlatformHeight; platforms.push(platform); game.addChild(platform); // Decrement the counter for each platform added platformsUntilNextChange--; } // Update platforms for (var i = platforms.length - 1; i >= 0; i--) { platforms[i].update(); // Remove platforms that are destroyed if (platforms[i].destroyed) { platforms.splice(i, 1); } } } // Update and spawn collectibles function updateCollectibles() { // Jar spawning jarSpawnCounter++; if (jarSpawnCounter >= jarSpawnInterval) { var availablePlatforms = platforms.filter(function (p) { return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300; }); if (availablePlatforms.length > 0 && Math.random() < 0.3) { var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)]; // Only spawn if position is clear if (GameUtils.canSpawnAtPosition(platform.x)) { var jar = new Jar(); jar.x = platform.x; jar.y = platform.y - 130; jar.currentPlatform = platform; collectibles.push(jar); game.addChild(jar); } } jarSpawnCounter = 0; } // Treasure chest spawning chestSpawnCounter++; if (chestSpawnCounter >= chestSpawnInterval) { var availablePlatforms = platforms.filter(function (p) { return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300; }); if (availablePlatforms.length > 0 && Math.random() < 0.15) { var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)]; // Only spawn if position is clear if (GameUtils.canSpawnAtPosition(platform.x)) { var chest = new TreasureChest(); chest.x = platform.x; chest.y = platform.y - 130; chest.currentPlatform = platform; collectibles.push(chest); game.addChild(chest); } } chestSpawnCounter = 0; } // Update existing collectibles for (var i = collectibles.length - 1; i >= 0; i--) { var collectible = collectibles[i]; if (collectible.currentPlatform) { collectible.x = collectible.currentPlatform.x; } var attackBounds = player.getAttackBounds(); if (attackBounds) { var itemBounds = { left: collectible.x - 50, right: collectible.x + 50, top: collectible.y - 75, bottom: collectible.y + 75 }; if (GameUtils.checkCollision(attackBounds, itemBounds)) { collectible["break"](); collectibles.splice(i, 1); continue; } } if (collectible.x < -50) { collectible.destroy(); collectibles.splice(i, 1); } } // Update coins and other collectibles for (var i = coins.length - 1; i >= 0; i--) { coins[i].update(); if (coins[i].destroyed) { coins.splice(i, 1); } } // Update score popups for (var i = game.children.length - 1; i >= 0; i--) { var child = game.children[i]; if (child instanceof ScorePopup) { child.update(); } } } // Update and spawn enemies function updateEnemies() { // Goblin spawning goblinSpawnCounter++; if (goblinSpawnCounter >= goblinSpawnInterval) { var availablePlatforms = platforms.filter(function (p) { return p.x > GAME_WIDTH - 100 && p.x < GAME_WIDTH + 300; }); if (availablePlatforms.length > 0) { var enemy = new Enemy('goblin'); var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)]; enemy.x = platform.x; enemy.y = platform.y - ENEMY_PLATFORM_OFFSET; enemy.currentPlatform = platform; enemies.push(enemy); game.addChild(enemy); goblinSpawnInterval = Math.floor(Math.random() * 150) + 100; goblinSpawnCounter = 0; } else { // No valid platforms, reset counter but don't spawn goblinSpawnCounter = Math.max(0, goblinSpawnCounter - 20); } } // Eyeball spawning eyeballSpawnCounter++; if (eyeballSpawnCounter >= eyeballSpawnInterval) { var enemy = new Enemy('eyeball'); var randomHeight = Math.random() * 400 + 200; enemy.x = GAME_WIDTH + 100; enemy.y = highPlatformHeight - randomHeight; enemies.push(enemy); game.addChild(enemy); eyeballSpawnInterval = Math.floor(Math.random() * 300) + 150; eyeballSpawnCounter = 0; } // Update enemies and check collisions for (var j = enemies.length - 1; j >= 0; j--) { enemies[j].update(); // Skip if enemy is far behind player if (enemies[j].x < player.x - 100) { continue; } var playerBounds = player.getBounds(); var enemyBounds = enemies[j].getBounds(); var attackBounds = player.getAttackBounds(); // Check for attack collision first if (attackBounds && !enemies[j].isHit && !enemies[j].isDying) { if (enemies[j].x > player.x && GameUtils.checkCollision(attackBounds, enemyBounds)) { enemies[j].hit(); if (enemies[j].type === 'eyeball') { LK.getSound('eyeballhit').play(); } else { LK.getSound('enemyhit').play(); } continue; } } // Check for body collision second if (GameUtils.checkCollision(playerBounds, enemyBounds)) { if (!enemies[j].isHit && !enemies[j].isDying) { player.takeDamage(); } } } } // Create the initial title screen createTitleScreen(); /**** * Game Loop & Input Handlers ****/ // Main game update loop game.update = function () { // Always update backgrounds for visual effect updateBackgrounds(); // If game hasn't started, don't update gameplay if (!gameStarted) { return; } // Update game entities player.update(); if (particleSystem) { particleSystem.update(); } // Update game world updatePlatforms(); updateCollectibles(); updateEnemies(); }; // Handle touch/click events game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; }; game.up = function (x, y, obj) { if (!gameStarted) { var buttonBounds = { left: playButton.x - playButton.width / 2, right: playButton.x + playButton.width / 2, top: playButton.y - playButton.height / 2, bottom: playButton.y + playButton.height / 2 }; if (x >= buttonBounds.left && x <= buttonBounds.right && y >= buttonBounds.top && y <= buttonBounds.bottom) { startGame(); } return; } touchEndX = x; touchEndY = y; var deltaY = touchStartY - touchEndY; var deltaX = Math.abs(touchStartX - touchEndX); var currentTime = Date.now(); if (deltaY > 50 && deltaY > deltaX) { if (currentTime - lastJumpTime > JUMP_COOLDOWN) { player.jump(); lastJumpTime = currentTime; } } else { // Only attack if it wasn't a jump attempt player.attack(); } }; game.move = function (x, y, obj) { if (!gameStarted) { return; } if (lastMoveY !== 0) { var deltaY = y - lastMoveY; var currentTime = Date.now(); if (deltaY < -MOVE_THRESHOLD && currentTime - lastJumpTime > JUMP_COOLDOWN) { player.jump(); lastJumpTime = currentTime; } } lastMoveY = y; };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Refactored classes - keeping original Container.expand pattern
var Coin = Container.expand(function (type) {
var self = Container.call(this);
// Set type and create sprite
self.type = type || 'coin';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF
});
// Initialize physics properties
CollectibleBehavior.initPhysics(self);
// Get value based on type
self.getValue = function () {
switch (self.type) {
case 'diamond':
return 10;
case 'emerald':
return 5;
case 'ruby':
return 3;
default:
return 1;
}
};
// Collection functionality
self.collect = function () {
if (!self.collected) {
self.collected = true;
var value = self.getValue();
scoreManager.addScore(value, self.x, self.y);
LK.getSound('coincollect').play();
self.destroy();
}
};
// Standard update method using shared behavior
self.update = function () {
CollectibleBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
// Enemy class with refactored animation management
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
// Enemy properties
self.type = type || 'basic';
self.speed = 7;
self.isOnGround = true;
self.velocityY = 0;
self.currentPlatform = null;
self.groundY = GAME_HEIGHT / 1.5;
self.isHit = false;
self.isDying = false;
self.deathTimer = 0;
self.throwBackSpeed = 15;
self.throwBackDistance = 0;
self.maxThrowBack = 200;
// Hitbox properties
self.hitboxWidth = 200;
self.hitboxHeight = self.type === 'eyeball' ? 90 : 260;
// Animation properties
self.sprites = [];
self.animationCounter = 0;
self.animationSpeed = 0.08;
// Initialize based on enemy type
if (self.type === 'eyeball') {
// Eyeball-specific properties
self.isFlying = true;
self.flyingHeight = 0;
self.verticalSpeed = 2;
self.maxVerticalSpeed = 4;
self.homingDelay = 80;
self.homingTimer = 0;
self.flyFrame = 0;
// Animation frames
self.flyAnimation = ['eyefly1', 'eyefly2', 'eyefly3', 'eyefly4', 'eyefly5', 'eyefly6', 'eyefly7', 'eyefly8'];
self.hitAnimation = ['eyedie1', 'eyedie2'];
self.dieAnimation = ['eyedie3', 'eyedie4', 'eyedie5'];
// Initialize animation sprites
self.initEyeballSprites();
} else if (self.type === 'goblin') {
// Goblin-specific properties
self.runFrame = 0;
// Animation frames
self.runAnimation = ['goblinrun1', 'goblinrun2', 'goblinrun3', 'goblinrun4', 'goblinrun5', 'goblinrun6', 'goblinrun7', 'goblinrun8'];
self.hitAnimation = ['goblinhit1'];
self.dieAnimation = ['goblindie1', 'goblindie2', 'goblindie3', 'goblindie4'];
// Initialize animation sprites
self.initGoblinSprites();
} else {
// Basic enemy
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Initialize animation sprites for eyeball
self.initEyeballSprites = function () {
// Add fly animations
for (var i = 0; i < self.flyAnimation.length; i++) {
var sprite = self.attachAsset(self.flyAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Add hit animations
for (var i = 0; i < self.hitAnimation.length; i++) {
var sprite = self.attachAsset(self.hitAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add die animations
for (var i = 0; i < self.dieAnimation.length; i++) {
var sprite = self.attachAsset(self.dieAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Initialize animation sprites for goblin
self.initGoblinSprites = function () {
// Add run animations
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Add hit animations
for (var i = 0; i < self.hitAnimation.length; i++) {
var sprite = self.attachAsset(self.hitAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add die animations
for (var i = 0; i < self.dieAnimation.length; i++) {
var sprite = self.attachAsset(self.dieAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Hide all animation sprites
self.hideAllSprites = function () {
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
};
// Update eyeball hit/die animation
self.updateEyeballDamageState = function () {
if (self.isHit) {
var hitOffset = self.flyAnimation.length;
self.throwBackDistance += Math.abs(self.throwBackSpeed);
self.x += self.throwBackSpeed;
self.throwBackSpeed *= 0.95;
// Show hit animation
var hitFrame = Math.floor(self.hitTimer / 100) % 2;
self.sprites[hitOffset + hitFrame].alpha = 1;
self.hitTimer--;
if (self.hitTimer <= 0) {
self.isHit = false;
self.isDying = true;
self.deathTimer = 180;
self.deathFrame = 0;
}
} else if (self.isDying) {
var dieOffset = self.flyAnimation.length + self.hitAnimation.length;
// Always check for ground collision during fall
if (!self.isOnGround) {
self.velocityY += 0.5;
self.y += self.velocityY;
}
// Progress through death frames
if (self.deathTimer > 120) {
self.sprites[dieOffset].alpha = 1; // eyedie3
} else if (self.deathTimer > 60) {
self.sprites[dieOffset + 1].alpha = 1; // eyedie4
} else {
self.sprites[dieOffset + 2].alpha = 1; // eyedie5
}
self.deathTimer--;
if (self.deathTimer <= 0) {
self.alpha -= 0.05;
if (self.alpha <= 0) {
self.destroy();
}
}
}
};
// Update eyeball normal movement
self.updateEyeballNormalState = function () {
self.x -= self.speed;
// Only start homing after delay
if (self.homingTimer >= self.homingDelay) {
// Home toward player
var deltaY = player.y - self.y;
self.velocityY += deltaY > 0 ? 0.2 : -0.2;
self.velocityY = Math.max(-self.maxVerticalSpeed, Math.min(self.maxVerticalSpeed, self.velocityY));
} else {
// Before homing, maintain height with slight wave motion
self.velocityY = Math.sin(self.homingTimer * 0.05) * 2;
self.homingTimer++;
}
self.y += self.velocityY;
// Animate
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.flyFrame = (self.flyFrame + 1) % self.flyAnimation.length;
}
self.sprites[self.flyFrame].alpha = 1;
};
// Update goblin hit animation
self.updateGoblinHitState = function () {
// Handle throw back motion
self.x += self.throwBackSpeed;
self.throwBackDistance += Math.abs(self.throwBackSpeed);
self.throwBackSpeed *= 0.95;
// Show hit animation
var hitOffset = self.runAnimation.length;
self.sprites[hitOffset].alpha = 1;
// Decrease hit timer
self.hitTimer--;
// Once hit timer expires, start death animation
if (self.hitTimer <= 0) {
self.isHit = false;
self.isDying = true;
self.deathTimer = 60;
self.deathFrame = 0;
}
};
// Update goblin dying animation
self.updateGoblinDyingState = function () {
// Continue throw back during death
self.x += self.throwBackSpeed;
// After halfway through death animation, match platform speed
if (self.deathFrame >= 2) {
self.x -= PLATFORM_SPEED;
}
self.throwBackSpeed *= 0.95;
// Handle death animation
var dieOffset = self.runAnimation.length + self.hitAnimation.length;
// Progress frame every 15 frames
if (self.deathTimer % 15 === 0 && self.deathFrame < self.dieAnimation.length - 1) {
self.deathFrame++;
}
self.sprites[dieOffset + self.deathFrame].alpha = 1;
// Count down death timer
self.deathTimer--;
// After timer expires, fade out
if (self.deathTimer <= 0) {
self.alpha -= 0.1;
if (self.alpha <= 0) {
self.destroy();
}
}
};
// Update goblin normal movement
self.updateGoblinNormalState = function () {
// Move left
self.x -= self.speed;
// Platform and gravity code
if (!self.isOnGround) {
self.velocityY += 0.7;
self.y += self.velocityY;
self.checkPlatformCollision();
}
// Ensure goblin stays on platform
if (self.currentPlatform) {
var stillOnPlatform = self.x >= self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x <= self.currentPlatform.x + PLATFORM_HALF_WIDTH;
if (!stillOnPlatform) {
var foundAnotherPlatform = false;
// Check for another platform
for (var i = 0; i < platforms.length; i++) {
var otherPlatform = platforms[i];
if (otherPlatform === self.currentPlatform) {
continue;
}
if (self.x >= otherPlatform.x - PLATFORM_HALF_WIDTH && self.x <= otherPlatform.x + PLATFORM_HALF_WIDTH && Math.abs(self.y - (otherPlatform.y - ENEMY_PLATFORM_OFFSET)) < 5) {
self.currentPlatform = otherPlatform;
foundAnotherPlatform = true;
break;
}
}
// Start falling if no other platform found
if (!foundAnotherPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
if (self.velocityY === 0) {
self.velocityY = 0.1;
}
}
}
}
// Animate running
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
}
self.sprites[self.runFrame].alpha = 1;
};
// Check platform collision
self.checkPlatformCollision = function () {
var onAnyPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var leftEdge = platform.x - PLATFORM_HALF_WIDTH;
var rightEdge = platform.x + PLATFORM_HALF_WIDTH;
// Check if we're on a platform (exact position check)
if (self.x >= leftEdge && self.x <= rightEdge) {
if (Math.abs(self.y - (platform.y - ENEMY_PLATFORM_OFFSET)) < 5) {
onAnyPlatform = true;
self.currentPlatform = platform;
self.y = platform.y - ENEMY_PLATFORM_OFFSET;
self.isOnGround = true;
self.velocityY = 0;
return true;
}
// Check for passing through platform during fall
if (self.velocityY > 0 && self.y < platform.y - ENEMY_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - ENEMY_PLATFORM_OFFSET) {
self.y = platform.y - ENEMY_PLATFORM_OFFSET;
self.velocityY = 0;
self.isOnGround = true;
self.currentPlatform = platform;
return true;
}
}
}
if (!onAnyPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
}
return false;
};
// Get collision bounds
self.getBounds = function () {
return {
left: self.x - self.hitboxWidth / 2,
right: self.x + self.hitboxWidth / 2,
top: self.y - self.hitboxHeight / 2,
bottom: self.y + self.hitboxHeight / 2
};
};
// Hit handling
self.hit = function () {
if (!self.isHit && !self.isDying) {
self.isHit = true;
self.throwBackSpeed = 25;
self.throwBackDistance = 0;
self.hitTimer = 35;
// Add particle effect
var particleOffset = self.type === 'eyeball' ? 175 : 250;
particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x);
}
};
// Main update method
self.update = function () {
// Hide all sprites first
self.hideAllSprites();
// Handle different enemy types and states
if (self.type === 'eyeball') {
if (self.isHit || self.isDying) {
self.updateEyeballDamageState();
} else {
self.updateEyeballNormalState();
}
} else {
// Goblin logic
if (self.isHit) {
self.updateGoblinHitState();
} else if (self.isDying) {
self.updateGoblinDyingState();
} else {
self.updateGoblinNormalState();
}
}
// Destroy if off screen
if (self.x < -50 || self.y > GAME_HEIGHT) {
self.destroy();
}
};
return self;
});
var HealthPotion = Container.expand(function () {
var self = Container.call(this);
// Create sprite
self.sprite = self.attachAsset('healthpotion', {
anchorX: 0.5,
anchorY: 0.8
});
// Initialize physics properties
CollectibleBehavior.initPhysics(self);
// Collection functionality
self.collect = function () {
if (!self.collected && player.currentHealth < player.maxHealth) {
self.collected = true;
player.currentHealth++;
player.heartContainer.updateHealth(player.currentHealth);
player.heartContainer.alpha = 1;
player.heartVisibilityTimer = player.heartVisibilityDuration;
// Create heart popup
var popup = new Text2('❤️', {
size: 80,
fill: 0xFF0000,
anchorX: 0.5,
anchorY: 0.5
});
popup.x = self.x;
popup.y = self.y - 30;
popup.velocityY = -3;
popup.lifespan = 45;
popup.update = function () {
this.y += this.velocityY;
this.lifespan--;
if (this.lifespan < 15) {
this.alpha -= 0.07;
}
if (this.alpha <= 0 || this.lifespan <= 0) {
this.destroy();
}
};
game.addChild(popup);
LK.getSound('potion').play();
self.destroy();
}
};
// Standard update using shared behavior
self.update = function () {
CollectibleBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
var HeartContainer = Container.expand(function () {
var self = Container.call(this);
self.maxHealth = 3;
self.currentHealth = 3;
self.hearts = [];
// Calculate total width of heart display
var heartSpacing = 80;
var totalWidth = (self.maxHealth - 1) * heartSpacing;
// Initialize hearts with centered positioning
for (var i = 0; i < self.maxHealth; i++) {
var heart = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xCC0000
});
heart.x = i * heartSpacing - totalWidth / 2;
heart.y = 0;
self.hearts.push(heart);
}
// Start invisible
self.alpha = 0;
self.updateHealth = function (newHealth) {
self.currentHealth = newHealth;
// Update heart display
for (var i = 0; i < self.maxHealth; i++) {
if (i < newHealth) {
// Full heart
self.hearts[i].tint = 0xFF0000;
self.hearts[i].alpha = 1;
} else {
// Empty heart
self.hearts[i].tint = 0x000000;
self.hearts[i].alpha = 0.5;
}
}
};
return self;
});
var Jar = Container.expand(function () {
var self = Container.call(this);
// Attach jar sprite
self.sprite = self.attachAsset('jar', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as breakable
BreakableBehavior.initBreakable(self);
// Break functionality
self["break"] = function () {
BreakableBehavior.standardBreak(self, JarPiece, 4, function (jar) {
// Spawn health potion with low chance
if (Math.random() < 0.05) {
var potion = new HealthPotion();
potion.x = jar.x;
potion.y = jar.y;
potion.velocityX = GameUtils.randomRange(4, 12);
potion.velocityY = -GameUtils.randomRange(12, 22);
game.addChild(potion);
coins.push(potion);
}
// Spawn coins
var coinCount = Math.floor(GameUtils.randomRange(1, 9));
for (var i = 0; i < coinCount; i++) {
var coin = new Coin();
coin.x = jar.x;
coin.y = jar.y;
coin.velocityX = GameUtils.randomRange(4, 12);
coin.velocityY = -GameUtils.randomRange(12, 22);
game.addChild(coin);
coins.push(coin);
}
LK.getSound('jarbreak').play();
});
};
return self;
});
var JarPiece = Container.expand(function (pieceNum) {
var self = Container.call(this);
// Attach piece sprite
self.sprite = self.attachAsset('jarpiece' + pieceNum, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as piece
PieceBehavior.initPiece(self);
// Update method
self.update = function () {
PieceBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
var ParticlePool = Container.expand(function (maxParticles) {
var self = Container.call(this);
self.particles = [];
self.activeParticles = [];
self.redTints = [0xff0000, 0xff3333, 0xcc0000];
for (var i = 0; i < maxParticles; i++) {
var particle = self.attachAsset('pixel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particle.alpha = 0;
particle.velocityX = 0;
particle.velocityY = 0;
particle.lifespan = 0;
particle.fadeSpeed = 0;
self.particles.push(particle);
}
self.emitFromHit = function (x, y, playerX) {
var directionX = x - playerX;
var directionSign = Math.sign(directionX);
for (var i = 0; i < 20; i++) {
if (self.particles.length === 0) {
break;
}
var particle = self.particles.pop();
self.activeParticles.push(particle);
particle.x = x;
particle.y = y;
particle.alpha = 1;
particle.tint = self.redTints[Math.floor(Math.random() * self.redTints.length)];
// Set scale
var particleSize = Math.random() * 0.2 + 0.2;
particle.scaleX = particleSize;
particle.scaleY = particleSize;
var angle = Math.random() * Math.PI / 2 - Math.PI / 4;
var speed = Math.random() * 5 + 10;
particle.velocityX = Math.cos(angle) * speed * directionSign;
particle.velocityY = Math.sin(angle) * speed;
particle.lifespan = 100;
particle.fadeSpeed = 1 / 60;
}
};
self.update = function () {
for (var i = self.activeParticles.length - 1; i >= 0; i--) {
var particle = self.activeParticles[i];
particle.x += particle.velocityX;
particle.y += particle.velocityY;
particle.alpha -= particle.fadeSpeed;
particle.lifespan--;
if (particle.lifespan <= 0 || particle.alpha <= 0) {
particle.alpha = 0;
self.activeParticles.splice(i, 1);
self.particles.push(particle);
}
}
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = PLATFORM_SPEED;
self.passed = false;
self.update = function () {
self.x -= self.speed;
if (self.x < -500) {
self.destroy();
}
};
return self;
});
// Player class with refactored animation management
var Player = Container.expand(function () {
var self = Container.call(this);
// Animation properties
self.runAnimation = ['playerrun1', 'playerrun2', 'playerrun3', 'playerrun4', 'playerrun5', 'playerrun6'];
self.jumpAnimation = ['playerjump1', 'playerjump2', 'playerjump3'];
self.attackAnimation = ['playerattack1', 'playerattack2', 'playerattack3', 'playerattack4', 'playerattack5'];
// Animation states
self.isAttacking = false;
self.attackFrame = 0;
self.runFrame = 0;
self.animationSpeed = 0.08;
self.attackAnimationSpeed = 0.15;
self.animationCounter = 0;
self.sprites = [];
// Physics properties
self.groundY = GAME_HEIGHT * 0.9;
self.hitboxWidth = 150;
self.hitboxHeight = 300;
self.attackHitboxWidth = 200;
self.attackHitboxHeight = 400;
self.attackHitboxOffset = 50;
// Platform collision properties
self.isOnGround = true;
self.currentPlatform = null;
// Health properties
self.heartContainer = heartContainer;
self.maxHealth = 3;
self.currentHealth = 3;
self.isInvulnerable = false;
self.invulnerabilityDuration = 90;
self.invulnerabilityTimer = 0;
self.heartVisibilityTimer = 0;
self.heartVisibilityDuration = 120;
// Movement properties
self.speed = 5;
self.jumpHeight = 40;
self.isJumping = false;
self.velocityY = 0;
self.jumpState = "none";
self.jumpStartTime = 0;
// Initialize animation sprites
self.initAnimations = function () {
// Run animations
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Jump animations
for (var i = 0; i < self.jumpAnimation.length; i++) {
var sprite = self.attachAsset(self.jumpAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Attack animations
for (var i = 0; i < self.attackAnimation.length; i++) {
var sprite = self.attachAsset(self.attackAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Call initialization
self.initAnimations();
// Get collision bounds
self.getBounds = function () {
return {
left: self.x - self.hitboxWidth / 2,
right: self.x + self.hitboxWidth / 2,
top: self.y - self.hitboxHeight / 2,
bottom: self.y + self.hitboxHeight / 2
};
};
// Get attack hitbox
self.getAttackBounds = function () {
if (!self.isAttacking) {
return null;
}
return {
left: self.x + (self.attackHitboxOffset - self.attackHitboxWidth / 2),
right: self.x + (self.attackHitboxOffset + self.attackHitboxWidth / 2),
top: self.y - self.attackHitboxHeight / 2,
bottom: self.y + self.attackHitboxHeight / 2
};
};
// Update heart container
self.updateHeartContainer = function () {
self.heartContainer.x = self.x + 80;
self.heartContainer.y = self.y - 200;
// Handle heart visibility
if (self.heartVisibilityTimer > 0) {
self.heartVisibilityTimer--;
if (self.heartVisibilityTimer <= 0) {
self.heartContainer.alpha = 0;
}
}
};
// Update invulnerability state
self.updateInvulnerability = function () {
if (self.isInvulnerable) {
self.invulnerabilityTimer--;
// Flash only the player, not the hearts
self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1;
if (self.invulnerabilityTimer <= 0) {
self.isInvulnerable = false;
self.alpha = 1;
}
}
};
// Handle platform collision
self.handlePlatformCollision = function () {
// Already checks in jumpstate
if (!self.isOnGround && !self.isJumping) {
self.velocityY += 0.7;
self.y += self.velocityY;
self.checkPlatformCollision();
}
// Check if on any platform
var onAnyPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var leftEdge = platform.x - PLATFORM_HALF_WIDTH;
var rightEdge = platform.x + PLATFORM_HALF_WIDTH;
// Check if player is within horizontal bounds of platform
if (self.x >= leftEdge && self.x <= rightEdge) {
// If we're at platform height and not jumping
if (Math.abs(self.y - (platform.y - PLAYER_PLATFORM_OFFSET)) < 5 && !self.isJumping) {
onAnyPlatform = true;
self.currentPlatform = platform;
self.y = platform.y - PLAYER_PLATFORM_OFFSET;
self.isOnGround = true;
self.velocityY = 0;
break;
}
}
}
// Handle falling state
if (!onAnyPlatform && !self.isJumping) {
self.isOnGround = false;
self.currentPlatform = null;
// Apply gravity
if (self.velocityY === 0) {
self.velocityY = 0.1;
}
self.velocityY += 0.1;
self.y += self.velocityY;
// Ground collision check comes AFTER falling movement
if (self.y >= self.groundY) {
// Reached the bottom of the screen - game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
// If on a platform, check if still above it
if (self.currentPlatform) {
var stillOnPlatform = self.x > self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x < self.currentPlatform.x + PLATFORM_HALF_WIDTH;
if (!stillOnPlatform) {
var foundAnotherPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var otherPlatform = platforms[i];
if (otherPlatform === self.currentPlatform) {
continue;
}
if (self.x > otherPlatform.x - PLATFORM_HALF_WIDTH && self.x < otherPlatform.x + PLATFORM_HALF_WIDTH) {
// Found another platform
self.currentPlatform = otherPlatform;
foundAnotherPlatform = true;
break;
}
}
// Fall if no other platform found
if (!foundAnotherPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
self.velocityY = 0.1;
}
}
}
};
// Update attack animation
self.updateAttackAnimation = function () {
var attackOffset = self.runAnimation.length + self.jumpAnimation.length;
self.animationCounter += self.attackAnimationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.attackFrame++;
if (self.attackFrame >= self.attackAnimation.length) {
self.isAttacking = false;
self.attackFrame = 0;
}
}
self.sprites[attackOffset + self.attackFrame].alpha = 1;
};
// Update jump animation
self.updateJumpAnimation = function () {
var jumpOffset = self.runAnimation.length;
var currentTime = Date.now();
self.y += self.velocityY;
self.velocityY += 0.7;
// Check for landing on platforms while falling
self.checkPlatformCollision();
// End jump if hitting the ground
if (self.y >= self.groundY && !self.currentPlatform) {
self.y = self.groundY;
self.isJumping = false;
self.velocityY = 0;
self.jumpState = "none";
self.isOnGround = true;
}
// Show appropriate jump frame
if (currentTime - self.jumpStartTime < 100) {
self.sprites[jumpOffset + 0].alpha = 1;
} else if (self.velocityY < 0) {
self.sprites[jumpOffset + 1].alpha = 1;
} else if (self.velocityY > 0) {
self.sprites[jumpOffset + 2].alpha = 1;
}
};
// Update run animation
self.updateRunAnimation = function () {
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
}
self.sprites[self.runFrame].alpha = 1;
};
// Check platform collision
self.checkPlatformCollision = function () {
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Check if player is above the platform and falling
if (self.velocityY > 0 && self.y < platform.y - PLAYER_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - PLAYER_PLATFORM_OFFSET && self.x > platform.x - PLATFORM_HALF_WIDTH && self.x < platform.x + PLATFORM_HALF_WIDTH) {
// Land on the platform
self.y = platform.y - PLAYER_PLATFORM_OFFSET;
self.velocityY = 0;
self.isJumping = false;
self.isOnGround = true;
self.currentPlatform = platform;
return;
}
}
};
// Hide all sprites
self.hideAllSprites = function () {
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
};
// Jump method
self.jump = function () {
if (self.isOnGround) {
self.isJumping = true;
self.isOnGround = false;
self.velocityY = -self.jumpHeight;
self.jumpState = "start";
self.jumpStartTime = Date.now();
LK.getSound('playerjump').play();
self.currentPlatform = null;
} else if (self.isJumping && self.velocityY < 10) {
// Small double-jump to reach higher platforms
self.velocityY = -self.jumpHeight * 0.7;
self.jumpStartTime = Date.now();
}
};
// Attack method
self.attack = function () {
if (!self.isAttacking) {
self.isAttacking = true;
self.attackFrame = 0;
self.animationCounter = 0;
LK.getSound('swordslash').play();
}
};
// Take damage method
self.takeDamage = function () {
if (!self.isInvulnerable) {
self.currentHealth--;
self.heartContainer.updateHealth(self.currentHealth);
// Show hearts and set visibility timer
self.heartContainer.alpha = 1;
self.heartVisibilityTimer = self.heartVisibilityDuration;
// Visual feedback
self.isInvulnerable = true;
self.invulnerabilityTimer = self.invulnerabilityDuration;
// Flash player red
self.tint = 0xFF0000;
tween(self, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
if (self.currentHealth <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
};
// Main update method
self.update = function () {
// Update heart container position
self.updateHeartContainer();
// Hide all sprites
self.hideAllSprites();
// Handle platform collision and falling
self.handlePlatformCollision();
// Handle invulnerability
self.updateInvulnerability();
// Update animations based on state
if (self.isAttacking) {
self.updateAttackAnimation();
} else if (self.isJumping || !self.isOnGround) {
self.updateJumpAnimation();
} else if (self.isOnGround) {
self.updateRunAnimation();
}
// Check for falling off screen
if (self.y > GAME_HEIGHT) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
var ScorePopup = Container.expand(function (x, y, amount) {
var self = Container.call(this);
// Create the text
self.text = new Text2('+' + amount, {
size: 80,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.text);
self.x = x;
self.y = y;
self.velocityY = -3;
self.lifespan = 45;
self.update = function () {
self.y += self.velocityY;
self.lifespan--;
if (self.lifespan < 15) {
self.alpha -= 0.07;
}
if (self.alpha <= 0 || self.lifespan <= 0) {
self.destroy();
}
};
return self;
});
/****
* Constants
****/
var Torch = Container.expand(function () {
var self = Container.call(this);
// Create base torch sprite
self.base = self.attachAsset('torch', {
anchorX: 0.5,
anchorY: 1
});
// Create flame sprite
self.flame = self.attachAsset('torchflame', {
anchorX: 0.5,
anchorY: 1,
y: -180
});
// Create aura sprite
self.aura = self.attachAsset('torchaura', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
y: -250
});
// Animation properties
self.flameTime = Math.random() * Math.PI * 2;
self.auraTime = Math.random() * Math.PI * 2;
self.flameSpeed = 0.05;
self.auraSpeed = 0.03;
// Update animation
self.update = function () {
// Animate flame scale
self.flameTime += self.flameSpeed;
var flameScale = 1 + Math.sin(self.flameTime) * 0.2;
self.flame.scaleY = flameScale;
// Random flip chance for flame
if (Math.random() < 0.02) {
self.flame.scaleX *= -1;
}
// Animate aura alpha
self.auraTime += self.auraSpeed;
var auraAlpha = 0.3 + Math.sin(self.auraTime) * 0.15;
self.aura.alpha = auraAlpha;
};
return self;
});
var TreasureChest = Container.expand(function () {
var self = Container.call(this);
// Attach chest sprite
self.sprite = self.attachAsset('treasurechest', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as breakable
BreakableBehavior.initBreakable(self);
// Break functionality
self["break"] = function () {
BreakableBehavior.standardBreak(self, TreasureChestPiece, 4, function (chest) {
// Spawn health potion with medium chance
if (Math.random() < 0.25) {
var potion = new HealthPotion();
potion.x = chest.x;
potion.y = chest.y;
potion.velocityX = GameUtils.randomRange(2, 12);
potion.velocityY = -GameUtils.randomRange(14, 26);
game.addChild(potion);
coins.push(potion);
}
// Spawn valuable items
var totalItems = Math.floor(GameUtils.randomRange(3, 9));
for (var i = 0; i < totalItems; i++) {
// Random chance for different gems
var rand = Math.random();
var item;
if (rand < 0.05) {
// 5% diamond
item = new Coin('diamond');
} else if (rand < 0.15) {
// 10% emerald
item = new Coin('emerald');
} else if (rand < 0.30) {
// 15% ruby
item = new Coin('ruby');
} else {
// 70% gold coin
item = new Coin('coin');
}
item.x = chest.x;
item.y = chest.y;
item.velocityX = GameUtils.randomRange(2, 12);
item.velocityY = -GameUtils.randomRange(14, 26);
game.addChild(item);
coins.push(item);
}
LK.getSound('woodbreak').play();
});
};
return self;
});
var TreasureChestPiece = Container.expand(function (pieceNum) {
var self = Container.call(this);
// Attach piece sprite
self.sprite = self.attachAsset('treasurechestpiece' + pieceNum, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as piece
PieceBehavior.initPiece(self);
// Update method
self.update = function () {
PieceBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
/****
* Initialize Game
****/
/****
* Game Initialization
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Base collectible behavior for items that can be collected
/****
* Game Management
****/
var CollectibleBehavior = {
initPhysics: function initPhysics(self) {
self.velocityX = 0;
self.velocityY = 0;
self.collected = false;
self.bounceCount = 0;
self.maxBounces = 2;
},
standardUpdate: function standardUpdate(self) {
if (self.collected) {
return;
}
// Apply physics
self.velocityY += 0.5; // gravity
self.x += self.velocityX;
self.y += self.velocityY;
// Check for platform collision with bounce
if (self.velocityY > 0) {
GameUtils.checkPlatformCollision(self, 80, true);
}
// Check if off screen
if (self.x < -50 || self.x > GAME_WIDTH + 50 || self.y > GAME_HEIGHT) {
self.destroy();
return;
}
// Player collection detection
var playerBounds = player.getBounds();
var itemBounds = {
left: self.x - 25,
right: self.x + 25,
top: self.y - 25,
bottom: self.y + 25
};
if (GameUtils.checkCollision(playerBounds, itemBounds)) {
self.collect();
}
}
};
// Base behavior for breakable objects
var BreakableBehavior = {
initBreakable: function initBreakable(self) {
self.isBreaking = false;
self.currentPlatform = null;
},
standardBreak: function standardBreak(self, pieceClass, pieceCount, itemSpawnCallback) {
if (self.isBreaking) {
return;
}
self.isBreaking = true;
// Spawn pieces
for (var i = 1; i <= pieceCount; i++) {
var piece = new pieceClass(i);
piece.x = self.x;
piece.y = self.y;
piece.velocityX = GameUtils.randomRange(-6, 6);
piece.velocityY = -GameUtils.randomRange(6, 12);
piece.rotationSpeed = GameUtils.randomRange(-0.1, 0.1);
game.addChild(piece);
}
// Call the custom item spawn callback
if (itemSpawnCallback) {
itemSpawnCallback(self);
}
self.destroy();
}
};
// Base behavior for pieces of broken objects
var PieceBehavior = {
initPiece: function initPiece(self) {
self.velocityX = 0;
self.velocityY = 0;
self.rotationSpeed = 0;
self.fadeSpeed = 0.02;
self.bounceCount = 0;
self.maxBounces = 2;
},
standardUpdate: function standardUpdate(self) {
// Apply physics
self.velocityY += 0.5; // gravity
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += self.rotationSpeed;
// Check for platform collision
var platformCollision = GameUtils.checkPlatformCollision(self, 80, self.bounceCount < self.maxBounces);
if (platformCollision && self.bounceCount >= self.maxBounces) {
// Start fading after max bounces
self.velocityY = 0;
self.velocityX = -PLATFORM_SPEED;
self.alpha -= self.fadeSpeed;
if (self.alpha <= 0) {
self.destroy();
}
}
// Destroy if off screen
if (self.x < -50 || self.y > GAME_HEIGHT) {
self.destroy();
}
}
};
/****
* Constants
****/
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLATFORM_WIDTH = 1000;
var PLATFORM_HALF_WIDTH = PLATFORM_WIDTH / 2;
var PLATFORM_OVERLAP = 50;
var PLATFORM_SPEED = 5;
var ENEMY_PLATFORM_OFFSET = 225;
var PLAYER_PLATFORM_OFFSET = 250;
var MIN_PLATFORMS_IN_SEQUENCE = 2;
var MAX_PLATFORMS_IN_SEQUENCE = 5;
var JUMP_COOLDOWN = 250;
var MOVE_THRESHOLD = 40;
/****
* Utilities
****/
var GameUtils = {
// Check for collision between two rectangular bounds
checkCollision: function checkCollision(bounds1, bounds2) {
return bounds1.left < bounds2.right && bounds1.right > bounds2.left && bounds1.top < bounds2.bottom && bounds1.bottom > bounds2.top;
},
// Check platform collision with standard offset
checkPlatformCollision: function checkPlatformCollision(obj, offsetY, bounceOnCollision) {
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
if (Math.abs(obj.y - (platform.y - offsetY)) < 10 && obj.x > platform.x - PLATFORM_HALF_WIDTH && obj.x < platform.x + PLATFORM_HALF_WIDTH) {
obj.y = platform.y - offsetY;
if (bounceOnCollision && obj.bounceCount < obj.maxBounces) {
LK.getSound('coinbounce').play();
var impactSpeed = Math.abs(obj.velocityY);
obj.velocityY = -(impactSpeed * 0.5);
obj.velocityX *= 0.8;
obj.bounceCount++;
return true;
} else if (bounceOnCollision) {
obj.velocityY = 0;
obj.velocityX = -PLATFORM_SPEED;
}
return platform;
}
}
return null;
},
// Get random value within range
randomRange: function randomRange(min, max) {
return Math.random() * (max - min) + min;
},
// Check if position is clear for spawning
canSpawnAtPosition: function canSpawnAtPosition(x) {
var safeDistance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200;
for (var i = 0; i < collectibles.length; i++) {
if (Math.abs(collectibles[i].x - x) < safeDistance) {
return false;
}
}
return true;
}
};
var ScoreManager = function ScoreManager() {
var self = {};
// Initialize score and UI
self.score = 0;
self.scoreText = new Text2('0', {
size: 160,
fill: 0xFFFFFF,
anchorX: 1,
anchorY: 0.5
});
self.coinIcon = LK.getAsset('coin', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// Initialize container
self.container = new Container();
self.container.addChild(self.scoreText);
self.container.addChild(self.coinIcon);
// Position container
self.container.x = GAME_WIDTH - 100;
self.container.y = 100;
// Position elements
self.scoreText.x = -100;
self.scoreText.y = -100;
self.coinIcon.x = 0;
self.coinIcon.y = 0;
// Getter and setter for score
self.getScore = function () {
return self.score;
};
self.setScore = function (newScore) {
self.score = newScore;
self.scoreText.setText(newScore);
// Adjust position based on digit count
if (newScore >= 10 && newScore < 100) {
self.scoreText.x = -200;
} else if (newScore >= 100 && newScore < 1000) {
self.scoreText.x = -300;
} else if (newScore >= 1000) {
self.scoreText.x = -400;
} else {
self.scoreText.x = -100;
}
};
// Add score and display popup
self.addScore = function (amount, x, y) {
self.setScore(self.score + amount);
LK.setScore(self.score);
// Create score popup if position is provided
if (x !== undefined && y !== undefined) {
var popup = new ScorePopup(x, y - 30, amount);
game.addChild(popup);
}
};
return self;
};
/****
* Game Variables
****/
// Containers
var backgroundContainer = game.addChild(new Container());
var midgroundContainer = game.addChild(new Container());
var foregroundContainer = game.addChild(new Container());
var scoreManager = new ScoreManager();
game.addChild(scoreManager.container);
var scoreContainer = game.addChild(new Container());
// Game state
var gameStarted = false;
var titleScreen;
var playButton;
// Platform management
var platforms = [];
var platformSpawnCounter = 0;
var platformsUntilNextChange = 5;
var lowPlatformHeight = GAME_HEIGHT / 1.5 + PLAYER_PLATFORM_OFFSET;
var highPlatformHeight = lowPlatformHeight - 600;
var currentPlatformHeight = lowPlatformHeight;
var lastPlatformHeight = lowPlatformHeight;
var lastPlatformX = 0;
// Touch controls
var touchStartX = 0;
var touchStartY = 0;
var touchEndX = 0;
var touchEndY = 0;
var lastMoveY = 0;
var lastJumpTime = 0;
// Game objects
var jars = [];
var coins = [];
var collectibles = [];
var jarSpawnCounter = 0;
var jarSpawnInterval = 20;
var chestSpawnCounter = 0;
var chestSpawnInterval = 100;
var enemies = [];
var enemySpawnInterval = 100;
var goblinSpawnCounter = 0;
var goblinSpawnInterval = 100;
var eyeballSpawnCounter = 150;
var eyeballSpawnInterval = 500;
var particleSystem;
var heartContainer = new HeartContainer();
var player;
// Background elements
var bg1 = backgroundContainer.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 1
}));
var bg2 = backgroundContainer.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 1
}));
bg1.y = GAME_HEIGHT;
bg2.y = GAME_HEIGHT;
bg2.x = GAME_WIDTH;
// Midground elements
var mg1 = midgroundContainer.addChild(LK.getAsset('midground', {
anchorX: 0,
anchorY: 1
}));
var mg2 = midgroundContainer.addChild(LK.getAsset('midground', {
anchorX: 0,
anchorY: 1
}));
mg1.y = GAME_HEIGHT;
mg2.y = GAME_HEIGHT;
mg2.x = GAME_WIDTH;
// Foreground elements
var fg1 = foregroundContainer.addChild(LK.getAsset('foreground', {
anchorX: 0,
anchorY: 1
}));
var fg2 = foregroundContainer.addChild(LK.getAsset('foreground', {
anchorX: 0,
anchorY: 1
}));
fg1.y = GAME_HEIGHT * 1.25;
fg2.y = GAME_HEIGHT * 1.25;
fg1.x = 0;
fg2.x = GAME_WIDTH;
/****
* Game Functions
****/
// Create the title screen
function createTitleScreen() {
titleScreen = new Container();
game.addChild(titleScreen);
// Add title image with fade-in
var titleImage = titleScreen.attachAsset('title', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
titleImage.x = GAME_WIDTH / 2;
titleImage.y = GAME_HEIGHT / 2.7;
// Fade in title
tween(titleImage, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeIn
});
// Add play button
playButton = titleScreen.attachAsset('playbutton', {
anchorX: 0.5,
anchorY: 0.5
});
playButton.x = GAME_WIDTH / 2;
playButton.y = GAME_HEIGHT / 1.3;
// Add flashing animation
function flashPlayButton() {
tween(playButton, {
alpha: 0
}, {
duration: 250,
easing: tween.linear,
onFinish: function onFinish() {
tween(playButton, {
alpha: 1
}, {
duration: 250,
easing: tween.linear
});
}
});
}
// Flash every 2 seconds
LK.setInterval(flashPlayButton, 2000);
// Initialize torch decorations
initializeTorches();
}
// Place torches in the scene
function initializeTorches() {
// Create torches for the two background sections
var torch1 = new Torch();
torch1.x = 25;
torch1.y = GAME_HEIGHT * 0.7;
midgroundContainer.addChild(torch1);
var torch2 = new Torch();
torch2.x = GAME_WIDTH + 25;
torch2.y = GAME_HEIGHT * 0.7;
midgroundContainer.addChild(torch2);
}
// Initialize game elements
function initializeGame() {
// Create player
player = game.addChild(new Player());
player.x = GAME_WIDTH / 4.5;
player.y = GAME_HEIGHT / 1.5;
game.addChild(heartContainer);
// Create initial platforms at the low level
for (var i = 0; i < 5; i++) {
var platform = new Platform();
if (i === 0) {
// First platform centered on player
platform.x = player.x;
} else {
// Position with slight overlap
platform.x = lastPlatformX + PLATFORM_WIDTH - PLATFORM_OVERLAP;
}
platform.y = lowPlatformHeight;
platforms.push(platform);
game.addChild(platform);
lastPlatformX = platform.x;
}
lastPlatformHeight = lowPlatformHeight;
player.isOnGround = true;
player.currentPlatform = platforms[0];
}
// Start the game
function startGame() {
gameStarted = true;
titleScreen.destroy();
initializeGame();
// Initialize particle system
particleSystem = new ParticlePool(100);
game.addChild(particleSystem);
// Show health
player.heartContainer.alpha = 1;
player.heartVisibilityTimer = 120;
// Play background music
LK.playMusic('backgroundmusic1', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
}
// Update background layers
function updateBackgrounds() {
// Background layer (slowest)
bg1.x -= PLATFORM_SPEED * 0.3;
bg2.x -= PLATFORM_SPEED * 0.3;
if (bg1.x <= -GAME_WIDTH) {
bg1.x = bg2.x + GAME_WIDTH;
}
if (bg2.x <= -GAME_WIDTH) {
bg2.x = bg1.x + GAME_WIDTH;
}
// Midground layer
mg1.x -= PLATFORM_SPEED * 0.6;
mg2.x -= PLATFORM_SPEED * 0.6;
if (mg1.x <= -GAME_WIDTH) {
mg1.x = mg2.x + GAME_WIDTH;
}
if (mg2.x <= -GAME_WIDTH) {
mg2.x = mg1.x + GAME_WIDTH;
}
// Foreground layer (fastest)
fg1.x -= PLATFORM_SPEED;
fg2.x -= PLATFORM_SPEED;
if (fg1.x <= -GAME_WIDTH) {
fg1.x = fg2.x + GAME_WIDTH;
}
if (fg2.x <= -GAME_WIDTH) {
fg2.x = fg1.x + GAME_WIDTH;
}
// Update torches
for (var i = 0; i < midgroundContainer.children.length; i++) {
var child = midgroundContainer.children[i];
if (child instanceof Torch) {
child.update();
child.x -= PLATFORM_SPEED * 0.6;
if (child.x <= -GAME_WIDTH) {
child.x = child.x + GAME_WIDTH * 2;
}
}
}
}
// Update and spawn platforms
function updatePlatforms() {
// Check if we need to spawn a new sequence of platforms
var lastPlatform = platforms[platforms.length - 1];
if (lastPlatform && lastPlatform.x < GAME_WIDTH + 500) {
// Time to spawn a new sequence
if (platformsUntilNextChange <= 0) {
// Switch height
currentPlatformHeight = currentPlatformHeight === lowPlatformHeight ? highPlatformHeight : lowPlatformHeight;
// Generate new random sequence length
platformsUntilNextChange = Math.floor(Math.random() * (MAX_PLATFORMS_IN_SEQUENCE - MIN_PLATFORMS_IN_SEQUENCE + 1)) + MIN_PLATFORMS_IN_SEQUENCE;
// Make ground sequences longer
if (currentPlatformHeight === lowPlatformHeight) {
platformsUntilNextChange += 5;
}
}
// Spawn a single platform in the sequence
var platform = new Platform();
platform.x = lastPlatform.x + (PLATFORM_WIDTH - PLATFORM_OVERLAP);
platform.y = currentPlatformHeight;
platforms.push(platform);
game.addChild(platform);
// Decrement the counter for each platform added
platformsUntilNextChange--;
}
// Update platforms
for (var i = platforms.length - 1; i >= 0; i--) {
platforms[i].update();
// Remove platforms that are destroyed
if (platforms[i].destroyed) {
platforms.splice(i, 1);
}
}
}
// Update and spawn collectibles
function updateCollectibles() {
// Jar spawning
jarSpawnCounter++;
if (jarSpawnCounter >= jarSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300;
});
if (availablePlatforms.length > 0 && Math.random() < 0.3) {
var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)];
// Only spawn if position is clear
if (GameUtils.canSpawnAtPosition(platform.x)) {
var jar = new Jar();
jar.x = platform.x;
jar.y = platform.y - 130;
jar.currentPlatform = platform;
collectibles.push(jar);
game.addChild(jar);
}
}
jarSpawnCounter = 0;
}
// Treasure chest spawning
chestSpawnCounter++;
if (chestSpawnCounter >= chestSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300;
});
if (availablePlatforms.length > 0 && Math.random() < 0.15) {
var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)];
// Only spawn if position is clear
if (GameUtils.canSpawnAtPosition(platform.x)) {
var chest = new TreasureChest();
chest.x = platform.x;
chest.y = platform.y - 130;
chest.currentPlatform = platform;
collectibles.push(chest);
game.addChild(chest);
}
}
chestSpawnCounter = 0;
}
// Update existing collectibles
for (var i = collectibles.length - 1; i >= 0; i--) {
var collectible = collectibles[i];
if (collectible.currentPlatform) {
collectible.x = collectible.currentPlatform.x;
}
var attackBounds = player.getAttackBounds();
if (attackBounds) {
var itemBounds = {
left: collectible.x - 50,
right: collectible.x + 50,
top: collectible.y - 75,
bottom: collectible.y + 75
};
if (GameUtils.checkCollision(attackBounds, itemBounds)) {
collectible["break"]();
collectibles.splice(i, 1);
continue;
}
}
if (collectible.x < -50) {
collectible.destroy();
collectibles.splice(i, 1);
}
}
// Update coins and other collectibles
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].update();
if (coins[i].destroyed) {
coins.splice(i, 1);
}
}
// Update score popups
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child instanceof ScorePopup) {
child.update();
}
}
}
// Update and spawn enemies
function updateEnemies() {
// Goblin spawning
goblinSpawnCounter++;
if (goblinSpawnCounter >= goblinSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH - 100 && p.x < GAME_WIDTH + 300;
});
if (availablePlatforms.length > 0) {
var enemy = new Enemy('goblin');
var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)];
enemy.x = platform.x;
enemy.y = platform.y - ENEMY_PLATFORM_OFFSET;
enemy.currentPlatform = platform;
enemies.push(enemy);
game.addChild(enemy);
goblinSpawnInterval = Math.floor(Math.random() * 150) + 100;
goblinSpawnCounter = 0;
} else {
// No valid platforms, reset counter but don't spawn
goblinSpawnCounter = Math.max(0, goblinSpawnCounter - 20);
}
}
// Eyeball spawning
eyeballSpawnCounter++;
if (eyeballSpawnCounter >= eyeballSpawnInterval) {
var enemy = new Enemy('eyeball');
var randomHeight = Math.random() * 400 + 200;
enemy.x = GAME_WIDTH + 100;
enemy.y = highPlatformHeight - randomHeight;
enemies.push(enemy);
game.addChild(enemy);
eyeballSpawnInterval = Math.floor(Math.random() * 300) + 150;
eyeballSpawnCounter = 0;
}
// Update enemies and check collisions
for (var j = enemies.length - 1; j >= 0; j--) {
enemies[j].update();
// Skip if enemy is far behind player
if (enemies[j].x < player.x - 100) {
continue;
}
var playerBounds = player.getBounds();
var enemyBounds = enemies[j].getBounds();
var attackBounds = player.getAttackBounds();
// Check for attack collision first
if (attackBounds && !enemies[j].isHit && !enemies[j].isDying) {
if (enemies[j].x > player.x && GameUtils.checkCollision(attackBounds, enemyBounds)) {
enemies[j].hit();
if (enemies[j].type === 'eyeball') {
LK.getSound('eyeballhit').play();
} else {
LK.getSound('enemyhit').play();
}
continue;
}
}
// Check for body collision second
if (GameUtils.checkCollision(playerBounds, enemyBounds)) {
if (!enemies[j].isHit && !enemies[j].isDying) {
player.takeDamage();
}
}
}
}
// Create the initial title screen
createTitleScreen();
/****
* Game Loop & Input Handlers
****/
// Main game update loop
game.update = function () {
// Always update backgrounds for visual effect
updateBackgrounds();
// If game hasn't started, don't update gameplay
if (!gameStarted) {
return;
}
// Update game entities
player.update();
if (particleSystem) {
particleSystem.update();
}
// Update game world
updatePlatforms();
updateCollectibles();
updateEnemies();
};
// Handle touch/click events
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
if (!gameStarted) {
var buttonBounds = {
left: playButton.x - playButton.width / 2,
right: playButton.x + playButton.width / 2,
top: playButton.y - playButton.height / 2,
bottom: playButton.y + playButton.height / 2
};
if (x >= buttonBounds.left && x <= buttonBounds.right && y >= buttonBounds.top && y <= buttonBounds.bottom) {
startGame();
}
return;
}
touchEndX = x;
touchEndY = y;
var deltaY = touchStartY - touchEndY;
var deltaX = Math.abs(touchStartX - touchEndX);
var currentTime = Date.now();
if (deltaY > 50 && deltaY > deltaX) {
if (currentTime - lastJumpTime > JUMP_COOLDOWN) {
player.jump();
lastJumpTime = currentTime;
}
} else {
// Only attack if it wasn't a jump attempt
player.attack();
}
};
game.move = function (x, y, obj) {
if (!gameStarted) {
return;
}
if (lastMoveY !== 0) {
var deltaY = y - lastMoveY;
var currentTime = Date.now();
if (deltaY < -MOVE_THRESHOLD && currentTime - lastJumpTime > JUMP_COOLDOWN) {
player.jump();
lastJumpTime = currentTime;
}
}
lastMoveY = y;
};
2D Single Monster. In-Game asset. 2d. Blank background. High contrast. No shadows..
A gold coin. 8 bit pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Dark and moody dungeon background. Infinite repeatable texture. 8 bit pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A ruby. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A wooden arrow with white feathers and a steel arrow head. Horizontal. Pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A thin crude sword, no pommel. 8 bit pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
An icon of white wings. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a magnet icon. 8 bit pixel art. In-Game asset. 2d. High contrast. No shadows
An icon of a glowing wooden arrow from a bow trailing pink particles. Pixel art.. In-Game asset. 2d. High contrast. No shadows
backgroundmusic1
Music
playerjump
Sound effect
swordslash
Sound effect
jarbreak
Sound effect
enemyhit
Sound effect
eyeballhit
Sound effect
coincollect
Sound effect
woodbreak
Sound effect
coinbounce
Sound effect
potion
Sound effect
playerouch
Sound effect
bowfiring
Sound effect
arrowfire
Sound effect
arrowpickup
Sound effect
gameover
Sound effect
skeletonhit
Sound effect
gameover2
Sound effect
shopbuy
Sound effect
menuselect
Sound effect
cantbuy
Sound effect
startgame
Sound effect
platformcrumble
Sound effect
rocksfall
Sound effect
airdash
Sound effect
groundimpact
Sound effect
groundsmashfalling
Sound effect
slide
Sound effect
mushroomburst
Sound effect
mushroomhiss
Sound effect
mushroomdie
Sound effect
mushroombounce
Sound effect