Code edit (1 edits merged)
Please save this source code
User prompt
David vs. Giants: Stone Sling Showdown
Initial prompt
I want to make a game exactly like David vs. Goliath by Zango Games. It will be a 2D game. All the weapons, health regeneration, and music will be the same. The character will continuously spin his right arm in a circular motion. First, there will be small giants coming along the path. You will try to defeat these incoming giants by throwing stones at them. However, there will be no blood. When a giant dies, it will immediately disappear. Then, a main giant (boss) will appear on the screen, and you'll have to try to destroy him. There will be different maps and varied environments.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Backpack class (for weapon upgrades) var Backpack = Container.expand(function () { var self = Container.call(this); // Determine color and shape by type after instantiation self.type = 0; // 1=laser, 2=plasma, 3=bazooka self.collected = false; self._bag = null; function updateBagVisual() { // Remove old bag if exists if (self._bag) { self.removeChild(self._bag); self._bag = null; } var color = 0x888888; if (self.type === 1) color = 0xff0000; // Laser: red else if (self.type === 2) color = 0xff9800; // Plasma: orange else if (self.type === 3) color = 0x9c27b0; // Bazooka: purple self._bag = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: color, shape: 'box' }); } updateBagVisual(); self.update = function () { // Bobbing animation self.y += Math.sin(LK.ticks / 10 + self.x) * 0.7; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Bazooka projectile (green, can destroy two giants) var Bazooka = Container.expand(function () { var self = Container.call(this); var bazooka = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, color: 0x00ff00 }); self.vx = 0; self.vy = -28; self.active = true; self.damage = 2; // Bazooka deals 2 damage (kills a giant in 1 hit) self.pierce = 2; // can hit two targets self.update = function () { if (!self.active) return; self.x += self.vx; self.y += self.vy; }; return self; }); // Boss Bomb projectile (thrown by boss) var BossBomb = Container.expand(function () { var self = Container.call(this); var bomb = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, color: 0x333333, shape: 'ellipse' }); self.vx = 0; self.vy = 0; self.hit = false; self._exploded = false; self.update = function () { if (self._exploded) return; self.x += self.vx; self.y += self.vy; self.vy += 1.2; // gravity // Simple spin bomb.rotation += 0.2; }; self.explode = function () { if (self._exploded) return; self._exploded = true; tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); // Optional: play explosion sound here }; return self; }); // Boss Giant var BossGiant = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('bossBody', { anchorX: 0.5, anchorY: 1 }); var head = self.attachAsset('bossHead', { anchorX: 0.5, anchorY: 1, y: -body.height }); self.hp = 15; self.speed = 2.2; self.active = true; self.update = function () { if (!self.active) return; // Move toward David (slightly faster than before) if (david && david.y < self.y) { // Move down (with world scrolling up) self.y += self.speed + 1.2; // Track David horizontally if (Math.abs(self.x - david.x) > 10) { if (self.x < david.x) self.x += 2.5;else self.x -= 2.5; } } else { self.y += self.speed + 1.2; } // Keep boss inside sky image area if (self.x < 150) self.x = 150; if (self.x > 2048 - 150) self.x = 2048 - 150; if (self.y < 200) self.y = 200; if (self.y > 1800) self.y = 1800; // --- Boss Stick (right hand) --- // Add stick if not present if (!self._stick) { self._stick = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.1, width: 30, height: 180, color: 0x8d5524, x: self.width / 2 + 80, y: -self.height + 120 }); self._stickSwingAngle = 0; self._stickSwingDown = true; self._stickBaseX = self._stick.x; self._stickBaseY = self._stick.y; } // Animate stick swinging downward to hit David if (typeof self._stickSwingAngle !== "number") self._stickSwingAngle = 0; if (typeof self._stickSwingDown !== "boolean") self._stickSwingDown = true; if (self._stickSwingDown) { self._stickSwingAngle += 0.10 + Math.random() * 0.03; if (self._stickSwingAngle > 1.2) self._stickSwingDown = false; } else { self._stickSwingAngle -= 0.08 + Math.random() * 0.02; if (self._stickSwingAngle < -0.3) self._stickSwingDown = true; } self._stick.rotation = self._stickSwingAngle + 1.1; // Keep stick attached to boss's right side self._stick.x = self.width / 2 + 80; self._stick.y = -self.height + 120; // --- End Boss Stick --- // Axe swing animation (right arm) if (!self._axe) { self._axe = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.1, x: self.width / 2 + 60, y: -self.height + 80, color: 0x444444 }); } if (!self._axeAngle) self._axeAngle = 0; self._axeAngle += 0.13 + Math.random() * 0.04; self._axe.rotation = Math.sin(self._axeAngle) * 1.2 + 1.2; // Boss health bar (tube) above boss if (!self._hpBarBg) { self._hpBarBg = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 32, color: 0x222222, shape: 'box', y: -self.height - 60 }); } if (!self._hpBar) { self._hpBar = self.attachAsset('coin', { anchorX: 0, anchorY: 0.5, width: 210, height: 20, color: 0x43a047, shape: 'box', x: -105, y: -self.height - 60 }); } if (self._hpBar) { var maxHp = self.maxHp || (self.hp > 0 ? self.hp : 1); if (!self.maxHp) self.maxHp = self.hp; var frac = Math.max(0, Math.min(1, self.hp / self.maxHp)); self._hpBar.width = 210 * frac; // Color: green to red var col = 0x43a047; if (frac < 0.5) col = 0xffa000; if (frac < 0.25) col = 0xd32f2f; self._hpBar.tint = col; } }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.active = false; LK.getSound('giantVanish').play(); tween(self, { alpha: 0 }, { duration: 600, onFinish: function onFinish() { // Remove health bar assets if present if (self._hpBarBg) { self.removeChild(self._hpBarBg); self._hpBarBg = null; } if (self._hpBar) { self.removeChild(self._hpBar); self._hpBar = null; } self.destroy(); } }); } else { LK.getSound('hit').play(); tween(self, { tint: 0xff7043 }, { duration: 180, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 180 }); } }); } }; return self; }); // Boss Whip hitbox (short-lived, for boss whip attack) var BossWhip = Container.expand(function () { var self = Container.call(this); var whip = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 40, color: 0x8d5524, shape: 'box' }); self._life = 0; self.hit = false; return self; }); // Chicken class var Chicken = Container.expand(function () { var self = Container.call(this); var chicken = self.attachAsset('chicken', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.animTimer = 0; self.update = function () { // Simple idle animation (bobbing) self.animTimer++; self.y += Math.sin(self.animTimer / 10) * 1.2; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Coin class var Coin = Container.expand(function () { var self = Container.call(this); var coin = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.animTimer = 0; self.update = function () { // Simple spin animation self.animTimer++; coin.rotation += 0.15; self.y += Math.sin(self.animTimer / 12) * 0.8; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 40 }, { duration: 200, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Spawn chickens and coins for the current wave // David (player) var David = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('davidBody', { anchorX: 0.5, anchorY: 1 }); // Arm removed (no davidArm asset) self.arm = { width: 100, height: 100 }; // dummy object to prevent errors self.armAngle = 0; self.armSpeed = 0.18; // radians per frame self.armRadius = 70; self.slingReady = true; self.stoneCooldown = 0; self.hp = 5; self.maxHp = 5; self.regenTimer = 0; self.update = function () { // Spin arm self.armAngle += self.armSpeed; if (self.armAngle > Math.PI * 2) self.armAngle -= Math.PI * 2; // Arm rotates in a circle self.arm.rotation = self.armAngle; // Stone cooldown if (self.stoneCooldown > 0) self.stoneCooldown--; // Health regen if (self.hp < self.maxHp) { self.regenTimer++; if (self.regenTimer > 180) { // 3 seconds self.hp++; self.regenTimer = 0; } } else { self.regenTimer = 0; } }; self.throwStone = function () { if (!self.slingReady || self.stoneCooldown > 0) return null; self.slingReady = false; self.stoneCooldown = 5; // fast fire rate for machine gun LK.getSound('throw').play(); // Fire from the middle of David's body var sx = self.x; var sy = self.y - body.height / 2; // Always fire a machine gun bullet straight upwards var proj = new Stone(); proj.x = sx; proj.y = sy; proj.vx = 0; proj.vy = -38; // faster than stone for machine gun return proj; }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp < 0) self.hp = 0; LK.effects.flashObject(self, 0xff0000, 400); // No weapon revert on damage in machine gun only mode }; return self; }); // Enemy spaceship bullet (missile-shaped, slower, 1 damage per hit) var EnemyShipBullet = Container.expand(function () { var self = Container.call(this); // Use a longer, missile-like shape var bullet = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.5, width: 24, height: 64, color: 0xff2222 }); self.vx = 0; self.vy = 0; self.active = true; self._life = 0; self.lastX = self.x; self.lastY = self.y; self.damage = 1; // Only 1 damage per hit self.update = function () { self._life++; self.x += self.vx; self.y += self.vy; // Rotate to face direction of movement if (self.vx !== 0 || self.vy !== 0) { bullet.rotation = Math.atan2(self.vy, self.vx) + Math.PI / 2; } // Remove if off screen if (self.x < -100 || self.x > 2200 || self.y < -100 || self.y > 2800) { self.active = false; self.destroy(); } self.lastX = self.x; self.lastY = self.y; }; return self; }); // Enemy Spaceship (appears every 2 minutes, shoots at David, exits after 10s) var EnemySpaceship = Container.expand(function () { var self = Container.call(this); // Use a box for the ship body (gray) var ship = self.attachAsset('bossBody', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 90, color: 0x8888ff, shape: 'box' }); // Gun visual (front) var gun = self.attachAsset('davidArm', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 60, color: 0xff2222, x: 0, y: 40 }); self.state = "entering"; // entering, firing, exiting self.fireTimer = 0; self.exitDir = 1; // 1 = right, -1 = left self.speed = 18; self.fireInterval = 18; // frames between shots self.lastFireTick = 0; self.boundaryX = 1024; // center of screen self.targetY = 600; // y position to hover at self.lastX = self.x; self.lastY = self.y; self._life = 0; self.update = function () { self._life++; // State machine if (self.state === "entering") { // Move horizontally toward boundaryX, stop at boundary if (self.exitDir === 1 && self.x < self.boundaryX || self.exitDir === -1 && self.x > self.boundaryX) { self.x += self.speed * self.exitDir; // Clamp to boundary if (self.exitDir === 1 && self.x > self.boundaryX || self.exitDir === -1 && self.x < self.boundaryX) { self.x = self.boundaryX; } } // Move vertically to targetY if (Math.abs(self.y - self.targetY) > 8) { self.y += (self.targetY - self.y) * 0.18; } // Arrived at boundary and targetY if (self.x === self.boundaryX && Math.abs(self.y - self.targetY) < 10) { self.state = "firing"; self.fireTimer = 0; } } else if (self.state === "firing") { // Stay at boundary, fire at David every fireInterval frames self.fireTimer++; if (self.fireTimer % self.fireInterval === 0) { self.fireAtDavid(); } // After 10 seconds (600 frames), exit if (self.fireTimer > 600) { self.state = "exiting"; } } else if (self.state === "exiting") { // Move quickly off screen in exitDir self.x += self.speed * 2.5 * self.exitDir; if (self.exitDir === 1 && self.x > 2200 || self.exitDir === -1 && self.x < -200) { self.destroy(); } } // Defensive: update lastX/lastY for event triggers self.lastX = self.x; self.lastY = self.y; }; // Fire a bullet at David's current position self.fireAtDavid = function () { if (!david || !gameActive) return; var bullet = new EnemyShipBullet(); bullet.x = self.x; bullet.y = self.y + 40; // Aim at David's current position var dx = david.x - self.x; var dy = david.y - 60 - bullet.y; var dist = Math.sqrt(dx * dx + dy * dy); // Make missile slower than normal (e.g. 18 instead of 32) var speed = 18; bullet.vx = dx / dist * speed; bullet.vy = dy / dist * speed; enemyShipBullets.push(bullet); game.addChild(bullet); }; return self; }); // Giant enemy var Giant = Container.expand(function () { var self = Container.call(this); var body = self.attachAsset('giantBody', { anchorX: 0.5, anchorY: 1 }); self.hp = 2; // Each giant requires 2 hits to die self.speed = 3 + Math.random() * 2; self.active = true; self.update = function () { if (!self.active) return; // Giants move downward (with world scrolling up, so add speed to y) self.y += self.speed; }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.active = false; LK.getSound('giantVanish').play(); tween(self, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); } else { LK.getSound('hit').play(); tween(self, { tint: 0xffe082 }, { duration: 120, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 120 }); } }); } }; return self; }); // Heart class (for health pickup) var Heart = Container.expand(function () { var self = Container.call(this); var heart = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: 0xff69b4, // pink shape: 'box' }); self.collected = false; self.update = function () { self.y += Math.sin(LK.ticks / 12 + self.x) * 0.8; }; self.collect = function () { if (self.collected) return; self.collected = true; tween(self, { alpha: 0, y: self.y - 60 }, { duration: 300, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // Laser projectile (covers whole screen, instant) var Laser = Container.expand(function () { var self = Container.call(this); var laser = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 1, width: 30, height: 400, // Match catapult (stone) range color: 0x9c27b0 // purple }); self.active = true; self.damage = 1; // Laser deals 1 damage to giants, so 2 hits are required self.update = function () { // Laser is instant, doesn't move, but can fade out if (!self.active) return; // Fade out after a few frames if (!self._life) self._life = 0; self._life++; if (self._life > 10) { self.active = false; self.destroy(); } }; return self; }); // Plasma projectile (fast, yellow, destroys instantly) var Plasma = Container.expand(function () { var self = Container.call(this); var plasma = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5, width: 44, height: 44, color: 0xffe600 }); self.vx = 0; self.vy = -40; self.active = true; self.damage = 99; // always kills (plasma is instant kill) self.update = function () { if (!self.active) return; self.x += self.vx; self.y += self.vy; }; return self; }); // Stone projectile var Stone = Container.expand(function () { var self = Container.call(this); var stone = self.attachAsset('slingStone', { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.active = true; self.damage = 1; // Stone deals 1 damage to giants self.update = function () { if (!self.active) return; if (typeof self._startY === "undefined") self._startY = self.y; self.x += self.vx; self.y += self.vy; // Gravity self.vy += 0.7; // Remove if exceeded maximum range (e.g. 900px upwards from start) if (self._startY - self.y > 900) { self.active = false; if (typeof self.destroy === "function") self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb }); /**** * Game Code ****/ enemySpaceshipTimer = 0; // --- PLAYER --- var enemySpaceshipActive = false; var enemySpaceship = null; var enemyShipBullets = []; var enemySpaceshipTimer = 0; // --- ENVIRONMENT --- // Character (David) var sky = LK.getAsset('sky', { x: 0, y: 0 }); game.addChild(sky); // Ensure sky is always at the bottom of the display list and never removed if (typeof game._skyLock === "undefined") { game._skyLock = true; var originalRemoveChild = game.removeChild; game.removeChild = function (child) { if (child === sky) { // Prevent sky from being removed return; } return originalRemoveChild.call(this, child); }; // Always keep sky at the bottom var originalAddChild = game.addChild; game.addChild = function (child) { if (child === sky) { // Only add sky if not already present if (this.children && this.children[0] !== sky) { originalAddChild.call(this, child); // Move sky to index 0 if (this.children && this.children.length > 1) { this.children.splice(this.children.indexOf(sky), 1); this.children.unshift(sky); } } return sky; } var result = originalAddChild.call(this, child); // After adding any child, ensure sky is at index 0 if (this.children && this.children[0] !== sky && this.children.indexOf(sky) !== -1) { this.children.splice(this.children.indexOf(sky), 1); this.children.unshift(sky); } return result; }; } var ground = LK.getAsset('ground', { x: 0, y: 2532 }); ground.y = 2532; // bottom of screen game.addChild(ground); // --- PLAYER --- var david = new David(); david.x = 1024; // center horizontally david.y = 2300; // near bottom, above ground game.addChild(david); // --- WEAPON SYSTEM --- // Machine Gun is the only weapon var WEAPON_MACHINEGUN = 0; var weaponNames = ["Machine Gun"]; var weaponColors = [0x888888]; // Start with Machine Gun only var weapon = WEAPON_MACHINEGUN; var weaponUnlocked = [true]; // Only Machine Gun unlocked var weaponLast = WEAPON_MACHINEGUN; var weaponTimeouts = [0]; // Weapon UI var weaponTxt = new Text2('Machine Gun', { size: 80, fill: "#fff" }); weaponTxt.anchor.set(0.5, 0); LK.gui.top.addChild(weaponTxt); weaponTxt.y = 220; // --- BACKPACKS & HEART --- var backpacks = []; var heart = null; var heartCollected = false; // --- SCROLLING STATE --- var scrollY = 0; // how much the world has scrolled up var scrollSpeed = 4; // px per frame, adjust for desired speed // --- UI --- var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var hpTxt = new Text2('♥♥♥♥♥', { size: 90, fill: 0xE53935 }); hpTxt.anchor.set(0.5, 0); LK.gui.top.addChild(hpTxt); hpTxt.y = 120; // --- LEADERBOARD --- // Leaderboard UI and logic removed as per new requirements // --- GAME STATE --- var stones = []; var giants = []; var boss = null; var chickens = []; var coins = []; var wave = 1; var giantsToSpawn = 5; var giantsSpawned = 0; var spawnTimer = 0; var gameActive = true; var bossActive = false; var bossDefeated = false; // --- CHAPTER STATE --- var chapter = 1; var bossScale = 1; // Chicken and coin spawn positions per level (max 5 coins per level) var chickenSpawns = [ // For each wave, array of {x, y} [{ x: 600, y: 1800 }, { x: 1500, y: 800 }], [{ x: 400, y: 1200 }, { x: 1700, y: 600 }, { x: 1200, y: 2000 }], [{ x: 900, y: 900 }, { x: 1800, y: 400 }, { x: 300, y: 2200 }]]; var coinSpawns = [[{ x: 400, y: 400 }, { x: 800, y: 600 }, { x: 1200, y: 800 }, { x: 1600, y: 1000 }, { x: 200, y: 2000 }], [{ x: 600, y: 500 }, { x: 1000, y: 700 }, { x: 1400, y: 900 }, { x: 1800, y: 1100 }, { x: 400, y: 2100 }], [{ x: 800, y: 300 }, { x: 1200, y: 500 }, { x: 1600, y: 700 }, { x: 200, y: 900 }, { x: 1800, y: 2300 }]]; // Spawn chickens and coins for the current wave function spawnCollectiblesForWave(waveIdx) { // Remove old for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } // Chickens var cSpawns = chickenSpawns[waveIdx % chickenSpawns.length]; for (var i = 0; i < cSpawns.length; i++) { var c = new Chicken(); c.x = cSpawns[i].x; c.y = cSpawns[i].y; chickens.push(c); game.addChild(c); } // Coins var coinSp = coinSpawns[waveIdx % coinSpawns.length]; for (var i = 0; i < coinSp.length; i++) { var coin = new Coin(); coin.x = coinSp[i].x; coin.y = coinSp[i].y; coins.push(coin); game.addChild(coin); } } // --- MUSIC --- LK.playMusic('mainTheme'); // --- MAPS/ENVIRONMENTS --- var environments = [{ sky: 0x87ceeb, ground: 0x7ec850 }, // blue sky, green grass { sky: 0xf7e9a0, ground: 0xe0c97f }, // desert { sky: 0x9ecae1, ground: 0x8c8c8c } // mountain/rocky ]; var currentEnv = 0; function setEnvironment(idx) { sky.tint = environments[idx].sky; ground.tint = environments[idx].ground; } setEnvironment(currentEnv); // Leaderboard name entry/update function removed as per new requirements // --- UI UPDATE --- function updateScore() { scoreTxt.setText(score); } function updateHp() { var s = ''; for (var i = 0; i < david.hp; i++) s += '♥'; for (var i = david.hp; i < david.maxHp; i++) s += '♡'; hpTxt.setText(s); } updateScore(); updateHp(); // --- INPUT --- var isTouching = false; var lastTouchTick = 0; // --- MACHINE GUN (AUTOMATIC FIRE) STATE --- var autoFireInterval = 5; // frames between shots (12 shots/sec at 60fps) var autoFireTick = 0; var autoFireActive = false; var autoFireTimeout = null; game.down = function (x, y, obj) { isTouching = true; lastTouchTick = LK.ticks; // Move David to pointer/touch position (centered, free movement) if (gameActive && !bossDefeated) { var minX = david.arm.width / 2 + 60; var maxX = 2048 - david.arm.width / 2 - 60; var minY = 0 + david.arm.height / 2 + 60; var maxY = 2532; var newX = x; var newY = y; if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; david.x = newX; david.y = newY; } // Start automatic fire if (gameActive && !bossDefeated && !autoFireActive) { autoFireActive = true; autoFireTick = 0; // Fire immediately fireMachineGun(); // Set up interval for continuous fire autoFireTimeout = LK.setInterval(fireMachineGun, autoFireInterval * 1000 / 60); } }; game.up = function (x, y, obj) { isTouching = false; // Stop automatic fire if (autoFireActive) { autoFireActive = false; if (autoFireTimeout) { LK.clearInterval(autoFireTimeout); autoFireTimeout = null; } } // Only allow throw if touch was short (tap) if (gameActive && !bossDefeated && LK.ticks - lastTouchTick < 30) { // Do not fire a single shot here, as auto fire already handles it } }; // Helper function for machine gun fire function fireMachineGun() { if (!gameActive || bossDefeated) return; if (!isTouching) return; var stone = david.throwStone(); if (stone) { stones.push(stone); // If the stone is a Laser, add it after all giants to ensure it's above them if (stone instanceof Laser) { // Always add the laser to the top of the display list so it is visible above all giants and other objects // Remove if already present, then add to top var idx = game.children.indexOf(stone); if (idx !== -1) { game.children.splice(idx, 1); } game.addChild(stone); if (game.children[game.children.length - 1] !== stone) { var idx2 = game.children.indexOf(stone); if (idx2 !== -1) { game.children.splice(idx2, 1); game.children.push(stone); } } stone.parent = game; if (typeof stone.onAdded === "function") stone.onAdded(); } else { game.addChild(stone); } // Arm can't throw again until next full rotation LK.setTimeout(function () { david.slingReady = true; }, 400); } } game.move = function (x, y, obj) { // Move David to pointer/touch position (centered, free movement) if (gameActive && !bossDefeated) { var minX = david.arm.width / 2 + 60; var maxX = 2048 - david.arm.width / 2 - 60; var minY = 0 + david.arm.height / 2 + 60; var maxY = 2532; // bottom of screen var newX = x; var newY = y; if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; david.x = newX; david.y = newY; } }; // --- GAME LOOP --- // --- GIANT GROUP SPAWN EVERY 5 SECONDS (3-5 GIANTS PER GROUP) --- var giantGroupTimer = 0; var giantGroupInterval = 5 * 60; // 5 seconds * 60 FPS game.update = function () { if (!gameActive) return; // --- ENEMY SPACESHIP TIMELINE LOGIC --- // Only spawn if not bossActive and not already present if (!bossActive && !enemySpaceshipActive) { enemySpaceshipTimer++; if (enemySpaceshipTimer >= 2 * 60 * 60) { // every 2 minutes (7200 frames) // Spawn from same side as giants (randomly left or right) var side = Math.random() < 0.5 ? -1 : 1; enemySpaceship = new EnemySpaceship(); enemySpaceship.exitDir = side; enemySpaceship.x = side === 1 ? -200 : 2248; enemySpaceship.y = 400; enemySpaceshipActive = true; enemySpaceshipTimer = 0; game.addChild(enemySpaceship); } } // Update enemy spaceship if present if (enemySpaceshipActive && enemySpaceship) { enemySpaceship.update(); // Remove if destroyed if (enemySpaceship.destroyed || typeof enemySpaceship.parent === "undefined" || enemySpaceship.parent === null) { enemySpaceship = null; enemySpaceshipActive = false; // Remove all its bullets for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) { enemyShipBullets[i].destroy(); } } enemyShipBullets.length = 0; } } // Update enemy spaceship bullets for (var i = enemyShipBullets.length - 1; i >= 0; i--) { var b = enemyShipBullets[i]; if (!b || !b.active) { enemyShipBullets.splice(i, 1); continue; } b.update(); b.y -= scrollSpeed; // move up with world // Collision with David if (b.active && david && b.intersects(david)) { b.active = false; b.destroy(); enemyShipBullets.splice(i, 1); // Always only 1 life per hit, regardless of bullet properties david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } } } // Spawn a group of 3-5 giants every 5 seconds, unless boss is active if (!bossActive) { giantGroupTimer++; if (giantGroupTimer >= giantGroupInterval) { var groupSize = 3 + Math.floor(Math.random() * 3); // 3, 4, or 5 for (var i = 0; i < groupSize; i++) { var g = new Giant(); g.x = 400 + Math.random() * 1200; g.y = -200 - i * 80; // stagger vertically so they don't overlap exactly giants.push(g); game.addChild(g); // Also spawn a gold coin for this giant var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); } giantGroupTimer = 0; } } else { // Reset timer if boss is active so we don't get a burst after boss giantGroupTimer = 0; } // --- SCROLLING LOGIC --- scrollY += scrollSpeed; // Move all world elements up by scrollSpeed sky.y = 0; // Sky remains static and always visible ground.y = 2532 - scrollY; david.y -= scrollSpeed; // Move enemy spaceship and its bullets up with world if (enemySpaceshipActive && enemySpaceship) { enemySpaceship.y -= scrollSpeed; } for (var i = 0; i < enemyShipBullets.length; i++) { if (enemyShipBullets[i]) { enemyShipBullets[i].y -= scrollSpeed; } } // Environment change per wave if (wave - 1 < environments.length) { setEnvironment(wave - 1); } // --- WEAPON/ITEM SPAWNS --- // No weapon unlocks or backpacks for machine gun only mode // Heart: appears at 5:00, only once, near center // Calculate minutes based on ticks (60 ticks per second) var minutes = Math.floor(LK.ticks / (60 * 60)); if (minutes === 5 && !heart && !heartCollected) { heart = new Heart(); heart.x = 1024; heart.y = 1366; game.addChild(heart); } // David update david.update(); // Stones update for (var i = stones.length - 1; i >= 0; i--) { var s = stones[i]; s.update(); s.y -= scrollSpeed; // move stone up with world // Remove if off screen (top or bottom) if (s.x > 2048 || s.y < -100 || s.x < -100 || s.y > 2732) { s.destroy(); stones.splice(i, 1); continue; } } // Chickens update and collect for (var i = chickens.length - 1; i >= 0; i--) { var c = chickens[i]; c.update(); c.y -= scrollSpeed; // move up with world if (!c.collected && c.intersects(david)) { c.collect(); chickens.splice(i, 1); if (david.hp < david.maxHp) { david.hp++; updateHp(); } // Optional: play sound here if you have a chicken collect sound } } // Backpacks update and collect // (No backpacks in machine gun only mode) // Heart update and collect if (heart && !heart.collected) { heart.update(); heart.y -= scrollSpeed; if (heart.intersects(david)) { heart.collect(); heartCollected = true; if (david.hp < david.maxHp) { david.hp++; updateHp(); } heart = null; } } // Coins update and collect for (var i = coins.length - 1; i >= 0; i--) { var coin = coins[i]; coin.update(); coin.y -= scrollSpeed; // move up with world if (!coin.collected && coin.intersects(david)) { coin.collect(); coins.splice(i, 1); score += 5; updateScore(); // Optional: play sound here if you have a coin collect sound // Respawn a new coin at a random location after collection var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); } } // Giants update for (var j = giants.length - 1; j >= 0; j--) { var g = giants[j]; g.update(); g.y -= scrollSpeed; // move up with world // Remove if off screen (bottom) if (g.y < -300) { g.destroy(); giants.splice(j, 1); continue; } // Collision with David if (g.active && g.intersects(david)) { g.active = false; g.destroy(); giants.splice(j, 1); david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } continue; } // Stones/projectiles hit giant // --- Laser: only damage the closest giant in front of David, and always visible above giants --- for (var k = stones.length - 1; k >= 0; k--) { var s2 = stones[k]; if (s2 instanceof Laser && s2.active) { // Find the closest active giant in front of David (y > 0, lowest y) var closestGiant = null; var minDist = Infinity; for (var m = 0; m < giants.length; m++) { var g2 = giants[m]; if (g2.active && g2.y < david.y && g2.y > -300) { var dist = Math.abs(g2.y - david.y); if (dist < minDist) { minDist = dist; closestGiant = g2; } } } if (closestGiant && closestGiant.active && s2.active && closestGiant.intersects(s2)) { closestGiant.takeDamage(s2.damage); s2.active = false; s2.destroy(); stones.splice(k, 1); if (!closestGiant.active) { score += 1; updateScore(); } } // Always keep the laser above all giants and other objects, every frame if (s2.parent === game) { var idx = game.children.indexOf(s2); if (idx !== -1 && idx !== game.children.length - 1) { game.children.splice(idx, 1); game.children.push(s2); } } continue; } // Non-laser projectiles if (g.active && s2.active && g.intersects(s2)) { g.takeDamage(s2.damage); if (s2 instanceof Bazooka) { s2.pierce--; if (s2.pierce <= 0) { s2.active = false; s2.destroy(); stones.splice(k, 1); } } else { s2.active = false; s2.destroy(); stones.splice(k, 1); } if (!g.active) { score += 1; updateScore(); } break; } } } // Boss update if (bossActive && boss && boss.active) { boss.update(); boss.y -= scrollSpeed; // move up with world // David can shoot at boss as soon as boss appears david.slingReady = true; // --- BOSS BOMB THROWING LOGIC --- // Boss throws bombs if David is far away (horizontally) if (!boss._bombCooldown) boss._bombCooldown = 0; boss._bombCooldown--; var bossToDavidDist = Math.abs(boss.x - david.x); if (bossToDavidDist > 400 && boss._bombCooldown <= 0) { // Throw a bomb toward David if (!boss._bombs) boss._bombs = []; var bomb = new BossBomb(); bomb.x = boss.x; bomb.y = boss.y - boss.height * boss.scale.y / 2; // Calculate velocity toward David's current position var dx = david.x - boss.x; var dy = david.y - boss.y; var dist = Math.sqrt(dx * dx + dy * dy); var speed = 18 + Math.random() * 4; bomb.vx = dx / dist * speed; bomb.vy = dy / dist * speed * 0.7 - 6; // arc upward boss._bombs.push(bomb); game.addChild(bomb); boss._bombCooldown = 90 + Math.floor(Math.random() * 60); // 1.5s cooldown } // Update boss bombs if (boss._bombs) { for (var b = boss._bombs.length - 1; b >= 0; b--) { var bomb = boss._bombs[b]; bomb.update(); bomb.y -= scrollSpeed; // Bomb hits David if (!bomb.hit && bomb.intersects(david)) { bomb.hit = true; bomb.explode(); boss._bombs.splice(b, 1); david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } continue; } // Remove if off screen or exploded if (bomb.y > 2732 || bomb.y < -100 || bomb._exploded) { bomb.destroy(); boss._bombs.splice(b, 1); } } } // --- BOSS WHIP ATTACK LOGIC --- // Boss whips if David is close horizontally if (!boss._whipCooldown) boss._whipCooldown = 0; boss._whipCooldown--; if (bossToDavidDist <= 400 && boss._whipCooldown <= 0) { // Whip attack: create a whip hitbox in front of boss if (!boss._whipHitbox) { boss._whipHitbox = new BossWhip(); boss._whipHitbox.x = boss.x + (boss.x < david.x ? 120 * boss.scale.x : -120 * boss.scale.x); boss._whipHitbox.y = boss.y - boss.height * boss.scale.y / 2 + 80 * boss.scale.y; boss._whipHitbox.scale.x = boss.scale.x; boss._whipHitbox.scale.y = boss.scale.y; game.addChild(boss._whipHitbox); } boss._whipHitbox._life = 0; boss._whipCooldown = 60 + Math.floor(Math.random() * 30); // 1s cooldown } // Update whip hitbox if (boss._whipHitbox) { boss._whipHitbox._life++; boss._whipHitbox.y -= scrollSpeed; // Whip lasts for 15 frames if (boss._whipHitbox._life > 15) { boss._whipHitbox.destroy(); boss._whipHitbox = null; } else if (!boss._whipHitbox.hit && boss._whipHitbox.intersects(david)) { boss._whipHitbox.hit = true; david.takeDamage(1); updateHp(); if (david.hp <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } } } // Boss hits David (body collision) if (boss.intersects(david)) { boss.active = false; boss.destroy(); david.takeDamage(2); updateHp(); LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameActive = false; return; } // Stones/projectiles hit boss for (var k = stones.length - 1; k >= 0; k--) { var s2 = stones[k]; if (boss.active && s2.active && boss.intersects(s2)) { boss.takeDamage(s2.damage); if (s2 instanceof Bazooka) { s2.pierce--; if (s2.pierce <= 0) { s2.active = false; s2.destroy(); stones.splice(k, 1); } } else { s2.active = false; s2.destroy(); stones.splice(k, 1); } if (!boss.active) { score += 10; updateScore(); bossDefeated = true; LK.effects.flashScreen(0x00ff00, 1200); // Show "Next Chapter" if not last chapter, else show "You Win" if (chapter < 3) { // Show "Next Chapter" overlay var nextChapterTxt = new Text2('Next Chapter', { size: 200, fill: "#fff" }); nextChapterTxt.anchor.set(0.5, 0.5); nextChapterTxt.x = 1024; nextChapterTxt.y = 1200; LK.gui.center.addChild(nextChapterTxt); // Prepare for next chapter after short delay LK.setTimeout(function () { LK.gui.center.removeChild(nextChapterTxt); // Advance chapter, reset state chapter++; wave = 1; giantsToSpawn = 5; giantsSpawned = 0; spawnTimer = 60; bossActive = false; bossDefeated = false; gameActive = true; // Scale up boss for next chapter bossScale = bossScale * 2; // Cycle environment currentEnv = (currentEnv + 1) % environments.length; setEnvironment(currentEnv); // Move David back to start david.x = 1024; david.y = 2300; // Remove all stones, chickens, coins, backpacks, heart for (var i = stones.length - 1; i >= 0; i--) { stones[i].destroy(); stones.splice(i, 1); } for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } for (var i = backpacks.length - 1; i >= 0; i--) { backpacks[i].destroy(); backpacks.splice(i, 1); } // Remove enemy spaceship and its bullets if (enemySpaceship) { enemySpaceship.destroy(); enemySpaceship = null; enemySpaceshipActive = false; } for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) enemyShipBullets[i].destroy(); enemyShipBullets.splice(i, 1); } if (heart) { heart.destroy(); heart = null; } heartCollected = false; // Reset scroll scrollY = 0; sky.y = 0; ground.y = 2532; // Reset weapons weapon = WEAPON_LASER; weaponUnlocked = [true, false, false]; weaponLast = WEAPON_LASER; weaponTxt.setText(weaponNames[0]); // Reset HP david.hp = david.maxHp; updateHp(); // Spawn collectibles for new wave spawnCollectiblesForWave(wave - 1); }, 2200); } else { // Last chapter, keep spawning giants forever bossActive = false; bossDefeated = false; gameActive = true; // Remove all stones, chickens, coins, backpacks, heart for (var i = stones.length - 1; i >= 0; i--) { stones[i].destroy(); stones.splice(i, 1); } for (var i = chickens.length - 1; i >= 0; i--) { chickens[i].destroy(); chickens.splice(i, 1); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].destroy(); coins.splice(i, 1); } for (var i = backpacks.length - 1; i >= 0; i--) { backpacks[i].destroy(); backpacks.splice(i, 1); } // Remove enemy spaceship and its bullets if (enemySpaceship) { enemySpaceship.destroy(); enemySpaceship = null; enemySpaceshipActive = false; } for (var i = enemyShipBullets.length - 1; i >= 0; i--) { if (enemyShipBullets[i]) enemyShipBullets[i].destroy(); enemyShipBullets.splice(i, 1); } if (heart) { heart.destroy(); heart = null; } heartCollected = false; // Reset scroll scrollY = 0; sky.y = 0; ground.y = 2532; // Move David back to start david.x = 1024; david.y = 2300; // Reset HP david.hp = david.maxHp; updateHp(); // Continue spawning giants forever wave++; giantsToSpawn += 3; giantsSpawned = 0; spawnTimer = 60; spawnCollectiblesForWave((wave - 1) % chickenSpawns.length); } return; } break; } } } // --- SPAWN LOGIC (vertical, from top) --- // Giants only spawn if boss is not active if (!bossActive) { // Only allow up to 5 giants at a time if (giants.length < 5 && giantsSpawned < giantsToSpawn) { spawnTimer--; if (spawnTimer <= 0) { var g = new Giant(); g.x = 400 + Math.random() * 1200; // random horizontal position g.y = -200; // spawn just above the top giants.push(g); game.addChild(g); giantsSpawned++; // --- SPAWN GOLD COIN FOR EACH GIANT --- var newCoin = new Coin(); newCoin.x = 400 + Math.random() * 1200; newCoin.y = 400 + Math.random() * 1800; coins.push(newCoin); game.addChild(newCoin); spawnTimer = 60 + Math.floor(Math.random() * 60); } } else if (giants.length === 0) { // Next wave or boss if (wave < 3) { wave++; giantsToSpawn += 3; giantsSpawned = 0; spawnTimer = 60; spawnCollectiblesForWave(wave - 1); // spawn chickens and coins for new wave } else { // Boss time: pause giants, spawn boss bossActive = true; boss = new BossGiant(); // Scale boss size and HP per chapter boss.x = 1024; // center horizontally boss.y = -300; // spawn above the top boss.scale.x = bossScale; boss.scale.y = bossScale; boss.hp = 15 * bossScale; game.addChild(boss); spawnCollectiblesForWave(wave - 1); // spawn for boss wave } } } // Giants will continuously respawn and keep coming without end // (Removed bossDefeated check and chapter2 popup, giants always respawn) };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Backpack class (for weapon upgrades)
var Backpack = Container.expand(function () {
var self = Container.call(this);
// Determine color and shape by type after instantiation
self.type = 0; // 1=laser, 2=plasma, 3=bazooka
self.collected = false;
self._bag = null;
function updateBagVisual() {
// Remove old bag if exists
if (self._bag) {
self.removeChild(self._bag);
self._bag = null;
}
var color = 0x888888;
if (self.type === 1) color = 0xff0000; // Laser: red
else if (self.type === 2) color = 0xff9800; // Plasma: orange
else if (self.type === 3) color = 0x9c27b0; // Bazooka: purple
self._bag = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90,
color: color,
shape: 'box'
});
}
updateBagVisual();
self.update = function () {
// Bobbing animation
self.y += Math.sin(LK.ticks / 10 + self.x) * 0.7;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Bazooka projectile (green, can destroy two giants)
var Bazooka = Container.expand(function () {
var self = Container.call(this);
var bazooka = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
color: 0x00ff00
});
self.vx = 0;
self.vy = -28;
self.active = true;
self.damage = 2; // Bazooka deals 2 damage (kills a giant in 1 hit)
self.pierce = 2; // can hit two targets
self.update = function () {
if (!self.active) return;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Boss Bomb projectile (thrown by boss)
var BossBomb = Container.expand(function () {
var self = Container.call(this);
var bomb = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
color: 0x333333,
shape: 'ellipse'
});
self.vx = 0;
self.vy = 0;
self.hit = false;
self._exploded = false;
self.update = function () {
if (self._exploded) return;
self.x += self.vx;
self.y += self.vy;
self.vy += 1.2; // gravity
// Simple spin
bomb.rotation += 0.2;
};
self.explode = function () {
if (self._exploded) return;
self._exploded = true;
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
// Optional: play explosion sound here
};
return self;
});
// Boss Giant
var BossGiant = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 1
});
var head = self.attachAsset('bossHead', {
anchorX: 0.5,
anchorY: 1,
y: -body.height
});
self.hp = 15;
self.speed = 2.2;
self.active = true;
self.update = function () {
if (!self.active) return;
// Move toward David (slightly faster than before)
if (david && david.y < self.y) {
// Move down (with world scrolling up)
self.y += self.speed + 1.2;
// Track David horizontally
if (Math.abs(self.x - david.x) > 10) {
if (self.x < david.x) self.x += 2.5;else self.x -= 2.5;
}
} else {
self.y += self.speed + 1.2;
}
// Keep boss inside sky image area
if (self.x < 150) self.x = 150;
if (self.x > 2048 - 150) self.x = 2048 - 150;
if (self.y < 200) self.y = 200;
if (self.y > 1800) self.y = 1800;
// --- Boss Stick (right hand) ---
// Add stick if not present
if (!self._stick) {
self._stick = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.1,
width: 30,
height: 180,
color: 0x8d5524,
x: self.width / 2 + 80,
y: -self.height + 120
});
self._stickSwingAngle = 0;
self._stickSwingDown = true;
self._stickBaseX = self._stick.x;
self._stickBaseY = self._stick.y;
}
// Animate stick swinging downward to hit David
if (typeof self._stickSwingAngle !== "number") self._stickSwingAngle = 0;
if (typeof self._stickSwingDown !== "boolean") self._stickSwingDown = true;
if (self._stickSwingDown) {
self._stickSwingAngle += 0.10 + Math.random() * 0.03;
if (self._stickSwingAngle > 1.2) self._stickSwingDown = false;
} else {
self._stickSwingAngle -= 0.08 + Math.random() * 0.02;
if (self._stickSwingAngle < -0.3) self._stickSwingDown = true;
}
self._stick.rotation = self._stickSwingAngle + 1.1;
// Keep stick attached to boss's right side
self._stick.x = self.width / 2 + 80;
self._stick.y = -self.height + 120;
// --- End Boss Stick ---
// Axe swing animation (right arm)
if (!self._axe) {
self._axe = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.1,
x: self.width / 2 + 60,
y: -self.height + 80,
color: 0x444444
});
}
if (!self._axeAngle) self._axeAngle = 0;
self._axeAngle += 0.13 + Math.random() * 0.04;
self._axe.rotation = Math.sin(self._axeAngle) * 1.2 + 1.2;
// Boss health bar (tube) above boss
if (!self._hpBarBg) {
self._hpBarBg = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 32,
color: 0x222222,
shape: 'box',
y: -self.height - 60
});
}
if (!self._hpBar) {
self._hpBar = self.attachAsset('coin', {
anchorX: 0,
anchorY: 0.5,
width: 210,
height: 20,
color: 0x43a047,
shape: 'box',
x: -105,
y: -self.height - 60
});
}
if (self._hpBar) {
var maxHp = self.maxHp || (self.hp > 0 ? self.hp : 1);
if (!self.maxHp) self.maxHp = self.hp;
var frac = Math.max(0, Math.min(1, self.hp / self.maxHp));
self._hpBar.width = 210 * frac;
// Color: green to red
var col = 0x43a047;
if (frac < 0.5) col = 0xffa000;
if (frac < 0.25) col = 0xd32f2f;
self._hpBar.tint = col;
}
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.active = false;
LK.getSound('giantVanish').play();
tween(self, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
// Remove health bar assets if present
if (self._hpBarBg) {
self.removeChild(self._hpBarBg);
self._hpBarBg = null;
}
if (self._hpBar) {
self.removeChild(self._hpBar);
self._hpBar = null;
}
self.destroy();
}
});
} else {
LK.getSound('hit').play();
tween(self, {
tint: 0xff7043
}, {
duration: 180,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 180
});
}
});
}
};
return self;
});
// Boss Whip hitbox (short-lived, for boss whip attack)
var BossWhip = Container.expand(function () {
var self = Container.call(this);
var whip = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 180,
height: 40,
color: 0x8d5524,
shape: 'box'
});
self._life = 0;
self.hit = false;
return self;
});
// Chicken class
var Chicken = Container.expand(function () {
var self = Container.call(this);
var chicken = self.attachAsset('chicken', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.animTimer = 0;
self.update = function () {
// Simple idle animation (bobbing)
self.animTimer++;
self.y += Math.sin(self.animTimer / 10) * 1.2;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
var coin = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.animTimer = 0;
self.update = function () {
// Simple spin animation
self.animTimer++;
coin.rotation += 0.15;
self.y += Math.sin(self.animTimer / 12) * 0.8;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 40
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Spawn chickens and coins for the current wave
// David (player)
var David = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('davidBody', {
anchorX: 0.5,
anchorY: 1
});
// Arm removed (no davidArm asset)
self.arm = {
width: 100,
height: 100
}; // dummy object to prevent errors
self.armAngle = 0;
self.armSpeed = 0.18; // radians per frame
self.armRadius = 70;
self.slingReady = true;
self.stoneCooldown = 0;
self.hp = 5;
self.maxHp = 5;
self.regenTimer = 0;
self.update = function () {
// Spin arm
self.armAngle += self.armSpeed;
if (self.armAngle > Math.PI * 2) self.armAngle -= Math.PI * 2;
// Arm rotates in a circle
self.arm.rotation = self.armAngle;
// Stone cooldown
if (self.stoneCooldown > 0) self.stoneCooldown--;
// Health regen
if (self.hp < self.maxHp) {
self.regenTimer++;
if (self.regenTimer > 180) {
// 3 seconds
self.hp++;
self.regenTimer = 0;
}
} else {
self.regenTimer = 0;
}
};
self.throwStone = function () {
if (!self.slingReady || self.stoneCooldown > 0) return null;
self.slingReady = false;
self.stoneCooldown = 5; // fast fire rate for machine gun
LK.getSound('throw').play();
// Fire from the middle of David's body
var sx = self.x;
var sy = self.y - body.height / 2;
// Always fire a machine gun bullet straight upwards
var proj = new Stone();
proj.x = sx;
proj.y = sy;
proj.vx = 0;
proj.vy = -38; // faster than stone for machine gun
return proj;
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp < 0) self.hp = 0;
LK.effects.flashObject(self, 0xff0000, 400);
// No weapon revert on damage in machine gun only mode
};
return self;
});
// Enemy spaceship bullet (missile-shaped, slower, 1 damage per hit)
var EnemyShipBullet = Container.expand(function () {
var self = Container.call(this);
// Use a longer, missile-like shape
var bullet = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.5,
width: 24,
height: 64,
color: 0xff2222
});
self.vx = 0;
self.vy = 0;
self.active = true;
self._life = 0;
self.lastX = self.x;
self.lastY = self.y;
self.damage = 1; // Only 1 damage per hit
self.update = function () {
self._life++;
self.x += self.vx;
self.y += self.vy;
// Rotate to face direction of movement
if (self.vx !== 0 || self.vy !== 0) {
bullet.rotation = Math.atan2(self.vy, self.vx) + Math.PI / 2;
}
// Remove if off screen
if (self.x < -100 || self.x > 2200 || self.y < -100 || self.y > 2800) {
self.active = false;
self.destroy();
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// Enemy Spaceship (appears every 2 minutes, shoots at David, exits after 10s)
var EnemySpaceship = Container.expand(function () {
var self = Container.call(this);
// Use a box for the ship body (gray)
var ship = self.attachAsset('bossBody', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 90,
color: 0x8888ff,
shape: 'box'
});
// Gun visual (front)
var gun = self.attachAsset('davidArm', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 60,
color: 0xff2222,
x: 0,
y: 40
});
self.state = "entering"; // entering, firing, exiting
self.fireTimer = 0;
self.exitDir = 1; // 1 = right, -1 = left
self.speed = 18;
self.fireInterval = 18; // frames between shots
self.lastFireTick = 0;
self.boundaryX = 1024; // center of screen
self.targetY = 600; // y position to hover at
self.lastX = self.x;
self.lastY = self.y;
self._life = 0;
self.update = function () {
self._life++;
// State machine
if (self.state === "entering") {
// Move horizontally toward boundaryX, stop at boundary
if (self.exitDir === 1 && self.x < self.boundaryX || self.exitDir === -1 && self.x > self.boundaryX) {
self.x += self.speed * self.exitDir;
// Clamp to boundary
if (self.exitDir === 1 && self.x > self.boundaryX || self.exitDir === -1 && self.x < self.boundaryX) {
self.x = self.boundaryX;
}
}
// Move vertically to targetY
if (Math.abs(self.y - self.targetY) > 8) {
self.y += (self.targetY - self.y) * 0.18;
}
// Arrived at boundary and targetY
if (self.x === self.boundaryX && Math.abs(self.y - self.targetY) < 10) {
self.state = "firing";
self.fireTimer = 0;
}
} else if (self.state === "firing") {
// Stay at boundary, fire at David every fireInterval frames
self.fireTimer++;
if (self.fireTimer % self.fireInterval === 0) {
self.fireAtDavid();
}
// After 10 seconds (600 frames), exit
if (self.fireTimer > 600) {
self.state = "exiting";
}
} else if (self.state === "exiting") {
// Move quickly off screen in exitDir
self.x += self.speed * 2.5 * self.exitDir;
if (self.exitDir === 1 && self.x > 2200 || self.exitDir === -1 && self.x < -200) {
self.destroy();
}
}
// Defensive: update lastX/lastY for event triggers
self.lastX = self.x;
self.lastY = self.y;
};
// Fire a bullet at David's current position
self.fireAtDavid = function () {
if (!david || !gameActive) return;
var bullet = new EnemyShipBullet();
bullet.x = self.x;
bullet.y = self.y + 40;
// Aim at David's current position
var dx = david.x - self.x;
var dy = david.y - 60 - bullet.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Make missile slower than normal (e.g. 18 instead of 32)
var speed = 18;
bullet.vx = dx / dist * speed;
bullet.vy = dy / dist * speed;
enemyShipBullets.push(bullet);
game.addChild(bullet);
};
return self;
});
// Giant enemy
var Giant = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('giantBody', {
anchorX: 0.5,
anchorY: 1
});
self.hp = 2; // Each giant requires 2 hits to die
self.speed = 3 + Math.random() * 2;
self.active = true;
self.update = function () {
if (!self.active) return;
// Giants move downward (with world scrolling up, so add speed to y)
self.y += self.speed;
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.active = false;
LK.getSound('giantVanish').play();
tween(self, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
} else {
LK.getSound('hit').play();
tween(self, {
tint: 0xffe082
}, {
duration: 120,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 120
});
}
});
}
};
return self;
});
// Heart class (for health pickup)
var Heart = Container.expand(function () {
var self = Container.call(this);
var heart = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90,
color: 0xff69b4,
// pink
shape: 'box'
});
self.collected = false;
self.update = function () {
self.y += Math.sin(LK.ticks / 12 + self.x) * 0.8;
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
tween(self, {
alpha: 0,
y: self.y - 60
}, {
duration: 300,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Laser projectile (covers whole screen, instant)
var Laser = Container.expand(function () {
var self = Container.call(this);
var laser = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 1,
width: 30,
height: 400,
// Match catapult (stone) range
color: 0x9c27b0 // purple
});
self.active = true;
self.damage = 1; // Laser deals 1 damage to giants, so 2 hits are required
self.update = function () {
// Laser is instant, doesn't move, but can fade out
if (!self.active) return;
// Fade out after a few frames
if (!self._life) self._life = 0;
self._life++;
if (self._life > 10) {
self.active = false;
self.destroy();
}
};
return self;
});
// Plasma projectile (fast, yellow, destroys instantly)
var Plasma = Container.expand(function () {
var self = Container.call(this);
var plasma = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5,
width: 44,
height: 44,
color: 0xffe600
});
self.vx = 0;
self.vy = -40;
self.active = true;
self.damage = 99; // always kills (plasma is instant kill)
self.update = function () {
if (!self.active) return;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Stone projectile
var Stone = Container.expand(function () {
var self = Container.call(this);
var stone = self.attachAsset('slingStone', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.active = true;
self.damage = 1; // Stone deals 1 damage to giants
self.update = function () {
if (!self.active) return;
if (typeof self._startY === "undefined") self._startY = self.y;
self.x += self.vx;
self.y += self.vy;
// Gravity
self.vy += 0.7;
// Remove if exceeded maximum range (e.g. 900px upwards from start)
if (self._startY - self.y > 900) {
self.active = false;
if (typeof self.destroy === "function") self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
enemySpaceshipTimer = 0;
// --- PLAYER ---
var enemySpaceshipActive = false;
var enemySpaceship = null;
var enemyShipBullets = [];
var enemySpaceshipTimer = 0;
// --- ENVIRONMENT ---
// Character (David)
var sky = LK.getAsset('sky', {
x: 0,
y: 0
});
game.addChild(sky);
// Ensure sky is always at the bottom of the display list and never removed
if (typeof game._skyLock === "undefined") {
game._skyLock = true;
var originalRemoveChild = game.removeChild;
game.removeChild = function (child) {
if (child === sky) {
// Prevent sky from being removed
return;
}
return originalRemoveChild.call(this, child);
};
// Always keep sky at the bottom
var originalAddChild = game.addChild;
game.addChild = function (child) {
if (child === sky) {
// Only add sky if not already present
if (this.children && this.children[0] !== sky) {
originalAddChild.call(this, child);
// Move sky to index 0
if (this.children && this.children.length > 1) {
this.children.splice(this.children.indexOf(sky), 1);
this.children.unshift(sky);
}
}
return sky;
}
var result = originalAddChild.call(this, child);
// After adding any child, ensure sky is at index 0
if (this.children && this.children[0] !== sky && this.children.indexOf(sky) !== -1) {
this.children.splice(this.children.indexOf(sky), 1);
this.children.unshift(sky);
}
return result;
};
}
var ground = LK.getAsset('ground', {
x: 0,
y: 2532
});
ground.y = 2532; // bottom of screen
game.addChild(ground);
// --- PLAYER ---
var david = new David();
david.x = 1024; // center horizontally
david.y = 2300; // near bottom, above ground
game.addChild(david);
// --- WEAPON SYSTEM ---
// Machine Gun is the only weapon
var WEAPON_MACHINEGUN = 0;
var weaponNames = ["Machine Gun"];
var weaponColors = [0x888888];
// Start with Machine Gun only
var weapon = WEAPON_MACHINEGUN;
var weaponUnlocked = [true]; // Only Machine Gun unlocked
var weaponLast = WEAPON_MACHINEGUN;
var weaponTimeouts = [0];
// Weapon UI
var weaponTxt = new Text2('Machine Gun', {
size: 80,
fill: "#fff"
});
weaponTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(weaponTxt);
weaponTxt.y = 220;
// --- BACKPACKS & HEART ---
var backpacks = [];
var heart = null;
var heartCollected = false;
// --- SCROLLING STATE ---
var scrollY = 0; // how much the world has scrolled up
var scrollSpeed = 4; // px per frame, adjust for desired speed
// --- UI ---
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var hpTxt = new Text2('♥♥♥♥♥', {
size: 90,
fill: 0xE53935
});
hpTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(hpTxt);
hpTxt.y = 120;
// --- LEADERBOARD ---
// Leaderboard UI and logic removed as per new requirements
// --- GAME STATE ---
var stones = [];
var giants = [];
var boss = null;
var chickens = [];
var coins = [];
var wave = 1;
var giantsToSpawn = 5;
var giantsSpawned = 0;
var spawnTimer = 0;
var gameActive = true;
var bossActive = false;
var bossDefeated = false;
// --- CHAPTER STATE ---
var chapter = 1;
var bossScale = 1;
// Chicken and coin spawn positions per level (max 5 coins per level)
var chickenSpawns = [
// For each wave, array of {x, y}
[{
x: 600,
y: 1800
}, {
x: 1500,
y: 800
}], [{
x: 400,
y: 1200
}, {
x: 1700,
y: 600
}, {
x: 1200,
y: 2000
}], [{
x: 900,
y: 900
}, {
x: 1800,
y: 400
}, {
x: 300,
y: 2200
}]];
var coinSpawns = [[{
x: 400,
y: 400
}, {
x: 800,
y: 600
}, {
x: 1200,
y: 800
}, {
x: 1600,
y: 1000
}, {
x: 200,
y: 2000
}], [{
x: 600,
y: 500
}, {
x: 1000,
y: 700
}, {
x: 1400,
y: 900
}, {
x: 1800,
y: 1100
}, {
x: 400,
y: 2100
}], [{
x: 800,
y: 300
}, {
x: 1200,
y: 500
}, {
x: 1600,
y: 700
}, {
x: 200,
y: 900
}, {
x: 1800,
y: 2300
}]];
// Spawn chickens and coins for the current wave
function spawnCollectiblesForWave(waveIdx) {
// Remove old
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
// Chickens
var cSpawns = chickenSpawns[waveIdx % chickenSpawns.length];
for (var i = 0; i < cSpawns.length; i++) {
var c = new Chicken();
c.x = cSpawns[i].x;
c.y = cSpawns[i].y;
chickens.push(c);
game.addChild(c);
}
// Coins
var coinSp = coinSpawns[waveIdx % coinSpawns.length];
for (var i = 0; i < coinSp.length; i++) {
var coin = new Coin();
coin.x = coinSp[i].x;
coin.y = coinSp[i].y;
coins.push(coin);
game.addChild(coin);
}
}
// --- MUSIC ---
LK.playMusic('mainTheme');
// --- MAPS/ENVIRONMENTS ---
var environments = [{
sky: 0x87ceeb,
ground: 0x7ec850
},
// blue sky, green grass
{
sky: 0xf7e9a0,
ground: 0xe0c97f
},
// desert
{
sky: 0x9ecae1,
ground: 0x8c8c8c
} // mountain/rocky
];
var currentEnv = 0;
function setEnvironment(idx) {
sky.tint = environments[idx].sky;
ground.tint = environments[idx].ground;
}
setEnvironment(currentEnv);
// Leaderboard name entry/update function removed as per new requirements
// --- UI UPDATE ---
function updateScore() {
scoreTxt.setText(score);
}
function updateHp() {
var s = '';
for (var i = 0; i < david.hp; i++) s += '♥';
for (var i = david.hp; i < david.maxHp; i++) s += '♡';
hpTxt.setText(s);
}
updateScore();
updateHp();
// --- INPUT ---
var isTouching = false;
var lastTouchTick = 0;
// --- MACHINE GUN (AUTOMATIC FIRE) STATE ---
var autoFireInterval = 5; // frames between shots (12 shots/sec at 60fps)
var autoFireTick = 0;
var autoFireActive = false;
var autoFireTimeout = null;
game.down = function (x, y, obj) {
isTouching = true;
lastTouchTick = LK.ticks;
// Move David to pointer/touch position (centered, free movement)
if (gameActive && !bossDefeated) {
var minX = david.arm.width / 2 + 60;
var maxX = 2048 - david.arm.width / 2 - 60;
var minY = 0 + david.arm.height / 2 + 60;
var maxY = 2532;
var newX = x;
var newY = y;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
if (newY < minY) newY = minY;
if (newY > maxY) newY = maxY;
david.x = newX;
david.y = newY;
}
// Start automatic fire
if (gameActive && !bossDefeated && !autoFireActive) {
autoFireActive = true;
autoFireTick = 0;
// Fire immediately
fireMachineGun();
// Set up interval for continuous fire
autoFireTimeout = LK.setInterval(fireMachineGun, autoFireInterval * 1000 / 60);
}
};
game.up = function (x, y, obj) {
isTouching = false;
// Stop automatic fire
if (autoFireActive) {
autoFireActive = false;
if (autoFireTimeout) {
LK.clearInterval(autoFireTimeout);
autoFireTimeout = null;
}
}
// Only allow throw if touch was short (tap)
if (gameActive && !bossDefeated && LK.ticks - lastTouchTick < 30) {
// Do not fire a single shot here, as auto fire already handles it
}
};
// Helper function for machine gun fire
function fireMachineGun() {
if (!gameActive || bossDefeated) return;
if (!isTouching) return;
var stone = david.throwStone();
if (stone) {
stones.push(stone);
// If the stone is a Laser, add it after all giants to ensure it's above them
if (stone instanceof Laser) {
// Always add the laser to the top of the display list so it is visible above all giants and other objects
// Remove if already present, then add to top
var idx = game.children.indexOf(stone);
if (idx !== -1) {
game.children.splice(idx, 1);
}
game.addChild(stone);
if (game.children[game.children.length - 1] !== stone) {
var idx2 = game.children.indexOf(stone);
if (idx2 !== -1) {
game.children.splice(idx2, 1);
game.children.push(stone);
}
}
stone.parent = game;
if (typeof stone.onAdded === "function") stone.onAdded();
} else {
game.addChild(stone);
}
// Arm can't throw again until next full rotation
LK.setTimeout(function () {
david.slingReady = true;
}, 400);
}
}
game.move = function (x, y, obj) {
// Move David to pointer/touch position (centered, free movement)
if (gameActive && !bossDefeated) {
var minX = david.arm.width / 2 + 60;
var maxX = 2048 - david.arm.width / 2 - 60;
var minY = 0 + david.arm.height / 2 + 60;
var maxY = 2532; // bottom of screen
var newX = x;
var newY = y;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
if (newY < minY) newY = minY;
if (newY > maxY) newY = maxY;
david.x = newX;
david.y = newY;
}
};
// --- GAME LOOP ---
// --- GIANT GROUP SPAWN EVERY 5 SECONDS (3-5 GIANTS PER GROUP) ---
var giantGroupTimer = 0;
var giantGroupInterval = 5 * 60; // 5 seconds * 60 FPS
game.update = function () {
if (!gameActive) return;
// --- ENEMY SPACESHIP TIMELINE LOGIC ---
// Only spawn if not bossActive and not already present
if (!bossActive && !enemySpaceshipActive) {
enemySpaceshipTimer++;
if (enemySpaceshipTimer >= 2 * 60 * 60) {
// every 2 minutes (7200 frames)
// Spawn from same side as giants (randomly left or right)
var side = Math.random() < 0.5 ? -1 : 1;
enemySpaceship = new EnemySpaceship();
enemySpaceship.exitDir = side;
enemySpaceship.x = side === 1 ? -200 : 2248;
enemySpaceship.y = 400;
enemySpaceshipActive = true;
enemySpaceshipTimer = 0;
game.addChild(enemySpaceship);
}
}
// Update enemy spaceship if present
if (enemySpaceshipActive && enemySpaceship) {
enemySpaceship.update();
// Remove if destroyed
if (enemySpaceship.destroyed || typeof enemySpaceship.parent === "undefined" || enemySpaceship.parent === null) {
enemySpaceship = null;
enemySpaceshipActive = false;
// Remove all its bullets
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) {
enemyShipBullets[i].destroy();
}
}
enemyShipBullets.length = 0;
}
}
// Update enemy spaceship bullets
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
var b = enemyShipBullets[i];
if (!b || !b.active) {
enemyShipBullets.splice(i, 1);
continue;
}
b.update();
b.y -= scrollSpeed; // move up with world
// Collision with David
if (b.active && david && b.intersects(david)) {
b.active = false;
b.destroy();
enemyShipBullets.splice(i, 1);
// Always only 1 life per hit, regardless of bullet properties
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
}
}
// Spawn a group of 3-5 giants every 5 seconds, unless boss is active
if (!bossActive) {
giantGroupTimer++;
if (giantGroupTimer >= giantGroupInterval) {
var groupSize = 3 + Math.floor(Math.random() * 3); // 3, 4, or 5
for (var i = 0; i < groupSize; i++) {
var g = new Giant();
g.x = 400 + Math.random() * 1200;
g.y = -200 - i * 80; // stagger vertically so they don't overlap exactly
giants.push(g);
game.addChild(g);
// Also spawn a gold coin for this giant
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
}
giantGroupTimer = 0;
}
} else {
// Reset timer if boss is active so we don't get a burst after boss
giantGroupTimer = 0;
}
// --- SCROLLING LOGIC ---
scrollY += scrollSpeed;
// Move all world elements up by scrollSpeed
sky.y = 0; // Sky remains static and always visible
ground.y = 2532 - scrollY;
david.y -= scrollSpeed;
// Move enemy spaceship and its bullets up with world
if (enemySpaceshipActive && enemySpaceship) {
enemySpaceship.y -= scrollSpeed;
}
for (var i = 0; i < enemyShipBullets.length; i++) {
if (enemyShipBullets[i]) {
enemyShipBullets[i].y -= scrollSpeed;
}
}
// Environment change per wave
if (wave - 1 < environments.length) {
setEnvironment(wave - 1);
}
// --- WEAPON/ITEM SPAWNS ---
// No weapon unlocks or backpacks for machine gun only mode
// Heart: appears at 5:00, only once, near center
// Calculate minutes based on ticks (60 ticks per second)
var minutes = Math.floor(LK.ticks / (60 * 60));
if (minutes === 5 && !heart && !heartCollected) {
heart = new Heart();
heart.x = 1024;
heart.y = 1366;
game.addChild(heart);
}
// David update
david.update();
// Stones update
for (var i = stones.length - 1; i >= 0; i--) {
var s = stones[i];
s.update();
s.y -= scrollSpeed; // move stone up with world
// Remove if off screen (top or bottom)
if (s.x > 2048 || s.y < -100 || s.x < -100 || s.y > 2732) {
s.destroy();
stones.splice(i, 1);
continue;
}
}
// Chickens update and collect
for (var i = chickens.length - 1; i >= 0; i--) {
var c = chickens[i];
c.update();
c.y -= scrollSpeed; // move up with world
if (!c.collected && c.intersects(david)) {
c.collect();
chickens.splice(i, 1);
if (david.hp < david.maxHp) {
david.hp++;
updateHp();
}
// Optional: play sound here if you have a chicken collect sound
}
}
// Backpacks update and collect
// (No backpacks in machine gun only mode)
// Heart update and collect
if (heart && !heart.collected) {
heart.update();
heart.y -= scrollSpeed;
if (heart.intersects(david)) {
heart.collect();
heartCollected = true;
if (david.hp < david.maxHp) {
david.hp++;
updateHp();
}
heart = null;
}
}
// Coins update and collect
for (var i = coins.length - 1; i >= 0; i--) {
var coin = coins[i];
coin.update();
coin.y -= scrollSpeed; // move up with world
if (!coin.collected && coin.intersects(david)) {
coin.collect();
coins.splice(i, 1);
score += 5;
updateScore();
// Optional: play sound here if you have a coin collect sound
// Respawn a new coin at a random location after collection
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
}
}
// Giants update
for (var j = giants.length - 1; j >= 0; j--) {
var g = giants[j];
g.update();
g.y -= scrollSpeed; // move up with world
// Remove if off screen (bottom)
if (g.y < -300) {
g.destroy();
giants.splice(j, 1);
continue;
}
// Collision with David
if (g.active && g.intersects(david)) {
g.active = false;
g.destroy();
giants.splice(j, 1);
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
continue;
}
// Stones/projectiles hit giant
// --- Laser: only damage the closest giant in front of David, and always visible above giants ---
for (var k = stones.length - 1; k >= 0; k--) {
var s2 = stones[k];
if (s2 instanceof Laser && s2.active) {
// Find the closest active giant in front of David (y > 0, lowest y)
var closestGiant = null;
var minDist = Infinity;
for (var m = 0; m < giants.length; m++) {
var g2 = giants[m];
if (g2.active && g2.y < david.y && g2.y > -300) {
var dist = Math.abs(g2.y - david.y);
if (dist < minDist) {
minDist = dist;
closestGiant = g2;
}
}
}
if (closestGiant && closestGiant.active && s2.active && closestGiant.intersects(s2)) {
closestGiant.takeDamage(s2.damage);
s2.active = false;
s2.destroy();
stones.splice(k, 1);
if (!closestGiant.active) {
score += 1;
updateScore();
}
}
// Always keep the laser above all giants and other objects, every frame
if (s2.parent === game) {
var idx = game.children.indexOf(s2);
if (idx !== -1 && idx !== game.children.length - 1) {
game.children.splice(idx, 1);
game.children.push(s2);
}
}
continue;
}
// Non-laser projectiles
if (g.active && s2.active && g.intersects(s2)) {
g.takeDamage(s2.damage);
if (s2 instanceof Bazooka) {
s2.pierce--;
if (s2.pierce <= 0) {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
} else {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
if (!g.active) {
score += 1;
updateScore();
}
break;
}
}
}
// Boss update
if (bossActive && boss && boss.active) {
boss.update();
boss.y -= scrollSpeed; // move up with world
// David can shoot at boss as soon as boss appears
david.slingReady = true;
// --- BOSS BOMB THROWING LOGIC ---
// Boss throws bombs if David is far away (horizontally)
if (!boss._bombCooldown) boss._bombCooldown = 0;
boss._bombCooldown--;
var bossToDavidDist = Math.abs(boss.x - david.x);
if (bossToDavidDist > 400 && boss._bombCooldown <= 0) {
// Throw a bomb toward David
if (!boss._bombs) boss._bombs = [];
var bomb = new BossBomb();
bomb.x = boss.x;
bomb.y = boss.y - boss.height * boss.scale.y / 2;
// Calculate velocity toward David's current position
var dx = david.x - boss.x;
var dy = david.y - boss.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var speed = 18 + Math.random() * 4;
bomb.vx = dx / dist * speed;
bomb.vy = dy / dist * speed * 0.7 - 6; // arc upward
boss._bombs.push(bomb);
game.addChild(bomb);
boss._bombCooldown = 90 + Math.floor(Math.random() * 60); // 1.5s cooldown
}
// Update boss bombs
if (boss._bombs) {
for (var b = boss._bombs.length - 1; b >= 0; b--) {
var bomb = boss._bombs[b];
bomb.update();
bomb.y -= scrollSpeed;
// Bomb hits David
if (!bomb.hit && bomb.intersects(david)) {
bomb.hit = true;
bomb.explode();
boss._bombs.splice(b, 1);
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
continue;
}
// Remove if off screen or exploded
if (bomb.y > 2732 || bomb.y < -100 || bomb._exploded) {
bomb.destroy();
boss._bombs.splice(b, 1);
}
}
}
// --- BOSS WHIP ATTACK LOGIC ---
// Boss whips if David is close horizontally
if (!boss._whipCooldown) boss._whipCooldown = 0;
boss._whipCooldown--;
if (bossToDavidDist <= 400 && boss._whipCooldown <= 0) {
// Whip attack: create a whip hitbox in front of boss
if (!boss._whipHitbox) {
boss._whipHitbox = new BossWhip();
boss._whipHitbox.x = boss.x + (boss.x < david.x ? 120 * boss.scale.x : -120 * boss.scale.x);
boss._whipHitbox.y = boss.y - boss.height * boss.scale.y / 2 + 80 * boss.scale.y;
boss._whipHitbox.scale.x = boss.scale.x;
boss._whipHitbox.scale.y = boss.scale.y;
game.addChild(boss._whipHitbox);
}
boss._whipHitbox._life = 0;
boss._whipCooldown = 60 + Math.floor(Math.random() * 30); // 1s cooldown
}
// Update whip hitbox
if (boss._whipHitbox) {
boss._whipHitbox._life++;
boss._whipHitbox.y -= scrollSpeed;
// Whip lasts for 15 frames
if (boss._whipHitbox._life > 15) {
boss._whipHitbox.destroy();
boss._whipHitbox = null;
} else if (!boss._whipHitbox.hit && boss._whipHitbox.intersects(david)) {
boss._whipHitbox.hit = true;
david.takeDamage(1);
updateHp();
if (david.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
}
}
// Boss hits David (body collision)
if (boss.intersects(david)) {
boss.active = false;
boss.destroy();
david.takeDamage(2);
updateHp();
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameActive = false;
return;
}
// Stones/projectiles hit boss
for (var k = stones.length - 1; k >= 0; k--) {
var s2 = stones[k];
if (boss.active && s2.active && boss.intersects(s2)) {
boss.takeDamage(s2.damage);
if (s2 instanceof Bazooka) {
s2.pierce--;
if (s2.pierce <= 0) {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
} else {
s2.active = false;
s2.destroy();
stones.splice(k, 1);
}
if (!boss.active) {
score += 10;
updateScore();
bossDefeated = true;
LK.effects.flashScreen(0x00ff00, 1200);
// Show "Next Chapter" if not last chapter, else show "You Win"
if (chapter < 3) {
// Show "Next Chapter" overlay
var nextChapterTxt = new Text2('Next Chapter', {
size: 200,
fill: "#fff"
});
nextChapterTxt.anchor.set(0.5, 0.5);
nextChapterTxt.x = 1024;
nextChapterTxt.y = 1200;
LK.gui.center.addChild(nextChapterTxt);
// Prepare for next chapter after short delay
LK.setTimeout(function () {
LK.gui.center.removeChild(nextChapterTxt);
// Advance chapter, reset state
chapter++;
wave = 1;
giantsToSpawn = 5;
giantsSpawned = 0;
spawnTimer = 60;
bossActive = false;
bossDefeated = false;
gameActive = true;
// Scale up boss for next chapter
bossScale = bossScale * 2;
// Cycle environment
currentEnv = (currentEnv + 1) % environments.length;
setEnvironment(currentEnv);
// Move David back to start
david.x = 1024;
david.y = 2300;
// Remove all stones, chickens, coins, backpacks, heart
for (var i = stones.length - 1; i >= 0; i--) {
stones[i].destroy();
stones.splice(i, 1);
}
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
for (var i = backpacks.length - 1; i >= 0; i--) {
backpacks[i].destroy();
backpacks.splice(i, 1);
}
// Remove enemy spaceship and its bullets
if (enemySpaceship) {
enemySpaceship.destroy();
enemySpaceship = null;
enemySpaceshipActive = false;
}
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) enemyShipBullets[i].destroy();
enemyShipBullets.splice(i, 1);
}
if (heart) {
heart.destroy();
heart = null;
}
heartCollected = false;
// Reset scroll
scrollY = 0;
sky.y = 0;
ground.y = 2532;
// Reset weapons
weapon = WEAPON_LASER;
weaponUnlocked = [true, false, false];
weaponLast = WEAPON_LASER;
weaponTxt.setText(weaponNames[0]);
// Reset HP
david.hp = david.maxHp;
updateHp();
// Spawn collectibles for new wave
spawnCollectiblesForWave(wave - 1);
}, 2200);
} else {
// Last chapter, keep spawning giants forever
bossActive = false;
bossDefeated = false;
gameActive = true;
// Remove all stones, chickens, coins, backpacks, heart
for (var i = stones.length - 1; i >= 0; i--) {
stones[i].destroy();
stones.splice(i, 1);
}
for (var i = chickens.length - 1; i >= 0; i--) {
chickens[i].destroy();
chickens.splice(i, 1);
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].destroy();
coins.splice(i, 1);
}
for (var i = backpacks.length - 1; i >= 0; i--) {
backpacks[i].destroy();
backpacks.splice(i, 1);
}
// Remove enemy spaceship and its bullets
if (enemySpaceship) {
enemySpaceship.destroy();
enemySpaceship = null;
enemySpaceshipActive = false;
}
for (var i = enemyShipBullets.length - 1; i >= 0; i--) {
if (enemyShipBullets[i]) enemyShipBullets[i].destroy();
enemyShipBullets.splice(i, 1);
}
if (heart) {
heart.destroy();
heart = null;
}
heartCollected = false;
// Reset scroll
scrollY = 0;
sky.y = 0;
ground.y = 2532;
// Move David back to start
david.x = 1024;
david.y = 2300;
// Reset HP
david.hp = david.maxHp;
updateHp();
// Continue spawning giants forever
wave++;
giantsToSpawn += 3;
giantsSpawned = 0;
spawnTimer = 60;
spawnCollectiblesForWave((wave - 1) % chickenSpawns.length);
}
return;
}
break;
}
}
}
// --- SPAWN LOGIC (vertical, from top) ---
// Giants only spawn if boss is not active
if (!bossActive) {
// Only allow up to 5 giants at a time
if (giants.length < 5 && giantsSpawned < giantsToSpawn) {
spawnTimer--;
if (spawnTimer <= 0) {
var g = new Giant();
g.x = 400 + Math.random() * 1200; // random horizontal position
g.y = -200; // spawn just above the top
giants.push(g);
game.addChild(g);
giantsSpawned++;
// --- SPAWN GOLD COIN FOR EACH GIANT ---
var newCoin = new Coin();
newCoin.x = 400 + Math.random() * 1200;
newCoin.y = 400 + Math.random() * 1800;
coins.push(newCoin);
game.addChild(newCoin);
spawnTimer = 60 + Math.floor(Math.random() * 60);
}
} else if (giants.length === 0) {
// Next wave or boss
if (wave < 3) {
wave++;
giantsToSpawn += 3;
giantsSpawned = 0;
spawnTimer = 60;
spawnCollectiblesForWave(wave - 1); // spawn chickens and coins for new wave
} else {
// Boss time: pause giants, spawn boss
bossActive = true;
boss = new BossGiant();
// Scale boss size and HP per chapter
boss.x = 1024; // center horizontally
boss.y = -300; // spawn above the top
boss.scale.x = bossScale;
boss.scale.y = bossScale;
boss.hp = 15 * bossScale;
game.addChild(boss);
spawnCollectiblesForWave(wave - 1); // spawn for boss wave
}
}
}
// Giants will continuously respawn and keep coming without end
// (Removed bossDefeated check and chapter2 popup, giants always respawn)
};
Create a spaceship as the main hero. It will be in the center, with the front tip pointing upwards. There will be a firing mechanism on both sides.
Imagine a multi-legged space machine, its legs sprawling at varied, dynamic angles, giving it an organic yet mechanical feel. Encased within a transparent tube, a one-eyed green alien watches intently. Beneath the ship, vivid rocket flames burst forth, illuminating the dark void of space with fiery intensity.
The background will be a dynamic space scene, featuring only blue and dark navy colors. top to bottom, creating a lively and immersive environment."