Code edit (9 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'isActive')' in or related to this line: 'if (!player.shield.isActive) {' Line Number: 1186
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemyCat', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'basic'; self.health = 1; self.speed = 2; self.pointValue = 10; self.isActive = true; self.movePattern = 'straight'; self.movementTime = 0; self.direction = 1; // 1 right, -1 left self.update = function () { if (self.isActive) { self.movementTime++; // Move based on pattern if (self.movePattern === 'straight') { self.y += self.speed; } else if (self.movePattern === 'zigzag') { self.y += self.speed; self.x += Math.sin(self.movementTime * 0.05) * 3; } else if (self.movePattern === 'teleport') { // Always move down self.y += self.speed; // And occasionally teleport horizontally if (self.movementTime % 120 === 0) { // Teleport every 2 seconds self.x = 100 + Math.random() * (2048 - 200); LK.effects.flashObject(self, 0xffffff, 300); } } // Advanced enemies occasionally shoot lasers with increasing frequency in higher waves var shootChance = 0.01; // Base chance if (wave > 10) { shootChance = 0.015 + (wave - 10) * 0.001; // Increases with each wave after 10 shootChance = Math.min(shootChance, 0.03); // Cap at 3% } if ((self.type === 'zigzag' || self.type === 'teleport') && Math.random() < shootChance) { var laser = new EnemyLaser(); // Adjust laser speed based on wave if (wave > 10) { laser.speed = 10 + Math.min(5, Math.floor((wave - 10) / 3)); } laser.x = self.x; laser.y = self.y + 30; enemyLasers.push(laser); game.addChild(laser); } // Remove enemy when it goes off screen if (self.y > 2732 + 50) { self.isActive = false; // Decrease lives when enemy passes bottom of screen if (livesCount > 0 && self.type !== 'boss' && self.type !== 'boss2') { livesCount--; livesText.setText('Lives: ' + livesCount); // Flash the lives text red to indicate loss tween(livesText, { tint: 0xff0000 }, { duration: 300, onFinish: function onFinish() { tween(livesText, { tint: 0xffffff }, { duration: 300 }); } }); // Game over if lives reach zero if (livesCount <= 0) { gameActive = false; LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } } } } }; self.takeDamage = function (damage) { self.health -= damage; // Flash the enemy red when hit LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { // Play explosion sound LK.getSound('explosion').play(); // Create explosion effect var explosion = new Explosion(); explosion.x = self.x; explosion.y = self.y; game.addChild(explosion); // Create particles for more dramatic effect for (var i = 0; i < 6; i++) { var particle = new Explosion(); particle.x = self.x + Math.random() * 60 - 30; particle.y = self.y + Math.random() * 60 - 30; particle.duration = 20 + Math.random() * 10; game.addChild(particle); } // Chance to drop power-up (15%) if (Math.random() < 0.15) { var powerUp = new PowerUp(); powerUp.x = self.x; powerUp.y = self.y; // Randomly choose power-up type var powerType = Math.random(); if (powerType < 0.5) { powerUp.setType('spreadshot'); } else if (powerType < 0.8) { powerUp.setType('shield'); } else { powerUp.setType('bomb'); } powerUps.push(powerUp); game.addChild(powerUp); } self.isActive = false; } }; self.setType = function (newType) { self.type = newType; // Get wave-based difficulty multiplier (increases after wave 10) var difficultyMult = wave > 10 ? 1.5 : 1; // First remove any existing graphics self.removeChildren(); // Add the appropriate graphic based on the wave var assetType = wave > 10 ? 'enemyCat2' : 'enemyCat'; var enemyGraphics = self.attachAsset(assetType, { anchorX: 0.5, anchorY: 0.5 }); // For enemyCat2, apply scaling since it's smaller by default if (wave > 10) { enemyGraphics.scale.set(3, 3); } if (newType === 'basic') { enemyGraphics.tint = 0xffcc00; self.health = Math.ceil(2 * difficultyMult); self.speed = Math.min(5, 3 * difficultyMult); self.pointValue = Math.ceil(10 * difficultyMult); self.movePattern = 'straight'; } else if (newType === 'fast') { enemyGraphics.tint = 0xff9900; self.health = Math.ceil(2 * difficultyMult); self.speed = Math.min(7, 5 * difficultyMult); self.pointValue = Math.ceil(15 * difficultyMult); self.movePattern = 'straight'; } else if (newType === 'zigzag') { enemyGraphics.tint = 0x66cc00; self.health = Math.ceil(3 * difficultyMult); self.speed = Math.min(5, 3 * difficultyMult); self.pointValue = Math.ceil(20 * difficultyMult); self.movePattern = 'zigzag'; } else if (newType === 'teleport') { enemyGraphics.tint = 0xcc33ff; self.health = Math.ceil(4 * difficultyMult); self.speed = Math.min(4, 2 * difficultyMult); self.pointValue = Math.ceil(30 * difficultyMult); self.movePattern = 'teleport'; } }; return self; }); var BossCat = Enemy.expand(function () { var self = Enemy.call(this); // Remove the previous graphic and add boss graphic self.removeChildren(); var bossGraphics = self.attachAsset('bossCat', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'boss'; self.health = 80; // First boss has 80 HP (increased from 50) self.speed = 1; self.pointValue = 200; self.movePattern = 'boss'; self.phase = 0; self.shootCooldown = 0; self.isFinalBoss = false; // Track if this is the final boss // Override update method self.update = function () { if (self.isActive) { self.movementTime++; // Boss movement pattern if (self.phase === 0) { // Move down to position self.y += self.speed; if (self.y >= 300) { self.phase = 1; self.movementTime = 0; } } else if (self.phase === 1) { // Move slowly towards player's position if (player && player.x) { // Calculate direction to player var directionX = player.x - self.x; // Move slowly towards player (0.5% of the distance per frame) self.x += directionX * 0.005; } // Add slight vertical movement self.y += Math.sin(self.movementTime * 0.01) * 1; // Phase transition based on health percentage var healthPercent = self.isFinalBoss ? self.health / 100 : self.health / 50; if (healthPercent <= 0.5 && self.phase < 2) { self.phase = 2; self.speed = 1.5; LK.effects.flashObject(self, 0xff0000, 1000); } } else if (self.phase === 2) { // Move towards player faster in aggressive phase if (player && player.x) { var directionX = player.x - self.x; // Move faster towards player (1% of the distance per frame) self.x += directionX * 0.01; } // Add some vertical movement self.y += Math.sin(self.movementTime * 0.02) * 2; } // Boss shooting logic self.shootCooldown--; if (self.shootCooldown <= 0) { // Shoot pattern based on phase if (self.phase === 1) { // Single shot during phase 1, less frequent self.shootLaser(); self.shootCooldown = 180; // 3 seconds (doubled from original) } else if (self.phase === 2) { // Double shot during phase 2, less frequent self.shootLaser(); self.shootLaser(-30, 0.3); self.shootCooldown = 120; // 2 seconds (doubled from original) } } } }; // Boss shooting method self.shootLaser = function (offsetX, rotation) { offsetX = offsetX || 0; rotation = rotation || 0; var laser = new EnemyLaser(); laser.x = self.x + offsetX; laser.y = self.y + 100; if (rotation !== 0) { laser.rotation = rotation; } enemyLasers.push(laser); game.addChild(laser); }; // Set boss as final boss self.setAsFinalBoss = function () { self.isFinalBoss = true; self.health = 150; // Final boss has 150 HP (increased from 100) self.pointValue = 500; tween(bossGraphics, { tint: 0xff0000 }, { duration: 1000 }); }; // Override takeDamage to show health var originalTakeDamage = self.takeDamage; self.takeDamage = function (damage) { originalTakeDamage.call(self, damage); // Update boss health display if (bossHealthText) { bossHealthText.setText("Boss HP: " + self.health); } // Drop heart power-up when boss is defeated if (self.health <= 0) { // 100% chance to drop heart from boss var heart = new PowerUp(); heart.x = self.x; heart.y = self.y; heart.setType('heart'); powerUps.push(heart); game.addChild(heart); } }; return self; }); var Boss2 = BossCat.expand(function () { var self = BossCat.call(this); // Replace boss graphic with new boss self.removeChildren(); var bossGraphics = self.attachAsset('enemyCat2', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3 }); self.type = 'boss2'; self.health = 200; // Second boss has 200 HP by default self.speed = 1.5; self.pointValue = 300; self.movementTime = 0; self.attackPattern = 0; self.attackTimer = 0; // Override update method var originalUpdate = self.update; self.update = function () { if (self.isActive) { self.movementTime++; self.attackTimer++; // Boss2 movement pattern if (self.phase === 0) { // Move down to position self.y += self.speed; if (self.y >= 350) { self.phase = 1; self.movementTime = 0; } } else if (self.phase === 1) { // More complex movement self.x += Math.sin(self.movementTime * 0.02) * 3; self.y += Math.cos(self.movementTime * 0.01) * 2; // Phase transition based on health percentage var healthPercent = self.isFinalBoss ? self.health / 250 : self.health / 200; if (healthPercent <= 0.5 && self.phase < 2) { self.phase = 2; self.speed = 2; self.attackPattern = 1; LK.effects.flashObject(self, 0xff0000, 1000); } } else if (self.phase === 2) { // More aggressive pattern self.x += Math.sin(self.movementTime * 0.03) * 5; self.y += Math.sin(self.movementTime * 0.02) * 3; // Occasionally teleport if (self.movementTime % 180 === 0) { self.x = 400 + Math.random() * (2048 - 800); LK.effects.flashObject(self, 0xffffff, 300); } } // Boss2 shooting logic self.shootCooldown--; if (self.shootCooldown <= 0) { if (self.phase === 1) { // Triple shot pattern self.shootLaser(); self.shootLaser(-50, -0.2); self.shootLaser(50, 0.2); self.shootCooldown = 150; } else if (self.phase === 2) { // More complex attack patterns if (self.attackPattern === 0) { // Spiral pattern for (var i = 0; i < 6; i++) { var angle = self.attackTimer * 0.01 + i * Math.PI / 3; var offsetX = Math.cos(angle) * 80; var offsetY = Math.sin(angle) * 80; self.shootLaser(offsetX, angle * 0.2); } self.shootCooldown = 240; self.attackPattern = 1; } else { // Targeted shots if (player && player.x) { var directionX = player.x - self.x; var angle = Math.atan2(player.y - self.y, directionX); self.shootLaser(0, angle * 0.3); self.shootLaser(-30, angle * 0.25); self.shootLaser(30, angle * 0.35); } self.shootCooldown = 180; self.attackPattern = 0; } } } } }; // Set boss as final boss self.setAsFinalBoss = function () { self.isFinalBoss = true; self.health = 250; // Final boss2 has 250 HP self.pointValue = 800; tween(bossGraphics, { tint: 0xff3300 }, { duration: 1000 }); }; // Override takeDamage to drop heart var originalTakeDamage = self.takeDamage; self.takeDamage = function (damage) { originalTakeDamage.call(self, damage); // Drop heart power-up when boss is defeated if (self.health <= 0) { // 100% chance to drop heart from boss var heart = new PowerUp(); heart.x = self.x; heart.y = self.y; heart.setType('heart'); powerUps.push(heart); game.addChild(heart); } }; return self; }); var EnemyLaser = Container.expand(function () { var self = Container.call(this); var laserGraphics = self.attachAsset('laser', { anchorX: 0.5, anchorY: 0.5, tint: 0xff6600 }); self.speed = 10; // Increased from 8 self.damage = 1; self.isActive = true; self.update = function () { if (self.isActive) { self.y += self.speed; // Remove laser when it goes off screen if (self.y > 2732 + 50) { self.isActive = false; } } }; return self; }); var Explosion = Container.expand(function () { var self = Container.call(this); // Create the explosion frames (will be hidden at first except frame 1) var frame1 = self.attachAsset('explosionFrame1', { anchorX: 0.5, anchorY: 0.5 }); var frame2 = self.attachAsset('explosionFrame2', { anchorX: 0.5, anchorY: 0.5, visible: false }); var frame3 = self.attachAsset('explosionFrame3', { anchorX: 0.5, anchorY: 0.5, visible: false }); var frame4 = self.attachAsset('explosionFrame4', { anchorX: 0.5, anchorY: 0.5, visible: false }); self.isActive = true; self.duration = 40; // Duration increased slightly self.timer = 0; self.currentFrame = 1; self.frames = [frame1, frame2, frame3, frame4]; self.update = function () { if (self.isActive) { self.timer++; // Animation frame timing (10 frames per explosion frame) var frameIndex = Math.floor(self.timer / 10); if (frameIndex >= 0 && frameIndex < 4 && frameIndex !== self.currentFrame - 1) { // Hide all frames self.frames.forEach(function (frame) { frame.visible = false; }); // Show current frame self.currentFrame = frameIndex + 1; self.frames[frameIndex].visible = true; // Apply scale and rotation effects to current frame var progress = self.timer / self.duration; var currentScale = 1 + progress; self.frames[frameIndex].scale.set(currentScale, currentScale); self.frames[frameIndex].rotation = progress * Math.PI * 0.5; } // Remove when animation complete if (self.timer >= self.duration) { self.isActive = false; } } }; return self; }); var Laser = Container.expand(function () { var self = Container.call(this); var laserGraphics = self.attachAsset('laser', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -15; self.damage = 1; self.isActive = true; self.update = function () { if (self.isActive) { self.y += self.speed; // Remove laser when it goes off screen if (self.y < -50) { self.isActive = false; } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.shootCooldown = 0; self.cooldownTime = 30; // Frames between shots (increased from 15) self.hasShield = false; self.hasSpreadshot = false; self.spreadDuration = 0; self.shield = null; self.maxHealth = 100; self.health = self.maxHealth; // Starting with full health // Define health state thresholds self.healthStateThresholds = { green: 0.66, // Above 66% is green (full health) yellow: 0.33 // Above 33% is yellow, below is red }; self.update = function () { // Decrease cooldown if (self.shootCooldown > 0) { self.shootCooldown--; } // Decrease spreadshot duration if (self.hasSpreadshot) { self.spreadDuration--; if (self.spreadDuration <= 0) { self.hasSpreadshot = false; } } // Update health bar directly on player healthBarFill.scale.x = self.health / self.maxHealth; // Update health text if (healthText) { healthText.setText(Math.ceil(self.health) + "/" + self.maxHealth); } }; self.shoot = function () { if (self.shootCooldown <= 0) { if (self.hasSpreadshot) { // Create spread shot (3 lasers) var centerLaser = new Laser(); centerLaser.x = self.x; centerLaser.y = self.y - 60; lasers.push(centerLaser); game.addChild(centerLaser); var leftLaser = new Laser(); leftLaser.x = self.x - 40; leftLaser.y = self.y - 60; leftLaser.rotation = -0.3; lasers.push(leftLaser); game.addChild(leftLaser); var rightLaser = new Laser(); rightLaser.x = self.x + 40; rightLaser.y = self.y - 60; rightLaser.rotation = 0.3; lasers.push(rightLaser); game.addChild(rightLaser); } else { // Create single laser var laser = new Laser(); laser.x = self.x; laser.y = self.y - 40; lasers.push(laser); game.addChild(laser); } LK.getSound('shoot').play(); self.shootCooldown = self.cooldownTime; } }; self.activateShield = function () { // Remove existing shield if there is one if (self.shield && self.shield.isActive) { self.shield.isActive = false; game.removeChild(self.shield); } // Create new shield self.hasShield = true; self.shield = new Shield(); self.shield.x = self.x; self.shield.y = self.y; game.addChild(self.shield); return self.shield; }; self.activateSpreadshot = function () { self.hasSpreadshot = true; self.spreadDuration = 180; // 3 seconds at 60fps (reduced from 5 seconds) }; self.activateBomb = function () { // Destroy all enemies on screen for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isActive) { enemy.takeDamage(999); // Instant kill LK.setScore(LK.getScore() + enemy.pointValue); } } // Visual effect for bomb LK.effects.flashScreen(0xff0000, 500); }; self.takeDamage = function (damage) { // Store previous health percentage to detect state transitions var prevHealthPercent = self.health / self.maxHealth; self.health -= damage; // Play hit sound LK.getSound('hitSound').play(); // Change player graphic to hit animation self.removeChildren(); self.attachAsset('playerHit', { anchorX: 0.5, anchorY: 0.5 }); // Add health bar and text back self.addChild(healthBarFill); self.addChild(healthText); // Return to normal graphic after a short delay LK.setTimeout(function () { self.removeChildren(); playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Add health bar and text back again self.addChild(healthBarFill); self.addChild(healthText); }, 300); // Flash the player red when hit LK.effects.flashObject(self, 0xff0000, 300); // Update health bar if (healthBarFill) { healthBarFill.scale.x = Math.max(0, self.health / self.maxHealth); // Get new health percentage var healthPercent = self.health / self.maxHealth; // Change health bar color based on percentage if (healthPercent >= 0.66) { healthBarFill.tint = 0x00ff00; // Green for high health } else if (healthPercent >= 0.33) { healthBarFill.tint = 0xffcc00; // Yellow/orange for medium health } else { healthBarFill.tint = 0xff0000; // Red for low health } // Check for transition between health states if (prevHealthPercent > 0.66 && healthPercent <= 0.66 || prevHealthPercent > 0.33 && healthPercent <= 0.33) { // Visual effect for health state degradation tween(healthBarFill, { alpha: 0.2 }, { duration: 300, onFinish: function onFinish() { tween(healthBarFill, { alpha: 1 }, { duration: 300 }); } }); } } // Check if player is dead if (self.health <= 0) { gameActive = false; LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); } return self.health <= 0; // Return true if player died }; self.heal = function (amount) { // Store previous health percentage to detect state transitions var prevHealthPercent = self.health / self.maxHealth; self.health = Math.min(self.maxHealth, self.health + amount); // Update health bar if (healthBarFill) { healthBarFill.scale.x = self.health / self.maxHealth; // Get new health percentage var healthPercent = self.health / self.maxHealth; // Change health bar color based on percentage if (healthPercent >= 0.66) { healthBarFill.tint = 0x00ff00; // Green for high health } else if (healthPercent >= 0.33) { healthBarFill.tint = 0xffcc00; // Yellow/orange for medium health } else { healthBarFill.tint = 0xff0000; // Red for low health } // Check for transition between health states if (prevHealthPercent <= 0.33 && healthPercent > 0.33 || prevHealthPercent <= 0.66 && healthPercent > 0.66) { // Visual effect for health state improvement tween(healthBarFill, { alpha: 0.2 }, { duration: 300, onFinish: function onFinish() { tween(healthBarFill, { alpha: 1 }, { duration: 300 }); } }); } } // Visual healing effect LK.effects.flashObject(self, 0x00ff00, 300); }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'spreadshot'; // Default type self.speed = 3; self.isActive = true; self.update = function () { if (self.isActive) { self.y += self.speed; // Rotate the power-up for visual effect powerUpGraphics.rotation += 0.05; // Remove power-up when it goes off screen if (self.y > 2732 + 50) { self.isActive = false; } } }; self.setType = function (newType) { self.type = newType; // Remove previous graphics self.removeChildren(); // Change appearance based on type if (newType === 'spreadshot') { powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5, tint: 0xff9900 }); } else if (newType === 'shield') { powerUpGraphics = self.attachAsset('powerUpShield', { anchorX: 0.5, anchorY: 0.5, tint: 0x33ccff }); } else if (newType === 'bomb') { powerUpGraphics = self.attachAsset('powerUp', { anchorX: 0.5, anchorY: 0.5, tint: 0xff3300 }); } else if (newType === 'heart') { powerUpGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); } }; return self; }); var Shield = Container.expand(function () { var self = Container.call(this); var shieldGraphics = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.duration = 6000; // Shield lasts 3 seconds (reduced from 5) self.timeLeft = self.duration; self.isActive = true; self.update = function () { if (self.isActive) { self.timeLeft -= 1000 / 60; // Decrease time left (60 fps) // Fade out as shield expires shieldGraphics.alpha = 0.2 + self.timeLeft / self.duration * 0.5; // Rotate the shield for visual effect shieldGraphics.rotation += 0.01; // Deactivate shield when time runs out if (self.timeLeft <= 0) { self.isActive = false; // Remove shield reference from player if (player) { player.hasShield = false; player.shield = null; } // Remove this shield from the game when it expires game.removeChild(self); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000033 }); /**** * Game Code ****/ // Game state variables function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) { throw o; } } } }; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } var player; var lasers = []; var enemyLasers = []; var enemies = []; var powerUps = []; var wave = 1; var enemiesInWave = 5; var enemiesSpawned = 0; var spawnCooldown = 0; var gameActive = true; var bossWave = false; var bossHealthText; var healthBar; var healthBarBg; var healthBarFill; var livesCount = 10; var livesText; // UI elements var scoreTxt = new Text2('Score\r\n0', { size: 100, fill: 0xFFFFFF, dropShadow: true, align: 'center' }); scoreTxt.setText("Score\r\n" + LK.getScore()); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var waveTxt = new Text2('Wave\r\n1', { size: 80, fill: 0xFFFFFF, dropShadow: true, align: 'center' }); waveTxt.anchor.set(1.0, 0); LK.gui.topRight.addChild(waveTxt); // Position it away from the top left corner (reserved for menu icon) waveTxt.x = 0; // Lives counter display livesText = new Text2('Lives: ' + livesCount, { size: 80, fill: 0xFFFFFF }); livesText.anchor.set(0.5, 0); LK.gui.bottom.addChild(livesText); // Add background image var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732 }); background.x = 2048 / 2; background.y = 2732 / 2; game.addChild(background); // Create background clone first to ensure it's below the player if (!game.backgroundClone) { // Create a clone of the background game.backgroundClone = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732 }); // Position the clone directly above the original background game.backgroundClone.x = background.x; game.backgroundClone.y = background.y - background.height; game.addChild(game.backgroundClone); } // Create player after all background elements player = new Player(); player.x = 2048 / 2; player.y = 2732 - 200; player.cooldownTime = 10; // Reduced cooldown between shots (from 30) // Add player last so it appears on top of all background elements game.addChild(player); // Create health bar - directly attached to player healthBarFill = LK.getAsset('baseBox', { anchorX: 0, // Anchor at the left edge for scaling from left to right anchorY: 0.5, tint: 0x00ff00, // Set tint to green for full health explicitly in creation scaleX: 10, scaleY: 0.5 // Base height scale factor (visual size) }); // Ensure bar scale correctly represents 100% health at the start healthBarFill.scale.x = player.health / player.maxHealth; // Set scale based on current health percentage // Create health text display var healthText = new Text2('100/100', { size: 40, fill: 0xFFFFFF }); healthText.anchor.set(0.5, 0); healthText.y = 30; // Position below health bar // Attach directly to player player.addChild(healthBarFill); player.addChild(healthText); // Position relative to player - higher placement healthBarFill.y = -80; // Position health text above health bar healthText.y = -120; // Set initial animation for health bar to draw player's attention tween(healthBarFill, { alpha: 0.5 }, { duration: 500, onFinish: function onFinish() { tween(healthBarFill, { alpha: 1 }, { duration: 500 }); } }); // Start background music with fade in effect LK.playMusic('gameMusic', { fade: { start: 0, end: 1, duration: 1000 } }); // Game input handlers game.down = function (x, y, obj) { // Shoot when tapping anywhere player.shoot(); }; game.move = function (x, y, obj) { // Move player horizontally (keep vertical position fixed) if (gameActive) { player.x = x; // Keep player within game bounds if (player.x < 75) { player.x = 75; } if (player.x > 2048 - 75) { player.x = 2048 - 75; } // Update shield position if active if (player.hasShield && player.shield && player.shield.isActive) { player.shield.x = player.x; player.shield.y = player.y; } } }; // Spawn enemies function spawnEnemy() { var enemy; if (bossWave) { // Use Boss2 for waves 15 and 20 if (wave >= 15 && wave % 5 === 0) { enemy = new Boss2(); enemy.x = 2048 / 2; enemy.y = -200; // If this is the final boss (wave 20), set it to have higher HP if (wave === 20) { enemy.setAsFinalBoss(); } } else { enemy = new BossCat(); enemy.x = 2048 / 2; enemy.y = -200; // If this is the final boss of the first stage (wave 10), set it to have higher HP if (wave === 10) { enemy.setAsFinalBoss(); } } // Create boss health display bossHealthText = new Text2("Boss HP: " + enemy.health, { size: 60, fill: 0xFF6600 }); bossHealthText.anchor.set(0.5, 0); bossHealthText.y = 150; LK.gui.top.addChild(bossHealthText); } else { enemy = new Enemy(); enemy.x = 100 + Math.random() * (2048 - 200); enemy.y = -100; // Set random enemy type based on current wave var typeRoll = Math.random(); if (wave <= 2) { // Waves 1-2: Only basic enemies enemy.setType('basic'); } else if (wave <= 4) { // Waves 3-4: Basic and fast enemies if (typeRoll < 0.6) { enemy.setType('basic'); } else { enemy.setType('fast'); } } else if (wave <= 6) { // Waves 5-6: Add zigzag enemies if (typeRoll < 0.4) { enemy.setType('basic'); } else if (typeRoll < 0.7) { enemy.setType('fast'); } else { enemy.setType('zigzag'); } } else if (wave <= 10) { // Waves 7-10: All enemy types if (typeRoll < 0.3) { enemy.setType('basic'); } else if (typeRoll < 0.5) { enemy.setType('fast'); } else if (typeRoll < 0.8) { enemy.setType('zigzag'); } else { enemy.setType('teleport'); } } else { // Waves 11-20: Harder distribution with fewer basic enemies if (typeRoll < 0.15) { enemy.setType('basic'); } else if (typeRoll < 0.4) { enemy.setType('fast'); } else if (typeRoll < 0.7) { enemy.setType('zigzag'); } else { enemy.setType('teleport'); } } } enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } // Main game update loop game.update = function () { if (!gameActive) { return; } // Track and update explosions var explosions = []; var _iterator = _createForOfIteratorHelper(game.children), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var child = _step.value; if (child instanceof Explosion) { explosions.push(child); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } for (var i = explosions.length - 1; i >= 0; i--) { var explosion = explosions[i]; explosion.update(); if (!explosion.isActive) { game.removeChild(explosion); } } // Set up background scrolling with two identical backgrounds side by side if (!game.backgroundClone) { // Create a clone of the background game.backgroundClone = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732 }); // Position the clone directly above the original background game.backgroundClone.x = background.x; game.backgroundClone.y = background.y - background.height; game.addChild(game.backgroundClone); } // Scroll both backgrounds background.y += 5; // Increased scroll speed game.backgroundClone.y += 5; // Same increased speed for clone // If original background goes completely off screen, move it above the clone if (background.y - background.height / 2 > 2732) { background.y = game.backgroundClone.y - background.height; } // If clone background goes completely off screen, move it above the original if (game.backgroundClone.y - game.backgroundClone.height / 2 > 2732) { game.backgroundClone.y = background.y - background.height; } // Update player player.update(); // Update shield if active if (player.hasShield && player.shield) { player.shield.update(); if (!player.shield.isActive) { player.hasShield = false; game.removeChild(player.shield); player.shield = null; } } // Handle enemy spawning if (spawnCooldown <= 0) { if (enemiesSpawned < enemiesInWave) { spawnEnemy(); // Cooldown between spawns if (bossWave) { spawnCooldown = 1; // Boss spawns immediately } else { spawnCooldown = 60; // 1 second between normal enemy spawns } } } else { spawnCooldown--; } // Update lasers and check for collisions for (var i = lasers.length - 1; i >= 0; i--) { var laser = lasers[i]; laser.update(); if (!laser.isActive) { game.removeChild(laser); lasers.splice(i, 1); continue; } // Check for collisions with enemies for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; if (enemy.isActive && laser.isActive && laser.intersects(enemy)) { enemy.takeDamage(laser.damage); laser.isActive = false; if (!enemy.isActive) { LK.setScore(LK.getScore() + enemy.pointValue); scoreTxt.setText("Score\r\n" + LK.getScore()); game.removeChild(enemy); enemies.splice(j, 1); } break; } } } // Update enemy lasers and check for collisions with player for (var el = enemyLasers.length - 1; el >= 0; el--) { var enemyLaser = enemyLasers[el]; enemyLaser.update(); if (!enemyLaser.isActive) { game.removeChild(enemyLaser); enemyLasers.splice(el, 1); continue; } // Check for collision with player if (enemyLaser.intersects(player)) { // If player has shield, block the laser if (player.hasShield) { enemyLaser.isActive = false; game.removeChild(enemyLaser); enemyLasers.splice(el, 1); // Flash shield to show impact if (player.shield) { LK.effects.flashObject(player.shield, 0xffff00, 300); } } else { // Player takes damage from laser - only one point of health var laserDamage = 1; player.takeDamage(laserDamage); enemyLaser.isActive = false; game.removeChild(enemyLaser); enemyLasers.splice(el, 1); if (!gameActive) { return; } // Return if player died } } } // Update enemies var activeEnemies = 0; for (var k = enemies.length - 1; k >= 0; k--) { var enemy = enemies[k]; enemy.update(); if (!enemy.isActive) { game.removeChild(enemy); enemies.splice(k, 1); continue; } activeEnemies++; // Check collision with player if (enemy.intersects(player)) { // Initialize lastDamageTime if not set if (enemy.lastDamageTime === undefined) { enemy.lastDamageTime = 0; } // Check if 3 seconds (180 frames at 60fps) have passed since last damage var currentTime = LK.ticks; var damageInterval = 180; // 3 seconds at 60fps if (player.hasShield) { // Shield absorbs collision damage if (player.shield) { LK.effects.flashObject(player.shield, 0xffff00, 300); } } else { // Player takes damage from collision only every 3 seconds var collisionDamage = 10; // Apply damage only if enough time has passed if (currentTime - enemy.lastDamageTime >= damageInterval) { player.takeDamage(collisionDamage); enemy.lastDamageTime = currentTime; // Visual feedback that damage was applied LK.effects.flashObject(enemy, 0xff0000, 300); } if (!gameActive) { return; } // Return if player died } } } // Update power-ups for (var l = powerUps.length - 1; l >= 0; l--) { var powerUp = powerUps[l]; powerUp.update(); if (!powerUp.isActive) { game.removeChild(powerUp); powerUps.splice(l, 1); continue; } // Check collision with player if (powerUp.intersects(player)) { // Apply power-up effect if (powerUp.type === 'spreadshot') { player.activateSpreadshot(); } else if (powerUp.type === 'shield') { player.activateShield(); } else if (powerUp.type === 'bomb') { player.activateBomb(); } else if (powerUp.type === 'heart') { player.heal(50); // Restore 50 health points } LK.getSound('powerup').play(); powerUp.isActive = false; game.removeChild(powerUp); powerUps.splice(l, 1); } } // Check if wave is complete if (enemiesSpawned >= enemiesInWave && activeEnemies === 0) { // Wave complete, start next wave wave++; waveTxt.setText("Wave\r\n" + wave); // Every 5th wave is a boss wave if (wave % 5 === 0) { bossWave = true; enemiesInWave = 1; } else { bossWave = false; // Reduced enemies per wave to make the game easier enemiesInWave = 5 + Math.floor(wave * 1.5); // Remove boss health display if it exists if (bossHealthText) { LK.gui.top.removeChild(bossHealthText); bossHealthText = null; } } enemiesSpawned = 0; spawnCooldown = 120; // 2 second delay between waves // Check for music changes between levels 1-10 and 11-20 if (wave == 11) { // Change to gameMusic2 for levels 11-20 LK.playMusic('gameMusic2', { fade: { start: 0, end: 1, duration: 1000 } }); } else if (wave == 1) { // Change back to gameMusic for levels 1-10 LK.playMusic('gameMusic', { fade: { start: 0, end: 1, duration: 1000 } }); } // Win condition - player has survived 20 waves if (wave > 20) { LK.showYouWin(); return; } } // Auto fire if player holds down finger (increased frequency) if (LK.ticks % 10 === 0) { player.shoot(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemyCat', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'basic';
self.health = 1;
self.speed = 2;
self.pointValue = 10;
self.isActive = true;
self.movePattern = 'straight';
self.movementTime = 0;
self.direction = 1; // 1 right, -1 left
self.update = function () {
if (self.isActive) {
self.movementTime++;
// Move based on pattern
if (self.movePattern === 'straight') {
self.y += self.speed;
} else if (self.movePattern === 'zigzag') {
self.y += self.speed;
self.x += Math.sin(self.movementTime * 0.05) * 3;
} else if (self.movePattern === 'teleport') {
// Always move down
self.y += self.speed;
// And occasionally teleport horizontally
if (self.movementTime % 120 === 0) {
// Teleport every 2 seconds
self.x = 100 + Math.random() * (2048 - 200);
LK.effects.flashObject(self, 0xffffff, 300);
}
}
// Advanced enemies occasionally shoot lasers with increasing frequency in higher waves
var shootChance = 0.01; // Base chance
if (wave > 10) {
shootChance = 0.015 + (wave - 10) * 0.001; // Increases with each wave after 10
shootChance = Math.min(shootChance, 0.03); // Cap at 3%
}
if ((self.type === 'zigzag' || self.type === 'teleport') && Math.random() < shootChance) {
var laser = new EnemyLaser();
// Adjust laser speed based on wave
if (wave > 10) {
laser.speed = 10 + Math.min(5, Math.floor((wave - 10) / 3));
}
laser.x = self.x;
laser.y = self.y + 30;
enemyLasers.push(laser);
game.addChild(laser);
}
// Remove enemy when it goes off screen
if (self.y > 2732 + 50) {
self.isActive = false;
// Decrease lives when enemy passes bottom of screen
if (livesCount > 0 && self.type !== 'boss' && self.type !== 'boss2') {
livesCount--;
livesText.setText('Lives: ' + livesCount);
// Flash the lives text red to indicate loss
tween(livesText, {
tint: 0xff0000
}, {
duration: 300,
onFinish: function onFinish() {
tween(livesText, {
tint: 0xffffff
}, {
duration: 300
});
}
});
// Game over if lives reach zero
if (livesCount <= 0) {
gameActive = false;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
}
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
// Flash the enemy red when hit
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
// Play explosion sound
LK.getSound('explosion').play();
// Create explosion effect
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Create particles for more dramatic effect
for (var i = 0; i < 6; i++) {
var particle = new Explosion();
particle.x = self.x + Math.random() * 60 - 30;
particle.y = self.y + Math.random() * 60 - 30;
particle.duration = 20 + Math.random() * 10;
game.addChild(particle);
}
// Chance to drop power-up (15%)
if (Math.random() < 0.15) {
var powerUp = new PowerUp();
powerUp.x = self.x;
powerUp.y = self.y;
// Randomly choose power-up type
var powerType = Math.random();
if (powerType < 0.5) {
powerUp.setType('spreadshot');
} else if (powerType < 0.8) {
powerUp.setType('shield');
} else {
powerUp.setType('bomb');
}
powerUps.push(powerUp);
game.addChild(powerUp);
}
self.isActive = false;
}
};
self.setType = function (newType) {
self.type = newType;
// Get wave-based difficulty multiplier (increases after wave 10)
var difficultyMult = wave > 10 ? 1.5 : 1;
// First remove any existing graphics
self.removeChildren();
// Add the appropriate graphic based on the wave
var assetType = wave > 10 ? 'enemyCat2' : 'enemyCat';
var enemyGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
// For enemyCat2, apply scaling since it's smaller by default
if (wave > 10) {
enemyGraphics.scale.set(3, 3);
}
if (newType === 'basic') {
enemyGraphics.tint = 0xffcc00;
self.health = Math.ceil(2 * difficultyMult);
self.speed = Math.min(5, 3 * difficultyMult);
self.pointValue = Math.ceil(10 * difficultyMult);
self.movePattern = 'straight';
} else if (newType === 'fast') {
enemyGraphics.tint = 0xff9900;
self.health = Math.ceil(2 * difficultyMult);
self.speed = Math.min(7, 5 * difficultyMult);
self.pointValue = Math.ceil(15 * difficultyMult);
self.movePattern = 'straight';
} else if (newType === 'zigzag') {
enemyGraphics.tint = 0x66cc00;
self.health = Math.ceil(3 * difficultyMult);
self.speed = Math.min(5, 3 * difficultyMult);
self.pointValue = Math.ceil(20 * difficultyMult);
self.movePattern = 'zigzag';
} else if (newType === 'teleport') {
enemyGraphics.tint = 0xcc33ff;
self.health = Math.ceil(4 * difficultyMult);
self.speed = Math.min(4, 2 * difficultyMult);
self.pointValue = Math.ceil(30 * difficultyMult);
self.movePattern = 'teleport';
}
};
return self;
});
var BossCat = Enemy.expand(function () {
var self = Enemy.call(this);
// Remove the previous graphic and add boss graphic
self.removeChildren();
var bossGraphics = self.attachAsset('bossCat', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'boss';
self.health = 80; // First boss has 80 HP (increased from 50)
self.speed = 1;
self.pointValue = 200;
self.movePattern = 'boss';
self.phase = 0;
self.shootCooldown = 0;
self.isFinalBoss = false; // Track if this is the final boss
// Override update method
self.update = function () {
if (self.isActive) {
self.movementTime++;
// Boss movement pattern
if (self.phase === 0) {
// Move down to position
self.y += self.speed;
if (self.y >= 300) {
self.phase = 1;
self.movementTime = 0;
}
} else if (self.phase === 1) {
// Move slowly towards player's position
if (player && player.x) {
// Calculate direction to player
var directionX = player.x - self.x;
// Move slowly towards player (0.5% of the distance per frame)
self.x += directionX * 0.005;
}
// Add slight vertical movement
self.y += Math.sin(self.movementTime * 0.01) * 1;
// Phase transition based on health percentage
var healthPercent = self.isFinalBoss ? self.health / 100 : self.health / 50;
if (healthPercent <= 0.5 && self.phase < 2) {
self.phase = 2;
self.speed = 1.5;
LK.effects.flashObject(self, 0xff0000, 1000);
}
} else if (self.phase === 2) {
// Move towards player faster in aggressive phase
if (player && player.x) {
var directionX = player.x - self.x;
// Move faster towards player (1% of the distance per frame)
self.x += directionX * 0.01;
}
// Add some vertical movement
self.y += Math.sin(self.movementTime * 0.02) * 2;
}
// Boss shooting logic
self.shootCooldown--;
if (self.shootCooldown <= 0) {
// Shoot pattern based on phase
if (self.phase === 1) {
// Single shot during phase 1, less frequent
self.shootLaser();
self.shootCooldown = 180; // 3 seconds (doubled from original)
} else if (self.phase === 2) {
// Double shot during phase 2, less frequent
self.shootLaser();
self.shootLaser(-30, 0.3);
self.shootCooldown = 120; // 2 seconds (doubled from original)
}
}
}
};
// Boss shooting method
self.shootLaser = function (offsetX, rotation) {
offsetX = offsetX || 0;
rotation = rotation || 0;
var laser = new EnemyLaser();
laser.x = self.x + offsetX;
laser.y = self.y + 100;
if (rotation !== 0) {
laser.rotation = rotation;
}
enemyLasers.push(laser);
game.addChild(laser);
};
// Set boss as final boss
self.setAsFinalBoss = function () {
self.isFinalBoss = true;
self.health = 150; // Final boss has 150 HP (increased from 100)
self.pointValue = 500;
tween(bossGraphics, {
tint: 0xff0000
}, {
duration: 1000
});
};
// Override takeDamage to show health
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage.call(self, damage);
// Update boss health display
if (bossHealthText) {
bossHealthText.setText("Boss HP: " + self.health);
}
// Drop heart power-up when boss is defeated
if (self.health <= 0) {
// 100% chance to drop heart from boss
var heart = new PowerUp();
heart.x = self.x;
heart.y = self.y;
heart.setType('heart');
powerUps.push(heart);
game.addChild(heart);
}
};
return self;
});
var Boss2 = BossCat.expand(function () {
var self = BossCat.call(this);
// Replace boss graphic with new boss
self.removeChildren();
var bossGraphics = self.attachAsset('enemyCat2', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
self.type = 'boss2';
self.health = 200; // Second boss has 200 HP by default
self.speed = 1.5;
self.pointValue = 300;
self.movementTime = 0;
self.attackPattern = 0;
self.attackTimer = 0;
// Override update method
var originalUpdate = self.update;
self.update = function () {
if (self.isActive) {
self.movementTime++;
self.attackTimer++;
// Boss2 movement pattern
if (self.phase === 0) {
// Move down to position
self.y += self.speed;
if (self.y >= 350) {
self.phase = 1;
self.movementTime = 0;
}
} else if (self.phase === 1) {
// More complex movement
self.x += Math.sin(self.movementTime * 0.02) * 3;
self.y += Math.cos(self.movementTime * 0.01) * 2;
// Phase transition based on health percentage
var healthPercent = self.isFinalBoss ? self.health / 250 : self.health / 200;
if (healthPercent <= 0.5 && self.phase < 2) {
self.phase = 2;
self.speed = 2;
self.attackPattern = 1;
LK.effects.flashObject(self, 0xff0000, 1000);
}
} else if (self.phase === 2) {
// More aggressive pattern
self.x += Math.sin(self.movementTime * 0.03) * 5;
self.y += Math.sin(self.movementTime * 0.02) * 3;
// Occasionally teleport
if (self.movementTime % 180 === 0) {
self.x = 400 + Math.random() * (2048 - 800);
LK.effects.flashObject(self, 0xffffff, 300);
}
}
// Boss2 shooting logic
self.shootCooldown--;
if (self.shootCooldown <= 0) {
if (self.phase === 1) {
// Triple shot pattern
self.shootLaser();
self.shootLaser(-50, -0.2);
self.shootLaser(50, 0.2);
self.shootCooldown = 150;
} else if (self.phase === 2) {
// More complex attack patterns
if (self.attackPattern === 0) {
// Spiral pattern
for (var i = 0; i < 6; i++) {
var angle = self.attackTimer * 0.01 + i * Math.PI / 3;
var offsetX = Math.cos(angle) * 80;
var offsetY = Math.sin(angle) * 80;
self.shootLaser(offsetX, angle * 0.2);
}
self.shootCooldown = 240;
self.attackPattern = 1;
} else {
// Targeted shots
if (player && player.x) {
var directionX = player.x - self.x;
var angle = Math.atan2(player.y - self.y, directionX);
self.shootLaser(0, angle * 0.3);
self.shootLaser(-30, angle * 0.25);
self.shootLaser(30, angle * 0.35);
}
self.shootCooldown = 180;
self.attackPattern = 0;
}
}
}
}
};
// Set boss as final boss
self.setAsFinalBoss = function () {
self.isFinalBoss = true;
self.health = 250; // Final boss2 has 250 HP
self.pointValue = 800;
tween(bossGraphics, {
tint: 0xff3300
}, {
duration: 1000
});
};
// Override takeDamage to drop heart
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (damage) {
originalTakeDamage.call(self, damage);
// Drop heart power-up when boss is defeated
if (self.health <= 0) {
// 100% chance to drop heart from boss
var heart = new PowerUp();
heart.x = self.x;
heart.y = self.y;
heart.setType('heart');
powerUps.push(heart);
game.addChild(heart);
}
};
return self;
});
var EnemyLaser = Container.expand(function () {
var self = Container.call(this);
var laserGraphics = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff6600
});
self.speed = 10; // Increased from 8
self.damage = 1;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.y += self.speed;
// Remove laser when it goes off screen
if (self.y > 2732 + 50) {
self.isActive = false;
}
}
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
// Create the explosion frames (will be hidden at first except frame 1)
var frame1 = self.attachAsset('explosionFrame1', {
anchorX: 0.5,
anchorY: 0.5
});
var frame2 = self.attachAsset('explosionFrame2', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
var frame3 = self.attachAsset('explosionFrame3', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
var frame4 = self.attachAsset('explosionFrame4', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
self.isActive = true;
self.duration = 40; // Duration increased slightly
self.timer = 0;
self.currentFrame = 1;
self.frames = [frame1, frame2, frame3, frame4];
self.update = function () {
if (self.isActive) {
self.timer++;
// Animation frame timing (10 frames per explosion frame)
var frameIndex = Math.floor(self.timer / 10);
if (frameIndex >= 0 && frameIndex < 4 && frameIndex !== self.currentFrame - 1) {
// Hide all frames
self.frames.forEach(function (frame) {
frame.visible = false;
});
// Show current frame
self.currentFrame = frameIndex + 1;
self.frames[frameIndex].visible = true;
// Apply scale and rotation effects to current frame
var progress = self.timer / self.duration;
var currentScale = 1 + progress;
self.frames[frameIndex].scale.set(currentScale, currentScale);
self.frames[frameIndex].rotation = progress * Math.PI * 0.5;
}
// Remove when animation complete
if (self.timer >= self.duration) {
self.isActive = false;
}
}
};
return self;
});
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserGraphics = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -15;
self.damage = 1;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.y += self.speed;
// Remove laser when it goes off screen
if (self.y < -50) {
self.isActive = false;
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.shootCooldown = 0;
self.cooldownTime = 30; // Frames between shots (increased from 15)
self.hasShield = false;
self.hasSpreadshot = false;
self.spreadDuration = 0;
self.shield = null;
self.maxHealth = 100;
self.health = self.maxHealth; // Starting with full health
// Define health state thresholds
self.healthStateThresholds = {
green: 0.66,
// Above 66% is green (full health)
yellow: 0.33 // Above 33% is yellow, below is red
};
self.update = function () {
// Decrease cooldown
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Decrease spreadshot duration
if (self.hasSpreadshot) {
self.spreadDuration--;
if (self.spreadDuration <= 0) {
self.hasSpreadshot = false;
}
}
// Update health bar directly on player
healthBarFill.scale.x = self.health / self.maxHealth;
// Update health text
if (healthText) {
healthText.setText(Math.ceil(self.health) + "/" + self.maxHealth);
}
};
self.shoot = function () {
if (self.shootCooldown <= 0) {
if (self.hasSpreadshot) {
// Create spread shot (3 lasers)
var centerLaser = new Laser();
centerLaser.x = self.x;
centerLaser.y = self.y - 60;
lasers.push(centerLaser);
game.addChild(centerLaser);
var leftLaser = new Laser();
leftLaser.x = self.x - 40;
leftLaser.y = self.y - 60;
leftLaser.rotation = -0.3;
lasers.push(leftLaser);
game.addChild(leftLaser);
var rightLaser = new Laser();
rightLaser.x = self.x + 40;
rightLaser.y = self.y - 60;
rightLaser.rotation = 0.3;
lasers.push(rightLaser);
game.addChild(rightLaser);
} else {
// Create single laser
var laser = new Laser();
laser.x = self.x;
laser.y = self.y - 40;
lasers.push(laser);
game.addChild(laser);
}
LK.getSound('shoot').play();
self.shootCooldown = self.cooldownTime;
}
};
self.activateShield = function () {
// Remove existing shield if there is one
if (self.shield && self.shield.isActive) {
self.shield.isActive = false;
game.removeChild(self.shield);
}
// Create new shield
self.hasShield = true;
self.shield = new Shield();
self.shield.x = self.x;
self.shield.y = self.y;
game.addChild(self.shield);
return self.shield;
};
self.activateSpreadshot = function () {
self.hasSpreadshot = true;
self.spreadDuration = 180; // 3 seconds at 60fps (reduced from 5 seconds)
};
self.activateBomb = function () {
// Destroy all enemies on screen
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.isActive) {
enemy.takeDamage(999); // Instant kill
LK.setScore(LK.getScore() + enemy.pointValue);
}
}
// Visual effect for bomb
LK.effects.flashScreen(0xff0000, 500);
};
self.takeDamage = function (damage) {
// Store previous health percentage to detect state transitions
var prevHealthPercent = self.health / self.maxHealth;
self.health -= damage;
// Play hit sound
LK.getSound('hitSound').play();
// Change player graphic to hit animation
self.removeChildren();
self.attachAsset('playerHit', {
anchorX: 0.5,
anchorY: 0.5
});
// Add health bar and text back
self.addChild(healthBarFill);
self.addChild(healthText);
// Return to normal graphic after a short delay
LK.setTimeout(function () {
self.removeChildren();
playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Add health bar and text back again
self.addChild(healthBarFill);
self.addChild(healthText);
}, 300);
// Flash the player red when hit
LK.effects.flashObject(self, 0xff0000, 300);
// Update health bar
if (healthBarFill) {
healthBarFill.scale.x = Math.max(0, self.health / self.maxHealth);
// Get new health percentage
var healthPercent = self.health / self.maxHealth;
// Change health bar color based on percentage
if (healthPercent >= 0.66) {
healthBarFill.tint = 0x00ff00; // Green for high health
} else if (healthPercent >= 0.33) {
healthBarFill.tint = 0xffcc00; // Yellow/orange for medium health
} else {
healthBarFill.tint = 0xff0000; // Red for low health
}
// Check for transition between health states
if (prevHealthPercent > 0.66 && healthPercent <= 0.66 || prevHealthPercent > 0.33 && healthPercent <= 0.33) {
// Visual effect for health state degradation
tween(healthBarFill, {
alpha: 0.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(healthBarFill, {
alpha: 1
}, {
duration: 300
});
}
});
}
}
// Check if player is dead
if (self.health <= 0) {
gameActive = false;
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
return self.health <= 0; // Return true if player died
};
self.heal = function (amount) {
// Store previous health percentage to detect state transitions
var prevHealthPercent = self.health / self.maxHealth;
self.health = Math.min(self.maxHealth, self.health + amount);
// Update health bar
if (healthBarFill) {
healthBarFill.scale.x = self.health / self.maxHealth;
// Get new health percentage
var healthPercent = self.health / self.maxHealth;
// Change health bar color based on percentage
if (healthPercent >= 0.66) {
healthBarFill.tint = 0x00ff00; // Green for high health
} else if (healthPercent >= 0.33) {
healthBarFill.tint = 0xffcc00; // Yellow/orange for medium health
} else {
healthBarFill.tint = 0xff0000; // Red for low health
}
// Check for transition between health states
if (prevHealthPercent <= 0.33 && healthPercent > 0.33 || prevHealthPercent <= 0.66 && healthPercent > 0.66) {
// Visual effect for health state improvement
tween(healthBarFill, {
alpha: 0.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(healthBarFill, {
alpha: 1
}, {
duration: 300
});
}
});
}
}
// Visual healing effect
LK.effects.flashObject(self, 0x00ff00, 300);
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'spreadshot'; // Default type
self.speed = 3;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.y += self.speed;
// Rotate the power-up for visual effect
powerUpGraphics.rotation += 0.05;
// Remove power-up when it goes off screen
if (self.y > 2732 + 50) {
self.isActive = false;
}
}
};
self.setType = function (newType) {
self.type = newType;
// Remove previous graphics
self.removeChildren();
// Change appearance based on type
if (newType === 'spreadshot') {
powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff9900
});
} else if (newType === 'shield') {
powerUpGraphics = self.attachAsset('powerUpShield', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x33ccff
});
} else if (newType === 'bomb') {
powerUpGraphics = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff3300
});
} else if (newType === 'heart') {
powerUpGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
return self;
});
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldGraphics = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
self.duration = 6000; // Shield lasts 3 seconds (reduced from 5)
self.timeLeft = self.duration;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.timeLeft -= 1000 / 60; // Decrease time left (60 fps)
// Fade out as shield expires
shieldGraphics.alpha = 0.2 + self.timeLeft / self.duration * 0.5;
// Rotate the shield for visual effect
shieldGraphics.rotation += 0.01;
// Deactivate shield when time runs out
if (self.timeLeft <= 0) {
self.isActive = false;
// Remove shield reference from player
if (player) {
player.hasShield = false;
player.shield = null;
}
// Remove this shield from the game when it expires
game.removeChild(self);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000033
});
/****
* Game Code
****/
// Game state variables
function _createForOfIteratorHelper(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var _n = 0,
F = function F() {};
return {
s: F,
n: function n() {
return _n >= r.length ? {
done: !0
} : {
done: !1,
value: r[_n++]
};
},
e: function e(r) {
throw r;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var o,
a = !0,
u = !1;
return {
s: function s() {
t = t.call(r);
},
n: function n() {
var r = t.next();
return a = r.done, r;
},
e: function e(r) {
u = !0, o = r;
},
f: function f() {
try {
a || null == t["return"] || t["return"]();
} finally {
if (u) {
throw o;
}
}
}
};
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) {
return _arrayLikeToArray(r, a);
}
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) {
n[e] = r[e];
}
return n;
}
var player;
var lasers = [];
var enemyLasers = [];
var enemies = [];
var powerUps = [];
var wave = 1;
var enemiesInWave = 5;
var enemiesSpawned = 0;
var spawnCooldown = 0;
var gameActive = true;
var bossWave = false;
var bossHealthText;
var healthBar;
var healthBarBg;
var healthBarFill;
var livesCount = 10;
var livesText;
// UI elements
var scoreTxt = new Text2('Score\r\n0', {
size: 100,
fill: 0xFFFFFF,
dropShadow: true,
align: 'center'
});
scoreTxt.setText("Score\r\n" + LK.getScore());
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var waveTxt = new Text2('Wave\r\n1', {
size: 80,
fill: 0xFFFFFF,
dropShadow: true,
align: 'center'
});
waveTxt.anchor.set(1.0, 0);
LK.gui.topRight.addChild(waveTxt);
// Position it away from the top left corner (reserved for menu icon)
waveTxt.x = 0;
// Lives counter display
livesText = new Text2('Lives: ' + livesCount, {
size: 80,
fill: 0xFFFFFF
});
livesText.anchor.set(0.5, 0);
LK.gui.bottom.addChild(livesText);
// Add background image
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
background.x = 2048 / 2;
background.y = 2732 / 2;
game.addChild(background);
// Create background clone first to ensure it's below the player
if (!game.backgroundClone) {
// Create a clone of the background
game.backgroundClone = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
// Position the clone directly above the original background
game.backgroundClone.x = background.x;
game.backgroundClone.y = background.y - background.height;
game.addChild(game.backgroundClone);
}
// Create player after all background elements
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 200;
player.cooldownTime = 10; // Reduced cooldown between shots (from 30)
// Add player last so it appears on top of all background elements
game.addChild(player);
// Create health bar - directly attached to player
healthBarFill = LK.getAsset('baseBox', {
anchorX: 0,
// Anchor at the left edge for scaling from left to right
anchorY: 0.5,
tint: 0x00ff00,
// Set tint to green for full health explicitly in creation
scaleX: 10,
scaleY: 0.5 // Base height scale factor (visual size)
});
// Ensure bar scale correctly represents 100% health at the start
healthBarFill.scale.x = player.health / player.maxHealth; // Set scale based on current health percentage
// Create health text display
var healthText = new Text2('100/100', {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0);
healthText.y = 30; // Position below health bar
// Attach directly to player
player.addChild(healthBarFill);
player.addChild(healthText);
// Position relative to player - higher placement
healthBarFill.y = -80;
// Position health text above health bar
healthText.y = -120;
// Set initial animation for health bar to draw player's attention
tween(healthBarFill, {
alpha: 0.5
}, {
duration: 500,
onFinish: function onFinish() {
tween(healthBarFill, {
alpha: 1
}, {
duration: 500
});
}
});
// Start background music with fade in effect
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Game input handlers
game.down = function (x, y, obj) {
// Shoot when tapping anywhere
player.shoot();
};
game.move = function (x, y, obj) {
// Move player horizontally (keep vertical position fixed)
if (gameActive) {
player.x = x;
// Keep player within game bounds
if (player.x < 75) {
player.x = 75;
}
if (player.x > 2048 - 75) {
player.x = 2048 - 75;
}
// Update shield position if active
if (player.hasShield && player.shield && player.shield.isActive) {
player.shield.x = player.x;
player.shield.y = player.y;
}
}
};
// Spawn enemies
function spawnEnemy() {
var enemy;
if (bossWave) {
// Use Boss2 for waves 15 and 20
if (wave >= 15 && wave % 5 === 0) {
enemy = new Boss2();
enemy.x = 2048 / 2;
enemy.y = -200;
// If this is the final boss (wave 20), set it to have higher HP
if (wave === 20) {
enemy.setAsFinalBoss();
}
} else {
enemy = new BossCat();
enemy.x = 2048 / 2;
enemy.y = -200;
// If this is the final boss of the first stage (wave 10), set it to have higher HP
if (wave === 10) {
enemy.setAsFinalBoss();
}
}
// Create boss health display
bossHealthText = new Text2("Boss HP: " + enemy.health, {
size: 60,
fill: 0xFF6600
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.y = 150;
LK.gui.top.addChild(bossHealthText);
} else {
enemy = new Enemy();
enemy.x = 100 + Math.random() * (2048 - 200);
enemy.y = -100;
// Set random enemy type based on current wave
var typeRoll = Math.random();
if (wave <= 2) {
// Waves 1-2: Only basic enemies
enemy.setType('basic');
} else if (wave <= 4) {
// Waves 3-4: Basic and fast enemies
if (typeRoll < 0.6) {
enemy.setType('basic');
} else {
enemy.setType('fast');
}
} else if (wave <= 6) {
// Waves 5-6: Add zigzag enemies
if (typeRoll < 0.4) {
enemy.setType('basic');
} else if (typeRoll < 0.7) {
enemy.setType('fast');
} else {
enemy.setType('zigzag');
}
} else if (wave <= 10) {
// Waves 7-10: All enemy types
if (typeRoll < 0.3) {
enemy.setType('basic');
} else if (typeRoll < 0.5) {
enemy.setType('fast');
} else if (typeRoll < 0.8) {
enemy.setType('zigzag');
} else {
enemy.setType('teleport');
}
} else {
// Waves 11-20: Harder distribution with fewer basic enemies
if (typeRoll < 0.15) {
enemy.setType('basic');
} else if (typeRoll < 0.4) {
enemy.setType('fast');
} else if (typeRoll < 0.7) {
enemy.setType('zigzag');
} else {
enemy.setType('teleport');
}
}
}
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
// Main game update loop
game.update = function () {
if (!gameActive) {
return;
}
// Track and update explosions
var explosions = [];
var _iterator = _createForOfIteratorHelper(game.children),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var child = _step.value;
if (child instanceof Explosion) {
explosions.push(child);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
for (var i = explosions.length - 1; i >= 0; i--) {
var explosion = explosions[i];
explosion.update();
if (!explosion.isActive) {
game.removeChild(explosion);
}
}
// Set up background scrolling with two identical backgrounds side by side
if (!game.backgroundClone) {
// Create a clone of the background
game.backgroundClone = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
// Position the clone directly above the original background
game.backgroundClone.x = background.x;
game.backgroundClone.y = background.y - background.height;
game.addChild(game.backgroundClone);
}
// Scroll both backgrounds
background.y += 5; // Increased scroll speed
game.backgroundClone.y += 5; // Same increased speed for clone
// If original background goes completely off screen, move it above the clone
if (background.y - background.height / 2 > 2732) {
background.y = game.backgroundClone.y - background.height;
}
// If clone background goes completely off screen, move it above the original
if (game.backgroundClone.y - game.backgroundClone.height / 2 > 2732) {
game.backgroundClone.y = background.y - background.height;
}
// Update player
player.update();
// Update shield if active
if (player.hasShield && player.shield) {
player.shield.update();
if (!player.shield.isActive) {
player.hasShield = false;
game.removeChild(player.shield);
player.shield = null;
}
}
// Handle enemy spawning
if (spawnCooldown <= 0) {
if (enemiesSpawned < enemiesInWave) {
spawnEnemy();
// Cooldown between spawns
if (bossWave) {
spawnCooldown = 1; // Boss spawns immediately
} else {
spawnCooldown = 60; // 1 second between normal enemy spawns
}
}
} else {
spawnCooldown--;
}
// Update lasers and check for collisions
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
if (!laser.isActive) {
game.removeChild(laser);
lasers.splice(i, 1);
continue;
}
// Check for collisions with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (enemy.isActive && laser.isActive && laser.intersects(enemy)) {
enemy.takeDamage(laser.damage);
laser.isActive = false;
if (!enemy.isActive) {
LK.setScore(LK.getScore() + enemy.pointValue);
scoreTxt.setText("Score\r\n" + LK.getScore());
game.removeChild(enemy);
enemies.splice(j, 1);
}
break;
}
}
}
// Update enemy lasers and check for collisions with player
for (var el = enemyLasers.length - 1; el >= 0; el--) {
var enemyLaser = enemyLasers[el];
enemyLaser.update();
if (!enemyLaser.isActive) {
game.removeChild(enemyLaser);
enemyLasers.splice(el, 1);
continue;
}
// Check for collision with player
if (enemyLaser.intersects(player)) {
// If player has shield, block the laser
if (player.hasShield) {
enemyLaser.isActive = false;
game.removeChild(enemyLaser);
enemyLasers.splice(el, 1);
// Flash shield to show impact
if (player.shield) {
LK.effects.flashObject(player.shield, 0xffff00, 300);
}
} else {
// Player takes damage from laser - only one point of health
var laserDamage = 1;
player.takeDamage(laserDamage);
enemyLaser.isActive = false;
game.removeChild(enemyLaser);
enemyLasers.splice(el, 1);
if (!gameActive) {
return;
} // Return if player died
}
}
}
// Update enemies
var activeEnemies = 0;
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
enemy.update();
if (!enemy.isActive) {
game.removeChild(enemy);
enemies.splice(k, 1);
continue;
}
activeEnemies++;
// Check collision with player
if (enemy.intersects(player)) {
// Initialize lastDamageTime if not set
if (enemy.lastDamageTime === undefined) {
enemy.lastDamageTime = 0;
}
// Check if 3 seconds (180 frames at 60fps) have passed since last damage
var currentTime = LK.ticks;
var damageInterval = 180; // 3 seconds at 60fps
if (player.hasShield) {
// Shield absorbs collision damage
if (player.shield) {
LK.effects.flashObject(player.shield, 0xffff00, 300);
}
} else {
// Player takes damage from collision only every 3 seconds
var collisionDamage = 10;
// Apply damage only if enough time has passed
if (currentTime - enemy.lastDamageTime >= damageInterval) {
player.takeDamage(collisionDamage);
enemy.lastDamageTime = currentTime;
// Visual feedback that damage was applied
LK.effects.flashObject(enemy, 0xff0000, 300);
}
if (!gameActive) {
return;
} // Return if player died
}
}
}
// Update power-ups
for (var l = powerUps.length - 1; l >= 0; l--) {
var powerUp = powerUps[l];
powerUp.update();
if (!powerUp.isActive) {
game.removeChild(powerUp);
powerUps.splice(l, 1);
continue;
}
// Check collision with player
if (powerUp.intersects(player)) {
// Apply power-up effect
if (powerUp.type === 'spreadshot') {
player.activateSpreadshot();
} else if (powerUp.type === 'shield') {
player.activateShield();
} else if (powerUp.type === 'bomb') {
player.activateBomb();
} else if (powerUp.type === 'heart') {
player.heal(50); // Restore 50 health points
}
LK.getSound('powerup').play();
powerUp.isActive = false;
game.removeChild(powerUp);
powerUps.splice(l, 1);
}
}
// Check if wave is complete
if (enemiesSpawned >= enemiesInWave && activeEnemies === 0) {
// Wave complete, start next wave
wave++;
waveTxt.setText("Wave\r\n" + wave);
// Every 5th wave is a boss wave
if (wave % 5 === 0) {
bossWave = true;
enemiesInWave = 1;
} else {
bossWave = false;
// Reduced enemies per wave to make the game easier
enemiesInWave = 5 + Math.floor(wave * 1.5);
// Remove boss health display if it exists
if (bossHealthText) {
LK.gui.top.removeChild(bossHealthText);
bossHealthText = null;
}
}
enemiesSpawned = 0;
spawnCooldown = 120; // 2 second delay between waves
// Check for music changes between levels 1-10 and 11-20
if (wave == 11) {
// Change to gameMusic2 for levels 11-20
LK.playMusic('gameMusic2', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
} else if (wave == 1) {
// Change back to gameMusic for levels 1-10
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// Win condition - player has survived 20 waves
if (wave > 20) {
LK.showYouWin();
return;
}
}
// Auto fire if player holds down finger (increased frequency)
if (LK.ticks % 10 === 0) {
player.shoot();
}
};
explosion 💥. In-Game asset. 2d. High contrast. No shadows
a cat head. In-Game asset. 2d. High contrast
make this cat expression goes silly but angry
2 cucumbers inside a glass bubble. In-Game asset. 2d. High contrast. No shadows
a shield inside a glass bubble. In-Game asset. 2d. High contrast. No shadows
an explosion inside a glass bubble. In-Game asset. 2d. High contrast. No shadows
a heart inside a glass bubble. In-Game asset. 2d. High contrast. No shadows