/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var CaptureZone = Container.expand(function () { var self = Container.call(this); var heart = self.attachAsset('heartCaptureZone', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); self.isExpanded = false; self.baseScale = 1; self.expandZone = function (duration) { self.isExpanded = true; var targetScale = self.baseScale * 1.5; tween(self, { scaleX: targetScale, scaleY: targetScale }, { duration: 200, easing: tween.easeOut }); LK.setTimeout(function () { self.isExpanded = false; tween(self, { scaleX: self.baseScale, scaleY: self.baseScale }, { duration: 200, easing: tween.easeIn }); }, duration); }; self.pulse = function () { tween(heart, { alpha: 0.9, scaleX: 1.1, scaleY: 1.1 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(heart, { alpha: 0.7, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); }; return self; }); var Ghost = Container.expand(function () { var self = Container.call(this); // Ghost body parts var body = self.attachAsset('ghostBody', { anchorX: 0.5, anchorY: 0.5 }); var bottom = self.attachAsset('ghostBottom', { anchorX: 0.5, anchorY: 0, y: 70 }); var leftEye = self.attachAsset('ghostEye', { anchorX: 0.5, anchorY: 0.5, x: -40, y: -30 }); var rightEye = self.attachAsset('ghostEye', { anchorX: 0.5, anchorY: 0.5, x: 40, y: -30 }); var mouth = self.attachAsset('ghostMouth', { anchorX: 0.5, anchorY: 0.5, y: 30 }); // Ghost properties self.speed = 3; self.targetX = 0; self.targetY = 0; self.changeDirectionTimer = 0; self.elusiveness = 1.0; self.isCaught = false; self.isSlowed = false; self.setRandomTarget = function () { self.targetX = Math.random() * (2048 - 400) + 200; self.targetY = Math.random() * (2732 - 400) + 200; self.changeDirectionTimer = Math.floor(Math.random() * 120) + 60; }; self.fleeFromPoint = function (x, y) { if (!self.isSlowed) { var dx = self.x - x; var dy = self.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 400) { self.targetX = self.x + dx / distance * 500; self.targetY = self.y + dy / distance * 500; // Constrain to screen boundaries self.targetX = Math.max(100, Math.min(2048 - 100, self.targetX)); self.targetY = Math.max(100, Math.min(2732 - 100, self.targetY)); self.changeDirectionTimer = 60; } } }; self.applySlowEffect = function (duration) { self.isSlowed = true; tween(self, { tint: 0x00ffff }, { duration: 200 }); LK.setTimeout(function () { self.isSlowed = false; tween(self, { tint: 0xffffff }, { duration: 200 }); }, duration); }; self.update = function () { if (self.isCaught) { return; } self.changeDirectionTimer--; if (self.changeDirectionTimer <= 0) { self.setRandomTarget(); } // Move toward target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { var actualSpeed = self.isSlowed ? self.speed / 2 : self.speed * self.elusiveness; self.x += dx / distance * actualSpeed; self.y += dy / distance * actualSpeed; } // Ghostly hovering animation body.y = Math.sin(LK.ticks / 20) * 5; bottom.y = 70 + Math.sin(LK.ticks / 20) * 5; leftEye.y = -30 + Math.sin(LK.ticks / 25) * 3; rightEye.y = -30 + Math.sin(LK.ticks / 25) * 3; mouth.y = 30 + Math.sin(LK.ticks / 22) * 3; }; self.setCaught = function (isCaught) { self.isCaught = isCaught; if (isCaught) { tween(self, { alpha: 0.6, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, easing: tween.easeOut }); } else { tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOut }); } }; self.down = function (x, y, obj) { if (!self.isCaught) { // Change direction when tapped self.fleeFromPoint(x, y); // Start dragging if close enough var localPoint = self.toLocal({ x: x, y: y }); var distance = Math.sqrt(localPoint.x * localPoint.x + localPoint.y * localPoint.y); if (distance < 100) { game.dragTarget = self; } } }; // Initialize with random target self.setRandomTarget(); return self; }); var PowerUp = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'slow'; var asset = self.type === 'slow' ? 'powerupSlow' : 'powerupExpand'; var graphic = self.attachAsset(asset, { anchorX: 0.5, anchorY: 0.5 }); self.lifespan = 300; // 5 seconds at 60fps self.update = function () { self.lifespan--; // Fade out when nearing the end of lifespan if (self.lifespan < 60) { graphic.alpha = self.lifespan / 60; } // Hover animation self.y += Math.sin(LK.ticks / 10) * 0.5; // Remove when expired if (self.lifespan <= 0) { self.destroy(); var index = powerups.indexOf(self); if (index !== -1) { powerups.splice(index, 1); } } }; self.down = function (x, y, obj) { // Collect the powerup LK.getSound('powerup').play(); if (self.type === 'slow') { ghost.applySlowEffect(3000); // 3 seconds slow } else if (self.type === 'expand') { captureZone.expandZone(3000); // 3 seconds expanded } // Remove powerup self.destroy(); var index = powerups.indexOf(self); if (index !== -1) { powerups.splice(index, 1); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x6a89cc }); /**** * Game Code ****/ // Variables to track game state var score = 0; var ghostsCaught = 0; var gameTime = 0; var level = 1; var powerups = []; var powerupTimer = 0; var ghostElusiveness = 1.0; // Create scoreboard var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 20; // Create level display var levelText = new Text2('Level: 1', { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(0, 0); LK.gui.topRight.addChild(levelText); levelText.x = -200; levelText.y = 30; // Create time display var timeText = new Text2('Time: 0', { size: 60, fill: 0xFFFFFF }); timeText.anchor.set(1, 0); LK.gui.topRight.addChild(timeText); timeText.x = -20; timeText.y = 30; // Create capture zone (heart) var captureZone = new CaptureZone(); captureZone.x = 2048 / 2; captureZone.y = 2732 / 2; game.addChild(captureZone); // Create ghost var ghost = new Ghost(); ghost.x = 2048 / 4; ghost.y = 2732 / 4; game.addChild(ghost); // Track dragging game.dragTarget = null; // Handle touch/mouse events game.down = function (x, y, obj) { // This will be primarily handled by the ghost's own down method if (!game.dragTarget) { var dragDist = Math.sqrt(Math.pow(x - captureZone.x, 2) + Math.pow(y - captureZone.y, 2)); if (dragDist < 150) { game.dragTarget = captureZone; } } }; game.move = function (x, y, obj) { if (game.dragTarget) { game.dragTarget.x = x; game.dragTarget.y = y; // Keep within screen bounds game.dragTarget.x = Math.max(150, Math.min(2048 - 150, game.dragTarget.x)); game.dragTarget.y = Math.max(150, Math.min(2732 - 150, game.dragTarget.y)); } // If dragging ghost, make the ghost flee if (game.dragTarget !== ghost && !ghost.isCaught) { ghost.fleeFromPoint(x, y); } }; game.up = function (x, y, obj) { game.dragTarget = null; }; function spawnPowerup() { var type = Math.random() > 0.5 ? 'slow' : 'expand'; var powerup = new PowerUp(type); // Position away from edges and the current ghost position var minDistance = 300; var maxTries = 10; var tries = 0; var validPosition = false; while (!validPosition && tries < maxTries) { powerup.x = Math.random() * (2048 - 300) + 150; powerup.y = Math.random() * (2732 - 300) + 150; var distanceToGhost = Math.sqrt(Math.pow(powerup.x - ghost.x, 2) + Math.pow(powerup.y - ghost.y, 2)); var distanceToCaptureZone = Math.sqrt(Math.pow(powerup.x - captureZone.x, 2) + Math.pow(powerup.y - captureZone.y, 2)); if (distanceToGhost > minDistance && distanceToCaptureZone > minDistance) { validPosition = true; } tries++; } powerups.push(powerup); game.addChild(powerup); // Reset powerup timer powerupTimer = Math.floor(Math.random() * 300) + 300; // 5-10 seconds } function updateLevel() { level = Math.floor(ghostsCaught / 5) + 1; levelText.setText('Level: ' + level); // Increase ghost elusiveness with level ghostElusiveness = 1.0 + (level - 1) * 0.15; ghost.elusiveness = ghostElusiveness; // Visual feedback for level up if (ghostsCaught % 5 === 0 && ghostsCaught > 0) { LK.effects.flashScreen(0xffff00, 500); } } function checkGhostCapture() { if (ghost.isCaught) { return; } var distance = Math.sqrt(Math.pow(ghost.x - captureZone.x, 2) + Math.pow(ghost.y - captureZone.y, 2)); var captureRadius = captureZone.isExpanded ? 150 * 1.5 : 150; if (distance < captureRadius) { // Ghost is caught! ghost.setCaught(true); LK.getSound('catch').play(); captureZone.pulse(); // Award points score += level * 10; scoreText.setText('Score: ' + score); LK.setScore(score); // Increment captures count ghostsCaught++; updateLevel(); // Release ghost after a short delay LK.setTimeout(function () { ghost.setCaught(false); // Move ghost to random position ghost.x = Math.random() * (2048 - 400) + 200; ghost.y = Math.random() * (2732 - 400) + 200; ghost.setRandomTarget(); LK.getSound('escaped').play(); }, 1500); } } // Save high score when game over function saveHighScore() { var currentHighScore = storage.highScore || 0; if (score > currentHighScore) { storage.highScore = score; } } // Update function called every frame game.update = function () { // Update game time (in seconds) if (LK.ticks % 60 === 0) { gameTime++; timeText.setText('Time: ' + gameTime); } // Check if ghost is in capture zone checkGhostCapture(); // Update powerup timer and spawn new ones powerupTimer--; if (powerupTimer <= 0 && powerups.length < 2) { spawnPowerup(); } // Game over condition - if player reaches very high level or time runs out if (level >= 20) { saveHighScore(); LK.showYouWin(); } }; // Start background music LK.playMusic('bgMusic', { 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 CaptureZone = Container.expand(function () {
var self = Container.call(this);
var heart = self.attachAsset('heartCaptureZone', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.isExpanded = false;
self.baseScale = 1;
self.expandZone = function (duration) {
self.isExpanded = true;
var targetScale = self.baseScale * 1.5;
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 200,
easing: tween.easeOut
});
LK.setTimeout(function () {
self.isExpanded = false;
tween(self, {
scaleX: self.baseScale,
scaleY: self.baseScale
}, {
duration: 200,
easing: tween.easeIn
});
}, duration);
};
self.pulse = function () {
tween(heart, {
alpha: 0.9,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(heart, {
alpha: 0.7,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
};
return self;
});
var Ghost = Container.expand(function () {
var self = Container.call(this);
// Ghost body parts
var body = self.attachAsset('ghostBody', {
anchorX: 0.5,
anchorY: 0.5
});
var bottom = self.attachAsset('ghostBottom', {
anchorX: 0.5,
anchorY: 0,
y: 70
});
var leftEye = self.attachAsset('ghostEye', {
anchorX: 0.5,
anchorY: 0.5,
x: -40,
y: -30
});
var rightEye = self.attachAsset('ghostEye', {
anchorX: 0.5,
anchorY: 0.5,
x: 40,
y: -30
});
var mouth = self.attachAsset('ghostMouth', {
anchorX: 0.5,
anchorY: 0.5,
y: 30
});
// Ghost properties
self.speed = 3;
self.targetX = 0;
self.targetY = 0;
self.changeDirectionTimer = 0;
self.elusiveness = 1.0;
self.isCaught = false;
self.isSlowed = false;
self.setRandomTarget = function () {
self.targetX = Math.random() * (2048 - 400) + 200;
self.targetY = Math.random() * (2732 - 400) + 200;
self.changeDirectionTimer = Math.floor(Math.random() * 120) + 60;
};
self.fleeFromPoint = function (x, y) {
if (!self.isSlowed) {
var dx = self.x - x;
var dy = self.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 400) {
self.targetX = self.x + dx / distance * 500;
self.targetY = self.y + dy / distance * 500;
// Constrain to screen boundaries
self.targetX = Math.max(100, Math.min(2048 - 100, self.targetX));
self.targetY = Math.max(100, Math.min(2732 - 100, self.targetY));
self.changeDirectionTimer = 60;
}
}
};
self.applySlowEffect = function (duration) {
self.isSlowed = true;
tween(self, {
tint: 0x00ffff
}, {
duration: 200
});
LK.setTimeout(function () {
self.isSlowed = false;
tween(self, {
tint: 0xffffff
}, {
duration: 200
});
}, duration);
};
self.update = function () {
if (self.isCaught) {
return;
}
self.changeDirectionTimer--;
if (self.changeDirectionTimer <= 0) {
self.setRandomTarget();
}
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
var actualSpeed = self.isSlowed ? self.speed / 2 : self.speed * self.elusiveness;
self.x += dx / distance * actualSpeed;
self.y += dy / distance * actualSpeed;
}
// Ghostly hovering animation
body.y = Math.sin(LK.ticks / 20) * 5;
bottom.y = 70 + Math.sin(LK.ticks / 20) * 5;
leftEye.y = -30 + Math.sin(LK.ticks / 25) * 3;
rightEye.y = -30 + Math.sin(LK.ticks / 25) * 3;
mouth.y = 30 + Math.sin(LK.ticks / 22) * 3;
};
self.setCaught = function (isCaught) {
self.isCaught = isCaught;
if (isCaught) {
tween(self, {
alpha: 0.6,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(self, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.down = function (x, y, obj) {
if (!self.isCaught) {
// Change direction when tapped
self.fleeFromPoint(x, y);
// Start dragging if close enough
var localPoint = self.toLocal({
x: x,
y: y
});
var distance = Math.sqrt(localPoint.x * localPoint.x + localPoint.y * localPoint.y);
if (distance < 100) {
game.dragTarget = self;
}
}
};
// Initialize with random target
self.setRandomTarget();
return self;
});
var PowerUp = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'slow';
var asset = self.type === 'slow' ? 'powerupSlow' : 'powerupExpand';
var graphic = self.attachAsset(asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.lifespan = 300; // 5 seconds at 60fps
self.update = function () {
self.lifespan--;
// Fade out when nearing the end of lifespan
if (self.lifespan < 60) {
graphic.alpha = self.lifespan / 60;
}
// Hover animation
self.y += Math.sin(LK.ticks / 10) * 0.5;
// Remove when expired
if (self.lifespan <= 0) {
self.destroy();
var index = powerups.indexOf(self);
if (index !== -1) {
powerups.splice(index, 1);
}
}
};
self.down = function (x, y, obj) {
// Collect the powerup
LK.getSound('powerup').play();
if (self.type === 'slow') {
ghost.applySlowEffect(3000); // 3 seconds slow
} else if (self.type === 'expand') {
captureZone.expandZone(3000); // 3 seconds expanded
}
// Remove powerup
self.destroy();
var index = powerups.indexOf(self);
if (index !== -1) {
powerups.splice(index, 1);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x6a89cc
});
/****
* Game Code
****/
// Variables to track game state
var score = 0;
var ghostsCaught = 0;
var gameTime = 0;
var level = 1;
var powerups = [];
var powerupTimer = 0;
var ghostElusiveness = 1.0;
// Create scoreboard
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 20;
// Create level display
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topRight.addChild(levelText);
levelText.x = -200;
levelText.y = 30;
// Create time display
var timeText = new Text2('Time: 0', {
size: 60,
fill: 0xFFFFFF
});
timeText.anchor.set(1, 0);
LK.gui.topRight.addChild(timeText);
timeText.x = -20;
timeText.y = 30;
// Create capture zone (heart)
var captureZone = new CaptureZone();
captureZone.x = 2048 / 2;
captureZone.y = 2732 / 2;
game.addChild(captureZone);
// Create ghost
var ghost = new Ghost();
ghost.x = 2048 / 4;
ghost.y = 2732 / 4;
game.addChild(ghost);
// Track dragging
game.dragTarget = null;
// Handle touch/mouse events
game.down = function (x, y, obj) {
// This will be primarily handled by the ghost's own down method
if (!game.dragTarget) {
var dragDist = Math.sqrt(Math.pow(x - captureZone.x, 2) + Math.pow(y - captureZone.y, 2));
if (dragDist < 150) {
game.dragTarget = captureZone;
}
}
};
game.move = function (x, y, obj) {
if (game.dragTarget) {
game.dragTarget.x = x;
game.dragTarget.y = y;
// Keep within screen bounds
game.dragTarget.x = Math.max(150, Math.min(2048 - 150, game.dragTarget.x));
game.dragTarget.y = Math.max(150, Math.min(2732 - 150, game.dragTarget.y));
}
// If dragging ghost, make the ghost flee
if (game.dragTarget !== ghost && !ghost.isCaught) {
ghost.fleeFromPoint(x, y);
}
};
game.up = function (x, y, obj) {
game.dragTarget = null;
};
function spawnPowerup() {
var type = Math.random() > 0.5 ? 'slow' : 'expand';
var powerup = new PowerUp(type);
// Position away from edges and the current ghost position
var minDistance = 300;
var maxTries = 10;
var tries = 0;
var validPosition = false;
while (!validPosition && tries < maxTries) {
powerup.x = Math.random() * (2048 - 300) + 150;
powerup.y = Math.random() * (2732 - 300) + 150;
var distanceToGhost = Math.sqrt(Math.pow(powerup.x - ghost.x, 2) + Math.pow(powerup.y - ghost.y, 2));
var distanceToCaptureZone = Math.sqrt(Math.pow(powerup.x - captureZone.x, 2) + Math.pow(powerup.y - captureZone.y, 2));
if (distanceToGhost > minDistance && distanceToCaptureZone > minDistance) {
validPosition = true;
}
tries++;
}
powerups.push(powerup);
game.addChild(powerup);
// Reset powerup timer
powerupTimer = Math.floor(Math.random() * 300) + 300; // 5-10 seconds
}
function updateLevel() {
level = Math.floor(ghostsCaught / 5) + 1;
levelText.setText('Level: ' + level);
// Increase ghost elusiveness with level
ghostElusiveness = 1.0 + (level - 1) * 0.15;
ghost.elusiveness = ghostElusiveness;
// Visual feedback for level up
if (ghostsCaught % 5 === 0 && ghostsCaught > 0) {
LK.effects.flashScreen(0xffff00, 500);
}
}
function checkGhostCapture() {
if (ghost.isCaught) {
return;
}
var distance = Math.sqrt(Math.pow(ghost.x - captureZone.x, 2) + Math.pow(ghost.y - captureZone.y, 2));
var captureRadius = captureZone.isExpanded ? 150 * 1.5 : 150;
if (distance < captureRadius) {
// Ghost is caught!
ghost.setCaught(true);
LK.getSound('catch').play();
captureZone.pulse();
// Award points
score += level * 10;
scoreText.setText('Score: ' + score);
LK.setScore(score);
// Increment captures count
ghostsCaught++;
updateLevel();
// Release ghost after a short delay
LK.setTimeout(function () {
ghost.setCaught(false);
// Move ghost to random position
ghost.x = Math.random() * (2048 - 400) + 200;
ghost.y = Math.random() * (2732 - 400) + 200;
ghost.setRandomTarget();
LK.getSound('escaped').play();
}, 1500);
}
}
// Save high score when game over
function saveHighScore() {
var currentHighScore = storage.highScore || 0;
if (score > currentHighScore) {
storage.highScore = score;
}
}
// Update function called every frame
game.update = function () {
// Update game time (in seconds)
if (LK.ticks % 60 === 0) {
gameTime++;
timeText.setText('Time: ' + gameTime);
}
// Check if ghost is in capture zone
checkGhostCapture();
// Update powerup timer and spawn new ones
powerupTimer--;
if (powerupTimer <= 0 && powerups.length < 2) {
spawnPowerup();
}
// Game over condition - if player reaches very high level or time runs out
if (level >= 20) {
saveHighScore();
LK.showYouWin();
}
};
// Start background music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});