/****
* 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
}
});