/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1; // 1 = right, -1 = left
self.isActive = true;
var bird = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
LK.getSound('bird_hit').play();
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Kite class
var Kite = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1;
self.isActive = true;
var kite = self.attachAsset('kite', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Paratrooper class
var Paratrooper = Container.expand(function () {
var self = Container.call(this);
// State: 0 = falling, 1 = parachute open, 2 = landed, 3 = crashed, 4 = rescued
self.state = 0;
self.speed = 0;
self.hasParachute = false;
self.isActive = true;
// Attach body
var body = self.attachAsset('paratrooper_body', {
anchorX: 0.5,
anchorY: 0.0
});
body.y = 0;
// Attach parachute (hidden at first)
var chute = self.attachAsset('parachute', {
anchorX: 0.5,
anchorY: 1.0,
y: 0
});
chute.visible = false;
// For tap detection
self.down = function (x, y, obj) {
if (self.state === 0 && self.isActive) {
self.openParachute();
}
};
// Open parachute
self.openParachute = function () {
if (self.state !== 0) return;
self.state = 1;
self.hasParachute = true;
chute.visible = true;
// Slow down
self.speed = self.speed * 0.28;
// Animate parachute pop
chute.scaleY = 0.1;
tween(chute, {
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
LK.getSound('parachute_open').play();
};
// Crash animation
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 3;
// Flash red
LK.effects.flashObject(self, 0xff0000, 600);
LK.getSound('crash').play();
// Fade out
tween(self, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Rescue animation
self.rescue = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 4;
// Flash green
LK.effects.flashObject(self, 0x00ff00, 400);
// Fade out
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Bird/kite collision
self.hitHazard = function () {
if (self.state === 0 && self.isActive) {
self.crash();
}
};
self.update = function () {
if (!self.isActive) return;
self.y += self.speed;
};
return self;
});
// Plane class (for visual, not interactive)
var Plane = Container.expand(function () {
var self = Container.call(this);
var plane = self.attachAsset('plane', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Paratrooper body (box), parachute (ellipse), bird (ellipse), kite (box), ground (box), plane (box)
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GROUND_HEIGHT = 60;
var LEVELS = 20;
// Level config: Each level increases paratroopers, speed, and hazards
var levelConfigs = [];
for (var i = 0; i < LEVELS; i++) {
// Level 1: 10 paratroopers, then +2 per level
var paratroopersToRescue = 10 + i * 2;
// Level duration: 1 minute (60 seconds * 60 FPS = 3600 ticks)
var levelDurationTicks = 60 * 60;
// Calculate spawn interval so all paratroopers spawn within the level duration
var spawnInterval = Math.floor(levelDurationTicks / paratroopersToRescue);
// Clamp spawnInterval to a minimum of 10 ticks for high levels
spawnInterval = Math.max(spawnInterval, 10);
levelConfigs.push({
paratroopers: paratroopersToRescue,
speed: 9 + i * 1.2,
hazards: i < 3 ? 0 : Math.min(1 + Math.floor((i - 2) / 2), 4),
hazardSpeed: 7 + i * 0.7,
hazardTypes: i < 6 ? ['bird'] : i < 12 ? ['bird', 'kite'] : ['bird', 'kite'],
spawnInterval: spawnInterval,
levelDurationTicks: levelDurationTicks
});
}
// State
// Load saved progress if available, otherwise start at level 1
var currentLevel = storage.currentLevel || 1;
var bestLevel = storage.bestLevel || 1;
var paratroopers = [];
var hazards = [];
var rescuedCount = 0;
var lostCount = 0;
var totalToRescue = 0;
var isLevelActive = false;
var plane = null;
var ground = null;
var levelText = null;
var rescuedText = null;
var lostText = null;
var bestLevelText = null;
var levelTimeout = null;
var spawnTick = 0;
var hazardTick = 0;
var dragNode = null;
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GAME_HEIGHT - GROUND_HEIGHT
});
game.addChild(ground);
// Add level text
levelText = new Text2('Level 1', {
size: 90,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
// Add objective text (upper left side, not in top left corner)
var objectiveText = new Text2('', {
size: 70,
fill: 0xFFFFAA
});
// Anchor to left, top
objectiveText.anchor.set(0, 0);
// Place at x=2 to avoid top left menu, y=0 (moved twice more left)
objectiveText.x = 2;
objectiveText.y = 50;
LK.gui.top.addChild(objectiveText);
// Add rescued/lost text
rescuedText = new Text2('Rescued: 0', {
size: 70,
fill: 0xFFFFFF
});
rescuedText.anchor.set(0, 0);
LK.gui.top.addChild(rescuedText);
lostText = new Text2('Lost: 0', {
size: 70,
fill: 0xFF4444
});
lostText.anchor.set(1, 0);
LK.gui.topRight.addChild(lostText);
// Add best level text
bestLevelText = new Text2('Best: ' + bestLevel, {
size: 60,
fill: 0xFFFF00
});
bestLevelText.anchor.set(0.5, 0);
LK.gui.bottom.addChild(bestLevelText);
// Start music
LK.playMusic('bgmusic');
// Show level number before starting, wait for tap to begin
function showLevelIntro(level, callback) {
// Hide timer and texts if visible
if (levelTimerText) levelTimerText.visible = false;
levelText.setText('Level ' + level);
levelText.alpha = 1;
levelText.visible = true;
// Overlay to block input except tap to continue
var introOverlay = new Container();
introOverlay.interactive = true;
introOverlay.hitArea = {
x: 0,
y: 0,
width: GAME_WIDTH,
height: GAME_HEIGHT
};
// Add a "Tap to Start" text
var tapText = new Text2('Tap to Start', {
size: 100,
fill: 0xffffff
});
tapText.anchor.set(0.5, 0.5);
tapText.x = GAME_WIDTH / 2;
tapText.y = GAME_HEIGHT / 2 + 180;
introOverlay.addChild(tapText);
// Add overlay to game
game.addChild(introOverlay);
// Only allow tap after a short delay to avoid accidental skip
var canTap = false;
LK.setTimeout(function () {
canTap = true;
}, 400);
introOverlay.down = function () {
if (!canTap) return;
// Animate out
tween(levelText, {
alpha: 0
}, {
duration: 400
});
tween(tapText, {
alpha: 0
}, {
duration: 400
});
LK.setTimeout(function () {
introOverlay.destroy();
if (callback) callback();
}, 420);
};
}
// Start first level after intro
showLevelIntro(currentLevel, function () {
startLevel(currentLevel);
});
var levelTimerText = null;
var levelTimerTimeout = null;
function startLevel(level) {
// Update best level if needed
if (level > bestLevel) {
bestLevel = level;
storage.bestLevel = bestLevel;
if (bestLevelText) bestLevelText.setText('Best: ' + bestLevel);
}
// Save progress
storage.currentLevel = level;
// Reset state
for (var i = 0; i < paratroopers.length; i++) {
paratroopers[i].destroy();
}
for (var i = 0; i < hazards.length; i++) {
hazards[i].destroy();
}
paratroopers = [];
hazards = [];
rescuedCount = 0;
lostCount = 0;
isLevelActive = true;
spawnTick = 0;
hazardTick = 0;
totalToRescue = levelConfigs[level - 1].paratroopers;
if (objectiveText) {
objectiveText.setText('Rescue ' + totalToRescue + ' Paratroopers');
}
levelText.setText('Level ' + level);
rescuedText.setText('Rescued: 0');
lostText.setText('Lost: 0');
// Animate level text
levelText.alpha = 1;
tween(levelText, {
alpha: 0
}, {
duration: 1200
});
// Plane flyby
if (plane) plane.destroy();
plane = new Plane();
plane.y = 180;
plane.x = -200;
game.addChild(plane);
tween(plane, {
x: GAME_WIDTH + 200
}, {
duration: 1800,
easing: tween.linear,
onFinish: function onFinish() {
plane.destroy();
plane = null;
}
});
// Add or reset timer text
if (!levelTimerText) {
levelTimerText = new Text2('01:00', {
size: 80,
fill: 0xFFFFFF
});
levelTimerText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTimerText);
}
// Place timer below level text
levelTimerText.y = levelText.height + 10;
levelTimerText.setText('01:00');
// Clear any previous timer
if (levelTimerTimeout) {
LK.clearTimeout(levelTimerTimeout);
levelTimerTimeout = null;
}
// Start timer countdown
var ticksLeft = levelConfigs[level - 1].levelDurationTicks;
if (levelTimerText) levelTimerText.visible = true;
function updateTimer() {
if (!isLevelActive) return;
ticksLeft--;
var seconds = Math.floor(ticksLeft / 60);
var ms = ticksLeft % 60;
var timeStr = (seconds < 10 ? '0' : '') + seconds + ':' + (ms < 10 ? '0' : '') + ms;
if (levelTimerText) levelTimerText.setText(timeStr);
if (ticksLeft > 0 && isLevelActive) {
levelTimerTimeout = LK.setTimeout(updateTimer, 1000 / 60);
} else if (isLevelActive) {
// Time's up, end level
isLevelActive = false;
// If not all rescued, game over, else win/next
if (rescuedCount === totalToRescue) {
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
} else {
LK.showGameOver();
}
}
}
updateTimer();
}
// Paratrooper spawn
function spawnParatrooper() {
var config = levelConfigs[currentLevel - 1];
var p = new Paratrooper();
// Random x, avoid edges
var margin = 120;
p.x = margin + Math.random() * (GAME_WIDTH - 2 * margin);
p.y = 220;
p.speed = config.speed + Math.random() * 2;
p.state = 0;
p.hasParachute = false;
p.isActive = true;
paratroopers.push(p);
game.addChild(p);
}
// Hazard spawn
function spawnHazard() {
var config = levelConfigs[currentLevel - 1];
var type = config.hazardTypes[Math.floor(Math.random() * config.hazardTypes.length)];
var h = null;
var y = 400 + Math.random() * (GAME_HEIGHT - GROUND_HEIGHT - 800);
var dir = Math.random() < 0.5 ? 1 : -1;
var speed = config.hazardSpeed + Math.random() * 2;
var x = dir === 1 ? -100 : GAME_WIDTH + 100;
if (type === 'bird') {
h = new Bird();
} else {
h = new Kite();
}
h.x = x;
h.y = y;
h.dir = dir;
h.speed = speed;
h.isActive = true;
hazards.push(h);
game.addChild(h);
}
// Game move handler (for drag, but not used in this game)
game.move = function (x, y, obj) {
// No drag in this game
};
// Game down handler (for tap on paratroopers)
game.down = function (x, y, obj) {
// Find topmost paratrooper under tap
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
if (p.isActive && p.state === 0 && p.visible) {
// Check if tap is inside body
var local = p.toLocal({
x: x,
y: y
});
var body = p.children[0];
if (local.x >= body.x - body.width / 2 && local.x <= body.x + body.width / 2 && local.y >= body.y && local.y <= body.y + body.height) {
p.openParachute();
break;
}
}
}
};
// Game update
game.update = function () {
if (!isLevelActive) return;
var config = levelConfigs[currentLevel - 1];
// Spawn paratroopers
if (paratroopers.length < totalToRescue && LK.ticks % config.spawnInterval === 0) {
spawnParatrooper();
}
// Spawn hazards
if (config.hazards > 0 && hazards.length < config.hazards && LK.ticks % (80 + Math.floor(Math.random() * 40)) === 0) {
spawnHazard();
}
// Update paratroopers
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
p.update();
// Check for ground collision
if (p.isActive && p.y + 90 >= GAME_HEIGHT - GROUND_HEIGHT) {
if (p.state === 1) {
// Landed safely
p.rescue();
rescuedCount++;
rescuedText.setText('Rescued: ' + rescuedCount);
} else if (p.state === 0) {
// Crashed
p.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
// Flash screen
LK.effects.flashScreen(0xff0000, 400);
}
// Remove after animation
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
continue;
}
// Check for hazard collision
for (var j = hazards.length - 1; j >= 0; j--) {
var h = hazards[j];
if (h.isActive && p.isActive && p.state === 0 && p.intersects(h)) {
p.hitHazard();
h.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
LK.effects.flashScreen(0xff0000, 400);
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
break;
}
}
}
// Update hazards
for (var i = hazards.length - 1; i >= 0; i--) {
var h = hazards[i];
h.update();
// Remove if off screen
if (h.dir === 1 && h.x > GAME_WIDTH + 120 || h.dir === -1 && h.x < -120) {
h.destroy();
hazards.splice(i, 1);
}
}
// Level end condition
// If all paratroopers rescued before timer ends, go to next level immediately
if (isLevelActive && rescuedCount === totalToRescue) {
isLevelActive = false;
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
return;
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1; // 1 = right, -1 = left
self.isActive = true;
var bird = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
LK.getSound('bird_hit').play();
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Kite class
var Kite = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.dir = 1;
self.isActive = true;
var kite = self.attachAsset('kite', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.isActive) return;
self.x += self.speed * self.dir;
};
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Paratrooper class
var Paratrooper = Container.expand(function () {
var self = Container.call(this);
// State: 0 = falling, 1 = parachute open, 2 = landed, 3 = crashed, 4 = rescued
self.state = 0;
self.speed = 0;
self.hasParachute = false;
self.isActive = true;
// Attach body
var body = self.attachAsset('paratrooper_body', {
anchorX: 0.5,
anchorY: 0.0
});
body.y = 0;
// Attach parachute (hidden at first)
var chute = self.attachAsset('parachute', {
anchorX: 0.5,
anchorY: 1.0,
y: 0
});
chute.visible = false;
// For tap detection
self.down = function (x, y, obj) {
if (self.state === 0 && self.isActive) {
self.openParachute();
}
};
// Open parachute
self.openParachute = function () {
if (self.state !== 0) return;
self.state = 1;
self.hasParachute = true;
chute.visible = true;
// Slow down
self.speed = self.speed * 0.28;
// Animate parachute pop
chute.scaleY = 0.1;
tween(chute, {
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
LK.getSound('parachute_open').play();
};
// Crash animation
self.crash = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 3;
// Flash red
LK.effects.flashObject(self, 0xff0000, 600);
LK.getSound('crash').play();
// Fade out
tween(self, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Rescue animation
self.rescue = function () {
if (!self.isActive) return;
self.isActive = false;
self.state = 4;
// Flash green
LK.effects.flashObject(self, 0x00ff00, 400);
// Fade out
tween(self, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
self.visible = false;
}
});
};
// Bird/kite collision
self.hitHazard = function () {
if (self.state === 0 && self.isActive) {
self.crash();
}
};
self.update = function () {
if (!self.isActive) return;
self.y += self.speed;
};
return self;
});
// Plane class (for visual, not interactive)
var Plane = Container.expand(function () {
var self = Container.call(this);
var plane = self.attachAsset('plane', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Paratrooper body (box), parachute (ellipse), bird (ellipse), kite (box), ground (box), plane (box)
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var GROUND_HEIGHT = 60;
var LEVELS = 20;
// Level config: Each level increases paratroopers, speed, and hazards
var levelConfigs = [];
for (var i = 0; i < LEVELS; i++) {
// Level 1: 10 paratroopers, then +2 per level
var paratroopersToRescue = 10 + i * 2;
// Level duration: 1 minute (60 seconds * 60 FPS = 3600 ticks)
var levelDurationTicks = 60 * 60;
// Calculate spawn interval so all paratroopers spawn within the level duration
var spawnInterval = Math.floor(levelDurationTicks / paratroopersToRescue);
// Clamp spawnInterval to a minimum of 10 ticks for high levels
spawnInterval = Math.max(spawnInterval, 10);
levelConfigs.push({
paratroopers: paratroopersToRescue,
speed: 9 + i * 1.2,
hazards: i < 3 ? 0 : Math.min(1 + Math.floor((i - 2) / 2), 4),
hazardSpeed: 7 + i * 0.7,
hazardTypes: i < 6 ? ['bird'] : i < 12 ? ['bird', 'kite'] : ['bird', 'kite'],
spawnInterval: spawnInterval,
levelDurationTicks: levelDurationTicks
});
}
// State
// Load saved progress if available, otherwise start at level 1
var currentLevel = storage.currentLevel || 1;
var bestLevel = storage.bestLevel || 1;
var paratroopers = [];
var hazards = [];
var rescuedCount = 0;
var lostCount = 0;
var totalToRescue = 0;
var isLevelActive = false;
var plane = null;
var ground = null;
var levelText = null;
var rescuedText = null;
var lostText = null;
var bestLevelText = null;
var levelTimeout = null;
var spawnTick = 0;
var hazardTick = 0;
var dragNode = null;
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GAME_HEIGHT - GROUND_HEIGHT
});
game.addChild(ground);
// Add level text
levelText = new Text2('Level 1', {
size: 90,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
// Add objective text (upper left side, not in top left corner)
var objectiveText = new Text2('', {
size: 70,
fill: 0xFFFFAA
});
// Anchor to left, top
objectiveText.anchor.set(0, 0);
// Place at x=2 to avoid top left menu, y=0 (moved twice more left)
objectiveText.x = 2;
objectiveText.y = 50;
LK.gui.top.addChild(objectiveText);
// Add rescued/lost text
rescuedText = new Text2('Rescued: 0', {
size: 70,
fill: 0xFFFFFF
});
rescuedText.anchor.set(0, 0);
LK.gui.top.addChild(rescuedText);
lostText = new Text2('Lost: 0', {
size: 70,
fill: 0xFF4444
});
lostText.anchor.set(1, 0);
LK.gui.topRight.addChild(lostText);
// Add best level text
bestLevelText = new Text2('Best: ' + bestLevel, {
size: 60,
fill: 0xFFFF00
});
bestLevelText.anchor.set(0.5, 0);
LK.gui.bottom.addChild(bestLevelText);
// Start music
LK.playMusic('bgmusic');
// Show level number before starting, wait for tap to begin
function showLevelIntro(level, callback) {
// Hide timer and texts if visible
if (levelTimerText) levelTimerText.visible = false;
levelText.setText('Level ' + level);
levelText.alpha = 1;
levelText.visible = true;
// Overlay to block input except tap to continue
var introOverlay = new Container();
introOverlay.interactive = true;
introOverlay.hitArea = {
x: 0,
y: 0,
width: GAME_WIDTH,
height: GAME_HEIGHT
};
// Add a "Tap to Start" text
var tapText = new Text2('Tap to Start', {
size: 100,
fill: 0xffffff
});
tapText.anchor.set(0.5, 0.5);
tapText.x = GAME_WIDTH / 2;
tapText.y = GAME_HEIGHT / 2 + 180;
introOverlay.addChild(tapText);
// Add overlay to game
game.addChild(introOverlay);
// Only allow tap after a short delay to avoid accidental skip
var canTap = false;
LK.setTimeout(function () {
canTap = true;
}, 400);
introOverlay.down = function () {
if (!canTap) return;
// Animate out
tween(levelText, {
alpha: 0
}, {
duration: 400
});
tween(tapText, {
alpha: 0
}, {
duration: 400
});
LK.setTimeout(function () {
introOverlay.destroy();
if (callback) callback();
}, 420);
};
}
// Start first level after intro
showLevelIntro(currentLevel, function () {
startLevel(currentLevel);
});
var levelTimerText = null;
var levelTimerTimeout = null;
function startLevel(level) {
// Update best level if needed
if (level > bestLevel) {
bestLevel = level;
storage.bestLevel = bestLevel;
if (bestLevelText) bestLevelText.setText('Best: ' + bestLevel);
}
// Save progress
storage.currentLevel = level;
// Reset state
for (var i = 0; i < paratroopers.length; i++) {
paratroopers[i].destroy();
}
for (var i = 0; i < hazards.length; i++) {
hazards[i].destroy();
}
paratroopers = [];
hazards = [];
rescuedCount = 0;
lostCount = 0;
isLevelActive = true;
spawnTick = 0;
hazardTick = 0;
totalToRescue = levelConfigs[level - 1].paratroopers;
if (objectiveText) {
objectiveText.setText('Rescue ' + totalToRescue + ' Paratroopers');
}
levelText.setText('Level ' + level);
rescuedText.setText('Rescued: 0');
lostText.setText('Lost: 0');
// Animate level text
levelText.alpha = 1;
tween(levelText, {
alpha: 0
}, {
duration: 1200
});
// Plane flyby
if (plane) plane.destroy();
plane = new Plane();
plane.y = 180;
plane.x = -200;
game.addChild(plane);
tween(plane, {
x: GAME_WIDTH + 200
}, {
duration: 1800,
easing: tween.linear,
onFinish: function onFinish() {
plane.destroy();
plane = null;
}
});
// Add or reset timer text
if (!levelTimerText) {
levelTimerText = new Text2('01:00', {
size: 80,
fill: 0xFFFFFF
});
levelTimerText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTimerText);
}
// Place timer below level text
levelTimerText.y = levelText.height + 10;
levelTimerText.setText('01:00');
// Clear any previous timer
if (levelTimerTimeout) {
LK.clearTimeout(levelTimerTimeout);
levelTimerTimeout = null;
}
// Start timer countdown
var ticksLeft = levelConfigs[level - 1].levelDurationTicks;
if (levelTimerText) levelTimerText.visible = true;
function updateTimer() {
if (!isLevelActive) return;
ticksLeft--;
var seconds = Math.floor(ticksLeft / 60);
var ms = ticksLeft % 60;
var timeStr = (seconds < 10 ? '0' : '') + seconds + ':' + (ms < 10 ? '0' : '') + ms;
if (levelTimerText) levelTimerText.setText(timeStr);
if (ticksLeft > 0 && isLevelActive) {
levelTimerTimeout = LK.setTimeout(updateTimer, 1000 / 60);
} else if (isLevelActive) {
// Time's up, end level
isLevelActive = false;
// If not all rescued, game over, else win/next
if (rescuedCount === totalToRescue) {
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
} else {
LK.showGameOver();
}
}
}
updateTimer();
}
// Paratrooper spawn
function spawnParatrooper() {
var config = levelConfigs[currentLevel - 1];
var p = new Paratrooper();
// Random x, avoid edges
var margin = 120;
p.x = margin + Math.random() * (GAME_WIDTH - 2 * margin);
p.y = 220;
p.speed = config.speed + Math.random() * 2;
p.state = 0;
p.hasParachute = false;
p.isActive = true;
paratroopers.push(p);
game.addChild(p);
}
// Hazard spawn
function spawnHazard() {
var config = levelConfigs[currentLevel - 1];
var type = config.hazardTypes[Math.floor(Math.random() * config.hazardTypes.length)];
var h = null;
var y = 400 + Math.random() * (GAME_HEIGHT - GROUND_HEIGHT - 800);
var dir = Math.random() < 0.5 ? 1 : -1;
var speed = config.hazardSpeed + Math.random() * 2;
var x = dir === 1 ? -100 : GAME_WIDTH + 100;
if (type === 'bird') {
h = new Bird();
} else {
h = new Kite();
}
h.x = x;
h.y = y;
h.dir = dir;
h.speed = speed;
h.isActive = true;
hazards.push(h);
game.addChild(h);
}
// Game move handler (for drag, but not used in this game)
game.move = function (x, y, obj) {
// No drag in this game
};
// Game down handler (for tap on paratroopers)
game.down = function (x, y, obj) {
// Find topmost paratrooper under tap
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
if (p.isActive && p.state === 0 && p.visible) {
// Check if tap is inside body
var local = p.toLocal({
x: x,
y: y
});
var body = p.children[0];
if (local.x >= body.x - body.width / 2 && local.x <= body.x + body.width / 2 && local.y >= body.y && local.y <= body.y + body.height) {
p.openParachute();
break;
}
}
}
};
// Game update
game.update = function () {
if (!isLevelActive) return;
var config = levelConfigs[currentLevel - 1];
// Spawn paratroopers
if (paratroopers.length < totalToRescue && LK.ticks % config.spawnInterval === 0) {
spawnParatrooper();
}
// Spawn hazards
if (config.hazards > 0 && hazards.length < config.hazards && LK.ticks % (80 + Math.floor(Math.random() * 40)) === 0) {
spawnHazard();
}
// Update paratroopers
for (var i = paratroopers.length - 1; i >= 0; i--) {
var p = paratroopers[i];
p.update();
// Check for ground collision
if (p.isActive && p.y + 90 >= GAME_HEIGHT - GROUND_HEIGHT) {
if (p.state === 1) {
// Landed safely
p.rescue();
rescuedCount++;
rescuedText.setText('Rescued: ' + rescuedCount);
} else if (p.state === 0) {
// Crashed
p.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
// Flash screen
LK.effects.flashScreen(0xff0000, 400);
}
// Remove after animation
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
continue;
}
// Check for hazard collision
for (var j = hazards.length - 1; j >= 0; j--) {
var h = hazards[j];
if (h.isActive && p.isActive && p.state === 0 && p.intersects(h)) {
p.hitHazard();
h.crash();
lostCount++;
lostText.setText('Lost: ' + lostCount);
LK.effects.flashScreen(0xff0000, 400);
LK.setTimeout(function (paratrooper) {
return function () {
paratrooper.destroy();
};
}(p), 700);
paratroopers.splice(i, 1);
break;
}
}
}
// Update hazards
for (var i = hazards.length - 1; i >= 0; i--) {
var h = hazards[i];
h.update();
// Remove if off screen
if (h.dir === 1 && h.x > GAME_WIDTH + 120 || h.dir === -1 && h.x < -120) {
h.destroy();
hazards.splice(i, 1);
}
}
// Level end condition
// If all paratroopers rescued before timer ends, go to next level immediately
if (isLevelActive && rescuedCount === totalToRescue) {
isLevelActive = false;
LK.getSound('levelup').play();
if (currentLevel < LEVELS) {
currentLevel++;
LK.setTimeout(function () {
startLevel(currentLevel);
}, 1200);
} else {
LK.showYouWin();
}
return;
}
};