/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Create bomb visual
var bombGraphic = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
}); // Use the dedicated bomb asset instead of repurposed laser
// Create eyes for character-like appearance
var leftEye = new Container();
var leftEyeWhite = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF,
scaleX: 0.2,
scaleY: 0.2
});
var leftEyePupil = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x0000FF,
scaleX: 0.1,
scaleY: 0.1
});
leftEye.addChild(leftEyeWhite);
leftEye.addChild(leftEyePupil);
leftEye.x = -20;
leftEye.y = -15;
var rightEye = new Container();
var rightEyeWhite = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF,
scaleX: 0.2,
scaleY: 0.2
});
var rightEyePupil = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x0000FF,
scaleX: 0.1,
scaleY: 0.1
});
rightEye.addChild(rightEyeWhite);
rightEye.addChild(rightEyePupil);
rightEye.x = 20;
rightEye.y = -15;
// Create a mouth that looks worried
var mouth = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000,
scaleX: 0.4,
scaleY: 0.1
});
mouth.y = 20;
self.addChild(leftEye);
self.addChild(rightEye);
self.addChild(mouth);
// Add timer visual
var timerText = new Text2("1.5", {
size: 40,
fill: 0xFF0000
});
timerText.anchor.set(0.5, 0.5);
timerText.y = -40; // Position above the bomb
self.addChild(timerText);
// Bomb properties
self.active = true;
self.exploding = false;
self.explosionRadius = 100; // Smaller explosion radius (was 150)
self.timer = 1.5; // seconds until explosion
self.lastIntersecting = false;
// Setup explosion visual (initially hidden)
var explosionGraphic = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 0.7,
// Scale down explosion graphic (70% of original size)
scaleY: 0.7 // Scale down explosion graphic (70% of original size)
});
self.update = function () {
if (!self.active) return;
if (!self.exploding) {
// Update timer
self.timer -= 1 / 60; // 60fps
timerText.setText(Math.max(0, self.timer).toFixed(1));
// Pulse effect as timer gets lower
var pulseScale = 1.0 + Math.sin(LK.ticks / 5) * 0.1 * (1.5 - self.timer) / 1.5;
bombGraphic.scale.set(pulseScale, pulseScale);
// Make eyes look more panicked as timer decreases
var eyeScale = 0.2 + (1.5 - self.timer) * 0.1;
leftEyeWhite.scale.set(eyeScale, eyeScale);
rightEyeWhite.scale.set(eyeScale, eyeScale);
// Make mouth more worried
mouth.scaleX = 0.4 + (1.5 - self.timer) * 0.2;
// Start explosion when timer reaches 0
if (self.timer <= 0) {
self.startExplosion();
}
}
};
self.startExplosion = function () {
self.exploding = true;
timerText.setText("");
bombGraphic.alpha = 0;
leftEye.alpha = 0;
rightEye.alpha = 0;
mouth.alpha = 0;
// Show explosion
explosionGraphic.alpha = 1;
// Immediately set active to false
// This prevents collision detection from triggering after explosion is complete
self.active = false;
// Explosion animation
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.4,
// Smaller expansion (was 2)
scaleY: 1.4 // Smaller expansion (was 2)
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Make sure explosion is completely inactive after animation finishes
self.exploding = false;
if (self && self.parent) {
self.destroy();
}
}
});
};
return self;
});
var CubeRoom = Container.expand(function () {
var self = Container.call(this);
// Room properties
self.walls = [];
self.wallThickness = 50;
self.roomWidth = 1600;
self.roomHeight = 1600;
self.roomX = 0;
self.roomY = 0;
self.init = function (x, y, width, height) {
self.roomX = x || (2048 - self.roomWidth) / 2;
self.roomY = y || 800;
self.roomWidth = width || 1200; // Smaller default room width
self.roomHeight = height || 1200; // Smaller default room height
// Create walls
self.createWalls();
};
self.createWalls = function () {
// Top wall
var topWall = new Wall();
topWall.init(self.roomWidth, self.wallThickness, 0x555555);
topWall.x = self.roomX;
topWall.y = self.roomY;
self.addChild(topWall);
self.walls.push(topWall);
// Bottom wall
var bottomWall = new Wall();
bottomWall.init(self.roomWidth, self.wallThickness, 0x555555);
bottomWall.x = self.roomX;
bottomWall.y = self.roomY + self.roomHeight - self.wallThickness;
self.addChild(bottomWall);
self.walls.push(bottomWall);
// Left wall
var leftWall = new Wall();
leftWall.init(self.wallThickness, self.roomHeight, 0x444444);
leftWall.x = self.roomX;
leftWall.y = self.roomY;
self.addChild(leftWall);
self.walls.push(leftWall);
// Right wall
var rightWall = new Wall();
rightWall.init(self.wallThickness, self.roomHeight, 0x444444);
rightWall.x = self.roomX + self.roomWidth - self.wallThickness;
rightWall.y = self.roomY;
self.addChild(rightWall);
self.walls.push(rightWall);
};
self.getWalls = function () {
return self.walls;
};
self.getInnerBounds = function () {
return {
x: self.roomX + self.wallThickness,
y: self.roomY + self.wallThickness,
width: self.roomWidth - self.wallThickness * 2,
height: self.roomHeight - self.wallThickness * 2
};
};
return self;
});
var HealthBar = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
var foreground = self.attachAsset('healthBarFg', {
anchorX: 0,
anchorY: 0
});
self.setHealth = function (percent) {
foreground.scale.x = Math.max(0, Math.min(1, percent));
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphic = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.invulnerable = false;
self.lastX = 0;
self.lastY = 0;
self.down = function (x, y, obj) {
self.dragging = true;
};
self.up = function (x, y, obj) {
self.dragging = false;
};
self.takeDamage = function () {
if (self.invulnerable) return;
// Set health to 0 directly to kill player immediately
self.health = 0;
LK.getSound('hit').play();
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// No need for invulnerability since player dies immediately
LK.getSound('gameOver').play();
LK.showGameOver();
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserGraphic = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
self.active = true;
self.direction = 1; // 1: vertical, 2: diagonal left, 3: diagonal right
self.speed = 15;
self.initialSpeed = 0; // Store initial speed for acceleration calculation
self.maxSpeed = 0; // Will be set to 4x initial speed
self.lastY = 0;
self.lastIntersecting = false;
self.setup = function (direction, speed) {
self.direction = direction || 1;
self.initialSpeed = speed ? speed / 4 : 3.75; // Start at 1/4 of the target speed
self.speed = self.initialSpeed;
self.maxSpeed = self.initialSpeed * 4; // Max speed is 4x the initial speed
if (self.direction === 1) {
// Vertical laser
laserGraphic.rotation = 0;
} else if (self.direction === 2) {
// Diagonal left
laserGraphic.rotation = Math.PI / 4;
} else if (self.direction === 3) {
// Diagonal right
laserGraphic.rotation = -Math.PI / 4;
}
// Start accelerating the laser using tween with faster acceleration
tween(self, {
speed: self.maxSpeed
}, {
duration: 1000,
// Accelerate over 1 second instead of 2
easing: tween.easeInQuad // More aggressive acceleration curve
});
};
self.update = function () {
self.lastY = self.y;
if (self.direction === 1) {
self.y += self.speed;
} else if (self.direction === 2) {
self.y += self.speed;
self.x -= self.speed * 0.8;
} else if (self.direction === 3) {
self.y += self.speed;
self.x += self.speed * 0.8;
}
};
return self;
});
var PlayerHealthDisplay = Container.expand(function () {
var self = Container.call(this);
var heartsContainer = new Container();
self.addChild(heartsContainer);
self.updateHealth = function (health) {
heartsContainer.removeChildren();
for (var i = 0; i < health; i++) {
var heartIcon = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scale: 0.3
});
heartIcon.x = i * 70;
heartsContainer.addChild(heartIcon);
}
};
return self;
});
var Skeleton = Container.expand(function () {
var self = Container.call(this);
// Create skeleton body
var skeletonGraphic = self.attachAsset('skeleton', {
anchorX: 0.5,
anchorY: 0.5
});
// Add jacket on top
var jacketGraphic = self.attachAsset('jacket', {
anchorX: 0.5,
anchorY: 0.5,
y: -20
});
self.maxHealth = 300;
self.health = self.maxHealth;
self.attackCooldown = 0;
self.attackPattern = 1;
self.lastAttackTime = 0;
self.warningIndicator = null;
self.nextAttackPosition = null;
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
LK.getSound('victory').play();
LK.showYouWin();
}
// Increase difficulty as health decreases
if (self.health < self.maxHealth * 0.7 && self.attackPattern === 1) {
self.attackPattern = 2; // More frequent attacks
} else if (self.health < self.maxHealth * 0.4 && self.attackPattern === 2) {
self.attackPattern = 3; // Even more frequent and complex attacks
}
};
self.update = function () {
self.attackCooldown--;
// Subtle movement
self.x += Math.sin(LK.ticks / 20) * 2;
// Update warning indicator position if it exists
if (self.warningIndicator && self.warningIndicator.parent) {
if (self.nextAttackPosition) {
self.warningIndicator.x = self.nextAttackPosition;
}
// Add subtle animation to make warning more noticeable
self.warningIndicator.y += Math.sin(LK.ticks / 10) * 0.7;
}
};
// Create attack based on current pattern
self.createAttack = function () {
if (self.attackCooldown > 0) return null;
var attack;
var now = Date.now();
// Ensure minimum time between attacks
if (now - self.lastAttackTime < 800) return null;
self.lastAttackTime = now;
// Check if we should create a bomb attack (if available and bombs are active)
if (bombAttackActive && Math.random() < 0.3) {
self.attackCooldown = 100; // Longer cooldown for bomb attacks
attack = {
type: 'bomb',
position: {
x: roomBounds.x + Math.random() * (roomBounds.width - 100) + 50,
y: roomBounds.y + Math.random() * (roomBounds.height - 100) + 50
}
};
return attack;
}
// Different attack patterns based on skeleton's health
if (self.attackPattern === 1) {
// Basic pattern: vertical lasers
self.attackCooldown = 60;
attack = {
type: 'laser',
direction: 1,
speed: 15,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 600 - 300 // Reduced position range for smaller room
}
};
} else if (self.attackPattern === 2) {
// Medium pattern: vertical and diagonal lasers
self.attackCooldown = 45;
var dir = Math.floor(Math.random() * 3) + 1;
attack = {
type: 'laser',
direction: dir,
speed: 18,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 700 - 350 // Reduced position range for smaller room
}
};
} else {
// Hard pattern: faster and more varied attacks
self.attackCooldown = 30;
var dir = Math.floor(Math.random() * 3) + 1;
attack = {
type: 'laser',
direction: dir,
speed: 22,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 800 - 400 // Reduced position range for smaller room
}
};
}
// Show warning for where the attack will come from
if (attack) {
// Remove any existing warning indicator
if (self.warningIndicator && self.warningIndicator.parent) {
self.warningIndicator.parent.removeChild(self.warningIndicator);
}
// Create a new warning indicator with enhanced visibility
self.warningIndicator = new Container();
var warningLine = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000,
// Red warning color for better visibility
scaleY: 2,
// Make it thicker
alpha: 0.8 // More visible alpha
});
// Create a background glow effect
var warningGlow = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFF00,
// Yellow glow
scaleY: 3,
// Even thicker for the glow
alpha: 0.4
});
// Position the warning indicator at the attack position
self.warningIndicator.x = attack.position.x;
self.warningIndicator.y = self.y + 150;
self.nextAttackPosition = attack.position.x;
// Adjust rotation based on attack direction
if (attack.direction === 1) {
warningLine.rotation = 0;
warningGlow.rotation = 0;
} else if (attack.direction === 2) {
warningLine.rotation = Math.PI / 4;
warningGlow.rotation = Math.PI / 4;
} else if (attack.direction === 3) {
warningLine.rotation = -Math.PI / 4;
warningGlow.rotation = -Math.PI / 4;
}
// Add the glow behind the warning line
self.warningIndicator.addChild(warningGlow);
self.warningIndicator.addChild(warningLine);
// Add text warning
var warningText = new Text2("!", {
size: 100,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0.5);
warningText.y = -warningLine.height * 0.6;
self.warningIndicator.addChild(warningText);
// Add warning to game immediately and make it visible longer
if (game) {
game.addChild(self.warningIndicator);
// Make the warning flash more dramatically
tween(warningLine, {
alpha: 0.3
}, {
duration: 300,
repeat: 6,
yoyo: true
});
// Pulse the glow
tween(warningGlow, {
scaleY: 4,
alpha: 0.2
}, {
duration: 500,
repeat: 4,
yoyo: true,
onComplete: function onComplete() {
if (self.warningIndicator && self.warningIndicator.parent) {
self.warningIndicator.parent.removeChild(self.warningIndicator);
}
}
});
}
}
return attack;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphic;
self.init = function (width, height, color) {
// Create a custom wall shape
wallGraphic = self.attachAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
// Set size and color
wallGraphic.width = width;
wallGraphic.height = height;
wallGraphic.tint = color || 0x888888;
};
return self;
});
/****
* Initialize Game
****/
// Game variables
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var heart;
var skeleton;
var healthBar;
var playerHealth;
var lasers = [];
var bombs = [];
var score = 0;
var attacksAvoided = 0;
var speedBoostActive = false;
var bombAttackActive = false;
var dragging = false;
var cubeRoom;
var wallCollisions = [];
// Set background color
game.setBackgroundColor(0x222222);
// Create cube room
cubeRoom = game.addChild(new CubeRoom());
cubeRoom.init((2048 - 1200) / 2, 700, 1200, 1200); // Smaller room size
var roomBounds = cubeRoom.getInnerBounds();
// Create player heart
heart = game.addChild(new Heart());
heart.x = 1024;
heart.y = roomBounds.y + roomBounds.height / 2;
// Create skeleton enemy
skeleton = game.addChild(new Skeleton());
skeleton.x = 1024;
skeleton.y = 400;
// Create health bar for skeleton
healthBar = game.addChild(new HealthBar());
healthBar.x = (2048 - 800) / 2;
healthBar.y = 200;
// Create player health display
playerHealth = game.addChild(new PlayerHealthDisplay());
playerHealth.x = 150;
playerHealth.y = 2600;
playerHealth.updateHealth(heart.health);
// Score text
var scoreTxt = new Text2('Attacks Avoided: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.x = 1024;
scoreTxt.y = 100;
game.addChild(scoreTxt);
// Instructions text
var instructionsTxt = new Text2('You are trapped in a cube room! Avoid attacks!', {
size: 50,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0);
instructionsTxt.x = 1024;
instructionsTxt.y = 2500;
game.addChild(instructionsTxt);
// Game movement handlers
game.move = function (x, y, obj) {
if (dragging) {
// Move heart to cursor position
heart.x = x;
heart.y = y;
// Keep heart within cube room bounds
var roomBounds = cubeRoom.getInnerBounds();
var halfHeartWidth = heart.width / 2;
var halfHeartHeight = heart.height / 2;
// Constrain movement to inside the room
heart.x = Math.max(roomBounds.x + halfHeartWidth, Math.min(heart.x, roomBounds.x + roomBounds.width - halfHeartWidth));
heart.y = Math.max(roomBounds.y + halfHeartHeight, Math.min(heart.y, roomBounds.y + roomBounds.height - halfHeartHeight));
}
};
game.down = function (x, y, obj) {
dragging = true;
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main game update loop
game.update = function () {
// Update heart
heart.update();
// Get room walls for collision checking
var walls = cubeRoom.getWalls();
var roomBounds = cubeRoom.getInnerBounds();
// Update skeleton and create new attacks
skeleton.update();
// Adjust attack cooldown based on number of avoided attacks
if (speedBoostActive && skeleton.attackCooldown <= 0) {
// Make attack spawn 1.5x slower after 20 attacks avoided
skeleton.attackCooldown *= 1.5;
}
// Check if we should activate bomb attacks at 10 avoided attacks
if (attacksAvoided >= 10 && !bombAttackActive) {
bombAttackActive = true;
// Display message about the new bomb attack
var bombTxt = new Text2('NEW ATTACK: BOMBS will appear in the room!', {
size: 70,
fill: 0xFF0000
});
bombTxt.anchor.set(0.5, 0.5);
bombTxt.x = 1024;
bombTxt.y = 1366;
game.addChild(bombTxt);
// Remove the message after 3 seconds
LK.setTimeout(function () {
bombTxt.destroy();
}, 3000);
}
var attack = skeleton.createAttack();
if (attack) {
if (attack.type === 'laser') {
// Add delay between warning and laser appearance
LK.setTimeout(function () {
// Only proceed if game is still active
if (!game) return;
var laser = new Laser();
// Position lasers to appear from within the cube room
laser.x = Math.max(roomBounds.x + 100, Math.min(attack.position.x, roomBounds.x + roomBounds.width - 100));
laser.y = skeleton.y + 200;
// Apply speed boost if 20 attacks have been avoided
var laserSpeed = attack.speed;
if (speedBoostActive) {
laserSpeed = attack.speed * 2; // Make lasers 2x faster instead of 3x
}
laser.setup(attack.direction, laserSpeed);
laser.lastY = laser.y;
laser.lastIntersecting = laser.intersects(heart);
lasers.push(laser);
game.addChild(laser);
// Play a sound when laser appears
LK.getSound('hit').play({
volume: 0.3
});
}, 1200); // 1.2 second delay between warning and laser
} else if (attack.type === 'bomb') {
// Create a new bomb
var bomb = new Bomb();
bomb.x = attack.position.x;
bomb.y = attack.position.y;
bomb.lastIntersecting = bomb.intersects(heart);
bombs.push(bomb);
game.addChild(bomb);
// Play a sound when bomb appears
LK.getSound('hit').play({
volume: 0.3
});
}
}
// Update bombs
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
bomb.update();
// Check for collision with heart during explosion
var currentIntersecting = bomb.intersects(heart);
// Only check collision if the bomb is exploding AND still active
if (bomb.exploding && bomb.active && !bomb.lastIntersecting && currentIntersecting) {
// Heart was hit by explosion
heart.takeDamage();
playerHealth.updateHealth(heart.health);
}
// Remove bombs that have finished exploding
if (bomb.exploding && bomb.alpha <= 0) {
// After bomb explodes, create a new one
LK.setTimeout(function () {
if (!game) return;
var newAttack = {
type: 'bomb',
position: {
x: roomBounds.x + Math.random() * (roomBounds.width - 100) + 50,
y: roomBounds.y + Math.random() * (roomBounds.height - 100) + 50
}
};
var newBomb = new Bomb();
newBomb.x = newAttack.position.x;
newBomb.y = newAttack.position.y;
newBomb.lastIntersecting = newBomb.intersects(heart);
bombs.push(newBomb);
game.addChild(newBomb);
}, 500);
bomb.destroy();
bombs.splice(i, 1);
}
bomb.lastIntersecting = currentIntersecting;
}
// Update lasers
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
// Check for collisions with heart
var currentIntersecting = laser.intersects(heart);
if (!laser.lastIntersecting && currentIntersecting) {
// Heart was hit by laser
heart.takeDamage();
playerHealth.updateHealth(heart.health);
}
// Remove lasers that go off screen
if (laser.lastY < 2800 && laser.y > 2800 || laser.x < -200 || laser.x > 2248) {
// If we successfully avoided the laser, damage the skeleton
if (!currentIntersecting && laser.active) {
score++;
attacksAvoided++;
scoreTxt.setText('Attacks Avoided: ' + score);
skeleton.takeDamage(10); // Increased damage to the skeleton when avoiding an attack
healthBar.setHealth(skeleton.health / skeleton.maxHealth);
laser.active = false; // Mark as processed
LK.getSound('avoid').play();
// Flash the skeleton green to indicate they're taking damage
LK.effects.flashObject(skeleton, 0x00ff00, 300);
// Check if we've avoided 20 attacks and haven't activated the speed boost yet
if (attacksAvoided >= 20 && !speedBoostActive) {
speedBoostActive = true;
// Display message about the speed boost and slower spawn rate
var boostTxt = new Text2('DANGER! Lasers 2x FASTER but SPAWN 1.5x SLOWER!', {
size: 70,
fill: 0xFF0000
});
boostTxt.anchor.set(0.5, 0.5);
boostTxt.x = 1024;
boostTxt.y = 1366;
game.addChild(boostTxt);
// Remove the message after 3 seconds
LK.setTimeout(function () {
boostTxt.destroy();
}, 3000);
}
}
// Remove laser
laser.destroy();
lasers.splice(i, 1);
}
laser.lastIntersecting = currentIntersecting;
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Create bomb visual
var bombGraphic = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
}); // Use the dedicated bomb asset instead of repurposed laser
// Create eyes for character-like appearance
var leftEye = new Container();
var leftEyeWhite = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF,
scaleX: 0.2,
scaleY: 0.2
});
var leftEyePupil = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x0000FF,
scaleX: 0.1,
scaleY: 0.1
});
leftEye.addChild(leftEyeWhite);
leftEye.addChild(leftEyePupil);
leftEye.x = -20;
leftEye.y = -15;
var rightEye = new Container();
var rightEyeWhite = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF,
scaleX: 0.2,
scaleY: 0.2
});
var rightEyePupil = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x0000FF,
scaleX: 0.1,
scaleY: 0.1
});
rightEye.addChild(rightEyeWhite);
rightEye.addChild(rightEyePupil);
rightEye.x = 20;
rightEye.y = -15;
// Create a mouth that looks worried
var mouth = LK.getAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000,
scaleX: 0.4,
scaleY: 0.1
});
mouth.y = 20;
self.addChild(leftEye);
self.addChild(rightEye);
self.addChild(mouth);
// Add timer visual
var timerText = new Text2("1.5", {
size: 40,
fill: 0xFF0000
});
timerText.anchor.set(0.5, 0.5);
timerText.y = -40; // Position above the bomb
self.addChild(timerText);
// Bomb properties
self.active = true;
self.exploding = false;
self.explosionRadius = 100; // Smaller explosion radius (was 150)
self.timer = 1.5; // seconds until explosion
self.lastIntersecting = false;
// Setup explosion visual (initially hidden)
var explosionGraphic = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 0.7,
// Scale down explosion graphic (70% of original size)
scaleY: 0.7 // Scale down explosion graphic (70% of original size)
});
self.update = function () {
if (!self.active) return;
if (!self.exploding) {
// Update timer
self.timer -= 1 / 60; // 60fps
timerText.setText(Math.max(0, self.timer).toFixed(1));
// Pulse effect as timer gets lower
var pulseScale = 1.0 + Math.sin(LK.ticks / 5) * 0.1 * (1.5 - self.timer) / 1.5;
bombGraphic.scale.set(pulseScale, pulseScale);
// Make eyes look more panicked as timer decreases
var eyeScale = 0.2 + (1.5 - self.timer) * 0.1;
leftEyeWhite.scale.set(eyeScale, eyeScale);
rightEyeWhite.scale.set(eyeScale, eyeScale);
// Make mouth more worried
mouth.scaleX = 0.4 + (1.5 - self.timer) * 0.2;
// Start explosion when timer reaches 0
if (self.timer <= 0) {
self.startExplosion();
}
}
};
self.startExplosion = function () {
self.exploding = true;
timerText.setText("");
bombGraphic.alpha = 0;
leftEye.alpha = 0;
rightEye.alpha = 0;
mouth.alpha = 0;
// Show explosion
explosionGraphic.alpha = 1;
// Immediately set active to false
// This prevents collision detection from triggering after explosion is complete
self.active = false;
// Explosion animation
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.4,
// Smaller expansion (was 2)
scaleY: 1.4 // Smaller expansion (was 2)
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Make sure explosion is completely inactive after animation finishes
self.exploding = false;
if (self && self.parent) {
self.destroy();
}
}
});
};
return self;
});
var CubeRoom = Container.expand(function () {
var self = Container.call(this);
// Room properties
self.walls = [];
self.wallThickness = 50;
self.roomWidth = 1600;
self.roomHeight = 1600;
self.roomX = 0;
self.roomY = 0;
self.init = function (x, y, width, height) {
self.roomX = x || (2048 - self.roomWidth) / 2;
self.roomY = y || 800;
self.roomWidth = width || 1200; // Smaller default room width
self.roomHeight = height || 1200; // Smaller default room height
// Create walls
self.createWalls();
};
self.createWalls = function () {
// Top wall
var topWall = new Wall();
topWall.init(self.roomWidth, self.wallThickness, 0x555555);
topWall.x = self.roomX;
topWall.y = self.roomY;
self.addChild(topWall);
self.walls.push(topWall);
// Bottom wall
var bottomWall = new Wall();
bottomWall.init(self.roomWidth, self.wallThickness, 0x555555);
bottomWall.x = self.roomX;
bottomWall.y = self.roomY + self.roomHeight - self.wallThickness;
self.addChild(bottomWall);
self.walls.push(bottomWall);
// Left wall
var leftWall = new Wall();
leftWall.init(self.wallThickness, self.roomHeight, 0x444444);
leftWall.x = self.roomX;
leftWall.y = self.roomY;
self.addChild(leftWall);
self.walls.push(leftWall);
// Right wall
var rightWall = new Wall();
rightWall.init(self.wallThickness, self.roomHeight, 0x444444);
rightWall.x = self.roomX + self.roomWidth - self.wallThickness;
rightWall.y = self.roomY;
self.addChild(rightWall);
self.walls.push(rightWall);
};
self.getWalls = function () {
return self.walls;
};
self.getInnerBounds = function () {
return {
x: self.roomX + self.wallThickness,
y: self.roomY + self.wallThickness,
width: self.roomWidth - self.wallThickness * 2,
height: self.roomHeight - self.wallThickness * 2
};
};
return self;
});
var HealthBar = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
var foreground = self.attachAsset('healthBarFg', {
anchorX: 0,
anchorY: 0
});
self.setHealth = function (percent) {
foreground.scale.x = Math.max(0, Math.min(1, percent));
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphic = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.invulnerable = false;
self.lastX = 0;
self.lastY = 0;
self.down = function (x, y, obj) {
self.dragging = true;
};
self.up = function (x, y, obj) {
self.dragging = false;
};
self.takeDamage = function () {
if (self.invulnerable) return;
// Set health to 0 directly to kill player immediately
self.health = 0;
LK.getSound('hit').play();
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// No need for invulnerability since player dies immediately
LK.getSound('gameOver').play();
LK.showGameOver();
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Laser = Container.expand(function () {
var self = Container.call(this);
var laserGraphic = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
self.active = true;
self.direction = 1; // 1: vertical, 2: diagonal left, 3: diagonal right
self.speed = 15;
self.initialSpeed = 0; // Store initial speed for acceleration calculation
self.maxSpeed = 0; // Will be set to 4x initial speed
self.lastY = 0;
self.lastIntersecting = false;
self.setup = function (direction, speed) {
self.direction = direction || 1;
self.initialSpeed = speed ? speed / 4 : 3.75; // Start at 1/4 of the target speed
self.speed = self.initialSpeed;
self.maxSpeed = self.initialSpeed * 4; // Max speed is 4x the initial speed
if (self.direction === 1) {
// Vertical laser
laserGraphic.rotation = 0;
} else if (self.direction === 2) {
// Diagonal left
laserGraphic.rotation = Math.PI / 4;
} else if (self.direction === 3) {
// Diagonal right
laserGraphic.rotation = -Math.PI / 4;
}
// Start accelerating the laser using tween with faster acceleration
tween(self, {
speed: self.maxSpeed
}, {
duration: 1000,
// Accelerate over 1 second instead of 2
easing: tween.easeInQuad // More aggressive acceleration curve
});
};
self.update = function () {
self.lastY = self.y;
if (self.direction === 1) {
self.y += self.speed;
} else if (self.direction === 2) {
self.y += self.speed;
self.x -= self.speed * 0.8;
} else if (self.direction === 3) {
self.y += self.speed;
self.x += self.speed * 0.8;
}
};
return self;
});
var PlayerHealthDisplay = Container.expand(function () {
var self = Container.call(this);
var heartsContainer = new Container();
self.addChild(heartsContainer);
self.updateHealth = function (health) {
heartsContainer.removeChildren();
for (var i = 0; i < health; i++) {
var heartIcon = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scale: 0.3
});
heartIcon.x = i * 70;
heartsContainer.addChild(heartIcon);
}
};
return self;
});
var Skeleton = Container.expand(function () {
var self = Container.call(this);
// Create skeleton body
var skeletonGraphic = self.attachAsset('skeleton', {
anchorX: 0.5,
anchorY: 0.5
});
// Add jacket on top
var jacketGraphic = self.attachAsset('jacket', {
anchorX: 0.5,
anchorY: 0.5,
y: -20
});
self.maxHealth = 300;
self.health = self.maxHealth;
self.attackCooldown = 0;
self.attackPattern = 1;
self.lastAttackTime = 0;
self.warningIndicator = null;
self.nextAttackPosition = null;
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
LK.getSound('victory').play();
LK.showYouWin();
}
// Increase difficulty as health decreases
if (self.health < self.maxHealth * 0.7 && self.attackPattern === 1) {
self.attackPattern = 2; // More frequent attacks
} else if (self.health < self.maxHealth * 0.4 && self.attackPattern === 2) {
self.attackPattern = 3; // Even more frequent and complex attacks
}
};
self.update = function () {
self.attackCooldown--;
// Subtle movement
self.x += Math.sin(LK.ticks / 20) * 2;
// Update warning indicator position if it exists
if (self.warningIndicator && self.warningIndicator.parent) {
if (self.nextAttackPosition) {
self.warningIndicator.x = self.nextAttackPosition;
}
// Add subtle animation to make warning more noticeable
self.warningIndicator.y += Math.sin(LK.ticks / 10) * 0.7;
}
};
// Create attack based on current pattern
self.createAttack = function () {
if (self.attackCooldown > 0) return null;
var attack;
var now = Date.now();
// Ensure minimum time between attacks
if (now - self.lastAttackTime < 800) return null;
self.lastAttackTime = now;
// Check if we should create a bomb attack (if available and bombs are active)
if (bombAttackActive && Math.random() < 0.3) {
self.attackCooldown = 100; // Longer cooldown for bomb attacks
attack = {
type: 'bomb',
position: {
x: roomBounds.x + Math.random() * (roomBounds.width - 100) + 50,
y: roomBounds.y + Math.random() * (roomBounds.height - 100) + 50
}
};
return attack;
}
// Different attack patterns based on skeleton's health
if (self.attackPattern === 1) {
// Basic pattern: vertical lasers
self.attackCooldown = 60;
attack = {
type: 'laser',
direction: 1,
speed: 15,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 600 - 300 // Reduced position range for smaller room
}
};
} else if (self.attackPattern === 2) {
// Medium pattern: vertical and diagonal lasers
self.attackCooldown = 45;
var dir = Math.floor(Math.random() * 3) + 1;
attack = {
type: 'laser',
direction: dir,
speed: 18,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 700 - 350 // Reduced position range for smaller room
}
};
} else {
// Hard pattern: faster and more varied attacks
self.attackCooldown = 30;
var dir = Math.floor(Math.random() * 3) + 1;
attack = {
type: 'laser',
direction: dir,
speed: 22,
// This will be scaled in Laser.setup to start slower
position: {
x: 1024 + Math.random() * 800 - 400 // Reduced position range for smaller room
}
};
}
// Show warning for where the attack will come from
if (attack) {
// Remove any existing warning indicator
if (self.warningIndicator && self.warningIndicator.parent) {
self.warningIndicator.parent.removeChild(self.warningIndicator);
}
// Create a new warning indicator with enhanced visibility
self.warningIndicator = new Container();
var warningLine = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000,
// Red warning color for better visibility
scaleY: 2,
// Make it thicker
alpha: 0.8 // More visible alpha
});
// Create a background glow effect
var warningGlow = LK.getAsset('laser', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFF00,
// Yellow glow
scaleY: 3,
// Even thicker for the glow
alpha: 0.4
});
// Position the warning indicator at the attack position
self.warningIndicator.x = attack.position.x;
self.warningIndicator.y = self.y + 150;
self.nextAttackPosition = attack.position.x;
// Adjust rotation based on attack direction
if (attack.direction === 1) {
warningLine.rotation = 0;
warningGlow.rotation = 0;
} else if (attack.direction === 2) {
warningLine.rotation = Math.PI / 4;
warningGlow.rotation = Math.PI / 4;
} else if (attack.direction === 3) {
warningLine.rotation = -Math.PI / 4;
warningGlow.rotation = -Math.PI / 4;
}
// Add the glow behind the warning line
self.warningIndicator.addChild(warningGlow);
self.warningIndicator.addChild(warningLine);
// Add text warning
var warningText = new Text2("!", {
size: 100,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0.5);
warningText.y = -warningLine.height * 0.6;
self.warningIndicator.addChild(warningText);
// Add warning to game immediately and make it visible longer
if (game) {
game.addChild(self.warningIndicator);
// Make the warning flash more dramatically
tween(warningLine, {
alpha: 0.3
}, {
duration: 300,
repeat: 6,
yoyo: true
});
// Pulse the glow
tween(warningGlow, {
scaleY: 4,
alpha: 0.2
}, {
duration: 500,
repeat: 4,
yoyo: true,
onComplete: function onComplete() {
if (self.warningIndicator && self.warningIndicator.parent) {
self.warningIndicator.parent.removeChild(self.warningIndicator);
}
}
});
}
}
return attack;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphic;
self.init = function (width, height, color) {
// Create a custom wall shape
wallGraphic = self.attachAsset('healthBarBg', {
anchorX: 0,
anchorY: 0
});
// Set size and color
wallGraphic.width = width;
wallGraphic.height = height;
wallGraphic.tint = color || 0x888888;
};
return self;
});
/****
* Initialize Game
****/
// Game variables
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var heart;
var skeleton;
var healthBar;
var playerHealth;
var lasers = [];
var bombs = [];
var score = 0;
var attacksAvoided = 0;
var speedBoostActive = false;
var bombAttackActive = false;
var dragging = false;
var cubeRoom;
var wallCollisions = [];
// Set background color
game.setBackgroundColor(0x222222);
// Create cube room
cubeRoom = game.addChild(new CubeRoom());
cubeRoom.init((2048 - 1200) / 2, 700, 1200, 1200); // Smaller room size
var roomBounds = cubeRoom.getInnerBounds();
// Create player heart
heart = game.addChild(new Heart());
heart.x = 1024;
heart.y = roomBounds.y + roomBounds.height / 2;
// Create skeleton enemy
skeleton = game.addChild(new Skeleton());
skeleton.x = 1024;
skeleton.y = 400;
// Create health bar for skeleton
healthBar = game.addChild(new HealthBar());
healthBar.x = (2048 - 800) / 2;
healthBar.y = 200;
// Create player health display
playerHealth = game.addChild(new PlayerHealthDisplay());
playerHealth.x = 150;
playerHealth.y = 2600;
playerHealth.updateHealth(heart.health);
// Score text
var scoreTxt = new Text2('Attacks Avoided: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.x = 1024;
scoreTxt.y = 100;
game.addChild(scoreTxt);
// Instructions text
var instructionsTxt = new Text2('You are trapped in a cube room! Avoid attacks!', {
size: 50,
fill: 0xFFFFFF
});
instructionsTxt.anchor.set(0.5, 0);
instructionsTxt.x = 1024;
instructionsTxt.y = 2500;
game.addChild(instructionsTxt);
// Game movement handlers
game.move = function (x, y, obj) {
if (dragging) {
// Move heart to cursor position
heart.x = x;
heart.y = y;
// Keep heart within cube room bounds
var roomBounds = cubeRoom.getInnerBounds();
var halfHeartWidth = heart.width / 2;
var halfHeartHeight = heart.height / 2;
// Constrain movement to inside the room
heart.x = Math.max(roomBounds.x + halfHeartWidth, Math.min(heart.x, roomBounds.x + roomBounds.width - halfHeartWidth));
heart.y = Math.max(roomBounds.y + halfHeartHeight, Math.min(heart.y, roomBounds.y + roomBounds.height - halfHeartHeight));
}
};
game.down = function (x, y, obj) {
dragging = true;
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main game update loop
game.update = function () {
// Update heart
heart.update();
// Get room walls for collision checking
var walls = cubeRoom.getWalls();
var roomBounds = cubeRoom.getInnerBounds();
// Update skeleton and create new attacks
skeleton.update();
// Adjust attack cooldown based on number of avoided attacks
if (speedBoostActive && skeleton.attackCooldown <= 0) {
// Make attack spawn 1.5x slower after 20 attacks avoided
skeleton.attackCooldown *= 1.5;
}
// Check if we should activate bomb attacks at 10 avoided attacks
if (attacksAvoided >= 10 && !bombAttackActive) {
bombAttackActive = true;
// Display message about the new bomb attack
var bombTxt = new Text2('NEW ATTACK: BOMBS will appear in the room!', {
size: 70,
fill: 0xFF0000
});
bombTxt.anchor.set(0.5, 0.5);
bombTxt.x = 1024;
bombTxt.y = 1366;
game.addChild(bombTxt);
// Remove the message after 3 seconds
LK.setTimeout(function () {
bombTxt.destroy();
}, 3000);
}
var attack = skeleton.createAttack();
if (attack) {
if (attack.type === 'laser') {
// Add delay between warning and laser appearance
LK.setTimeout(function () {
// Only proceed if game is still active
if (!game) return;
var laser = new Laser();
// Position lasers to appear from within the cube room
laser.x = Math.max(roomBounds.x + 100, Math.min(attack.position.x, roomBounds.x + roomBounds.width - 100));
laser.y = skeleton.y + 200;
// Apply speed boost if 20 attacks have been avoided
var laserSpeed = attack.speed;
if (speedBoostActive) {
laserSpeed = attack.speed * 2; // Make lasers 2x faster instead of 3x
}
laser.setup(attack.direction, laserSpeed);
laser.lastY = laser.y;
laser.lastIntersecting = laser.intersects(heart);
lasers.push(laser);
game.addChild(laser);
// Play a sound when laser appears
LK.getSound('hit').play({
volume: 0.3
});
}, 1200); // 1.2 second delay between warning and laser
} else if (attack.type === 'bomb') {
// Create a new bomb
var bomb = new Bomb();
bomb.x = attack.position.x;
bomb.y = attack.position.y;
bomb.lastIntersecting = bomb.intersects(heart);
bombs.push(bomb);
game.addChild(bomb);
// Play a sound when bomb appears
LK.getSound('hit').play({
volume: 0.3
});
}
}
// Update bombs
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
bomb.update();
// Check for collision with heart during explosion
var currentIntersecting = bomb.intersects(heart);
// Only check collision if the bomb is exploding AND still active
if (bomb.exploding && bomb.active && !bomb.lastIntersecting && currentIntersecting) {
// Heart was hit by explosion
heart.takeDamage();
playerHealth.updateHealth(heart.health);
}
// Remove bombs that have finished exploding
if (bomb.exploding && bomb.alpha <= 0) {
// After bomb explodes, create a new one
LK.setTimeout(function () {
if (!game) return;
var newAttack = {
type: 'bomb',
position: {
x: roomBounds.x + Math.random() * (roomBounds.width - 100) + 50,
y: roomBounds.y + Math.random() * (roomBounds.height - 100) + 50
}
};
var newBomb = new Bomb();
newBomb.x = newAttack.position.x;
newBomb.y = newAttack.position.y;
newBomb.lastIntersecting = newBomb.intersects(heart);
bombs.push(newBomb);
game.addChild(newBomb);
}, 500);
bomb.destroy();
bombs.splice(i, 1);
}
bomb.lastIntersecting = currentIntersecting;
}
// Update lasers
for (var i = lasers.length - 1; i >= 0; i--) {
var laser = lasers[i];
laser.update();
// Check for collisions with heart
var currentIntersecting = laser.intersects(heart);
if (!laser.lastIntersecting && currentIntersecting) {
// Heart was hit by laser
heart.takeDamage();
playerHealth.updateHealth(heart.health);
}
// Remove lasers that go off screen
if (laser.lastY < 2800 && laser.y > 2800 || laser.x < -200 || laser.x > 2248) {
// If we successfully avoided the laser, damage the skeleton
if (!currentIntersecting && laser.active) {
score++;
attacksAvoided++;
scoreTxt.setText('Attacks Avoided: ' + score);
skeleton.takeDamage(10); // Increased damage to the skeleton when avoiding an attack
healthBar.setHealth(skeleton.health / skeleton.maxHealth);
laser.active = false; // Mark as processed
LK.getSound('avoid').play();
// Flash the skeleton green to indicate they're taking damage
LK.effects.flashObject(skeleton, 0x00ff00, 300);
// Check if we've avoided 20 attacks and haven't activated the speed boost yet
if (attacksAvoided >= 20 && !speedBoostActive) {
speedBoostActive = true;
// Display message about the speed boost and slower spawn rate
var boostTxt = new Text2('DANGER! Lasers 2x FASTER but SPAWN 1.5x SLOWER!', {
size: 70,
fill: 0xFF0000
});
boostTxt.anchor.set(0.5, 0.5);
boostTxt.x = 1024;
boostTxt.y = 1366;
game.addChild(boostTxt);
// Remove the message after 3 seconds
LK.setTimeout(function () {
boostTxt.destroy();
}, 3000);
}
}
// Remove laser
laser.destroy();
lasers.splice(i, 1);
}
laser.lastIntersecting = currentIntersecting;
}
};