/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function (speed, lifetime) {
var self = Container.call(this);
// Create eye white
var eyeWhite = self.attachAsset('eye', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
// Create pupil
var pupil = self.attachAsset('pupil', {
anchorX: 0.5,
anchorY: 0.5
});
// Create hit indicator (initially invisible)
var hitCircle = self.attachAsset('hitCircle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Set properties
self.speed = speed || 1;
self.vx = (Math.random() - 0.5) * self.speed * 3;
self.vy = (Math.random() - 0.5) * self.speed * 3;
self.targetTime = lifetime || 2000; // Time needed to hold to destroy (in ms)
self.holdTime = 0; // Current time being held
self.isHeld = false;
self.isDying = false;
self.value = Math.floor(self.targetTime / 500); // Score value based on difficulty
// Looking animation
self.lookTimer = 0;
self.lookInterval = Math.random() * 1000 + 500;
self.lookAt = function (x, y) {
// Calculate angle to look at cursor
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Limit pupil movement within eye
var maxDistance = eyeWhite.width * 0.3;
var scale = distance > maxDistance ? maxDistance / distance : 1;
// Move pupil
pupil.x = dx * scale * 0.3;
pupil.y = dy * scale * 0.3;
};
// Hold and destroy functionality
self.hold = function (deltaTime) {
if (self.isDying) return;
if (!self.isHeld) {
self.isHeld = true;
hitCircle.alpha = 0.7;
hitCircle.scaleX = 0;
hitCircle.scaleY = 0;
tween(hitCircle, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
self.holdTime += deltaTime;
// Update visual feedback
var progress = Math.min(self.holdTime / self.targetTime, 1);
hitCircle.alpha = 0.3 + progress * 0.7;
eyeWhite.scaleX = eyeWhite.scaleY = 1 + progress * 0.2;
// Check if enemy is destroyed
if (self.holdTime >= self.targetTime) {
self.destroy();
return true;
}
return false;
};
self.release = function () {
if (self.isHeld && !self.isDying) {
self.isHeld = false;
// Shrink hit circle
tween(hitCircle, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn
});
// Shrink eye back to normal
tween(eyeWhite, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Decay hold time when released
self.holdTime = Math.max(0, self.holdTime - 500);
}
};
self.destroy = function () {
if (self.isDying) return;
self.isDying = true;
LK.getSound('destroy').play();
// Play destroy animation
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from parent after animation
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.update = function (cursorX, cursorY) {
if (self.isDying) return;
// Move the enemy
self.x += self.vx;
self.y += self.vy;
// Bounce off edges
if (self.x < 50 || self.x > 2048 - 50) {
self.vx *= -1;
}
if (self.y < 50 || self.y > 2732 - 50) {
self.vy *= -1;
}
// Look at cursor if provided
if (cursorX !== undefined && cursorY !== undefined) {
self.lookAt(cursorX, cursorY);
} else {
// Random eye movement when cursor not provided
self.lookTimer += 16.67; // Approx 1 frame at 60fps
if (self.lookTimer > self.lookInterval) {
pupil.x = (Math.random() - 0.5) * 15;
pupil.y = (Math.random() - 0.5) * 15;
self.lookTimer = 0;
self.lookInterval = Math.random() * 1000 + 500;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111
});
/****
* Game Code
****/
// Background
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0
}));
// Game variables
var enemies = [];
var score = 0;
var level = 1;
var lives = 3;
var spawnTimer = 0;
var spawnInterval = 3000; // Initial spawn interval
var minSpawnInterval = 500; // Minimum spawn interval at highest difficulty
var maxEnemies = 5 + level * 2; // Max enemies on screen
var heldEnemy = null; // Kept for compatibility but no longer used
var cursorX = 1024;
var cursorY = 1366;
var gameRunning = true;
// Initialize score text
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 40;
LK.gui.top.addChild(scoreTxt);
// Initialize level text
var levelTxt = new Text2('Level: 1', {
size: 50,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 50;
levelTxt.y = 40;
LK.gui.topRight.addChild(levelTxt);
// Initialize lives text
var livesTxt = new Text2('Lives: 3', {
size: 50,
fill: 0xFFFFFF
});
livesTxt.anchor.set(1, 0);
livesTxt.x = -50;
livesTxt.y = 40;
LK.gui.topRight.addChild(livesTxt);
// Helper function to spawn an enemy
function spawnEnemy() {
if (enemies.length >= maxEnemies || !gameRunning) return;
// Create enemy with speed and lifetime based on level
var enemySpeed = 1 + level * 0.3;
var lifetime = 2000 - level * 100;
lifetime = Math.max(lifetime, 800); // Minimum 800ms to destroy
var enemy = new Enemy(enemySpeed, lifetime);
// Position randomly, keeping away from edges
enemy.x = Math.random() * (2048 - 200) + 100;
enemy.y = Math.random() * (2732 - 200) + 100;
// Add to game and array
game.addChild(enemy);
enemies.push(enemy);
// Reset spawn timer
spawnTimer = 0;
}
// Increase difficulty level
function levelUp() {
level++;
// Increase difficulty parameters
maxEnemies = 5 + level * 2;
spawnInterval = Math.max(minSpawnInterval, 3000 - level * 200);
// Update level display
levelTxt.setText('Level: ' + level);
// Play level up sound
LK.getSound('levelup').play();
// Visual feedback
LK.effects.flashScreen(0x6666FF, 500);
}
// Check for level up condition
function checkLevelUp() {
// Level up every 10 score points
if (score > 0 && score % 10 === 0) {
levelUp();
}
}
// Update score
function updateScore(points) {
var oldScore = score;
score += points;
scoreTxt.setText('Score: ' + score);
// Check if we should level up
if (Math.floor(score / 10) > Math.floor(oldScore / 10)) {
levelUp();
}
}
// Lose a life
function loseLife() {
lives--;
livesTxt.setText('Lives: ' + lives);
// Visual feedback
LK.effects.flashScreen(0xFF0000, 500);
// Check for game over
if (lives <= 0) {
gameOver();
}
}
// Game over
function gameOver() {
gameRunning = false;
LK.setScore(score);
LK.showGameOver();
}
// Mouse/touch down event
game.down = function (x, y) {
if (!gameRunning) return;
cursorX = x;
cursorY = y;
// Check if we hit an enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Simple distance check (could be replaced with intersects())
var dx = enemy.x - x;
var dy = enemy.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50) {
// Instead of holding, destroy enemy immediately
enemy.isDying = true;
updateScore(enemy.value);
enemy.destroy();
// Remove from array
enemies.splice(i, 1);
LK.getSound('catch').play();
break;
}
}
};
// Mouse/touch move event
game.move = function (x, y) {
cursorX = x;
cursorY = y;
};
// Mouse/touch up event is no longer needed for tap-to-kill
game.up = function () {};
// Main game update loop
game.update = function () {
if (!gameRunning) return;
// Spawn enemies
spawnTimer += 16.67; // Approx 1 frame at 60fps
if (spawnTimer >= spawnInterval) {
spawnEnemy();
}
// Check for escaped enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Check if enemy is completely off screen
if (enemy.x < -100 || enemy.x > 2148 || enemy.y < -100 || enemy.y > 2832) {
// Remove enemy and lose a life
enemy.parent.removeChild(enemy);
enemies.splice(i, 1);
loseLife();
continue;
}
// Update enemy
enemy.update(cursorX, cursorY);
}
// We no longer need to handle held enemies as they are destroyed immediately on tap
// Keeping empty block to maintain line identifiers
};
// Start game music
LK.playMusic('gameBgm', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function (speed, lifetime) {
var self = Container.call(this);
// Create eye white
var eyeWhite = self.attachAsset('eye', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
// Create pupil
var pupil = self.attachAsset('pupil', {
anchorX: 0.5,
anchorY: 0.5
});
// Create hit indicator (initially invisible)
var hitCircle = self.attachAsset('hitCircle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Set properties
self.speed = speed || 1;
self.vx = (Math.random() - 0.5) * self.speed * 3;
self.vy = (Math.random() - 0.5) * self.speed * 3;
self.targetTime = lifetime || 2000; // Time needed to hold to destroy (in ms)
self.holdTime = 0; // Current time being held
self.isHeld = false;
self.isDying = false;
self.value = Math.floor(self.targetTime / 500); // Score value based on difficulty
// Looking animation
self.lookTimer = 0;
self.lookInterval = Math.random() * 1000 + 500;
self.lookAt = function (x, y) {
// Calculate angle to look at cursor
var dx = x - self.x;
var dy = y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Limit pupil movement within eye
var maxDistance = eyeWhite.width * 0.3;
var scale = distance > maxDistance ? maxDistance / distance : 1;
// Move pupil
pupil.x = dx * scale * 0.3;
pupil.y = dy * scale * 0.3;
};
// Hold and destroy functionality
self.hold = function (deltaTime) {
if (self.isDying) return;
if (!self.isHeld) {
self.isHeld = true;
hitCircle.alpha = 0.7;
hitCircle.scaleX = 0;
hitCircle.scaleY = 0;
tween(hitCircle, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
self.holdTime += deltaTime;
// Update visual feedback
var progress = Math.min(self.holdTime / self.targetTime, 1);
hitCircle.alpha = 0.3 + progress * 0.7;
eyeWhite.scaleX = eyeWhite.scaleY = 1 + progress * 0.2;
// Check if enemy is destroyed
if (self.holdTime >= self.targetTime) {
self.destroy();
return true;
}
return false;
};
self.release = function () {
if (self.isHeld && !self.isDying) {
self.isHeld = false;
// Shrink hit circle
tween(hitCircle, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeIn
});
// Shrink eye back to normal
tween(eyeWhite, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Decay hold time when released
self.holdTime = Math.max(0, self.holdTime - 500);
}
};
self.destroy = function () {
if (self.isDying) return;
self.isDying = true;
LK.getSound('destroy').play();
// Play destroy animation
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from parent after animation
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.update = function (cursorX, cursorY) {
if (self.isDying) return;
// Move the enemy
self.x += self.vx;
self.y += self.vy;
// Bounce off edges
if (self.x < 50 || self.x > 2048 - 50) {
self.vx *= -1;
}
if (self.y < 50 || self.y > 2732 - 50) {
self.vy *= -1;
}
// Look at cursor if provided
if (cursorX !== undefined && cursorY !== undefined) {
self.lookAt(cursorX, cursorY);
} else {
// Random eye movement when cursor not provided
self.lookTimer += 16.67; // Approx 1 frame at 60fps
if (self.lookTimer > self.lookInterval) {
pupil.x = (Math.random() - 0.5) * 15;
pupil.y = (Math.random() - 0.5) * 15;
self.lookTimer = 0;
self.lookInterval = Math.random() * 1000 + 500;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111
});
/****
* Game Code
****/
// Background
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0
}));
// Game variables
var enemies = [];
var score = 0;
var level = 1;
var lives = 3;
var spawnTimer = 0;
var spawnInterval = 3000; // Initial spawn interval
var minSpawnInterval = 500; // Minimum spawn interval at highest difficulty
var maxEnemies = 5 + level * 2; // Max enemies on screen
var heldEnemy = null; // Kept for compatibility but no longer used
var cursorX = 1024;
var cursorY = 1366;
var gameRunning = true;
// Initialize score text
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 40;
LK.gui.top.addChild(scoreTxt);
// Initialize level text
var levelTxt = new Text2('Level: 1', {
size: 50,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 50;
levelTxt.y = 40;
LK.gui.topRight.addChild(levelTxt);
// Initialize lives text
var livesTxt = new Text2('Lives: 3', {
size: 50,
fill: 0xFFFFFF
});
livesTxt.anchor.set(1, 0);
livesTxt.x = -50;
livesTxt.y = 40;
LK.gui.topRight.addChild(livesTxt);
// Helper function to spawn an enemy
function spawnEnemy() {
if (enemies.length >= maxEnemies || !gameRunning) return;
// Create enemy with speed and lifetime based on level
var enemySpeed = 1 + level * 0.3;
var lifetime = 2000 - level * 100;
lifetime = Math.max(lifetime, 800); // Minimum 800ms to destroy
var enemy = new Enemy(enemySpeed, lifetime);
// Position randomly, keeping away from edges
enemy.x = Math.random() * (2048 - 200) + 100;
enemy.y = Math.random() * (2732 - 200) + 100;
// Add to game and array
game.addChild(enemy);
enemies.push(enemy);
// Reset spawn timer
spawnTimer = 0;
}
// Increase difficulty level
function levelUp() {
level++;
// Increase difficulty parameters
maxEnemies = 5 + level * 2;
spawnInterval = Math.max(minSpawnInterval, 3000 - level * 200);
// Update level display
levelTxt.setText('Level: ' + level);
// Play level up sound
LK.getSound('levelup').play();
// Visual feedback
LK.effects.flashScreen(0x6666FF, 500);
}
// Check for level up condition
function checkLevelUp() {
// Level up every 10 score points
if (score > 0 && score % 10 === 0) {
levelUp();
}
}
// Update score
function updateScore(points) {
var oldScore = score;
score += points;
scoreTxt.setText('Score: ' + score);
// Check if we should level up
if (Math.floor(score / 10) > Math.floor(oldScore / 10)) {
levelUp();
}
}
// Lose a life
function loseLife() {
lives--;
livesTxt.setText('Lives: ' + lives);
// Visual feedback
LK.effects.flashScreen(0xFF0000, 500);
// Check for game over
if (lives <= 0) {
gameOver();
}
}
// Game over
function gameOver() {
gameRunning = false;
LK.setScore(score);
LK.showGameOver();
}
// Mouse/touch down event
game.down = function (x, y) {
if (!gameRunning) return;
cursorX = x;
cursorY = y;
// Check if we hit an enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Simple distance check (could be replaced with intersects())
var dx = enemy.x - x;
var dy = enemy.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50) {
// Instead of holding, destroy enemy immediately
enemy.isDying = true;
updateScore(enemy.value);
enemy.destroy();
// Remove from array
enemies.splice(i, 1);
LK.getSound('catch').play();
break;
}
}
};
// Mouse/touch move event
game.move = function (x, y) {
cursorX = x;
cursorY = y;
};
// Mouse/touch up event is no longer needed for tap-to-kill
game.up = function () {};
// Main game update loop
game.update = function () {
if (!gameRunning) return;
// Spawn enemies
spawnTimer += 16.67; // Approx 1 frame at 60fps
if (spawnTimer >= spawnInterval) {
spawnEnemy();
}
// Check for escaped enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Check if enemy is completely off screen
if (enemy.x < -100 || enemy.x > 2148 || enemy.y < -100 || enemy.y > 2832) {
// Remove enemy and lose a life
enemy.parent.removeChild(enemy);
enemies.splice(i, 1);
loseLife();
continue;
}
// Update enemy
enemy.update(cursorX, cursorY);
}
// We no longer need to handle held enemies as they are destroyed immediately on tap
// Keeping empty block to maintain line identifiers
};
// Start game music
LK.playMusic('gameBgm', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});