User prompt
The vertical trajectory is not being passed properly to the eyeball during the hit. Please analyze and propose fix.
Code edit (1 edits merged)
Please save this source code
User prompt
Add the same vertical trajectory to particle effects when the eyeball is hit.
User prompt
Add more vertical trajectory to the eyeball when it’s hit. Double it.
User prompt
When the player attacks an eyeball, compare their Y positions and add an appropriate amount of vertical trajectory to the pushback during eyeball hit animation. Do not add anything else.
User prompt
When the player attacks an eyeball, compare their Y positions and add an appropriate amount of vertical trajectory to the pushback during eyeball hit animation.
User prompt
When the player dies from taking damage, put the game over call in a 2 second timeout.
User prompt
When the player dies from taking damage, set game speed multiplier to 0
User prompt
Create a death animation sequence for player using playerdie1-8 assets.
User prompt
When the player loses their last health, instead of going right to game over, add a death animation using playerdie1-8 assets before calling game over.
User prompt
Add a red screen flash and a screen shake when player gets hit. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: LK.effects.shakeScreen is not a function. (In 'LK.effects.shakeScreen(10, 500)', 'LK.effects.shakeScreen' is undefined)' in or related to this line: 'LK.effects.shakeScreen(10, 500);' Line Number: 1232
User prompt
Add a red screen flash and a screen shake animation when the player gets hit.
User prompt
Update with: self.updateSlideAnimation = function () { // Fix the offset calculation to include airAttackAnimation var slideOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length; var standUpOffset = slideOffset + self.slideAnimation.length; self.slideTimer--; if (self.slideTimer > self.standUpDuration) { // Main slide animation (alternate between first two frames) self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; } var slideFrame = Math.floor(self.animationCounter * 2); self.sprites[slideOffset + slideFrame].alpha = 1; } else if (self.slideTimer > 0) { // Stand up animation var standUpFrame = Math.floor((self.standUpDuration - self.slideTimer) / (self.standUpDuration / self.standUpAnimation.length)); self.sprites[standUpOffset + standUpFrame].alpha = 1; } else { // End slide 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; } } };
Code edit (1 edits merged)
Please save this source code
User prompt
Reorder the player update so that attacking doesn’t happen during a slide.
User prompt
When a player attacks in while in the air, use the playerairattack1-4 assets for the animation.
User prompt
Play player ouch sound effect when player is hit.
Code edit (2 edits merged)
Please save this source code
User prompt
Update as needed with: 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; // Add dropLoot here when transitioning to dying state self.dropLoot(); } };
User prompt
Update with: updateEyeballDamageState method: 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; // Add dropLoot here when transitioning to dying state self.dropLoot(); } } // ... rest of the method };
User prompt
Update with: self.hit = function() { if (!self.isHit && !self.isDying) { self.isHit = true; self.throwBackSpeed = 25; self.throwBackDistance = 0; self.hitTimer = 35; // Drop loot when hit self.dropLoot(); // Add particle effect var particleOffset = self.type === 'eyeball' ? 175 : 250; particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x); } };
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Update as needed with: if (slideAttackBounds && !enemies[j].isHit && !enemies[j].isDying) { if (enemies[j].x > player.x && GameUtils.checkCollision(slideAttackBounds, enemyBounds)) { enemies[j].slideHit(); if (enemies[j].type === 'eyeball') { LK.getSound('eyeballhit').play(); } else { LK.getSound('enemyhit').play(); } continue; } }
/**** * 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; self.hitType = 'none'; // Can be 'none', 'attack', or 'slide' // Hitbox properties self.hitboxWidth = 200; self.hitboxHeight = self.type === 'eyeball' ? 90 : 260; // Animation properties self.sprites = []; self.animationCounter = 0; self.animationSpeed = 0.08; // Define all animation arrays 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']; } 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 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 - fixed to allow falling without platform collision 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; if (self.hitType === 'attack') { // Only start dying if it was a regular attack self.isDying = true; self.deathTimer = 180; self.deathFrame = 0; // Add dropLoot here when transitioning to dying state self.dropLoot(); } self.hitType = 'none'; } } else if (self.isDying) { var dieOffset = self.flyAnimation.length + self.hitAnimation.length; // Apply gravity without platform collision - this ensures constant falling self.velocityY += 0.5; self.y += self.velocityY; // Keep moving horizontally during fall self.x -= PLATFORM_SPEED * 0.5; // 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 * gameSpeedMultiplier; // 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, check hit type if (self.hitTimer <= 0) { self.isHit = false; if (self.hitType === 'attack') { // Only start death animation if it was a regular attack self.isDying = true; self.deathTimer = 60; self.deathFrame = 0; // Add dropLoot here when transitioning to dying state self.dropLoot(); } self.hitType = 'none'; } }; // 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 with speed multiplier self.x -= self.speed * gameSpeedMultiplier; // 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 }; }; self.slideHit = function () { if (!self.isHit && !self.isDying) { self.isHit = true; self.hitType = 'slide'; // Specify this is a slide hit self.throwBackSpeed = 25; self.throwBackDistance = 0; self.hitTimer = 30; // Add particle effect var particleOffset = self.type === 'eyeball' ? 175 : 250; particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x); } }; // Hit handling self.hit = function () { if (!self.isHit && !self.isDying) { self.isHit = true; self.hitType = 'attack'; // Specify this is a regular attack hit 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); } }; self.dropLoot = function () { // Determine number of coins to drop (1-5) var coinCount = Math.floor(GameUtils.randomRange(1, 6)); // Small chance for jewels (2% each) var dropJewel = Math.random(); if (dropJewel < 0.02) { // Diamond (rarest) var diamond = new Coin('diamond'); diamond.x = self.x; diamond.y = self.y; diamond.velocityX = GameUtils.randomRange(-4, 4); diamond.velocityY = -GameUtils.randomRange(10, 15); game.addChild(diamond); coins.push(diamond); } else if (dropJewel < 0.04) { // Emerald var emerald = new Coin('emerald'); emerald.x = self.x; emerald.y = self.y; emerald.velocityX = GameUtils.randomRange(-4, 4); emerald.velocityY = -GameUtils.randomRange(10, 15); game.addChild(emerald); coins.push(emerald); } else if (dropJewel < 0.06) { // Ruby var ruby = new Coin('ruby'); ruby.x = self.x; ruby.y = self.y; ruby.velocityX = GameUtils.randomRange(-4, 4); ruby.velocityY = -GameUtils.randomRange(10, 15); game.addChild(ruby); coins.push(ruby); } // Drop regular coins for (var i = 0; i < coinCount; i++) { var coin = new Coin(); coin.x = self.x; coin.y = self.y; coin.velocityX = GameUtils.randomRange(-4, 4); coin.velocityY = -GameUtils.randomRange(10, 15); game.addChild(coin); coins.push(coin); } }; // 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(); } }; // Initialize based on enemy type AFTER defining the methods if (self.type === 'eyeball') { self.initEyeballSprites(); } else if (self.type === 'goblin') { self.initGoblinSprites(); } else { // Basic enemy var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); } 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 = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF0000 }); popup.scaleX = 1.6; // Adjust scale to match original size popup.scaleY = 1.6; 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 -= PLATFORM_SPEED * gameSpeedMultiplier; 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']; self.airAttackAnimation = ['playerairattack1', 'playerairattack2', 'playerairattack3', 'playerairattack4']; self.slideAnimation = ['playerslide1', 'playerslide2']; self.standUpAnimation = ['playerstand1', 'playerstand2', 'playerstand3']; self.deathAnimation = ['playerdie1', 'playerdie2', 'playerdie3', 'playerdie4', 'playerdie5', 'playerdie6', 'playerdie7']; // Animation states self.isAttacking = false; self.attackFrame = 0; self.runFrame = 0; self.animationSpeed = 0.08; self.attackAnimationSpeed = 0.15; self.animationCounter = 0; self.sprites = []; // Death animation properties self.isDying = false; self.deathFrame = 0; self.deathTimer = 0; self.deathDuration = 100; self.deathAnimationSpeed = 0.1; // 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; self.isSliding = false; self.slideTimer = 0; self.slideDuration = 90; self.standUpDuration = 30; self.slideCooldown = 30; self.lastSlideTime = 0; self.slideSpeedMultiplier = 1.75; self.normalHitboxHeight = self.hitboxHeight; // Store original height self.slideHitboxHeight = 200; // Make it taller self.slideHitboxYOffset = 150; // Move it much lower // 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); } // Air attack animations for (var i = 0; i < self.airAttackAnimation.length; i++) { var sprite = self.attachAsset(self.airAttackAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Add slide animations for (var i = 0; i < self.slideAnimation.length; i++) { var sprite = self.attachAsset(self.slideAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Add stand up animations for (var i = 0; i < self.standUpAnimation.length; i++) { var sprite = self.attachAsset(self.standUpAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } // Add death animations for (var i = 0; i < self.deathAnimation.length; i++) { var sprite = self.attachAsset(self.deathAnimation[i], { anchorX: 0.5, anchorY: 0.5 }); sprite.alpha = 0; self.sprites.push(sprite); } }; // Call initialization self.initAnimations(); // Get collision bounds self.getBounds = function () { // For collecting items, always use the full-size hitbox return { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2, top: self.y - self.normalHitboxHeight / 2, bottom: self.y + self.normalHitboxHeight / 2 }; }; // Get attack hitbox self.getAttackBounds = function () { if (!self.isAttacking || self.isSliding) { // Add sliding check here 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 }; }; self.getSlideAttackBounds = function () { if (!self.isSliding) { return null; } return { left: self.x + self.hitboxWidth / 2, // Extend hitbox forward during slide right: self.x + self.hitboxWidth / 2 + 100, // Adjust width as needed top: self.y - self.slideHitboxHeight / 2 + self.slideHitboxYOffset, bottom: self.y + self.slideHitboxHeight / 2 + self.slideHitboxYOffset }; }; self.getCollisionBounds = function () { if (self.isSliding) { return { left: self.x - self.hitboxWidth / 2, right: self.x + self.hitboxWidth / 2, top: self.y - self.slideHitboxHeight / 2 + self.slideHitboxYOffset, bottom: self.y + self.slideHitboxHeight / 2 + self.slideHitboxYOffset }; } return self.getBounds(); }; // 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 () { // 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; } } // 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; } } } }; // Apply physics (separated from animation) - fixed to check collisions for ALL falling cases self.applyPhysics = function () { // Apply gravity and velocity to position if (!self.isOnGround || self.isJumping) { // Apply velocity and gravity self.y += self.velocityY; self.velocityY += 0.7; // Check for landing on platforms var hitPlatform = self.checkPlatformCollision(); // REMOVE THE GROUND CHECK - No invisible ground at the bottom! // We only want to land on actual platforms } // Check for falling off screen - moved this check here to ensure it happens // immediately after physics update if (self.y > GAME_HEIGHT) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } }; // 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 air attack animation self.updateAirAttackAnimation = function () { var airAttackOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length; self.animationCounter += self.attackAnimationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; self.attackFrame++; if (self.attackFrame >= self.airAttackAnimation.length) { self.isAttacking = false; self.attackFrame = 0; } } self.sprites[airAttackOffset + self.attackFrame].alpha = 1; }; // Update jump animation (only animation, not physics) self.updateJumpAnimation = function () { var jumpOffset = self.runAnimation.length; var currentTime = Date.now(); // 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 slide animation self.updateSlideAnimation = function () { var slideOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length; var standUpOffset = slideOffset + self.slideAnimation.length; self.slideTimer--; if (self.slideTimer > self.standUpDuration) { // Main slide animation (alternate between first two frames) self.animationCounter += self.animationSpeed; if (self.animationCounter >= 1) { self.animationCounter = 0; } var slideFrame = Math.floor(self.animationCounter * 2); self.sprites[slideOffset + slideFrame].alpha = 1; } else if (self.slideTimer > 0) { // Stand up animation var standUpFrame = Math.floor((self.standUpDuration - self.slideTimer) / (self.standUpDuration / self.standUpAnimation.length)); self.sprites[standUpOffset + standUpFrame].alpha = 1; } else { // End slide 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; } } }; // Update death animation self.updateDeathAnimation = function () { // Calculate offset to find the death animation sprites var deathOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length + self.slideAnimation.length + self.standUpAnimation.length; // Progress timer self.deathTimer++; // Calculate frame based on timer // We want the animation to play through all frames over the death duration var frameProgress = self.deathTimer / self.deathDuration * self.deathAnimation.length; var newFrame = Math.min(Math.floor(frameProgress), self.deathAnimation.length - 1); // Update frame if it's changed if (newFrame != self.deathFrame) { self.deathFrame = newFrame; } // Show current frame self.sprites[deathOffset + self.deathFrame].alpha = 1; // Death animation is complete, but game over is handled by the timeout if (self.deathTimer >= self.deathDuration) { // Game over is now triggered by the timeout in takeDamage } }; // 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 - fix to properly detect platforms when falling 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 true; } } return false; }; // Hide all sprites self.hideAllSprites = function () { for (var i = 0; i < self.sprites.length; i++) { self.sprites[i].alpha = 0; } }; self.checkCollectibles = function () { // Always check for collectibles regardless of state for (var i = 0; i < coins.length; i++) { var coin = coins[i]; var itemBounds = { left: coin.x - 25, right: coin.x + 25, top: coin.y - 25, bottom: coin.y + 25 }; if (GameUtils.checkCollision(self.getBounds(), itemBounds)) { coin.collect(); } } }; // Jump method 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(); } }; // Add slide method 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; } }; // Attack method self.attack = function () { if (!self.isAttacking && !self.isSliding) { // Add sliding check here self.isAttacking = true; self.attackFrame = 0; self.animationCounter = 0; LK.getSound('swordslash').play(); } }; // Take damage method self.takeDamage = function () { if (!self.isInvulnerable && !self.isDying) { LK.getSound('playerouch').play(); 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 }); // Add red screen flash LK.effects.flashScreen(0xff0000, 300); // Add screen shake effect applyScreenShake(); if (self.currentHealth <= 0) { // Instead of immediately showing game over, start death animation self.isDying = true; self.deathTimer = 0; self.deathFrame = 0; // Set game speed multiplier to 0 when player dies gameSpeedMultiplier = 0; // Flash screen LK.effects.flashScreen(0xff0000, 1000); // Apply one final screen shake for dramatic effect applyScreenShake(); // Schedule game over call after 2 seconds LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } }; // Main update method self.update = function () { // Update heart container self.updateHeartContainer(); // Hide all sprites self.hideAllSprites(); // If player is dying, only update death animation if (self.isDying) { self.updateDeathAnimation(); return; } // Apply physics FIRST - this now happens regardless of animation state self.applyPhysics(); // Handle platform collision and falling self.handlePlatformCollision(); // Handle invulnerability self.updateInvulnerability(); // Update animations based on state if (self.isSliding) { self.updateSlideAnimation(); self.checkCollectibles(); // Explicitly check during slide } else if (self.isAttacking) { if (self.isJumping || !self.isOnGround && !self.isSliding) { self.updateAirAttackAnimation(); } else { 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 && !self.isDying) { // Start death animation instead of immediately showing game over self.isDying = true; self.deathTimer = 0; self.deathFrame = 0; LK.effects.flashScreen(0xff0000, 1000); } }; 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; // Original throw physics self.y += self.velocityY; self.x -= PLATFORM_SPEED * (gameSpeedMultiplier - 1); // Only apply the extra speed from sliding // Check for platform collision with bounce if (self.velocityY > 0) { GameUtils.checkPlatformCollision(self, 80, true); } // Check if off screen if (self.x < -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(); } } }; // 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 = 120; var SLIDE_MOVE_THRESHOLD = 300; /**** * 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 gameSpeedMultiplier = 1.0; 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 ****/ // Screen shake effect function function applyScreenShake() { var shakeIntensity = 30; var shakeDuration = 400; var shakeCount = 8; var originalX = game.x; var originalY = game.y; // Stop any existing shake tween.stop(game, { x: true, y: true }); // Reset position to ensure we start from 0,0 game.x = 0; game.y = 0; // Recursive function to create multiple shakes function createShake(count) { if (count <= 0) { // Final tween to settle back to original position tween(game, { x: originalX, y: originalY }, { duration: shakeDuration / 4, easing: tween.easeOut }); return; } // Calculate decreasing intensity var currentIntensity = shakeIntensity * (count / shakeCount); // Random offset direction var offsetX = (Math.random() - 0.5) * 2 * currentIntensity; var offsetY = (Math.random() - 0.5) * 2 * currentIntensity; // Apply shake movement tween(game, { x: offsetX, y: offsetY }, { duration: shakeDuration / shakeCount, easing: tween.linear, onFinish: function onFinish() { createShake(count - 1); } }); } // Start the shake sequence createShake(shakeCount); } // 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 * gameSpeedMultiplier; bg2.x -= PLATFORM_SPEED * 0.3 * gameSpeedMultiplier; 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 * gameSpeedMultiplier; mg2.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier; 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 * gameSpeedMultiplier; fg2.x -= PLATFORM_SPEED * gameSpeedMultiplier; 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 * gameSpeedMultiplier; 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(); var slideAttackBounds = player.getSlideAttackBounds(); var itemBounds = { left: collectible.x - 50, right: collectible.x + 50, top: collectible.y - 75, bottom: collectible.y + 75 }; if (attackBounds && GameUtils.checkCollision(attackBounds, itemBounds) || slideAttackBounds && GameUtils.checkCollision(slideAttackBounds, 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.getCollisionBounds(); var enemyBounds = enemies[j].getBounds(); var attackBounds = player.getAttackBounds(); var slideAttackBounds = player.getSlideAttackBounds(); // 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 slide collision if (slideAttackBounds && !enemies[j].isHit && !enemies[j].isDying) { if (enemies[j].x > player.x && GameUtils.checkCollision(slideAttackBounds, enemyBounds)) { enemies[j].slideHit(); continue; } } 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 = touchEndX - touchStartX; var currentTime = Date.now(); if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (deltaX > 70) { // 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(); } }; game.move = function (x, y, obj) { if (!gameStarted) { return; } // Add horizontal movement check using MOVE_THRESHOLD var deltaX = x - touchStartX; if (Math.abs(deltaX) > Math.abs(y - touchStartY) && deltaX > SLIDE_MOVE_THRESHOLD) { player.slide(); } 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; };
===================================================================
--- original.js
+++ change.js
@@ -1063,12 +1063,11 @@
self.deathFrame = newFrame;
}
// Show current frame
self.sprites[deathOffset + self.deathFrame].alpha = 1;
- // When animation completes, trigger game over
+ // Death animation is complete, but game over is handled by the timeout
if (self.deathTimer >= self.deathDuration) {
- // Show game over after animation completes
- LK.showGameOver();
+ // Game over is now triggered by the timeout in takeDamage
}
};
// Update run animation
self.updateRunAnimation = function () {
@@ -1192,8 +1191,12 @@
// Flash screen
LK.effects.flashScreen(0xff0000, 1000);
// Apply one final screen shake for dramatic effect
applyScreenShake();
+ // Schedule game over call after 2 seconds
+ LK.setTimeout(function () {
+ LK.showGameOver();
+ }, 2000);
}
}
};
// Main update method
@@ -1396,12 +1399,12 @@
/****
* Game Code
****/
+// Base collectible behavior for items that can be collected
/****
* Game Management
****/
-// Base collectible behavior for items that can be collected
var CollectibleBehavior = {
initPhysics: function initPhysics(self) {
self.velocityX = 0;
self.velocityY = 0;
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
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