User prompt
Alright, then add 4 buttons: a forward arrow, a backward arrow, a right arrow, and a left arrow.
User prompt
Don't let the tomatoes come from below.
User prompt
build a store on the game screen and have 5 guns
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(m).to({' Line Number: 252
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(m).to({' Line Number: 252
User prompt
Please fix the bug: 'TypeError: tween.to is not a function' in or related to this line: 'tween.to(m, {' Line Number: 252
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(m).to({' Line Number: 252
User prompt
Please fix the bug: 'TypeError: tween.to is not a function' in or related to this line: 'tween.to(m, {' Line Number: 252
User prompt
The money should come directly and the amount of my money should be visible.
User prompt
Kill the tomato and let the money come.
Code edit (1 edits merged)
Please save this source code
User prompt
Ball Blaster: Tomato Attack
Initial prompt
make a ball that shoots bullets, the bullets should kill the tomatoes
/**** * 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)