/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Broom = Container.expand(function () {
var self = Container.call(this);
var broomGraphics = self.attachAsset('broom', {
anchorX: 0.5,
anchorY: 0.8,
// Anchor at bottom for sweeping feel
rotation: Math.PI / 4 // 45-degree angle
});
// Trail to make the broom more visible
self.lastPositions = [];
self.maxTrailLength = 5;
// Sweeping animation
self.isSweeping = false;
self.sweepAngle = Math.PI / 4;
self.sweepDirection = 1;
self.sweepSpeed = 0.1;
self.startSweeping = function () {
self.isSweeping = true;
};
self.stopSweeping = function () {
self.isSweeping = false;
// Reset broom rotation
tween(broomGraphics, {
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut
});
};
self.update = function () {
// Update trail
if (self.lastPositions.length >= self.maxTrailLength) {
self.lastPositions.shift();
}
self.lastPositions.push({
x: self.x,
y: self.y
});
// Update sweeping animation
if (self.isSweeping) {
broomGraphics.rotation += self.sweepDirection * self.sweepSpeed;
if (broomGraphics.rotation > Math.PI / 3 || broomGraphics.rotation < Math.PI / 6) {
self.sweepDirection *= -1;
}
}
};
return self;
});
var Ghost = Container.expand(function () {
var self = Container.call(this);
var ghostGraphics = self.attachAsset('ghost', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Ghost properties
self.speed = 3;
self.targetX = 0;
self.targetY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
// Movement behavior
self.moveRandomly = function () {
if (Math.random() < 0.02 || Math.abs(self.x - self.targetX) < 10 && Math.abs(self.y - self.targetY) < 10) {
self.setTarget(Math.random() * 1948 + 50,
// Keep away from edges
Math.random() * 2532 + 100 // Keep away from top
);
}
};
// Update every frame
self.update = function () {
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Bobbing animation
self.y += Math.sin(LK.ticks / 20) * 0.5;
// Fading in and out slightly
ghostGraphics.alpha = 0.7 + Math.sin(LK.ticks / 30) * 0.3;
};
// Flash and disappear when captured
self.capture = function () {
var captureEffect = LK.getAsset('captureEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.addChild(captureEffect);
// Capture animation
tween(captureEffect, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
captureEffect.destroy();
}
});
// Ghost disappearing animation
tween(ghostGraphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 500,
easing: tween.easeIn
});
// Play capture sound
LK.getSound('ghostCapture').play();
return true;
};
return self;
});
var HUD = Container.expand(function () {
var self = Container.call(this);
// Score display
self.scoreText = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
self.scoreText.anchor.set(0.5, 0);
// Level display
self.levelText = new Text2('Level: 1', {
size: 80,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0, 0);
// Lives display
self.livesText = new Text2('Lives: 3', {
size: 80,
fill: 0xFFFFFF
});
self.livesText.anchor.set(1, 0);
// Update HUD
self.updateScore = function (score) {
self.scoreText.setText(score.toString());
};
self.updateLevel = function (level) {
self.levelText.setText('Level: ' + level);
};
self.updateLives = function (lives) {
self.livesText.setText('Lives: ' + lives);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var score = 0;
var level = 1;
var lives = 3;
var ghostCaptureRadius = 100; // How close broom needs to be to ghost to capture it
var ghostCount = 0;
var maxGhosts = 5; // Starting max ghosts
var ghostSpawnInterval = 3000; // ms between ghost spawns
var lastGhostSpawnTime = 0;
var isGameActive = true;
// Set up background
var houseBackground = LK.getAsset('house', {
anchorX: 0,
anchorY: 0
});
game.addChild(houseBackground);
// Create player's broom
var broom = new Broom();
game.addChild(broom);
broom.x = 2048 / 2;
broom.y = 2732 / 2;
// Create ghosts array
var ghosts = [];
// Create HUD
var hud = new HUD();
LK.gui.top.addChild(hud.scoreText);
LK.gui.topLeft.addChild(hud.levelText);
LK.gui.topRight.addChild(hud.livesText);
// Position HUD elements
hud.scoreText.x = 2048 / 2;
hud.scoreText.y = 20;
hud.levelText.x = 120; // Keep away from top-left menu icon
hud.levelText.y = 20;
hud.livesText.x = 2048 - 20;
hud.livesText.y = 20;
// Update HUD with initial values
hud.updateScore(score);
hud.updateLevel(level);
hud.updateLives(lives);
// Play background music
LK.playMusic('hauntedMusic');
// Spawn a new ghost
function spawnGhost() {
if (ghosts.length >= maxGhosts || !isGameActive) return;
var ghost = new Ghost();
// Decide spawn location (from edge of screen)
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
ghost.x = Math.random() * 2048;
ghost.y = -100;
break;
case 1:
// Right
ghost.x = 2048 + 100;
ghost.y = Math.random() * 2732;
break;
case 2:
// Bottom
ghost.x = Math.random() * 2048;
ghost.y = 2732 + 100;
break;
case 3:
// Left
ghost.x = -100;
ghost.y = Math.random() * 2732;
break;
}
// Initial target is center of screen
ghost.setTarget(Math.random() * 1948 + 50, Math.random() * 2532 + 100);
// Speed increases with level
ghost.speed = 3 + level * 0.5;
ghosts.push(ghost);
game.addChild(ghost);
ghostCount++;
// Play ghost appear sound
LK.getSound('ghostAppear').play();
// Ghost fades in
ghost.alpha = 0;
tween(ghost, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
}
// Handle input for broom movement
var isDragging = false;
game.down = function (x, y) {
isDragging = true;
broom.x = x;
broom.y = y;
broom.startSweeping();
};
game.move = function (x, y) {
if (isDragging) {
broom.x = x;
broom.y = y;
}
};
game.up = function () {
isDragging = false;
broom.stopSweeping();
};
// Check if player captured a ghost
function checkGhostCapture() {
for (var i = ghosts.length - 1; i >= 0; i--) {
var ghost = ghosts[i];
var distance = Math.sqrt(Math.pow(ghost.x - broom.x, 2) + Math.pow(ghost.y - broom.y, 2));
if (distance < ghostCaptureRadius && isDragging) {
// Capture the ghost
if (ghost.capture()) {
score += 10 * level;
hud.updateScore(score);
// Remove the ghost after capture animation
LK.setTimeout(function () {
game.removeChild(ghost);
ghosts.splice(i, 1);
ghostCount--;
// Check for level completion
if (ghostCount === 0 && isGameActive) {
levelUp();
}
}, 500);
}
}
}
}
// Check if ghost hit the player
function checkPlayerCollision() {
if (!isDragging) return; // Only check collisions when player is active
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
var distance = Math.sqrt(Math.pow(ghost.x - broom.x, 2) + Math.pow(ghost.y - broom.y, 2));
if (distance < 40 && !ghost.isBeingCaptured) {
// Player hit by ghost
LK.getSound('playerHit').play();
LK.effects.flashScreen(0xFF0000, 500);
lives--;
hud.updateLives(lives);
// Check game over
if (lives <= 0) {
isGameActive = false;
LK.setScore(score);
LK.showGameOver();
}
// Temporary invulnerability
break;
}
}
}
// Level up function
function levelUp() {
level++;
hud.updateLevel(level);
// Increase difficulty
maxGhosts = 5 + level;
ghostSpawnInterval = Math.max(500, 3000 - level * 200);
// Visual feedback for level up
LK.effects.flashScreen(0x00FF00, 500);
// Reset ghost counter
ghostCount = 0;
}
// Main game update loop
game.update = function () {
// Update broom
broom.update();
// Update all ghosts
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
ghost.moveRandomly();
ghost.update();
}
// Spawn ghosts on interval
var currentTime = Date.now();
if (currentTime - lastGhostSpawnTime > ghostSpawnInterval && isGameActive) {
spawnGhost();
lastGhostSpawnTime = currentTime;
}
// Check for ghost captures
checkGhostCapture();
// Check for player collisions
checkPlayerCollision();
};
// Initialize by spawning first ghost
lastGhostSpawnTime = Date.now();
spawnGhost(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Broom = Container.expand(function () {
var self = Container.call(this);
var broomGraphics = self.attachAsset('broom', {
anchorX: 0.5,
anchorY: 0.8,
// Anchor at bottom for sweeping feel
rotation: Math.PI / 4 // 45-degree angle
});
// Trail to make the broom more visible
self.lastPositions = [];
self.maxTrailLength = 5;
// Sweeping animation
self.isSweeping = false;
self.sweepAngle = Math.PI / 4;
self.sweepDirection = 1;
self.sweepSpeed = 0.1;
self.startSweeping = function () {
self.isSweeping = true;
};
self.stopSweeping = function () {
self.isSweeping = false;
// Reset broom rotation
tween(broomGraphics, {
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut
});
};
self.update = function () {
// Update trail
if (self.lastPositions.length >= self.maxTrailLength) {
self.lastPositions.shift();
}
self.lastPositions.push({
x: self.x,
y: self.y
});
// Update sweeping animation
if (self.isSweeping) {
broomGraphics.rotation += self.sweepDirection * self.sweepSpeed;
if (broomGraphics.rotation > Math.PI / 3 || broomGraphics.rotation < Math.PI / 6) {
self.sweepDirection *= -1;
}
}
};
return self;
});
var Ghost = Container.expand(function () {
var self = Container.call(this);
var ghostGraphics = self.attachAsset('ghost', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Ghost properties
self.speed = 3;
self.targetX = 0;
self.targetY = 0;
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
// Movement behavior
self.moveRandomly = function () {
if (Math.random() < 0.02 || Math.abs(self.x - self.targetX) < 10 && Math.abs(self.y - self.targetY) < 10) {
self.setTarget(Math.random() * 1948 + 50,
// Keep away from edges
Math.random() * 2532 + 100 // Keep away from top
);
}
};
// Update every frame
self.update = function () {
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
// Bobbing animation
self.y += Math.sin(LK.ticks / 20) * 0.5;
// Fading in and out slightly
ghostGraphics.alpha = 0.7 + Math.sin(LK.ticks / 30) * 0.3;
};
// Flash and disappear when captured
self.capture = function () {
var captureEffect = LK.getAsset('captureEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.addChild(captureEffect);
// Capture animation
tween(captureEffect, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
captureEffect.destroy();
}
});
// Ghost disappearing animation
tween(ghostGraphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 500,
easing: tween.easeIn
});
// Play capture sound
LK.getSound('ghostCapture').play();
return true;
};
return self;
});
var HUD = Container.expand(function () {
var self = Container.call(this);
// Score display
self.scoreText = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
self.scoreText.anchor.set(0.5, 0);
// Level display
self.levelText = new Text2('Level: 1', {
size: 80,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0, 0);
// Lives display
self.livesText = new Text2('Lives: 3', {
size: 80,
fill: 0xFFFFFF
});
self.livesText.anchor.set(1, 0);
// Update HUD
self.updateScore = function (score) {
self.scoreText.setText(score.toString());
};
self.updateLevel = function (level) {
self.levelText.setText('Level: ' + level);
};
self.updateLives = function (lives) {
self.livesText.setText('Lives: ' + lives);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var score = 0;
var level = 1;
var lives = 3;
var ghostCaptureRadius = 100; // How close broom needs to be to ghost to capture it
var ghostCount = 0;
var maxGhosts = 5; // Starting max ghosts
var ghostSpawnInterval = 3000; // ms between ghost spawns
var lastGhostSpawnTime = 0;
var isGameActive = true;
// Set up background
var houseBackground = LK.getAsset('house', {
anchorX: 0,
anchorY: 0
});
game.addChild(houseBackground);
// Create player's broom
var broom = new Broom();
game.addChild(broom);
broom.x = 2048 / 2;
broom.y = 2732 / 2;
// Create ghosts array
var ghosts = [];
// Create HUD
var hud = new HUD();
LK.gui.top.addChild(hud.scoreText);
LK.gui.topLeft.addChild(hud.levelText);
LK.gui.topRight.addChild(hud.livesText);
// Position HUD elements
hud.scoreText.x = 2048 / 2;
hud.scoreText.y = 20;
hud.levelText.x = 120; // Keep away from top-left menu icon
hud.levelText.y = 20;
hud.livesText.x = 2048 - 20;
hud.livesText.y = 20;
// Update HUD with initial values
hud.updateScore(score);
hud.updateLevel(level);
hud.updateLives(lives);
// Play background music
LK.playMusic('hauntedMusic');
// Spawn a new ghost
function spawnGhost() {
if (ghosts.length >= maxGhosts || !isGameActive) return;
var ghost = new Ghost();
// Decide spawn location (from edge of screen)
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
ghost.x = Math.random() * 2048;
ghost.y = -100;
break;
case 1:
// Right
ghost.x = 2048 + 100;
ghost.y = Math.random() * 2732;
break;
case 2:
// Bottom
ghost.x = Math.random() * 2048;
ghost.y = 2732 + 100;
break;
case 3:
// Left
ghost.x = -100;
ghost.y = Math.random() * 2732;
break;
}
// Initial target is center of screen
ghost.setTarget(Math.random() * 1948 + 50, Math.random() * 2532 + 100);
// Speed increases with level
ghost.speed = 3 + level * 0.5;
ghosts.push(ghost);
game.addChild(ghost);
ghostCount++;
// Play ghost appear sound
LK.getSound('ghostAppear').play();
// Ghost fades in
ghost.alpha = 0;
tween(ghost, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
}
// Handle input for broom movement
var isDragging = false;
game.down = function (x, y) {
isDragging = true;
broom.x = x;
broom.y = y;
broom.startSweeping();
};
game.move = function (x, y) {
if (isDragging) {
broom.x = x;
broom.y = y;
}
};
game.up = function () {
isDragging = false;
broom.stopSweeping();
};
// Check if player captured a ghost
function checkGhostCapture() {
for (var i = ghosts.length - 1; i >= 0; i--) {
var ghost = ghosts[i];
var distance = Math.sqrt(Math.pow(ghost.x - broom.x, 2) + Math.pow(ghost.y - broom.y, 2));
if (distance < ghostCaptureRadius && isDragging) {
// Capture the ghost
if (ghost.capture()) {
score += 10 * level;
hud.updateScore(score);
// Remove the ghost after capture animation
LK.setTimeout(function () {
game.removeChild(ghost);
ghosts.splice(i, 1);
ghostCount--;
// Check for level completion
if (ghostCount === 0 && isGameActive) {
levelUp();
}
}, 500);
}
}
}
}
// Check if ghost hit the player
function checkPlayerCollision() {
if (!isDragging) return; // Only check collisions when player is active
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
var distance = Math.sqrt(Math.pow(ghost.x - broom.x, 2) + Math.pow(ghost.y - broom.y, 2));
if (distance < 40 && !ghost.isBeingCaptured) {
// Player hit by ghost
LK.getSound('playerHit').play();
LK.effects.flashScreen(0xFF0000, 500);
lives--;
hud.updateLives(lives);
// Check game over
if (lives <= 0) {
isGameActive = false;
LK.setScore(score);
LK.showGameOver();
}
// Temporary invulnerability
break;
}
}
}
// Level up function
function levelUp() {
level++;
hud.updateLevel(level);
// Increase difficulty
maxGhosts = 5 + level;
ghostSpawnInterval = Math.max(500, 3000 - level * 200);
// Visual feedback for level up
LK.effects.flashScreen(0x00FF00, 500);
// Reset ghost counter
ghostCount = 0;
}
// Main game update loop
game.update = function () {
// Update broom
broom.update();
// Update all ghosts
for (var i = 0; i < ghosts.length; i++) {
var ghost = ghosts[i];
ghost.moveRandomly();
ghost.update();
}
// Spawn ghosts on interval
var currentTime = Date.now();
if (currentTime - lastGhostSpawnTime > ghostSpawnInterval && isGameActive) {
spawnGhost();
lastGhostSpawnTime = currentTime;
}
// Check for ghost captures
checkGhostCapture();
// Check for player collisions
checkPlayerCollision();
};
// Initialize by spawning first ghost
lastGhostSpawnTime = Date.now();
spawnGhost();