User prompt
The game could be better if you added a collectible heart-like item that increases your health.
User prompt
The game could be better if you added a collectible heart-like item that increases your health.
Code edit (1 edits merged)
Please save this source code
User prompt
add sound effects and music for each event
User prompt
Enemy tanks should come in a more aligned manner from above and below the game over text.
User prompt
animate the losing screen. Tanks and enemies come. Red from right and left, sometimes it says game over, it should be very dynamic and cool. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Well done, make the counter look better.
User prompt
We understand the problem, the problem is that when you press the game started text once, it starts, but when you press it again, the menu returns, this should not be the problem. Make sure the game is running smoothly.
User prompt
We understand the problem, the problem is that when you press the game started text once, it starts, but when you press it again, the menu returns, this should not be the problem. Make sure the game is running smoothly.
User prompt
We understand the problem, the problem is that when you press the game started text once, it starts, but when you press it again, the menu returns, this should not be the problem. Make sure the game is running smoothly.
User prompt
Say tap to satart and the menu should go and the game should start.
User prompt
Say tap to satart and the menu should go and the game should start.
User prompt
It would be better if the tank image in the main menu was lower than it is now. Also, there are a lot of problems with starting the game.
User prompt
when the game starts the tank image leaves a picture that stays where it is, fix this. also there should be an animated intro, i.e. at first a blue tank clears 3 red tanks and then the menu comes, it would be cool. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make an intro. then click and the game will start.
User prompt
No, when you say color, you don't mean like this, you add things like objects, depth and similar items.
User prompt
Add some color to the playground, make it like a real tank battlefield.
User prompt
Move the lives a little more to the left.
User prompt
Move the lives a little more to the right.Move the lives a little more to the right.
User prompt
Write "Glaud" in small orange in the upper right corner.
User prompt
enemies should fire slower.
User prompt
enemies should fire slower.enemies should fire slower.enemies should fire slower.
User prompt
enemies should move slower.enemies should move slower.enemies should move slower.enemies should move slower.
User prompt
enemies should move slower.
User prompt
the controlled tank should be faster
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Enemy Shell Class var EnemyShell = Container.expand(function () { var self = Container.call(this); var shell = self.attachAsset('enemyShell', { anchorX: 0.5, anchorY: 0.5 }); self.width = shell.width; self.height = shell.height; self.vx = 0; self.vy = 8; // Reduced speed self.update = function () { self.x += self.vx; self.y += self.vy; }; return self; }); // Enemy Tank Class var EnemyTank = Container.expand(function () { var self = Container.call(this); var tank = self.attachAsset('enemyTank', { anchorX: 0.5, anchorY: 0.5 }); self.width = tank.width; self.height = tank.height; // Movement self.speed = 3 + Math.random() * 1.5 + Math.floor(LK.getScore() / 15); // Slower movement, less score scaling // Firing self.fireInterval = 120; // 2 seconds at 60fps self.fireCounter = 0; self.update = function () { self.y += self.speed; // Move downwards self.fireCounter++; if (self.fireCounter >= self.fireInterval) { self.fireCounter = 0; self.fire(); } }; self.fire = function () { var shell = new EnemyShell(); shell.x = self.x; shell.y = self.y + self.height / 2 + shell.height / 2; // Start below the enemy tank // Aim at player var dx = player.x - shell.x; var dy = player.y - shell.y; var mag = Math.sqrt(dx * dx + dy * dy); // Rotate tank to face direction of fire var angle = Math.atan2(dy, dx); self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up // Lowered speed for enemy shells var shellSpeed = 7 + Math.floor(LK.getScore() / 20); shell.vx = dx / mag * shellSpeed; shell.vy = dy / mag * shellSpeed; enemyShells.push(shell); game.addChild(shell); LK.getSound('enemyFire').play(); }; return self; }); // Explosion effect (for destroyed tanks) var Explosion = Container.expand(function () { var self = Container.call(this); var exp = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.1, scaleY: 0.1 }); exp.alpha = 0.9; // Animate explosion growth and fade tween(exp, { scaleX: 2.5, scaleY: 2.5, alpha: 0.7 }, { duration: 150, easing: tween.easeOut }); // Animate fade out and destroy tween(exp, { alpha: 0 }, { duration: 450, easing: tween.linear, onFinish: function onFinish() { self.destroy(); } }); return self; }); // Player Shell Class var PlayerShell = Container.expand(function () { var self = Container.call(this); var shell = self.attachAsset('playerShell', { anchorX: 0.5, anchorY: 0.5 }); self.width = shell.width; self.height = shell.height; self.vx = 0; self.vy = -32; // Default upwards self.lastX = 0; self.lastY = 0; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.vx; self.y += self.vy; }; return self; }); // Player Tank Class var PlayerTank = Container.expand(function () { var self = Container.call(this); var tank = self.attachAsset('playerTank', { anchorX: 0.5, anchorY: 0.5 }); self.width = tank.width; self.height = tank.height; // Fire cooldown self.canFire = true; self.fireCooldown = 18; // frames (0.3s) self.cooldownCounter = 0; // Movement physics self.targetX = 0; self.targetY = 0; self.velocityX = 0; self.velocityY = 0; self.acceleration = 0.4; // Increased acceleration self.maxSpeed = 60; // Increased max speed self.friction = 0.94; // Slightly less friction for smoother movement self.isMoving = false; self.reachedTarget = true; self.rotationSpeed = 0.3; // Faster rotation self.tracksFX = null; // Move to a position with physics self.moveTo = function (x, y) { self.targetX = x; self.targetY = y; self.isMoving = true; self.reachedTarget = false; // Create dust/tracks effect if (!self.tracksFX) { self.createTracksFX(); } }; // Create track/dust effect self.createTracksFX = function () { if (self.tracksFX) { self.tracksFX.alpha = 0.7; return; } self.tracksFX = self.attachAsset('explosion', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3 }); self.tracksFX.y = self.height / 3; self.tracksFX.alpha = 0.3; self.tracksFX.tint = 0xCCCCCC; }; // Stop movement self.stopMoving = function () { self.isMoving = false; if (self.tracksFX) { tween(self.tracksFX, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } }; self.update = function () { // Handle fire cooldown if (!self.canFire) { self.cooldownCounter++; if (self.cooldownCounter >= self.fireCooldown) { self.canFire = true; self.cooldownCounter = 0; } } // Handle physics-based movement if (self.isMoving) { // Calculate distance and direction to target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distSq = dx * dx + dy * dy; // Calculate desired direction var targetAngle = Math.atan2(dy, dx) + Math.PI / 2; // Smoothly rotate toward target direction var angleDiff = targetAngle - self.rotation; // Normalize angle difference to -PI to PI while (angleDiff > Math.PI) angleDiff -= Math.PI * 2; while (angleDiff < -Math.PI) angleDiff += Math.PI * 2; self.rotation += angleDiff * self.rotationSpeed; // If we're close to target, slow down if (distSq < 10000) { // 100*100 self.velocityX *= 0.9; self.velocityY *= 0.9; if (distSq < 100) { // 10*10 self.reachedTarget = true; self.velocityX = 0; self.velocityY = 0; self.stopMoving(); } } else { // Accelerate toward target based on tank's orientation var facingX = Math.sin(self.rotation); var facingY = -Math.cos(self.rotation); // Only accelerate if facing approximately the right direction var dotProduct = facingX * dx + facingY * dy; if (dotProduct > 0 || distSq > 90000) { // Allow movement if far away regardless of direction self.velocityX += facingX * self.acceleration; self.velocityY += facingY * self.acceleration; } } // Apply friction and speed limits self.velocityX *= self.friction; self.velocityY *= self.friction; // Limit max speed var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY); if (speed > self.maxSpeed) { self.velocityX = self.velocityX / speed * self.maxSpeed; self.velocityY = self.velocityY / speed * self.maxSpeed; } // Apply velocity self.x += self.velocityX; self.y += self.velocityY; // Animate tracks effect if (self.tracksFX && speed > 0.5) { self.tracksFX.rotation = Math.random() * 0.2 - 0.1; self.tracksFX.alpha = Math.min(0.2 + speed / 30, 0.7); self.tracksFX.scaleX = 0.3 + Math.random() * 0.1; self.tracksFX.scaleY = 0.3 + Math.random() * 0.1; } } }; // Fire a shell self.fire = function (targetX, targetY) { if (self.canFire) { var shell = new PlayerShell(); shell.x = self.x; shell.y = self.y - self.height / 3; // Offset slightly to start from tank cannon // Calculate direction if (targetX !== undefined && targetY !== undefined) { var dx = targetX - shell.x; var dy = targetY - shell.y; var mag = Math.sqrt(dx * dx + dy * dy); var shellSpeed = 32; // Shell speed shell.vx = dx / mag * shellSpeed; shell.vy = dy / mag * shellSpeed; // Rotate tank to face direction of fire var angle = Math.atan2(dy, dx); self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up } else { // Default upward if no target shell.vx = 0; shell.vy = -32; self.rotation = 0; } playerShells.push(shell); game.addChild(shell); LK.getSound('fire').play(); self.canFire = false; shell.lastX = shell.x; shell.lastY = shell.y; // Animate recoil effect on firing var originalY = self.y; tween(self, { y: self.y + 20 }, { duration: 50, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { y: originalY }, { duration: 150, easing: tween.elasticOut }); } }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x22272b }); /**** * Game Code ****/ // Game area // Player tank: green box // Player shell: yellow ellipse // Enemy tank: red box // Enemy shell: orange ellipse // Explosion: white ellipse (for flash effect) // Sound effects var GAME_W = 2048; var GAME_H = 2732; // Player var player = new PlayerTank(); player.x = GAME_W / 2; player.y = GAME_H - 350; game.addChild(player); // Arrays for game objects var playerShells = []; var enemyTanks = []; var enemyShells = []; // Score display var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Player health var playerHealth = 5; var healthTxt = new Text2('❤❤❤❤❤', { size: 100, fill: 0xFF4444 }); healthTxt.anchor.set(0.5, 0); LK.gui.top.addChild(healthTxt); healthTxt.x = 300; healthTxt.y = 0; // Dragging var dragNode = null; // Spawn enemy timer var enemySpawnInterval = 90; // frames (1.5s) var enemySpawnCounter = 0; // Start at 0 to spawn immediately // Difficulty escalation function getEnemySpawnInterval() { var s = LK.getScore(); if (s < 10) return 90; if (s < 25) return 70; if (s < 50) return 55; return 40; } // Move handler (physics-based tank movement) function handleMove(x, y, obj) { if (dragNode) { // Calculate target position with bounds checking var minX = dragNode.width / 2; var maxX = GAME_W - dragNode.width / 2; var minY = 100 + dragNode.height / 2; var maxY = GAME_H - dragNode.height / 2; var targetX = Math.max(minX, Math.min(maxX, x)); var targetY = Math.max(minY, Math.min(maxY, y)); // If it's the player tank, use the physics system if (dragNode === player) { player.moveTo(targetX, targetY); } else { // For other objects, use direct positioning dragNode.x = targetX; dragNode.y = targetY; } } else if (isFiring) { // Update firing target if holding down fireTarget.x = x; fireTarget.y = y; // Rotate tank to face direction var dx = x - player.x; var dy = y - player.y; var angle = Math.atan2(dy, dx); player.rotation = angle + Math.PI / 2; // +90 degrees since tank points up } } game.move = handleMove; game.down = function (x, y, obj) { // Only drag if touch is on player tank var local = player.toLocal(game.toGlobal({ x: x, y: y })); if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) { dragNode = player; } }; game.up = function (x, y, obj) { // If we were dragging the player, let it continue to its last target if (dragNode === player) { // Already set target in handleMove, just release drag indicator } dragNode = null; isFiring = false; autoFireCounter = 0; }; // Tap to move and fire game.tap = function (x, y, obj) { // Avoid firing if already dragging if (!dragNode) { // Move the tank to the target position (using physics) player.moveTo(x, y); // Fire toward target player.fire(x, y); } }; // Variables for firing control var isFiring = false; var fireTarget = { x: 0, y: 0 }; var autoFireRate = 10; // Fire every 10 frames while holding var autoFireCounter = 0; // Handle both movement and firing game.down = function (x, y, obj) { var local = player.toLocal(game.toGlobal({ x: x, y: y })); if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) { dragNode = player; // When clicking on tank, start listening for drag } else { // When clicking elsewhere, move tank there and fire // Set firing target and start firing fireTarget.x = x; fireTarget.y = y; isFiring = true; autoFireCounter = 0; // Move the tank to the target position (using physics) player.moveTo(x, y); // Fire toward target player.fire(fireTarget.x, fireTarget.y); } }; // Main update loop game.update = function () { // Update player player.update(); // Handle continuous firing while holding down if (isFiring) { autoFireCounter++; if (autoFireCounter >= autoFireRate && player.canFire) { player.fire(fireTarget.x, fireTarget.y); autoFireCounter = 0; } } // Update player shells for (var i = playerShells.length - 1; i >= 0; i--) { var shell = playerShells[i]; shell.update(); // Remove if off screen if (shell.y < -shell.height / 2) { shell.destroy(); playerShells.splice(i, 1); continue; } // Check collision with enemy tanks for (var j = enemyTanks.length - 1; j >= 0; j--) { var enemy = enemyTanks[j]; if (shell.intersects(enemy)) { // Explosion var exp = new Explosion(); exp.x = enemy.x; exp.y = enemy.y; game.addChild(exp); LK.getSound('explode').play(); // Remove both shell.destroy(); playerShells.splice(i, 1); enemy.destroy(); enemyTanks.splice(j, 1); // Score LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); break; } } } // Update enemy tanks for (var i = enemyTanks.length - 1; i >= 0; i--) { var enemy = enemyTanks[i]; enemy.update(); // Remove if off screen if (enemy.y > GAME_H + enemy.height / 2) { enemy.destroy(); enemyTanks.splice(i, 1); continue; } // Check collision with player if (enemy.intersects(player)) { // Explosion var exp = new Explosion(); exp.x = player.x; exp.y = player.y; game.addChild(exp); LK.getSound('explode').play(); LK.effects.flashScreen(0xff0000, 800); // Decrease health playerHealth--; // Update health display var hearts = ''; for (var h = 0; h < playerHealth; h++) hearts += '❤'; healthTxt.setText(hearts); if (playerHealth <= 0) { LK.showGameOver(); return; } // Animate player hit effect tween.stop(player, { rotation: true, scaleX: true, scaleY: true, alpha: true }); tween(player, { scaleX: 1.3, scaleY: 1.3, alpha: 0.6 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 300, easing: tween.elasticOut }); } }); // Remove enemy tank enemy.destroy(); enemyTanks.splice(i, 1); continue; } } // Update enemy shells for (var i = enemyShells.length - 1; i >= 0; i--) { var shell = enemyShells[i]; shell.update(); // Remove if off screen if (shell.y > GAME_H + shell.height / 2 || shell.x < -shell.width / 2 || shell.x > GAME_W + shell.width / 2) { shell.destroy(); enemyShells.splice(i, 1); continue; } // Check collision with player if (shell.intersects(player)) { // Explosion var exp = new Explosion(); exp.x = shell.x; exp.y = shell.y; game.addChild(exp); LK.getSound('explode').play(); LK.effects.flashScreen(0xff0000, 800); // Decrease health playerHealth--; // Update health display var hearts = ''; for (var h = 0; h < playerHealth; h++) hearts += '❤'; healthTxt.setText(hearts); if (playerHealth <= 0) { LK.showGameOver(); return; } // Animate player hit by shell tween.stop(player, { rotation: true, alpha: true }); // Push player slightly in the direction of the hit var pushX = (player.x - shell.x) * 0.05; var pushY = (player.y - shell.y) * 0.05; var origX = player.x; var origY = player.y; tween(player, { x: player.x + pushX, y: player.y + pushY, alpha: 0.7 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(player, { x: origX, y: origY, alpha: 1 }, { duration: 280, easing: tween.easeInOut }); } }); // Remove shell shell.destroy(); enemyShells.splice(i, 1); continue; } } // Spawn enemies enemySpawnCounter++; if (enemySpawnCounter >= getEnemySpawnInterval()) { enemySpawnCounter = 0; // Only spawn an enemy if there are not too many already if (enemyTanks.length < 5 + Math.floor(LK.getScore() / 10)) { var enemy = new EnemyTank(); // Position enemies at the top, across the width of the screen var margin = 200; enemy.x = margin / 2 + Math.random() * (GAME_W - margin); enemy.y = -enemy.height / 2; // Start just off-screen at the top // Rotate to face down enemy.rotation = Math.PI; // 180 degrees to face down enemyTanks.push(enemy); game.addChild(enemy); // Add spawn animation enemy.alpha = 0; enemy.scaleX = 0.2; enemy.scaleY = 0.2; tween(enemy, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 400, easing: tween.elasticOut }); console.log("Spawned enemy tank from top"); } } }; // Set initial score LK.setScore(0); scoreTxt.setText('0'); ; // Set initial health playerHealth = 5; var hearts = ''; for (var h = 0; h < playerHealth; h++) hearts += '❤'; healthTxt.setText(hearts);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy Shell Class
var EnemyShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('enemyShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = 8; // Reduced speed
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Enemy Tank Class
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Movement
self.speed = 3 + Math.random() * 1.5 + Math.floor(LK.getScore() / 15); // Slower movement, less score scaling
// Firing
self.fireInterval = 120; // 2 seconds at 60fps
self.fireCounter = 0;
self.update = function () {
self.y += self.speed; // Move downwards
self.fireCounter++;
if (self.fireCounter >= self.fireInterval) {
self.fireCounter = 0;
self.fire();
}
};
self.fire = function () {
var shell = new EnemyShell();
shell.x = self.x;
shell.y = self.y + self.height / 2 + shell.height / 2; // Start below the enemy tank
// Aim at player
var dx = player.x - shell.x;
var dy = player.y - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
// Lowered speed for enemy shells
var shellSpeed = 7 + Math.floor(LK.getScore() / 20);
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
enemyShells.push(shell);
game.addChild(shell);
LK.getSound('enemyFire').play();
};
return self;
});
// Explosion effect (for destroyed tanks)
var Explosion = Container.expand(function () {
var self = Container.call(this);
var exp = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
exp.alpha = 0.9;
// Animate explosion growth and fade
tween(exp, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.7
}, {
duration: 150,
easing: tween.easeOut
});
// Animate fade out and destroy
tween(exp, {
alpha: 0
}, {
duration: 450,
easing: tween.linear,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
// Player Shell Class
var PlayerShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = -32; // Default upwards
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Player Tank Class
var PlayerTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Fire cooldown
self.canFire = true;
self.fireCooldown = 18; // frames (0.3s)
self.cooldownCounter = 0;
// Movement physics
self.targetX = 0;
self.targetY = 0;
self.velocityX = 0;
self.velocityY = 0;
self.acceleration = 0.4; // Increased acceleration
self.maxSpeed = 60; // Increased max speed
self.friction = 0.94; // Slightly less friction for smoother movement
self.isMoving = false;
self.reachedTarget = true;
self.rotationSpeed = 0.3; // Faster rotation
self.tracksFX = null;
// Move to a position with physics
self.moveTo = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
self.reachedTarget = false;
// Create dust/tracks effect
if (!self.tracksFX) {
self.createTracksFX();
}
};
// Create track/dust effect
self.createTracksFX = function () {
if (self.tracksFX) {
self.tracksFX.alpha = 0.7;
return;
}
self.tracksFX = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
self.tracksFX.y = self.height / 3;
self.tracksFX.alpha = 0.3;
self.tracksFX.tint = 0xCCCCCC;
};
// Stop movement
self.stopMoving = function () {
self.isMoving = false;
if (self.tracksFX) {
tween(self.tracksFX, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.update = function () {
// Handle fire cooldown
if (!self.canFire) {
self.cooldownCounter++;
if (self.cooldownCounter >= self.fireCooldown) {
self.canFire = true;
self.cooldownCounter = 0;
}
}
// Handle physics-based movement
if (self.isMoving) {
// Calculate distance and direction to target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distSq = dx * dx + dy * dy;
// Calculate desired direction
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2;
// Smoothly rotate toward target direction
var angleDiff = targetAngle - self.rotation;
// Normalize angle difference to -PI to PI
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI) angleDiff += Math.PI * 2;
self.rotation += angleDiff * self.rotationSpeed;
// If we're close to target, slow down
if (distSq < 10000) {
// 100*100
self.velocityX *= 0.9;
self.velocityY *= 0.9;
if (distSq < 100) {
// 10*10
self.reachedTarget = true;
self.velocityX = 0;
self.velocityY = 0;
self.stopMoving();
}
} else {
// Accelerate toward target based on tank's orientation
var facingX = Math.sin(self.rotation);
var facingY = -Math.cos(self.rotation);
// Only accelerate if facing approximately the right direction
var dotProduct = facingX * dx + facingY * dy;
if (dotProduct > 0 || distSq > 90000) {
// Allow movement if far away regardless of direction
self.velocityX += facingX * self.acceleration;
self.velocityY += facingY * self.acceleration;
}
}
// Apply friction and speed limits
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Limit max speed
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (speed > self.maxSpeed) {
self.velocityX = self.velocityX / speed * self.maxSpeed;
self.velocityY = self.velocityY / speed * self.maxSpeed;
}
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Animate tracks effect
if (self.tracksFX && speed > 0.5) {
self.tracksFX.rotation = Math.random() * 0.2 - 0.1;
self.tracksFX.alpha = Math.min(0.2 + speed / 30, 0.7);
self.tracksFX.scaleX = 0.3 + Math.random() * 0.1;
self.tracksFX.scaleY = 0.3 + Math.random() * 0.1;
}
}
};
// Fire a shell
self.fire = function (targetX, targetY) {
if (self.canFire) {
var shell = new PlayerShell();
shell.x = self.x;
shell.y = self.y - self.height / 3; // Offset slightly to start from tank cannon
// Calculate direction
if (targetX !== undefined && targetY !== undefined) {
var dx = targetX - shell.x;
var dy = targetY - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
var shellSpeed = 32; // Shell speed
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
} else {
// Default upward if no target
shell.vx = 0;
shell.vy = -32;
self.rotation = 0;
}
playerShells.push(shell);
game.addChild(shell);
LK.getSound('fire').play();
self.canFire = false;
shell.lastX = shell.x;
shell.lastY = shell.y;
// Animate recoil effect on firing
var originalY = self.y;
tween(self, {
y: self.y + 20
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: originalY
}, {
duration: 150,
easing: tween.elasticOut
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22272b
});
/****
* Game Code
****/
// Game area
// Player tank: green box
// Player shell: yellow ellipse
// Enemy tank: red box
// Enemy shell: orange ellipse
// Explosion: white ellipse (for flash effect)
// Sound effects
var GAME_W = 2048;
var GAME_H = 2732;
// Player
var player = new PlayerTank();
player.x = GAME_W / 2;
player.y = GAME_H - 350;
game.addChild(player);
// Arrays for game objects
var playerShells = [];
var enemyTanks = [];
var enemyShells = [];
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Player health
var playerHealth = 5;
var healthTxt = new Text2('❤❤❤❤❤', {
size: 100,
fill: 0xFF4444
});
healthTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(healthTxt);
healthTxt.x = 300;
healthTxt.y = 0;
// Dragging
var dragNode = null;
// Spawn enemy timer
var enemySpawnInterval = 90; // frames (1.5s)
var enemySpawnCounter = 0; // Start at 0 to spawn immediately
// Difficulty escalation
function getEnemySpawnInterval() {
var s = LK.getScore();
if (s < 10) return 90;
if (s < 25) return 70;
if (s < 50) return 55;
return 40;
}
// Move handler (physics-based tank movement)
function handleMove(x, y, obj) {
if (dragNode) {
// Calculate target position with bounds checking
var minX = dragNode.width / 2;
var maxX = GAME_W - dragNode.width / 2;
var minY = 100 + dragNode.height / 2;
var maxY = GAME_H - dragNode.height / 2;
var targetX = Math.max(minX, Math.min(maxX, x));
var targetY = Math.max(minY, Math.min(maxY, y));
// If it's the player tank, use the physics system
if (dragNode === player) {
player.moveTo(targetX, targetY);
} else {
// For other objects, use direct positioning
dragNode.x = targetX;
dragNode.y = targetY;
}
} else if (isFiring) {
// Update firing target if holding down
fireTarget.x = x;
fireTarget.y = y;
// Rotate tank to face direction
var dx = x - player.x;
var dy = y - player.y;
var angle = Math.atan2(dy, dx);
player.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only drag if touch is on player tank
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
}
};
game.up = function (x, y, obj) {
// If we were dragging the player, let it continue to its last target
if (dragNode === player) {
// Already set target in handleMove, just release drag indicator
}
dragNode = null;
isFiring = false;
autoFireCounter = 0;
};
// Tap to move and fire
game.tap = function (x, y, obj) {
// Avoid firing if already dragging
if (!dragNode) {
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(x, y);
}
};
// Variables for firing control
var isFiring = false;
var fireTarget = {
x: 0,
y: 0
};
var autoFireRate = 10; // Fire every 10 frames while holding
var autoFireCounter = 0;
// Handle both movement and firing
game.down = function (x, y, obj) {
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
// When clicking on tank, start listening for drag
} else {
// When clicking elsewhere, move tank there and fire
// Set firing target and start firing
fireTarget.x = x;
fireTarget.y = y;
isFiring = true;
autoFireCounter = 0;
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(fireTarget.x, fireTarget.y);
}
};
// Main update loop
game.update = function () {
// Update player
player.update();
// Handle continuous firing while holding down
if (isFiring) {
autoFireCounter++;
if (autoFireCounter >= autoFireRate && player.canFire) {
player.fire(fireTarget.x, fireTarget.y);
autoFireCounter = 0;
}
}
// Update player shells
for (var i = playerShells.length - 1; i >= 0; i--) {
var shell = playerShells[i];
shell.update();
// Remove if off screen
if (shell.y < -shell.height / 2) {
shell.destroy();
playerShells.splice(i, 1);
continue;
}
// Check collision with enemy tanks
for (var j = enemyTanks.length - 1; j >= 0; j--) {
var enemy = enemyTanks[j];
if (shell.intersects(enemy)) {
// Explosion
var exp = new Explosion();
exp.x = enemy.x;
exp.y = enemy.y;
game.addChild(exp);
LK.getSound('explode').play();
// Remove both
shell.destroy();
playerShells.splice(i, 1);
enemy.destroy();
enemyTanks.splice(j, 1);
// Score
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
break;
}
}
}
// Update enemy tanks
for (var i = enemyTanks.length - 1; i >= 0; i--) {
var enemy = enemyTanks[i];
enemy.update();
// Remove if off screen
if (enemy.y > GAME_H + enemy.height / 2) {
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
// Check collision with player
if (enemy.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = player.x;
exp.y = player.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) hearts += '❤';
healthTxt.setText(hearts);
if (playerHealth <= 0) {
LK.showGameOver();
return;
}
// Animate player hit effect
tween.stop(player, {
rotation: true,
scaleX: true,
scaleY: true,
alpha: true
});
tween(player, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
// Remove enemy tank
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
}
// Update enemy shells
for (var i = enemyShells.length - 1; i >= 0; i--) {
var shell = enemyShells[i];
shell.update();
// Remove if off screen
if (shell.y > GAME_H + shell.height / 2 || shell.x < -shell.width / 2 || shell.x > GAME_W + shell.width / 2) {
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
// Check collision with player
if (shell.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = shell.x;
exp.y = shell.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) hearts += '❤';
healthTxt.setText(hearts);
if (playerHealth <= 0) {
LK.showGameOver();
return;
}
// Animate player hit by shell
tween.stop(player, {
rotation: true,
alpha: true
});
// Push player slightly in the direction of the hit
var pushX = (player.x - shell.x) * 0.05;
var pushY = (player.y - shell.y) * 0.05;
var origX = player.x;
var origY = player.y;
tween(player, {
x: player.x + pushX,
y: player.y + pushY,
alpha: 0.7
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
x: origX,
y: origY,
alpha: 1
}, {
duration: 280,
easing: tween.easeInOut
});
}
});
// Remove shell
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
}
// Spawn enemies
enemySpawnCounter++;
if (enemySpawnCounter >= getEnemySpawnInterval()) {
enemySpawnCounter = 0;
// Only spawn an enemy if there are not too many already
if (enemyTanks.length < 5 + Math.floor(LK.getScore() / 10)) {
var enemy = new EnemyTank();
// Position enemies at the top, across the width of the screen
var margin = 200;
enemy.x = margin / 2 + Math.random() * (GAME_W - margin);
enemy.y = -enemy.height / 2; // Start just off-screen at the top
// Rotate to face down
enemy.rotation = Math.PI; // 180 degrees to face down
enemyTanks.push(enemy);
game.addChild(enemy);
// Add spawn animation
enemy.alpha = 0;
enemy.scaleX = 0.2;
enemy.scaleY = 0.2;
tween(enemy, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.elasticOut
});
console.log("Spawned enemy tank from top");
}
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0');
;
// Set initial health
playerHealth = 5;
var hearts = '';
for (var h = 0; h < playerHealth; h++) hearts += '❤';
healthTxt.setText(hearts);