/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
highScore: 0
});
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
sprite.tint = 0x00ffff; // Cyan for player bullets
self.speed = 15;
self.direction = {
x: 0,
y: -1
}; // Default moving upward
self.isEnemyBullet = false;
self.lastPosition = {
x: 0,
y: 0
};
self.setDirection = function (dirX, dirY) {
var length = Math.sqrt(dirX * dirX + dirY * dirY);
if (length > 0) {
self.direction.x = dirX / length;
self.direction.y = dirY / length;
}
};
self.setEnemyBullet = function (isEnemy) {
self.isEnemyBullet = isEnemy;
if (isEnemy) {
sprite.tint = 0xff0000; // Red for enemy bullets
}
};
self.update = function () {
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Apply time slowing effect - slow to 0.25x speed when time is frozen
var actualSpeed = timeIsFrozen ? self.speed * 0.25 : self.speed;
self.x += self.direction.x * actualSpeed;
self.y += self.direction.y * actualSpeed;
// Remove if out of bounds
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.shouldRemove = true;
}
};
return self;
});
var Collectible = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('collectible', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.type = "standard"; // can be "standard" or "timeRecharge"
self.setValue = function (value) {
self.value = value;
};
self.setType = function (type) {
self.type = type;
if (type === "timeRecharge") {
sprite.tint = 0x3498db; // Blue for time recharge
} else if (type === "shield") {
sprite.tint = 0xE74C3C; // Red for shield power-up
}
};
self.update = function () {
// Animate collectible
if (!timeIsFrozen) {
sprite.rotation += 0.02;
}
};
return self;
});
var DirectionalButton = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('pauseBarBackground', {
anchorX: 0.5,
anchorY: 0.5
});
sprite.width = 150;
sprite.height = 150;
sprite.alpha = 0.5;
var arrow = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
arrow.tint = 0xFFFFFF;
self.direction = {
x: 0,
y: 0
};
self.isPressed = false;
self.setDirection = function (dirX, dirY) {
self.direction.x = dirX;
self.direction.y = dirY;
// Rotate arrow based on direction
if (dirX === 1) {
arrow.rotation = Math.PI / 2;
} // Right
else if (dirX === -1) {
arrow.rotation = -Math.PI / 2;
} // Left
else if (dirY === 1) {
arrow.rotation = Math.PI;
} // Down
else if (dirY === -1) {
arrow.rotation = 0;
} // Up
};
self.down = function (x, y, obj) {
self.isPressed = true;
sprite.alpha = 0.8;
};
self.up = function (x, y, obj) {
self.isPressed = false;
sprite.alpha = 0.5;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.target = null;
self.movementPattern = "chase"; // can be "chase", "patrol", or "random"
self.patrolPoints = [];
self.currentPatrolIndex = 0;
self.randomMoveCounter = 0;
self.setTarget = function (target) {
self.target = target;
};
self.setPatrolPoints = function (points) {
self.patrolPoints = points;
};
self.setMovementPattern = function (pattern) {
self.movementPattern = pattern;
};
self.health = 3; // Enemy health
self.damaged = false;
self.damagedTimer = 0;
self.damagedMoveSpeed = 20;
self.damagedDirection = {
x: 0,
y: 0
};
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
return true; // Enemy is dead
}
// Set damaged state
self.damaged = true;
self.damagedTimer = 20; // Frames to stay in damaged state
// Set random direction to move when damaged
var angle = Math.random() * Math.PI * 2;
self.damagedDirection = {
x: Math.cos(angle),
y: Math.sin(angle)
};
// Flash enemy red
sprite.tint = 0xFF0000;
LK.setTimeout(function () {
sprite.tint = 0xFFFFFF;
}, 200);
return false; // Enemy still alive
};
self.update = function () {
// Process damaged movement first
if (self.damaged) {
self.x += self.damagedDirection.x * self.damagedMoveSpeed;
self.y += self.damagedDirection.y * self.damagedMoveSpeed;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.damagedDirection.x *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.damagedDirection.y *= -1;
}
// Keep in bounds
if (self.x < sprite.width / 2) {
self.x = sprite.width / 2;
}
if (self.x > 2048 - sprite.width / 2) {
self.x = 2048 - sprite.width / 2;
}
if (self.y < sprite.height / 2) {
self.y = sprite.height / 2;
}
if (self.y > 2732 - sprite.height / 2) {
self.y = 2732 - sprite.height / 2;
}
self.damagedTimer--;
if (self.damagedTimer <= 0) {
self.damaged = false;
}
return;
}
// Normal movement - now works during time freeze but at reduced speed
var actualSpeed = timeIsFrozen ? self.speed * 0.25 : self.speed;
if (self.movementPattern === "chase" && self.target) {
// Chase player
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 * actualSpeed;
self.y += dy / dist * actualSpeed;
}
} else if (self.movementPattern === "patrol" && self.patrolPoints.length > 0) {
// Patrol between points
var targetPoint = self.patrolPoints[self.currentPatrolIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) {
// Reached the point, move to next patrol point
self.currentPatrolIndex = (self.currentPatrolIndex + 1) % self.patrolPoints.length;
} else {
self.x += dx / dist * actualSpeed;
self.y += dy / dist * actualSpeed;
}
} else if (self.movementPattern === "random") {
// Random movement
self.randomMoveCounter--;
if (self.randomMoveCounter <= 0) {
self.vx = Math.random() * actualSpeed * 2 - actualSpeed;
self.vy = Math.random() * actualSpeed * 2 - actualSpeed;
self.randomMoveCounter = Math.floor(Math.random() * 60) + 30;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.vx *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.vy *= -1;
}
}
};
return self;
});
// Joystick class removed in favor of directional buttons
var LevelEnd = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('levelEnd', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Animate the level end gate
if (!timeIsFrozen) {
sprite.rotation += 0.01;
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.update = function () {
if (!timeIsFrozen) {
self.x += self.vx;
self.y += self.vy;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.vx *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.vy *= -1;
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.isDragging = false;
self.isInvulnerable = false;
self.lastPosition = {
x: 0,
y: 0
};
self.setDragging = function (isDragging) {
self.isDragging = isDragging;
};
self.move = function (x, y) {
if (self.isDragging && !timeIsFrozen) {
self.x = x;
self.y = y;
}
};
self.update = function () {
// Store last position
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Process movement from directional buttons
if (directionButtons) {
var moveX = 0;
var moveY = 0;
// Calculate movement based on all pressed buttons
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
if (button.isPressed) {
moveX += button.direction.x;
moveY += button.direction.y;
}
}
// Normalize diagonal movement
if (moveX !== 0 && moveY !== 0) {
var length = Math.sqrt(moveX * moveX + moveY * moveY);
moveX /= length;
moveY /= length;
}
// Apply movement with or without time slowing
var speed = timeIsFrozen ? self.speed * 0.7 : self.speed;
self.x += moveX * speed;
self.y += moveY * speed;
}
// Constrain player to game boundaries
if (self.x < sprite.width / 2) {
self.x = sprite.width / 2;
}
if (self.x > 2048 - sprite.width / 2) {
self.x = 2048 - sprite.width / 2;
}
if (self.y < sprite.height / 2) {
self.y = sprite.height / 2;
}
if (self.y > 2732 - sprite.height / 2) {
self.y = 2732 - sprite.height / 2;
}
// Update shield effect if it exists
if (self.shieldEffect) {
self.shieldEffect.update();
}
};
return self;
});
var ShieldEffect = Container.expand(function () {
var self = Container.call(this);
// Create shield circle
var shield = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: 0.6
});
// Set initial shield properties
shield.tint = 0x3498db; // Blue shield
self.update = function () {
// Rotate shield for visual effect
shield.rotation += 0.02;
// Pulsing effect
if (self.pulseDirection) {
shield.alpha += 0.01;
if (shield.alpha >= 0.7) {
self.pulseDirection = false;
}
} else {
shield.alpha -= 0.01;
if (shield.alpha <= 0.3) {
self.pulseDirection = true;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x34495e
});
/****
* Game Code
****/
// Game state variables
var currentLevel = storage.level || 1;
var score = 0;
var timeIsFrozen = false;
var pauseEnergy = 100;
var pauseEnergyMax = 100;
var pauseEnergyDrainRate = 0.5;
var levelComplete = false;
var player;
var obstacles = [];
var enemies = [];
var collectibles = [];
var bullets = [];
var enemyBullets = [];
var levelEnd;
var freezeEffect;
var directionButtons;
var shootingTimer = 0;
var enemyShootingTimer = {};
// UI elements
var scoreTxt;
var levelTxt;
var pauseBarBg;
var pauseBar;
var instructionsTxt;
function initGame() {
// Clear existing objects if any
obstacles = [];
enemies = [];
collectibles = [];
bullets = [];
enemyBullets = [];
// Reset game state
pauseEnergy = pauseEnergyMax;
timeIsFrozen = false;
levelComplete = false;
score = LK.getScore();
shootingTimer = 0;
enemyShootingTimer = {};
// Create UI
createUI();
// Create player
player = new Player();
player.x = 1024; // Center of screen
player.y = 2200; // Near bottom
game.addChild(player);
// Apply shield effect for 1.5 seconds
player.isInvulnerable = true;
// Create and add shield effect
var shieldEffect = new ShieldEffect();
shieldEffect.x = 0;
shieldEffect.y = 0;
player.addChild(shieldEffect);
player.shieldEffect = shieldEffect;
// Create pulse effect with tween
tween(player, {
alpha: 0.9
}, {
duration: 750,
easing: tween.sinceOut,
repeat: true,
yoyo: true
});
// Remove shield effect after 1.5 seconds
LK.setTimeout(function () {
player.isInvulnerable = false;
player.removeChild(shieldEffect);
player.shieldEffect = null;
tween(player, {
alpha: 1.0
}, {
duration: 300
});
}, 1500);
// Create freeze effect (initially invisible)
freezeEffect = LK.getAsset('freezeEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
freezeEffect.x = 2048 / 2;
freezeEffect.y = 2732 / 2;
game.addChild(freezeEffect);
// Create directional buttons
var buttonSize = 300;
var buttonSpacing = 160;
var baseX = 300;
var baseY = 2500;
// Create up, down, left, right buttons
var upButton = new DirectionalButton();
upButton.x = baseX;
upButton.y = baseY - buttonSpacing;
upButton.setDirection(0, -1);
game.addChild(upButton);
var downButton = new DirectionalButton();
downButton.x = baseX;
downButton.y = baseY + buttonSpacing;
downButton.setDirection(0, 1);
game.addChild(downButton);
var leftButton = new DirectionalButton();
leftButton.x = baseX - buttonSpacing;
leftButton.y = baseY;
leftButton.setDirection(-1, 0);
game.addChild(leftButton);
var rightButton = new DirectionalButton();
rightButton.x = baseX + buttonSpacing;
rightButton.y = baseY;
rightButton.setDirection(1, 0);
game.addChild(rightButton);
// Create button references array
directionButtons = [upButton, downButton, leftButton, rightButton];
// Create level based on current level
createLevel(currentLevel);
// Start background music
LK.playMusic('bgmusic');
}
function createUI() {
// Score text
scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120;
scoreTxt.y = 30;
LK.gui.addChild(scoreTxt);
// Level text
levelTxt = new Text2('Level: ' + currentLevel, {
size: 70,
fill: 0xFFFFFF
});
levelTxt.anchor.set(1, 0);
levelTxt.x = 2048 - 120;
levelTxt.y = 30;
LK.gui.addChild(levelTxt);
// Pause energy bar background
pauseBarBg = LK.getAsset('pauseBarBackground', {
anchorX: 0.5,
anchorY: 0.5
});
pauseBarBg.x = 2048 / 2;
pauseBarBg.y = 100;
LK.gui.addChild(pauseBarBg);
// Pause energy bar
pauseBar = LK.getAsset('pauseBar', {
anchorX: 0,
anchorY: 0.5
});
pauseBar.x = pauseBarBg.x - pauseBarBg.width / 2;
pauseBar.y = pauseBarBg.y;
pauseBar.width = pauseBarBg.width * (pauseEnergy / pauseEnergyMax);
LK.gui.addChild(pauseBar);
// Instructions
instructionsTxt = new Text2('Use directional buttons to move. Tap screen to slow time (enemies move at 0.25x speed).\nShoot automatically every 1.5 seconds. Enemies shoot every 2 seconds.\nDamage enemies to make them move. Defeat enemies to spawn new ones.', {
size: 40,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0);
instructionsTxt.x = 2048 / 2;
instructionsTxt.y = 150;
LK.gui.addChild(instructionsTxt);
// Auto-hide instructions after 5 seconds
LK.setTimeout(function () {
tween(instructionsTxt, {
alpha: 0
}, {
duration: 1000
});
}, 5000);
}
function createLevel(level) {
// Each level has a different configuration
switch (level) {
case 1:
// Level 1: Simple introduction
createObstacle(600, 500, 3, 0);
createObstacle(1200, 800, -2, 2);
createObstacle(1600, 1200, 0, 4);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500, "timeRecharge");
createCollectible(900, 1200, "shield");
// Make level end harder to reach by adding guards
createObstacle(1600, 2200, 3, 1);
createObstacle(2000, 2300, -2, 2);
// Add enemy guarding level end
var guardEnemy = createEnemy(1700, 2200, "patrol");
guardEnemy.setPatrolPoints([{
x: 1600,
y: 2200
}, {
x: 2000,
y: 2200
}, {
x: 2000,
y: 2500
}, {
x: 1600,
y: 2500
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
case 2:
// Level 2: Introduce enemies
createObstacle(600, 500, 4, 0);
createObstacle(1200, 800, -3, 3);
createObstacle(1600, 1200, 0, 5);
createObstacle(900, 1600, 4, -2);
var enemy = createEnemy(1000, 1000, "chase");
enemy.setTarget(player);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500);
createCollectible(1200, 2200, "timeRecharge");
// Make level end harder to reach by adding more guards
createObstacle(1600, 2200, 4, 2);
createObstacle(2000, 2300, -3, 3);
createObstacle(1800, 2100, 2, -2);
// Add multiple enemies guarding level end
var guardEnemy1 = createEnemy(1700, 2200, "chase");
guardEnemy1.setTarget(player);
var guardEnemy2 = createEnemy(1900, 2300, "patrol");
guardEnemy2.setPatrolPoints([{
x: 1700,
y: 2200
}, {
x: 1900,
y: 2200
}, {
x: 1900,
y: 2500
}, {
x: 1700,
y: 2500
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
case 3:
// Level 3: More complex with patrol enemies
createObstacle(600, 500, 5, 0);
createObstacle(1200, 800, -4, 3);
createObstacle(1600, 1200, 0, 6);
createObstacle(900, 1600, 5, -3);
createObstacle(1400, 2000, -3, -3);
var enemy1 = createEnemy(1000, 1000, "chase");
enemy1.setTarget(player);
var enemy2 = createEnemy(1500, 1500, "patrol");
enemy2.setPatrolPoints([{
x: 1500,
y: 1500
}, {
x: 1500,
y: 2000
}, {
x: 1000,
y: 2000
}, {
x: 1000,
y: 1500
}]);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500);
createCollectible(1200, 2200);
createCollectible(1800, 1300, "timeRecharge");
// Add a ring of obstacles around the level end
var obstacleCount = 8;
var radius = 250;
for (var i = 0; i < obstacleCount; i++) {
var angle = i / obstacleCount * Math.PI * 2;
var obsX = 1800 + Math.cos(angle) * radius;
var obsY = 2400 + Math.sin(angle) * radius;
var vx = Math.cos(angle + Math.PI / 2) * 3;
var vy = Math.sin(angle + Math.PI / 2) * 3;
createObstacle(obsX, obsY, vx, vy);
}
// Add multiple enemies guarding level end
var guardEnemy1 = createEnemy(1700, 2200, "chase");
guardEnemy1.setTarget(player);
var guardEnemy2 = createEnemy(1900, 2300, "chase");
guardEnemy2.setTarget(player);
var guardEnemy3 = createEnemy(1800, 2100, "patrol");
guardEnemy3.setPatrolPoints([{
x: 1600,
y: 2100
}, {
x: 2000,
y: 2100
}, {
x: 2000,
y: 2600
}, {
x: 1600,
y: 2600
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
default:
// Higher levels: Procedurally generate increasingly difficult layouts
var obstacleCount = 3 + Math.min(level, 7);
var enemyCount = 1 + Math.min(level - 2, 5);
var collectibleCount = 4 + Math.min(level, 6);
// Create obstacles
for (var i = 0; i < obstacleCount; i++) {
var x = 400 + Math.random() * 1600;
var y = 400 + Math.random() * 1800;
var vx = (Math.random() * 6 + 2) * (Math.random() > 0.5 ? 1 : -1);
var vy = (Math.random() * 6 + 2) * (Math.random() > 0.5 ? 1 : -1);
createObstacle(x, y, vx, vy);
}
// Create enemies
for (var i = 0; i < enemyCount; i++) {
var x = 400 + Math.random() * 1600;
var y = 400 + Math.random() * 1800;
var patternType = Math.random();
if (patternType < 0.4) {
var enemy = createEnemy(x, y, "chase");
enemy.setTarget(player);
} else if (patternType < 0.7) {
var enemy = createEnemy(x, y, "patrol");
var patrolRadius = 300 + Math.random() * 300;
var patrolPoints = [];
var patrolPointCount = 3 + Math.floor(Math.random() * 3);
for (var j = 0; j < patrolPointCount; j++) {
var angle = j / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: x + Math.cos(angle) * patrolRadius,
y: y + Math.sin(angle) * patrolRadius
});
}
enemy.setPatrolPoints(patrolPoints);
} else {
createEnemy(x, y, "random");
}
}
// Create collectibles
for (var i = 0; i < collectibleCount; i++) {
var x = 300 + Math.random() * 1700;
var y = 300 + Math.random() * 2000;
if (i === collectibleCount - 1) {
createCollectible(x, y, "timeRecharge");
} else {
createCollectible(x, y);
}
}
// Add a complex ring of obstacles and enemies around the level end
var levelEndX = 1700 + Math.random() * 200;
var levelEndY = 2200 + Math.random() * 300;
// Add a ring of obstacles around the level end
var obstacleCount = 8 + Math.min(level, 4);
var radius = 200 + Math.min(level * 10, 100);
for (var i = 0; i < obstacleCount; i++) {
var angle = i / obstacleCount * Math.PI * 2;
var obsX = levelEndX + Math.cos(angle) * radius;
var obsY = levelEndY + Math.sin(angle) * radius;
var vx = Math.cos(angle + Math.PI / 2) * (2 + Math.min(level * 0.5, 4));
var vy = Math.sin(angle + Math.PI / 2) * (2 + Math.min(level * 0.5, 4));
createObstacle(obsX, obsY, vx, vy);
}
// Add guardian enemies based on level
var guardCount = Math.min(level, 5);
for (var i = 0; i < guardCount; i++) {
var angle = i / guardCount * Math.PI * 2;
var enemyX = levelEndX + Math.cos(angle) * (radius - 50);
var enemyY = levelEndY + Math.sin(angle) * (radius - 50);
if (i % 3 === 0) {
var enemy = createEnemy(enemyX, enemyY, "chase");
enemy.setTarget(player);
} else if (i % 3 === 1) {
var enemy = createEnemy(enemyX, enemyY, "patrol");
var patrolRadius = radius * 0.6;
var patrolPoints = [];
var patrolPointCount = 4;
for (var j = 0; j < patrolPointCount; j++) {
var patrolAngle = j / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: levelEndX + Math.cos(patrolAngle) * patrolRadius,
y: levelEndY + Math.sin(patrolAngle) * patrolRadius
});
}
enemy.setPatrolPoints(patrolPoints);
} else {
createEnemy(enemyX, enemyY, "random");
}
}
// Level end
levelEnd = new LevelEnd();
levelEnd.x = levelEndX;
levelEnd.y = levelEndY;
game.addChild(levelEnd);
break;
}
}
function createObstacle(x, y, vx, vy) {
var obstacle = new Obstacle();
obstacle.x = x;
obstacle.y = y;
obstacle.vx = vx;
obstacle.vy = vy;
obstacles.push(obstacle);
game.addChild(obstacle);
return obstacle;
}
function createEnemy(x, y, movementPattern) {
var enemy = new Enemy();
enemy.x = x;
enemy.y = y;
enemy.setMovementPattern(movementPattern);
enemies.push(enemy);
game.addChild(enemy);
return enemy;
}
function createCollectible(x, y, type) {
var collectible = new Collectible();
collectible.x = x;
collectible.y = y;
if (type === "timeRecharge") {
collectible.setType("timeRecharge");
} else if (type === "shield") {
collectible.setType("shield");
collectible.setValue(5); // Shield worth more points
var sprite = collectible.getChildAt(0);
if (sprite) {
sprite.tint = 0xE74C3C; // Red for shield power-up
}
}
collectibles.push(collectible);
game.addChild(collectible);
return collectible;
}
function createPlayerBullet() {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y - 30;
bullet.setDirection(0, -1); // Shoot upward
bullets.push(bullet);
game.addChild(bullet);
return bullet;
}
function createEnemyBullet(enemy) {
var bullet = new Bullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 30;
// Direction toward player
var dx = player.x - enemy.x;
var dy = player.y - enemy.y;
bullet.setDirection(dx, dy);
bullet.setEnemyBullet(true);
enemyBullets.push(bullet);
game.addChild(bullet);
return bullet;
}
function toggleFreezeTime() {
if (pauseEnergy <= 0 && timeIsFrozen) {
// Can't stay frozen with no energy
unfreezeTime();
return;
}
if (!timeIsFrozen && pauseEnergy > 0) {
// Slow time (not completely freeze)
timeIsFrozen = true;
tween(freezeEffect, {
alpha: 0.2
}, {
duration: 300
});
LK.getSound('freeze').play();
} else if (timeIsFrozen) {
// Unfreeze time
unfreezeTime();
}
}
function unfreezeTime() {
timeIsFrozen = false;
tween(freezeEffect, {
alpha: 0
}, {
duration: 300
});
LK.getSound('unfreeze').play();
}
function updatePauseEnergyBar() {
pauseBar.width = pauseBarBg.width * (pauseEnergy / pauseEnergyMax);
}
function checkCollisions() {
// Check player-obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
if (player.intersects(obstacles[i]) && !player.isInvulnerable) {
handlePlayerHit();
return;
}
}
// Check player-enemy collisions
for (var i = 0; i < enemies.length; i++) {
if (player.intersects(enemies[i]) && !player.isInvulnerable) {
handlePlayerHit();
return;
}
}
// Check player-collectible collisions
for (var i = collectibles.length - 1; i >= 0; i--) {
if (player.intersects(collectibles[i])) {
// Collect the item
if (collectibles[i].type === "timeRecharge") {
pauseEnergy = Math.min(pauseEnergyMax, pauseEnergy + 50);
updatePauseEnergyBar();
} else if (collectibles[i].type === "shield") {
// Apply shield effect
player.isInvulnerable = true;
if (!player.shieldEffect) {
// Create new shield effect
var shieldEffect = new ShieldEffect();
shieldEffect.x = 0;
shieldEffect.y = 0;
player.addChild(shieldEffect);
player.shieldEffect = shieldEffect;
// Flash player
LK.effects.flashObject(player, 0xE74C3C, 300);
// Remove shield after 5 seconds
LK.setTimeout(function () {
if (player.shieldEffect) {
player.removeChild(player.shieldEffect);
player.shieldEffect = null;
player.isInvulnerable = false;
}
}, 5000);
}
score += collectibles[i].value;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
} else {
score += collectibles[i].value;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
}
LK.getSound('collect').play();
collectibles[i].destroy();
collectibles.splice(i, 1);
}
}
// Check if player reached the level end
if (levelEnd && player.intersects(levelEnd)) {
completeLevel();
}
}
function handlePlayerHit() {
if (!timeIsFrozen) {
// Flash the screen red
LK.effects.flashScreen(0xff0000, 500);
LK.getSound('hit').play();
// Show game over
LK.showGameOver();
}
}
function completeLevel() {
if (!levelComplete) {
levelComplete = true;
// Save progress
currentLevel++;
storage.level = currentLevel;
// Save high score
if (score > storage.highScore) {
storage.highScore = score;
}
// Play win sound
LK.getSound('win').play();
// Show level complete
LK.showYouWin();
}
}
// Game event handlers
game.down = function (x, y, obj) {
// Check if any directional button was clicked
var buttonPressed = false;
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 75) {
// Button radius
button.down(x, y, obj);
buttonPressed = true;
break;
}
}
}
if (!buttonPressed) {
// If no button was pressed, handle time slowing
if (!timeIsFrozen && pauseEnergy > 0) {
// Slow time when clicking anywhere else (if we have energy)
toggleFreezeTime();
} else if (timeIsFrozen) {
// Unfreeze time when clicking anywhere else if already frozen
toggleFreezeTime();
}
}
};
game.up = function (x, y, obj) {
// Release all directional buttons
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 75) {
button.up(x, y, obj);
}
}
}
};
game.move = function (x, y, obj) {
// Handle touch/mouse movement for buttons
// Detect if we've moved over or out of any buttons
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If finger/pointer moved outside a pressed button, release it
if (button.isPressed && distance > 75) {
button.up(x, y, obj);
}
}
}
};
// Game update loop
game.update = function () {
// Check if game is initialized
if (!player) {
initGame();
return;
}
// Drain pause energy when time is frozen
if (timeIsFrozen) {
pauseEnergy = Math.max(0, pauseEnergy - pauseEnergyDrainRate);
updatePauseEnergyBar();
// Automatically unfreeze when energy is depleted
if (pauseEnergy <= 0) {
unfreezeTime();
}
} else if (pauseEnergy < pauseEnergyMax) {
// Slowly recharge when not frozen
pauseEnergy = Math.min(pauseEnergyMax, pauseEnergy + 0.1);
updatePauseEnergyBar();
}
// Handle player shooting (every 1.5 seconds)
shootingTimer++;
var shootingInterval = timeIsFrozen ? 90 * 1.5 : 90; // 90 ticks = 1.5 seconds, slower when time is frozen
if (shootingTimer >= shootingInterval) {
createPlayerBullet();
shootingTimer = 0;
}
// Handle enemy shooting (every 2 seconds)
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (!enemyShootingTimer[i]) {
enemyShootingTimer[i] = 0;
}
enemyShootingTimer[i]++;
var enemyShootInterval = timeIsFrozen ? 120 * 1.5 : 120; // 120 ticks = 2 seconds, slower when time is frozen
if (enemyShootingTimer[i] >= enemyShootInterval) {
createEnemyBullet(enemy);
enemyShootingTimer[i] = 0;
}
}
// Update game objects
player.update();
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
if (bullets[i].shouldRemove) {
bullets[i].destroy();
bullets.splice(i, 1);
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].update();
if (enemyBullets[i].shouldRemove) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].update();
}
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
for (var i = 0; i < collectibles.length; i++) {
collectibles[i].update();
}
if (levelEnd) {
levelEnd.update();
}
// Check for collisions
checkCollisions();
// Check bullet collisions with enemies
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullets[i] && bullets[i].intersects(enemies[j])) {
// Enemy hit by player bullet
var enemyDead = enemies[j].takeDamage();
if (enemyDead) {
// Spawn a new enemy at a random position when one is destroyed
var spawnX = Math.random() < 0.5 ? 200 + Math.random() * 200 : 1848 - Math.random() * 200;
var spawnY = Math.random() < 0.5 ? 200 + Math.random() * 200 : 2532 - Math.random() * 200;
// Choose a random movement pattern with increased difficulty
var patterns = ["chase", "patrol", "random"];
var patternIndex = Math.floor(Math.random() * patterns.length);
var newEnemy = createEnemy(spawnX, spawnY, patterns[patternIndex]);
// Make new enemies faster and more challenging
newEnemy.speed = enemies[j].speed + 0.5;
if (patterns[patternIndex] === "chase") {
newEnemy.setTarget(player);
} else if (patterns[patternIndex] === "patrol") {
var patrolRadius = 300 + Math.random() * 300;
var patrolPoints = [];
var patrolPointCount = 3 + Math.floor(Math.random() * 3);
for (var k = 0; k < patrolPointCount; k++) {
var angle = k / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: spawnX + Math.cos(angle) * patrolRadius,
y: spawnY + Math.sin(angle) * patrolRadius
});
}
newEnemy.setPatrolPoints(patrolPoints);
}
// Destroy the old enemy
enemies[j].destroy();
enemies.splice(j, 1);
// Update score
score += 10;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
}
// Always destroy the bullet that hit
bullets[i].destroy();
bullets.splice(i, 1);
break;
}
}
}
// Check enemy bullet collisions with player
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i] && player.intersects(enemyBullets[i])) {
// Player hit by enemy bullet
if (!timeIsFrozen && !player.isInvulnerable) {
handlePlayerHit();
} else {
// Just destroy the bullet if time is frozen or player is invulnerable
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
highScore: 0
});
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
sprite.tint = 0x00ffff; // Cyan for player bullets
self.speed = 15;
self.direction = {
x: 0,
y: -1
}; // Default moving upward
self.isEnemyBullet = false;
self.lastPosition = {
x: 0,
y: 0
};
self.setDirection = function (dirX, dirY) {
var length = Math.sqrt(dirX * dirX + dirY * dirY);
if (length > 0) {
self.direction.x = dirX / length;
self.direction.y = dirY / length;
}
};
self.setEnemyBullet = function (isEnemy) {
self.isEnemyBullet = isEnemy;
if (isEnemy) {
sprite.tint = 0xff0000; // Red for enemy bullets
}
};
self.update = function () {
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Apply time slowing effect - slow to 0.25x speed when time is frozen
var actualSpeed = timeIsFrozen ? self.speed * 0.25 : self.speed;
self.x += self.direction.x * actualSpeed;
self.y += self.direction.y * actualSpeed;
// Remove if out of bounds
if (self.x < 0 || self.x > 2048 || self.y < 0 || self.y > 2732) {
self.shouldRemove = true;
}
};
return self;
});
var Collectible = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('collectible', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.type = "standard"; // can be "standard" or "timeRecharge"
self.setValue = function (value) {
self.value = value;
};
self.setType = function (type) {
self.type = type;
if (type === "timeRecharge") {
sprite.tint = 0x3498db; // Blue for time recharge
} else if (type === "shield") {
sprite.tint = 0xE74C3C; // Red for shield power-up
}
};
self.update = function () {
// Animate collectible
if (!timeIsFrozen) {
sprite.rotation += 0.02;
}
};
return self;
});
var DirectionalButton = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('pauseBarBackground', {
anchorX: 0.5,
anchorY: 0.5
});
sprite.width = 150;
sprite.height = 150;
sprite.alpha = 0.5;
var arrow = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
arrow.tint = 0xFFFFFF;
self.direction = {
x: 0,
y: 0
};
self.isPressed = false;
self.setDirection = function (dirX, dirY) {
self.direction.x = dirX;
self.direction.y = dirY;
// Rotate arrow based on direction
if (dirX === 1) {
arrow.rotation = Math.PI / 2;
} // Right
else if (dirX === -1) {
arrow.rotation = -Math.PI / 2;
} // Left
else if (dirY === 1) {
arrow.rotation = Math.PI;
} // Down
else if (dirY === -1) {
arrow.rotation = 0;
} // Up
};
self.down = function (x, y, obj) {
self.isPressed = true;
sprite.alpha = 0.8;
};
self.up = function (x, y, obj) {
self.isPressed = false;
sprite.alpha = 0.5;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.target = null;
self.movementPattern = "chase"; // can be "chase", "patrol", or "random"
self.patrolPoints = [];
self.currentPatrolIndex = 0;
self.randomMoveCounter = 0;
self.setTarget = function (target) {
self.target = target;
};
self.setPatrolPoints = function (points) {
self.patrolPoints = points;
};
self.setMovementPattern = function (pattern) {
self.movementPattern = pattern;
};
self.health = 3; // Enemy health
self.damaged = false;
self.damagedTimer = 0;
self.damagedMoveSpeed = 20;
self.damagedDirection = {
x: 0,
y: 0
};
self.takeDamage = function () {
self.health--;
if (self.health <= 0) {
return true; // Enemy is dead
}
// Set damaged state
self.damaged = true;
self.damagedTimer = 20; // Frames to stay in damaged state
// Set random direction to move when damaged
var angle = Math.random() * Math.PI * 2;
self.damagedDirection = {
x: Math.cos(angle),
y: Math.sin(angle)
};
// Flash enemy red
sprite.tint = 0xFF0000;
LK.setTimeout(function () {
sprite.tint = 0xFFFFFF;
}, 200);
return false; // Enemy still alive
};
self.update = function () {
// Process damaged movement first
if (self.damaged) {
self.x += self.damagedDirection.x * self.damagedMoveSpeed;
self.y += self.damagedDirection.y * self.damagedMoveSpeed;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.damagedDirection.x *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.damagedDirection.y *= -1;
}
// Keep in bounds
if (self.x < sprite.width / 2) {
self.x = sprite.width / 2;
}
if (self.x > 2048 - sprite.width / 2) {
self.x = 2048 - sprite.width / 2;
}
if (self.y < sprite.height / 2) {
self.y = sprite.height / 2;
}
if (self.y > 2732 - sprite.height / 2) {
self.y = 2732 - sprite.height / 2;
}
self.damagedTimer--;
if (self.damagedTimer <= 0) {
self.damaged = false;
}
return;
}
// Normal movement - now works during time freeze but at reduced speed
var actualSpeed = timeIsFrozen ? self.speed * 0.25 : self.speed;
if (self.movementPattern === "chase" && self.target) {
// Chase player
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 * actualSpeed;
self.y += dy / dist * actualSpeed;
}
} else if (self.movementPattern === "patrol" && self.patrolPoints.length > 0) {
// Patrol between points
var targetPoint = self.patrolPoints[self.currentPatrolIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) {
// Reached the point, move to next patrol point
self.currentPatrolIndex = (self.currentPatrolIndex + 1) % self.patrolPoints.length;
} else {
self.x += dx / dist * actualSpeed;
self.y += dy / dist * actualSpeed;
}
} else if (self.movementPattern === "random") {
// Random movement
self.randomMoveCounter--;
if (self.randomMoveCounter <= 0) {
self.vx = Math.random() * actualSpeed * 2 - actualSpeed;
self.vy = Math.random() * actualSpeed * 2 - actualSpeed;
self.randomMoveCounter = Math.floor(Math.random() * 60) + 30;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.vx *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.vy *= -1;
}
}
};
return self;
});
// Joystick class removed in favor of directional buttons
var LevelEnd = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('levelEnd', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Animate the level end gate
if (!timeIsFrozen) {
sprite.rotation += 0.01;
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.update = function () {
if (!timeIsFrozen) {
self.x += self.vx;
self.y += self.vy;
// Bounce off walls
if (self.x <= sprite.width / 2 || self.x >= 2048 - sprite.width / 2) {
self.vx *= -1;
}
if (self.y <= sprite.height / 2 || self.y >= 2732 - sprite.height / 2) {
self.vy *= -1;
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.isDragging = false;
self.isInvulnerable = false;
self.lastPosition = {
x: 0,
y: 0
};
self.setDragging = function (isDragging) {
self.isDragging = isDragging;
};
self.move = function (x, y) {
if (self.isDragging && !timeIsFrozen) {
self.x = x;
self.y = y;
}
};
self.update = function () {
// Store last position
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Process movement from directional buttons
if (directionButtons) {
var moveX = 0;
var moveY = 0;
// Calculate movement based on all pressed buttons
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
if (button.isPressed) {
moveX += button.direction.x;
moveY += button.direction.y;
}
}
// Normalize diagonal movement
if (moveX !== 0 && moveY !== 0) {
var length = Math.sqrt(moveX * moveX + moveY * moveY);
moveX /= length;
moveY /= length;
}
// Apply movement with or without time slowing
var speed = timeIsFrozen ? self.speed * 0.7 : self.speed;
self.x += moveX * speed;
self.y += moveY * speed;
}
// Constrain player to game boundaries
if (self.x < sprite.width / 2) {
self.x = sprite.width / 2;
}
if (self.x > 2048 - sprite.width / 2) {
self.x = 2048 - sprite.width / 2;
}
if (self.y < sprite.height / 2) {
self.y = sprite.height / 2;
}
if (self.y > 2732 - sprite.height / 2) {
self.y = 2732 - sprite.height / 2;
}
// Update shield effect if it exists
if (self.shieldEffect) {
self.shieldEffect.update();
}
};
return self;
});
var ShieldEffect = Container.expand(function () {
var self = Container.call(this);
// Create shield circle
var shield = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: 0.6
});
// Set initial shield properties
shield.tint = 0x3498db; // Blue shield
self.update = function () {
// Rotate shield for visual effect
shield.rotation += 0.02;
// Pulsing effect
if (self.pulseDirection) {
shield.alpha += 0.01;
if (shield.alpha >= 0.7) {
self.pulseDirection = false;
}
} else {
shield.alpha -= 0.01;
if (shield.alpha <= 0.3) {
self.pulseDirection = true;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x34495e
});
/****
* Game Code
****/
// Game state variables
var currentLevel = storage.level || 1;
var score = 0;
var timeIsFrozen = false;
var pauseEnergy = 100;
var pauseEnergyMax = 100;
var pauseEnergyDrainRate = 0.5;
var levelComplete = false;
var player;
var obstacles = [];
var enemies = [];
var collectibles = [];
var bullets = [];
var enemyBullets = [];
var levelEnd;
var freezeEffect;
var directionButtons;
var shootingTimer = 0;
var enemyShootingTimer = {};
// UI elements
var scoreTxt;
var levelTxt;
var pauseBarBg;
var pauseBar;
var instructionsTxt;
function initGame() {
// Clear existing objects if any
obstacles = [];
enemies = [];
collectibles = [];
bullets = [];
enemyBullets = [];
// Reset game state
pauseEnergy = pauseEnergyMax;
timeIsFrozen = false;
levelComplete = false;
score = LK.getScore();
shootingTimer = 0;
enemyShootingTimer = {};
// Create UI
createUI();
// Create player
player = new Player();
player.x = 1024; // Center of screen
player.y = 2200; // Near bottom
game.addChild(player);
// Apply shield effect for 1.5 seconds
player.isInvulnerable = true;
// Create and add shield effect
var shieldEffect = new ShieldEffect();
shieldEffect.x = 0;
shieldEffect.y = 0;
player.addChild(shieldEffect);
player.shieldEffect = shieldEffect;
// Create pulse effect with tween
tween(player, {
alpha: 0.9
}, {
duration: 750,
easing: tween.sinceOut,
repeat: true,
yoyo: true
});
// Remove shield effect after 1.5 seconds
LK.setTimeout(function () {
player.isInvulnerable = false;
player.removeChild(shieldEffect);
player.shieldEffect = null;
tween(player, {
alpha: 1.0
}, {
duration: 300
});
}, 1500);
// Create freeze effect (initially invisible)
freezeEffect = LK.getAsset('freezeEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
freezeEffect.x = 2048 / 2;
freezeEffect.y = 2732 / 2;
game.addChild(freezeEffect);
// Create directional buttons
var buttonSize = 300;
var buttonSpacing = 160;
var baseX = 300;
var baseY = 2500;
// Create up, down, left, right buttons
var upButton = new DirectionalButton();
upButton.x = baseX;
upButton.y = baseY - buttonSpacing;
upButton.setDirection(0, -1);
game.addChild(upButton);
var downButton = new DirectionalButton();
downButton.x = baseX;
downButton.y = baseY + buttonSpacing;
downButton.setDirection(0, 1);
game.addChild(downButton);
var leftButton = new DirectionalButton();
leftButton.x = baseX - buttonSpacing;
leftButton.y = baseY;
leftButton.setDirection(-1, 0);
game.addChild(leftButton);
var rightButton = new DirectionalButton();
rightButton.x = baseX + buttonSpacing;
rightButton.y = baseY;
rightButton.setDirection(1, 0);
game.addChild(rightButton);
// Create button references array
directionButtons = [upButton, downButton, leftButton, rightButton];
// Create level based on current level
createLevel(currentLevel);
// Start background music
LK.playMusic('bgmusic');
}
function createUI() {
// Score text
scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 120;
scoreTxt.y = 30;
LK.gui.addChild(scoreTxt);
// Level text
levelTxt = new Text2('Level: ' + currentLevel, {
size: 70,
fill: 0xFFFFFF
});
levelTxt.anchor.set(1, 0);
levelTxt.x = 2048 - 120;
levelTxt.y = 30;
LK.gui.addChild(levelTxt);
// Pause energy bar background
pauseBarBg = LK.getAsset('pauseBarBackground', {
anchorX: 0.5,
anchorY: 0.5
});
pauseBarBg.x = 2048 / 2;
pauseBarBg.y = 100;
LK.gui.addChild(pauseBarBg);
// Pause energy bar
pauseBar = LK.getAsset('pauseBar', {
anchorX: 0,
anchorY: 0.5
});
pauseBar.x = pauseBarBg.x - pauseBarBg.width / 2;
pauseBar.y = pauseBarBg.y;
pauseBar.width = pauseBarBg.width * (pauseEnergy / pauseEnergyMax);
LK.gui.addChild(pauseBar);
// Instructions
instructionsTxt = new Text2('Use directional buttons to move. Tap screen to slow time (enemies move at 0.25x speed).\nShoot automatically every 1.5 seconds. Enemies shoot every 2 seconds.\nDamage enemies to make them move. Defeat enemies to spawn new ones.', {
size: 40,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0);
instructionsTxt.x = 2048 / 2;
instructionsTxt.y = 150;
LK.gui.addChild(instructionsTxt);
// Auto-hide instructions after 5 seconds
LK.setTimeout(function () {
tween(instructionsTxt, {
alpha: 0
}, {
duration: 1000
});
}, 5000);
}
function createLevel(level) {
// Each level has a different configuration
switch (level) {
case 1:
// Level 1: Simple introduction
createObstacle(600, 500, 3, 0);
createObstacle(1200, 800, -2, 2);
createObstacle(1600, 1200, 0, 4);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500, "timeRecharge");
createCollectible(900, 1200, "shield");
// Make level end harder to reach by adding guards
createObstacle(1600, 2200, 3, 1);
createObstacle(2000, 2300, -2, 2);
// Add enemy guarding level end
var guardEnemy = createEnemy(1700, 2200, "patrol");
guardEnemy.setPatrolPoints([{
x: 1600,
y: 2200
}, {
x: 2000,
y: 2200
}, {
x: 2000,
y: 2500
}, {
x: 1600,
y: 2500
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
case 2:
// Level 2: Introduce enemies
createObstacle(600, 500, 4, 0);
createObstacle(1200, 800, -3, 3);
createObstacle(1600, 1200, 0, 5);
createObstacle(900, 1600, 4, -2);
var enemy = createEnemy(1000, 1000, "chase");
enemy.setTarget(player);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500);
createCollectible(1200, 2200, "timeRecharge");
// Make level end harder to reach by adding more guards
createObstacle(1600, 2200, 4, 2);
createObstacle(2000, 2300, -3, 3);
createObstacle(1800, 2100, 2, -2);
// Add multiple enemies guarding level end
var guardEnemy1 = createEnemy(1700, 2200, "chase");
guardEnemy1.setTarget(player);
var guardEnemy2 = createEnemy(1900, 2300, "patrol");
guardEnemy2.setPatrolPoints([{
x: 1700,
y: 2200
}, {
x: 1900,
y: 2200
}, {
x: 1900,
y: 2500
}, {
x: 1700,
y: 2500
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
case 3:
// Level 3: More complex with patrol enemies
createObstacle(600, 500, 5, 0);
createObstacle(1200, 800, -4, 3);
createObstacle(1600, 1200, 0, 6);
createObstacle(900, 1600, 5, -3);
createObstacle(1400, 2000, -3, -3);
var enemy1 = createEnemy(1000, 1000, "chase");
enemy1.setTarget(player);
var enemy2 = createEnemy(1500, 1500, "patrol");
enemy2.setPatrolPoints([{
x: 1500,
y: 1500
}, {
x: 1500,
y: 2000
}, {
x: 1000,
y: 2000
}, {
x: 1000,
y: 1500
}]);
createCollectible(800, 600);
createCollectible(1400, 1000);
createCollectible(1700, 1800);
createCollectible(500, 1500);
createCollectible(1200, 2200);
createCollectible(1800, 1300, "timeRecharge");
// Add a ring of obstacles around the level end
var obstacleCount = 8;
var radius = 250;
for (var i = 0; i < obstacleCount; i++) {
var angle = i / obstacleCount * Math.PI * 2;
var obsX = 1800 + Math.cos(angle) * radius;
var obsY = 2400 + Math.sin(angle) * radius;
var vx = Math.cos(angle + Math.PI / 2) * 3;
var vy = Math.sin(angle + Math.PI / 2) * 3;
createObstacle(obsX, obsY, vx, vy);
}
// Add multiple enemies guarding level end
var guardEnemy1 = createEnemy(1700, 2200, "chase");
guardEnemy1.setTarget(player);
var guardEnemy2 = createEnemy(1900, 2300, "chase");
guardEnemy2.setTarget(player);
var guardEnemy3 = createEnemy(1800, 2100, "patrol");
guardEnemy3.setPatrolPoints([{
x: 1600,
y: 2100
}, {
x: 2000,
y: 2100
}, {
x: 2000,
y: 2600
}, {
x: 1600,
y: 2600
}]);
// Level end
levelEnd = new LevelEnd();
levelEnd.x = 1800;
levelEnd.y = 2400;
game.addChild(levelEnd);
break;
default:
// Higher levels: Procedurally generate increasingly difficult layouts
var obstacleCount = 3 + Math.min(level, 7);
var enemyCount = 1 + Math.min(level - 2, 5);
var collectibleCount = 4 + Math.min(level, 6);
// Create obstacles
for (var i = 0; i < obstacleCount; i++) {
var x = 400 + Math.random() * 1600;
var y = 400 + Math.random() * 1800;
var vx = (Math.random() * 6 + 2) * (Math.random() > 0.5 ? 1 : -1);
var vy = (Math.random() * 6 + 2) * (Math.random() > 0.5 ? 1 : -1);
createObstacle(x, y, vx, vy);
}
// Create enemies
for (var i = 0; i < enemyCount; i++) {
var x = 400 + Math.random() * 1600;
var y = 400 + Math.random() * 1800;
var patternType = Math.random();
if (patternType < 0.4) {
var enemy = createEnemy(x, y, "chase");
enemy.setTarget(player);
} else if (patternType < 0.7) {
var enemy = createEnemy(x, y, "patrol");
var patrolRadius = 300 + Math.random() * 300;
var patrolPoints = [];
var patrolPointCount = 3 + Math.floor(Math.random() * 3);
for (var j = 0; j < patrolPointCount; j++) {
var angle = j / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: x + Math.cos(angle) * patrolRadius,
y: y + Math.sin(angle) * patrolRadius
});
}
enemy.setPatrolPoints(patrolPoints);
} else {
createEnemy(x, y, "random");
}
}
// Create collectibles
for (var i = 0; i < collectibleCount; i++) {
var x = 300 + Math.random() * 1700;
var y = 300 + Math.random() * 2000;
if (i === collectibleCount - 1) {
createCollectible(x, y, "timeRecharge");
} else {
createCollectible(x, y);
}
}
// Add a complex ring of obstacles and enemies around the level end
var levelEndX = 1700 + Math.random() * 200;
var levelEndY = 2200 + Math.random() * 300;
// Add a ring of obstacles around the level end
var obstacleCount = 8 + Math.min(level, 4);
var radius = 200 + Math.min(level * 10, 100);
for (var i = 0; i < obstacleCount; i++) {
var angle = i / obstacleCount * Math.PI * 2;
var obsX = levelEndX + Math.cos(angle) * radius;
var obsY = levelEndY + Math.sin(angle) * radius;
var vx = Math.cos(angle + Math.PI / 2) * (2 + Math.min(level * 0.5, 4));
var vy = Math.sin(angle + Math.PI / 2) * (2 + Math.min(level * 0.5, 4));
createObstacle(obsX, obsY, vx, vy);
}
// Add guardian enemies based on level
var guardCount = Math.min(level, 5);
for (var i = 0; i < guardCount; i++) {
var angle = i / guardCount * Math.PI * 2;
var enemyX = levelEndX + Math.cos(angle) * (radius - 50);
var enemyY = levelEndY + Math.sin(angle) * (radius - 50);
if (i % 3 === 0) {
var enemy = createEnemy(enemyX, enemyY, "chase");
enemy.setTarget(player);
} else if (i % 3 === 1) {
var enemy = createEnemy(enemyX, enemyY, "patrol");
var patrolRadius = radius * 0.6;
var patrolPoints = [];
var patrolPointCount = 4;
for (var j = 0; j < patrolPointCount; j++) {
var patrolAngle = j / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: levelEndX + Math.cos(patrolAngle) * patrolRadius,
y: levelEndY + Math.sin(patrolAngle) * patrolRadius
});
}
enemy.setPatrolPoints(patrolPoints);
} else {
createEnemy(enemyX, enemyY, "random");
}
}
// Level end
levelEnd = new LevelEnd();
levelEnd.x = levelEndX;
levelEnd.y = levelEndY;
game.addChild(levelEnd);
break;
}
}
function createObstacle(x, y, vx, vy) {
var obstacle = new Obstacle();
obstacle.x = x;
obstacle.y = y;
obstacle.vx = vx;
obstacle.vy = vy;
obstacles.push(obstacle);
game.addChild(obstacle);
return obstacle;
}
function createEnemy(x, y, movementPattern) {
var enemy = new Enemy();
enemy.x = x;
enemy.y = y;
enemy.setMovementPattern(movementPattern);
enemies.push(enemy);
game.addChild(enemy);
return enemy;
}
function createCollectible(x, y, type) {
var collectible = new Collectible();
collectible.x = x;
collectible.y = y;
if (type === "timeRecharge") {
collectible.setType("timeRecharge");
} else if (type === "shield") {
collectible.setType("shield");
collectible.setValue(5); // Shield worth more points
var sprite = collectible.getChildAt(0);
if (sprite) {
sprite.tint = 0xE74C3C; // Red for shield power-up
}
}
collectibles.push(collectible);
game.addChild(collectible);
return collectible;
}
function createPlayerBullet() {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y - 30;
bullet.setDirection(0, -1); // Shoot upward
bullets.push(bullet);
game.addChild(bullet);
return bullet;
}
function createEnemyBullet(enemy) {
var bullet = new Bullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 30;
// Direction toward player
var dx = player.x - enemy.x;
var dy = player.y - enemy.y;
bullet.setDirection(dx, dy);
bullet.setEnemyBullet(true);
enemyBullets.push(bullet);
game.addChild(bullet);
return bullet;
}
function toggleFreezeTime() {
if (pauseEnergy <= 0 && timeIsFrozen) {
// Can't stay frozen with no energy
unfreezeTime();
return;
}
if (!timeIsFrozen && pauseEnergy > 0) {
// Slow time (not completely freeze)
timeIsFrozen = true;
tween(freezeEffect, {
alpha: 0.2
}, {
duration: 300
});
LK.getSound('freeze').play();
} else if (timeIsFrozen) {
// Unfreeze time
unfreezeTime();
}
}
function unfreezeTime() {
timeIsFrozen = false;
tween(freezeEffect, {
alpha: 0
}, {
duration: 300
});
LK.getSound('unfreeze').play();
}
function updatePauseEnergyBar() {
pauseBar.width = pauseBarBg.width * (pauseEnergy / pauseEnergyMax);
}
function checkCollisions() {
// Check player-obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
if (player.intersects(obstacles[i]) && !player.isInvulnerable) {
handlePlayerHit();
return;
}
}
// Check player-enemy collisions
for (var i = 0; i < enemies.length; i++) {
if (player.intersects(enemies[i]) && !player.isInvulnerable) {
handlePlayerHit();
return;
}
}
// Check player-collectible collisions
for (var i = collectibles.length - 1; i >= 0; i--) {
if (player.intersects(collectibles[i])) {
// Collect the item
if (collectibles[i].type === "timeRecharge") {
pauseEnergy = Math.min(pauseEnergyMax, pauseEnergy + 50);
updatePauseEnergyBar();
} else if (collectibles[i].type === "shield") {
// Apply shield effect
player.isInvulnerable = true;
if (!player.shieldEffect) {
// Create new shield effect
var shieldEffect = new ShieldEffect();
shieldEffect.x = 0;
shieldEffect.y = 0;
player.addChild(shieldEffect);
player.shieldEffect = shieldEffect;
// Flash player
LK.effects.flashObject(player, 0xE74C3C, 300);
// Remove shield after 5 seconds
LK.setTimeout(function () {
if (player.shieldEffect) {
player.removeChild(player.shieldEffect);
player.shieldEffect = null;
player.isInvulnerable = false;
}
}, 5000);
}
score += collectibles[i].value;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
} else {
score += collectibles[i].value;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
}
LK.getSound('collect').play();
collectibles[i].destroy();
collectibles.splice(i, 1);
}
}
// Check if player reached the level end
if (levelEnd && player.intersects(levelEnd)) {
completeLevel();
}
}
function handlePlayerHit() {
if (!timeIsFrozen) {
// Flash the screen red
LK.effects.flashScreen(0xff0000, 500);
LK.getSound('hit').play();
// Show game over
LK.showGameOver();
}
}
function completeLevel() {
if (!levelComplete) {
levelComplete = true;
// Save progress
currentLevel++;
storage.level = currentLevel;
// Save high score
if (score > storage.highScore) {
storage.highScore = score;
}
// Play win sound
LK.getSound('win').play();
// Show level complete
LK.showYouWin();
}
}
// Game event handlers
game.down = function (x, y, obj) {
// Check if any directional button was clicked
var buttonPressed = false;
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 75) {
// Button radius
button.down(x, y, obj);
buttonPressed = true;
break;
}
}
}
if (!buttonPressed) {
// If no button was pressed, handle time slowing
if (!timeIsFrozen && pauseEnergy > 0) {
// Slow time when clicking anywhere else (if we have energy)
toggleFreezeTime();
} else if (timeIsFrozen) {
// Unfreeze time when clicking anywhere else if already frozen
toggleFreezeTime();
}
}
};
game.up = function (x, y, obj) {
// Release all directional buttons
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 75) {
button.up(x, y, obj);
}
}
}
};
game.move = function (x, y, obj) {
// Handle touch/mouse movement for buttons
// Detect if we've moved over or out of any buttons
if (directionButtons) {
for (var i = 0; i < directionButtons.length; i++) {
var button = directionButtons[i];
var dx = x - button.x;
var dy = y - button.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If finger/pointer moved outside a pressed button, release it
if (button.isPressed && distance > 75) {
button.up(x, y, obj);
}
}
}
};
// Game update loop
game.update = function () {
// Check if game is initialized
if (!player) {
initGame();
return;
}
// Drain pause energy when time is frozen
if (timeIsFrozen) {
pauseEnergy = Math.max(0, pauseEnergy - pauseEnergyDrainRate);
updatePauseEnergyBar();
// Automatically unfreeze when energy is depleted
if (pauseEnergy <= 0) {
unfreezeTime();
}
} else if (pauseEnergy < pauseEnergyMax) {
// Slowly recharge when not frozen
pauseEnergy = Math.min(pauseEnergyMax, pauseEnergy + 0.1);
updatePauseEnergyBar();
}
// Handle player shooting (every 1.5 seconds)
shootingTimer++;
var shootingInterval = timeIsFrozen ? 90 * 1.5 : 90; // 90 ticks = 1.5 seconds, slower when time is frozen
if (shootingTimer >= shootingInterval) {
createPlayerBullet();
shootingTimer = 0;
}
// Handle enemy shooting (every 2 seconds)
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (!enemyShootingTimer[i]) {
enemyShootingTimer[i] = 0;
}
enemyShootingTimer[i]++;
var enemyShootInterval = timeIsFrozen ? 120 * 1.5 : 120; // 120 ticks = 2 seconds, slower when time is frozen
if (enemyShootingTimer[i] >= enemyShootInterval) {
createEnemyBullet(enemy);
enemyShootingTimer[i] = 0;
}
}
// Update game objects
player.update();
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
if (bullets[i].shouldRemove) {
bullets[i].destroy();
bullets.splice(i, 1);
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].update();
if (enemyBullets[i].shouldRemove) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].update();
}
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
for (var i = 0; i < collectibles.length; i++) {
collectibles[i].update();
}
if (levelEnd) {
levelEnd.update();
}
// Check for collisions
checkCollisions();
// Check bullet collisions with enemies
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullets[i] && bullets[i].intersects(enemies[j])) {
// Enemy hit by player bullet
var enemyDead = enemies[j].takeDamage();
if (enemyDead) {
// Spawn a new enemy at a random position when one is destroyed
var spawnX = Math.random() < 0.5 ? 200 + Math.random() * 200 : 1848 - Math.random() * 200;
var spawnY = Math.random() < 0.5 ? 200 + Math.random() * 200 : 2532 - Math.random() * 200;
// Choose a random movement pattern with increased difficulty
var patterns = ["chase", "patrol", "random"];
var patternIndex = Math.floor(Math.random() * patterns.length);
var newEnemy = createEnemy(spawnX, spawnY, patterns[patternIndex]);
// Make new enemies faster and more challenging
newEnemy.speed = enemies[j].speed + 0.5;
if (patterns[patternIndex] === "chase") {
newEnemy.setTarget(player);
} else if (patterns[patternIndex] === "patrol") {
var patrolRadius = 300 + Math.random() * 300;
var patrolPoints = [];
var patrolPointCount = 3 + Math.floor(Math.random() * 3);
for (var k = 0; k < patrolPointCount; k++) {
var angle = k / patrolPointCount * Math.PI * 2;
patrolPoints.push({
x: spawnX + Math.cos(angle) * patrolRadius,
y: spawnY + Math.sin(angle) * patrolRadius
});
}
newEnemy.setPatrolPoints(patrolPoints);
}
// Destroy the old enemy
enemies[j].destroy();
enemies.splice(j, 1);
// Update score
score += 10;
LK.setScore(score);
scoreTxt.setText('Score: ' + score);
}
// Always destroy the bullet that hit
bullets[i].destroy();
bullets.splice(i, 1);
break;
}
}
}
// Check enemy bullet collisions with player
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i] && player.intersects(enemyBullets[i])) {
// Player hit by enemy bullet
if (!timeIsFrozen && !player.isInvulnerable) {
handlePlayerHit();
} else {
// Just destroy the bullet if time is frozen or player is invulnerable
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
}
}
}
};
Modern App Store icon, high definition, square with rounded corners, for a game titled "Time Freeze" and with the description "A puzzle-action game where pausing is a superpower. Strategically freeze time to navigate hazards, avoid enemies, and collect items while managing your limited pause energy. Plan your moves carefully in this time-bending adventure where stopping the clock is your greatest tool for success.". No text on icon!
Monster 2d
A big red flag with the word end on it. In-Game asset. 2d. High contrast. No shadows
2d electronic visible coin. In-Game asset. 2d. High contrast. No shadows
2d dangerous robot bomb. In-Game asset. 2d. High contrast. No shadows
2d beam bullet. In-Game asset. 2d. High contrast. No shadows
2d shield magic. In-Game asset. 2d. High contrast. No shadows