/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Player Ball var Ball = Container.expand(function () { var self = Container.call(this); var ballGfx = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); // Gun graphic (use separate 'gun' asset, as gun barrel) var gunGfx = self.attachAsset('gun', { anchorX: 0.5, // center horizontally anchorY: 0.85, // anchor at base of gun scaleX: 1, scaleY: 1, rotation: 0 }); gunGfx.y = -ballGfx.height * 0.08; // slightly above center self.gunGfx = gunGfx; // For possible future use self.radius = ballGfx.width / 2; // Track gun angle (radians) self.gunAngle = -Math.PI / 2; // default up // Method to set gun angle self.setGunAngle = function (angle) { self.gunAngle = angle; self.gunGfx.rotation = angle; }; return self; }); // Bullet var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGfx = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Direction (unit vector) self.dx = 0; self.dy = -1; self.speed = 40; // px per frame self.update = function () { self.x += self.dx * self.speed; self.y += self.dy * self.speed; }; return self; }); // Stone Wall var StoneWall = Container.expand(function () { var self = Container.call(this); // Make the wall as wide as the game area var wallGfx = self.attachAsset('stonewall', { width: 2048, height: 80, color: 0x888888, anchorX: 0.5, anchorY: 0.5 }); // Wall health and lives self.maxHits = 5; // Number of attacks required to damage wall health self.hits = 0; // Current number of attacks taken in this cycle self.lives = 5; // Wall health (number of times wall can be damaged) // Show wall health as text self.healthTxt = new Text2("Wall: " + (self.maxHits - self.hits) + " / " + self.maxHits + "\nLives: " + self.lives, { size: 48, fill: "#fff" }); self.healthTxt.anchor.set(0.5, 0.5); self.healthTxt.y = 0; self.addChild(self.healthTxt); // Method to update health text self.updateHealthText = function () { self.healthTxt.setText("Wall: " + (self.maxHits - self.hits) + " / " + self.maxHits + "\nLives: " + self.lives); }; // Method to handle a tomato hit self.hitByTomato = function () { self.hits++; if (self.hits >= self.maxHits) { // Wall takes a real damage only after 5 attacks self.lives--; self.hits = 0; // Flash wall to show it lost a life LK.effects.flashObject(self, 0xff0000, 400); } else { // Flash wall for a normal hit (not enough attacks yet) LK.effects.flashObject(self, 0xffffff, 200); } self.updateHealthText(); }; return self; }); // Tomato Enemy var Tomato = Container.expand(function () { var self = Container.call(this); var tomatoGfx = self.attachAsset('tomato', { anchorX: 0.5, anchorY: 0.5 }); // Target to move towards (the player ball) self.target = null; self.speed = 7 + Math.random() * 3; // px per frame, randomize a bit self.update = function () { if (!self.target) return; var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Sound for shooting // Tomato enemy // Bullet // Ball (player) // Game area: 2048x2732 // Center player ball var player = new Ball(); player.x = 2048 / 2; player.y = 2732 - 350; game.addChild(player); // Add stone wall(s) a short distance in front of the player var stoneWall = new StoneWall(); stoneWall.x = player.x; stoneWall.y = player.y - 180; // ~18cm ahead (assuming 10px ~ 1mm) game.addChild(stoneWall); // Score display var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Bullets and tomatoes arrays var bullets = []; var tomatoes = []; // Dragging var dragNode = null; // For shooting: track last tap position var lastTapX = null; var lastTapY = null; // Store UI: 5 gun buttons at the bottom of the screen var gunCount = 5; var gunButtons = []; var selectedGun = 0; // default gun // Gun names (could be used for upgrades later) var gunNames = ["Basic", "Spread", "Rapid", "Big", "Pierce"]; // Gun colors for button visuals var gunColors = [0x3a9efd, 0x6cbb3c, 0xf7b32b, 0xd7263d, 0x7c3aed]; // Create gun buttons and add to GUI bottom for (var i = 0; i < gunCount; i++) { var btn = new Container(); // Button background var btnBg = LK.getAsset('bullet', { width: 180, height: 180, color: gunColors[i], anchorX: 0.5, anchorY: 0.5 }); btn.addChild(btnBg); // Button label var btnLabel = new Text2(gunNames[i], { size: 48, fill: "#fff" }); btnLabel.anchor.set(0.5, 0.5); btnLabel.y = 50; btn.addChild(btnLabel); // Position buttons evenly along the bottom, avoiding bottom corners btn.x = 2048 / (gunCount + 1) * (i + 1); btn.y = 2732 - 120; // Store index for event btn.gunIndex = i; // Highlight selected gun btnBg.alpha = i === selectedGun ? 1 : 0.5; // Add to GUI LK.gui.bottom.addChild(btn); gunButtons.push(btn); } // (Arrow button code removed for walk-by-hold movement) // Store gun selection handler function handleGunButtonDown(x, y, obj) { // Find which button was pressed for (var i = 0; i < gunButtons.length; i++) { var btn = gunButtons[i]; // Convert event to local button space var local = btn.toLocal({ x: x, y: y }); // Button is 180x180 centered if (local.x > -90 && local.x < 90 && local.y > -90 && local.y < 90) { selectedGun = btn.gunIndex; // Update button highlights for (var j = 0; j < gunButtons.length; j++) { gunButtons[j].children[0].alpha = j === selectedGun ? 1 : 0.5; } break; } } } // Add attack button at bottom left using LK.gui.bottomLeft for reliable placement var attackBtn = new Container(); var attackBtnBg = LK.getAsset('ball', { width: 220, height: 220, color: 0xd7263d, anchorX: 0.5, anchorY: 0.5 }); attackBtn.addChild(attackBtnBg); var attackBtnLabel = new Text2("Attack", { size: 60, fill: "#fff" }); attackBtnLabel.anchor.set(0.5, 0.5); attackBtn.addChild(attackBtnLabel); // Place at bottom left, with margin so it's not flush to the edge attackBtn.x = 140; attackBtn.y = -140; LK.gui.bottomLeft.addChild(attackBtn); // Track attack button hold state var attackBtnHeld = false; var attackBtnShootCooldown = 0; // Track where the gun is aiming (defaults up) var gunAimX = player.x; var gunAimY = player.y - 400; // Attack button event attackBtn.down = function (x, y, obj) { attackBtnHeld = true; // Shoot in the direction the gun is facing shootBullet(gunAimX, gunAimY); attackBtnShootCooldown = 8; // frames between shots }; attackBtn.up = function (x, y, obj) { attackBtnHeld = false; }; // Add event to GUI bottom for gun selection LK.gui.bottom.down = function (x, y, obj) { // If attack button is pressed, don't select gun var local = attackBtn.toLocal({ x: x, y: y }); if (local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110) { // handled by attackBtn.down return; } handleGunButtonDown(x, y, obj); }; // Prevent elements in top left 100x100 // (player and GUI are well away from this area) // Spawn tomato at random edge (but NOT from the bottom) function spawnTomato() { var t = new Tomato(); // Only pick from top, left, or right edges (no bottom, so never behind the wall) var edge = Math.floor(Math.random() * 3); var margin = 100; if (edge === 0) { // Top t.x = margin + Math.random() * (2048 - 2 * margin); t.y = -80; } else if (edge === 1) { // Left t.x = -80; t.y = margin + Math.random() * (stoneWall.y - 2 * margin); } else { // Right t.x = 2048 + 80; t.y = margin + Math.random() * (stoneWall.y - 2 * margin); } t.target = player; tomatoes.push(t); game.addChild(t); } // Initial tomato spawn for (var i = 0; i < 2; i++) { spawnTomato(); } // Tomato spawn timer var tomatoTimer = LK.setInterval(function () { spawnTomato(); }, 1200); // Handle dragging the player ball function handleMove(x, y, obj) { // Player movement disabled: do not update player position // Always update gun aim to point at the current touch/move position // Only if not on attack button (so we don't aim while holding attack) var local = attackBtn.toLocal({ x: x, y: y }); if (!(local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110)) { gunAimX = x; gunAimY = y; // Calculate angle from player to aim point var dx = gunAimX - player.x; var dy = gunAimY - player.y; var angle = Math.atan2(dy, dx); player.setGunAngle(angle); } } game.move = handleMove; game.down = function (x, y, obj) { // Player movement disabled: do not set dragNode // Update gun aim to this position (unless on attack button) var local = attackBtn.toLocal({ x: x, y: y }); if (!(local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110)) { gunAimX = x; gunAimY = y; var dx = gunAimX - player.x; var dy = gunAimY - player.y; var angle = Math.atan2(dy, dx); player.setGunAngle(angle); } }; game.up = function (x, y, obj) { // Player movement disabled: do not clear dragNode }; // Shoot bullet from player towards (tx, ty) function shootBullet(tx, ty) { // Calculate direction var dx = tx - player.x; var dy = ty - player.y; var dist = Math.sqrt(dx * dx + dy * dy); var dirX = dist === 0 ? 0 : dx / dist; var dirY = dist === 0 ? -1 : dy / dist; // Gun logic if (selectedGun === 0) { // Basic: single bullet var b = new Bullet(); b.x = player.x; b.y = player.y; b.dx = dirX; b.dy = dirY; bullets.push(b); game.addChild(b); } else if (selectedGun === 1) { // Spread: 3 bullets, spread angle for (var i = -1; i <= 1; i++) { var angle = Math.atan2(dirY, dirX) + i * 0.18; var b = new Bullet(); b.x = player.x; b.y = player.y; b.dx = Math.cos(angle); b.dy = Math.sin(angle); bullets.push(b); game.addChild(b); } } else if (selectedGun === 2) { // Rapid: 2 fast bullets for (var i = 0; i < 2; i++) { var b = new Bullet(); b.x = player.x; b.y = player.y; b.dx = dirX; b.dy = dirY; b.speed = 60; bullets.push(b); game.addChild(b); } } else if (selectedGun === 3) { // Big: 1 slow, big bullet var b = new Bullet(); b.x = player.x; b.y = player.y; b.dx = dirX; b.dy = dirY; b.speed = 25; // Make bullet bigger b.children[0].scale.x = 2; b.children[0].scale.y = 2; bullets.push(b); game.addChild(b); } else if (selectedGun === 4) { // Pierce: 1 bullet, mark as piercing var b = new Bullet(); b.x = player.x; b.y = player.y; b.dx = dirX; b.dy = dirY; b.pierce = true; bullets.push(b); game.addChild(b); } LK.getSound('shoot').play(); } // Main game update game.update = function () { // Attack button hold-to-shoot logic if (attackBtnHeld) { if (attackBtnShootCooldown <= 0) { // Shoot in the direction the gun is facing shootBullet(gunAimX, gunAimY); attackBtnShootCooldown = 8; // frames between shots } else { attackBtnShootCooldown--; } } else { attackBtnShootCooldown = 0; } // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); // Remove if off screen if (b.x < -60 || b.x > 2048 + 60 || b.y < -60 || b.y > 2732 + 60) { b.destroy(); bullets.splice(i, 1); continue; } } // Update tomatoes for (var j = tomatoes.length - 1; j >= 0; j--) { var t = tomatoes[j]; t.update(); // Check collision with stone wall first if (t.intersects(stoneWall)) { // Smash tomato against wall: destroy tomato, damage wall, no game over unless wall is out of lives t.destroy(); tomatoes.splice(j, 1); stoneWall.hitByTomato(); // If wall is out of lives, destroy wall and game over if (stoneWall.lives <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } continue; } // Check collision with player if (t.intersects(player)) { // Flash screen, game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } // Check collision with bullets for (var k = bullets.length - 1; k >= 0; k--) { var b = bullets[k]; if (t.intersects(b)) { // Destroy tomato t.destroy(); tomatoes.splice(j, 1); // If not piercing, destroy bullet if (!b.pierce) { b.destroy(); bullets.splice(k, 1); } // Score up score += 1; LK.setScore(score); scoreTxt.setText(score); // Only allow one bullet to hit this tomato break; } } } }; // Play music if you want, but not required for MVP // Clean up on game over (handled by LK automatically)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Player Ball
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGfx = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Gun graphic (use separate 'gun' asset, as gun barrel)
var gunGfx = self.attachAsset('gun', {
anchorX: 0.5,
// center horizontally
anchorY: 0.85,
// anchor at base of gun
scaleX: 1,
scaleY: 1,
rotation: 0
});
gunGfx.y = -ballGfx.height * 0.08; // slightly above center
self.gunGfx = gunGfx;
// For possible future use
self.radius = ballGfx.width / 2;
// Track gun angle (radians)
self.gunAngle = -Math.PI / 2; // default up
// Method to set gun angle
self.setGunAngle = function (angle) {
self.gunAngle = angle;
self.gunGfx.rotation = angle;
};
return self;
});
// Bullet
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Direction (unit vector)
self.dx = 0;
self.dy = -1;
self.speed = 40; // px per frame
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
};
return self;
});
// Stone Wall
var StoneWall = Container.expand(function () {
var self = Container.call(this);
// Make the wall as wide as the game area
var wallGfx = self.attachAsset('stonewall', {
width: 2048,
height: 80,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5
});
// Wall health and lives
self.maxHits = 5; // Number of attacks required to damage wall health
self.hits = 0; // Current number of attacks taken in this cycle
self.lives = 5; // Wall health (number of times wall can be damaged)
// Show wall health as text
self.healthTxt = new Text2("Wall: " + (self.maxHits - self.hits) + " / " + self.maxHits + "\nLives: " + self.lives, {
size: 48,
fill: "#fff"
});
self.healthTxt.anchor.set(0.5, 0.5);
self.healthTxt.y = 0;
self.addChild(self.healthTxt);
// Method to update health text
self.updateHealthText = function () {
self.healthTxt.setText("Wall: " + (self.maxHits - self.hits) + " / " + self.maxHits + "\nLives: " + self.lives);
};
// Method to handle a tomato hit
self.hitByTomato = function () {
self.hits++;
if (self.hits >= self.maxHits) {
// Wall takes a real damage only after 5 attacks
self.lives--;
self.hits = 0;
// Flash wall to show it lost a life
LK.effects.flashObject(self, 0xff0000, 400);
} else {
// Flash wall for a normal hit (not enough attacks yet)
LK.effects.flashObject(self, 0xffffff, 200);
}
self.updateHealthText();
};
return self;
});
// Tomato Enemy
var Tomato = Container.expand(function () {
var self = Container.call(this);
var tomatoGfx = self.attachAsset('tomato', {
anchorX: 0.5,
anchorY: 0.5
});
// Target to move towards (the player ball)
self.target = null;
self.speed = 7 + Math.random() * 3; // px per frame, randomize a bit
self.update = function () {
if (!self.target) return;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Sound for shooting
// Tomato enemy
// Bullet
// Ball (player)
// Game area: 2048x2732
// Center player ball
var player = new Ball();
player.x = 2048 / 2;
player.y = 2732 - 350;
game.addChild(player);
// Add stone wall(s) a short distance in front of the player
var stoneWall = new StoneWall();
stoneWall.x = player.x;
stoneWall.y = player.y - 180; // ~18cm ahead (assuming 10px ~ 1mm)
game.addChild(stoneWall);
// Score display
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Bullets and tomatoes arrays
var bullets = [];
var tomatoes = [];
// Dragging
var dragNode = null;
// For shooting: track last tap position
var lastTapX = null;
var lastTapY = null;
// Store UI: 5 gun buttons at the bottom of the screen
var gunCount = 5;
var gunButtons = [];
var selectedGun = 0; // default gun
// Gun names (could be used for upgrades later)
var gunNames = ["Basic", "Spread", "Rapid", "Big", "Pierce"];
// Gun colors for button visuals
var gunColors = [0x3a9efd, 0x6cbb3c, 0xf7b32b, 0xd7263d, 0x7c3aed];
// Create gun buttons and add to GUI bottom
for (var i = 0; i < gunCount; i++) {
var btn = new Container();
// Button background
var btnBg = LK.getAsset('bullet', {
width: 180,
height: 180,
color: gunColors[i],
anchorX: 0.5,
anchorY: 0.5
});
btn.addChild(btnBg);
// Button label
var btnLabel = new Text2(gunNames[i], {
size: 48,
fill: "#fff"
});
btnLabel.anchor.set(0.5, 0.5);
btnLabel.y = 50;
btn.addChild(btnLabel);
// Position buttons evenly along the bottom, avoiding bottom corners
btn.x = 2048 / (gunCount + 1) * (i + 1);
btn.y = 2732 - 120;
// Store index for event
btn.gunIndex = i;
// Highlight selected gun
btnBg.alpha = i === selectedGun ? 1 : 0.5;
// Add to GUI
LK.gui.bottom.addChild(btn);
gunButtons.push(btn);
}
// (Arrow button code removed for walk-by-hold movement)
// Store gun selection handler
function handleGunButtonDown(x, y, obj) {
// Find which button was pressed
for (var i = 0; i < gunButtons.length; i++) {
var btn = gunButtons[i];
// Convert event to local button space
var local = btn.toLocal({
x: x,
y: y
});
// Button is 180x180 centered
if (local.x > -90 && local.x < 90 && local.y > -90 && local.y < 90) {
selectedGun = btn.gunIndex;
// Update button highlights
for (var j = 0; j < gunButtons.length; j++) {
gunButtons[j].children[0].alpha = j === selectedGun ? 1 : 0.5;
}
break;
}
}
}
// Add attack button at bottom left using LK.gui.bottomLeft for reliable placement
var attackBtn = new Container();
var attackBtnBg = LK.getAsset('ball', {
width: 220,
height: 220,
color: 0xd7263d,
anchorX: 0.5,
anchorY: 0.5
});
attackBtn.addChild(attackBtnBg);
var attackBtnLabel = new Text2("Attack", {
size: 60,
fill: "#fff"
});
attackBtnLabel.anchor.set(0.5, 0.5);
attackBtn.addChild(attackBtnLabel);
// Place at bottom left, with margin so it's not flush to the edge
attackBtn.x = 140;
attackBtn.y = -140;
LK.gui.bottomLeft.addChild(attackBtn);
// Track attack button hold state
var attackBtnHeld = false;
var attackBtnShootCooldown = 0;
// Track where the gun is aiming (defaults up)
var gunAimX = player.x;
var gunAimY = player.y - 400;
// Attack button event
attackBtn.down = function (x, y, obj) {
attackBtnHeld = true;
// Shoot in the direction the gun is facing
shootBullet(gunAimX, gunAimY);
attackBtnShootCooldown = 8; // frames between shots
};
attackBtn.up = function (x, y, obj) {
attackBtnHeld = false;
};
// Add event to GUI bottom for gun selection
LK.gui.bottom.down = function (x, y, obj) {
// If attack button is pressed, don't select gun
var local = attackBtn.toLocal({
x: x,
y: y
});
if (local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110) {
// handled by attackBtn.down
return;
}
handleGunButtonDown(x, y, obj);
};
// Prevent elements in top left 100x100
// (player and GUI are well away from this area)
// Spawn tomato at random edge (but NOT from the bottom)
function spawnTomato() {
var t = new Tomato();
// Only pick from top, left, or right edges (no bottom, so never behind the wall)
var edge = Math.floor(Math.random() * 3);
var margin = 100;
if (edge === 0) {
// Top
t.x = margin + Math.random() * (2048 - 2 * margin);
t.y = -80;
} else if (edge === 1) {
// Left
t.x = -80;
t.y = margin + Math.random() * (stoneWall.y - 2 * margin);
} else {
// Right
t.x = 2048 + 80;
t.y = margin + Math.random() * (stoneWall.y - 2 * margin);
}
t.target = player;
tomatoes.push(t);
game.addChild(t);
}
// Initial tomato spawn
for (var i = 0; i < 2; i++) {
spawnTomato();
}
// Tomato spawn timer
var tomatoTimer = LK.setInterval(function () {
spawnTomato();
}, 1200);
// Handle dragging the player ball
function handleMove(x, y, obj) {
// Player movement disabled: do not update player position
// Always update gun aim to point at the current touch/move position
// Only if not on attack button (so we don't aim while holding attack)
var local = attackBtn.toLocal({
x: x,
y: y
});
if (!(local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110)) {
gunAimX = x;
gunAimY = y;
// Calculate angle from player to aim point
var dx = gunAimX - player.x;
var dy = gunAimY - player.y;
var angle = Math.atan2(dy, dx);
player.setGunAngle(angle);
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Player movement disabled: do not set dragNode
// Update gun aim to this position (unless on attack button)
var local = attackBtn.toLocal({
x: x,
y: y
});
if (!(local.x > -110 && local.x < 110 && local.y > -110 && local.y < 110)) {
gunAimX = x;
gunAimY = y;
var dx = gunAimX - player.x;
var dy = gunAimY - player.y;
var angle = Math.atan2(dy, dx);
player.setGunAngle(angle);
}
};
game.up = function (x, y, obj) {
// Player movement disabled: do not clear dragNode
};
// Shoot bullet from player towards (tx, ty)
function shootBullet(tx, ty) {
// Calculate direction
var dx = tx - player.x;
var dy = ty - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var dirX = dist === 0 ? 0 : dx / dist;
var dirY = dist === 0 ? -1 : dy / dist;
// Gun logic
if (selectedGun === 0) {
// Basic: single bullet
var b = new Bullet();
b.x = player.x;
b.y = player.y;
b.dx = dirX;
b.dy = dirY;
bullets.push(b);
game.addChild(b);
} else if (selectedGun === 1) {
// Spread: 3 bullets, spread angle
for (var i = -1; i <= 1; i++) {
var angle = Math.atan2(dirY, dirX) + i * 0.18;
var b = new Bullet();
b.x = player.x;
b.y = player.y;
b.dx = Math.cos(angle);
b.dy = Math.sin(angle);
bullets.push(b);
game.addChild(b);
}
} else if (selectedGun === 2) {
// Rapid: 2 fast bullets
for (var i = 0; i < 2; i++) {
var b = new Bullet();
b.x = player.x;
b.y = player.y;
b.dx = dirX;
b.dy = dirY;
b.speed = 60;
bullets.push(b);
game.addChild(b);
}
} else if (selectedGun === 3) {
// Big: 1 slow, big bullet
var b = new Bullet();
b.x = player.x;
b.y = player.y;
b.dx = dirX;
b.dy = dirY;
b.speed = 25;
// Make bullet bigger
b.children[0].scale.x = 2;
b.children[0].scale.y = 2;
bullets.push(b);
game.addChild(b);
} else if (selectedGun === 4) {
// Pierce: 1 bullet, mark as piercing
var b = new Bullet();
b.x = player.x;
b.y = player.y;
b.dx = dirX;
b.dy = dirY;
b.pierce = true;
bullets.push(b);
game.addChild(b);
}
LK.getSound('shoot').play();
}
// Main game update
game.update = function () {
// Attack button hold-to-shoot logic
if (attackBtnHeld) {
if (attackBtnShootCooldown <= 0) {
// Shoot in the direction the gun is facing
shootBullet(gunAimX, gunAimY);
attackBtnShootCooldown = 8; // frames between shots
} else {
attackBtnShootCooldown--;
}
} else {
attackBtnShootCooldown = 0;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
// Remove if off screen
if (b.x < -60 || b.x > 2048 + 60 || b.y < -60 || b.y > 2732 + 60) {
b.destroy();
bullets.splice(i, 1);
continue;
}
}
// Update tomatoes
for (var j = tomatoes.length - 1; j >= 0; j--) {
var t = tomatoes[j];
t.update();
// Check collision with stone wall first
if (t.intersects(stoneWall)) {
// Smash tomato against wall: destroy tomato, damage wall, no game over unless wall is out of lives
t.destroy();
tomatoes.splice(j, 1);
stoneWall.hitByTomato();
// If wall is out of lives, destroy wall and game over
if (stoneWall.lives <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
continue;
}
// Check collision with player
if (t.intersects(player)) {
// Flash screen, game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
// Check collision with bullets
for (var k = bullets.length - 1; k >= 0; k--) {
var b = bullets[k];
if (t.intersects(b)) {
// Destroy tomato
t.destroy();
tomatoes.splice(j, 1);
// If not piercing, destroy bullet
if (!b.pierce) {
b.destroy();
bullets.splice(k, 1);
}
// Score up
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// Only allow one bullet to hit this tomato
break;
}
}
}
};
// Play music if you want, but not required for MVP
// Clean up on game over (handled by LK automatically)