/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PianoKey = Container.expand(function (type, lane) {
var self = Container.call(this);
self.type = type; // 'normal', 'hold', 'trap'
self.lane = lane; // 0-4 for 5 lanes
self.speed = 8;
self.isActive = true;
self.isHeld = false;
self.hasBeenHit = false;
self.isPressed = false;
self.holdStartTime = 0;
self.holdDuration = 2000; // 2 seconds in milliseconds
self.isHoldComplete = false;
self.lastY = 0; // For tracking position transitions
// Create visual based on type
var assetName = type + 'Key';
var keyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (self.isActive) {
self.y += self.speed;
}
// Handle hold key timer
if (self.type === 'hold' && self.isHeld && self.isPressed && !self.isHoldComplete) {
var holdTime = Date.now() - self.holdStartTime;
var progress = holdTime / self.holdDuration;
// Animate progress with pulsing effect
var pulseScale = 1 + Math.sin(holdTime * 0.01) * 0.1;
keyGraphics.scaleX = pulseScale;
keyGraphics.scaleY = pulseScale;
// Change color based on progress
var greenValue = Math.floor(144 + progress * 111); // 144 to 255
keyGraphics.tint = greenValue << 8 | 0x4b025f;
if (holdTime >= self.holdDuration) {
self.isHoldComplete = true;
self.isActive = false;
LK.setScore(LK.getScore() + 50); // Bonus points for completing hold
keyGraphics.tint = 0xffd700; // Gold color to show completion
// Create particles
createParticles(self.x, self.y, 0xffd700, 8);
// Add completion animation
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
}
}
};
self.hit = function () {
if (self.hasBeenHit) return {
success: false,
isTrap: false
};
self.hasBeenHit = true;
if (self.type === 'normal') {
self.isActive = false;
LK.getSound('keyHit').play();
LK.setScore(LK.getScore() + 10);
// Add fade out animation for successful hit
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return {
success: true,
isTrap: false
};
} else if (self.type === 'hold') {
if (!self.isPressed) {
self.isPressed = true;
self.isHeld = true;
self.holdStartTime = Date.now();
LK.getSound('keyHit').play();
keyGraphics.tint = 0x90ee90; // Light green to show it's being held
}
return {
success: true,
isTrap: false
};
} else if (self.type === 'trap') {
self.isActive = false;
LK.getSound('trapHit').play();
// Add fade out animation for trap hit
tween(self, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return {
success: false,
isTrap: true
}; // Trap hit is bad
}
return {
success: false,
isTrap: false
};
};
self.release = function () {
if (self.type === 'hold' && self.isHeld && self.isPressed) {
// Always disappear when released, regardless of hold time
self.isActive = false;
self.isHeld = false;
var holdTime = Date.now() - self.holdStartTime;
// Give points based on how long it was held
LK.setScore(LK.getScore() + Math.floor(holdTime / 100)); // Partial points based on hold time
keyGraphics.tint = 0xff6666; // Red tint for release
// Add fade out animation for hold release
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Game variables
var lanes = [];
var keys = [];
var lives = 2;
var dangerLineY = 2400;
var spawnTimer = 0;
var spawnInterval = 60; // frames between spawns
var heldKeys = [];
var particles = [];
// Particle creation function
function createParticles(x, y, color, count) {
for (var i = 0; i < count; i++) {
var particle = LK.getAsset('normalKey', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 50,
y: y + (Math.random() - 0.5) * 50,
scaleX: 0.2,
scaleY: 0.2,
tint: color
});
game.addChild(particle);
particles.push(particle);
// Animate particle
tween(particle, {
alpha: 0,
x: particle.x + (Math.random() - 0.5) * 200,
y: particle.y - Math.random() * 150,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
var index = particles.indexOf(particle);
if (index > -1) {
particles.splice(index, 1);
}
}
});
}
}
// Lane positions
var lanePositions = [2048 * 0.1, 2048 * 0.3, 2048 * 0.5, 2048 * 0.7, 2048 * 0.9];
// Create lane backgrounds
for (var i = 0; i < 5; i++) {
var lane = game.addChild(LK.getAsset('keyLane', {
anchorX: 0.5,
anchorY: 0,
x: lanePositions[i],
y: 0,
alpha: 0.3
}));
lanes.push(lane);
}
// Create danger line
var dangerLine = game.addChild(LK.getAsset('dangerLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: dangerLineY
}));
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: '#ffffff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var livesTxt = new Text2('Lives: 2', {
size: 60,
fill: '#ff4444'
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
// Touch handling
var currentTouches = {};
function getLaneFromX(x) {
for (var i = 0; i < lanePositions.length; i++) {
if (Math.abs(x - lanePositions[i]) < 100) {
return i;
}
}
return -1;
}
function getKeyInLane(lane, y) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.lane === lane && Math.abs(key.y - y) < 100 && key.isActive) {
return key;
}
}
return null;
}
game.down = function (x, y, obj) {
var lane = getLaneFromX(x);
if (lane === -1) return;
var key = getKeyInLane(lane, y);
if (key) {
var result = key.hit();
if (key.type === 'hold' && result.success) {
heldKeys.push({
key: key,
lane: lane
});
} else if (result.isTrap) {
lives--;
livesTxt.setText('Lives: ' + lives);
LK.effects.flashScreen(0xff0000, 500);
if (lives <= 0) {
LK.showGameOver();
}
}
}
currentTouches[lane] = true;
};
game.up = function (x, y, obj) {
var lane = getLaneFromX(x);
if (lane === -1) return;
// Release held keys in this lane
for (var i = heldKeys.length - 1; i >= 0; i--) {
if (heldKeys[i].lane === lane) {
heldKeys[i].key.release();
heldKeys.splice(i, 1);
}
}
currentTouches[lane] = false;
};
function spawnKey() {
var lane = Math.floor(Math.random() * 5);
var keyTypes = ['normal', 'normal', 'normal', 'hold', 'trap']; // Weighted towards normal
var type = keyTypes[Math.floor(Math.random() * keyTypes.length)];
var newKey = new PianoKey(type, lane);
newKey.x = lanePositions[lane];
newKey.y = -50;
newKey.lastY = -50; // Initialize lastY for transition tracking
keys.push(newKey);
game.addChild(newKey);
}
game.update = function () {
// Update score display
scoreTxt.setText('Score: ' + LK.getScore());
// Spawn new keys
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnKey();
spawnTimer = 0;
// Gradually increase difficulty
if (spawnInterval > 20) {
spawnInterval--;
}
}
// Update and check keys
for (var i = keys.length - 1; i >= 0; i--) {
var key = keys[i];
// Check if key crossed danger line for the first time
if (key.lastY <= dangerLineY && key.y > dangerLineY && key.isActive) {
if ((key.type === 'normal' || key.type === 'hold') && !key.hasBeenHit) {
// Missing normal/hold keys is bad
lives--;
livesTxt.setText('Lives: ' + lives);
LK.getSound('keyMiss').play();
LK.effects.flashScreen(0xff0000, 300);
if (lives <= 0) {
LK.showGameOver();
return;
}
// Don't deactivate - let them continue falling and dealing damage
}
// For trap keys, don't deactivate them - let them continue falling
}
// Update lastY for transition tracking
key.lastY = key.y;
// Remove keys that are off screen or destroyed
if (key.y > 2800 || key.destroyed) {
if (!key.destroyed) {
key.destroy();
}
keys.splice(i, 1);
// Remove from held keys if present
for (var j = heldKeys.length - 1; j >= 0; j--) {
if (heldKeys[j].key === key) {
heldKeys.splice(j, 1);
}
}
}
}
// Clean up completed hold keys from heldKeys array
for (var h = heldKeys.length - 1; h >= 0; h--) {
var heldKey = heldKeys[h].key;
if (heldKey.isHoldComplete || heldKey.destroyed) {
heldKeys.splice(h, 1);
}
}
// Win condition at score 100000
if (LK.getScore() >= 100000) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var PianoKey = Container.expand(function (type, lane) {
var self = Container.call(this);
self.type = type; // 'normal', 'hold', 'trap'
self.lane = lane; // 0-4 for 5 lanes
self.speed = 8;
self.isActive = true;
self.isHeld = false;
self.hasBeenHit = false;
self.isPressed = false;
self.holdStartTime = 0;
self.holdDuration = 2000; // 2 seconds in milliseconds
self.isHoldComplete = false;
self.lastY = 0; // For tracking position transitions
// Create visual based on type
var assetName = type + 'Key';
var keyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (self.isActive) {
self.y += self.speed;
}
// Handle hold key timer
if (self.type === 'hold' && self.isHeld && self.isPressed && !self.isHoldComplete) {
var holdTime = Date.now() - self.holdStartTime;
var progress = holdTime / self.holdDuration;
// Animate progress with pulsing effect
var pulseScale = 1 + Math.sin(holdTime * 0.01) * 0.1;
keyGraphics.scaleX = pulseScale;
keyGraphics.scaleY = pulseScale;
// Change color based on progress
var greenValue = Math.floor(144 + progress * 111); // 144 to 255
keyGraphics.tint = greenValue << 8 | 0x4b025f;
if (holdTime >= self.holdDuration) {
self.isHoldComplete = true;
self.isActive = false;
LK.setScore(LK.getScore() + 50); // Bonus points for completing hold
keyGraphics.tint = 0xffd700; // Gold color to show completion
// Create particles
createParticles(self.x, self.y, 0xffd700, 8);
// Add completion animation
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
}
}
};
self.hit = function () {
if (self.hasBeenHit) return {
success: false,
isTrap: false
};
self.hasBeenHit = true;
if (self.type === 'normal') {
self.isActive = false;
LK.getSound('keyHit').play();
LK.setScore(LK.getScore() + 10);
// Add fade out animation for successful hit
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return {
success: true,
isTrap: false
};
} else if (self.type === 'hold') {
if (!self.isPressed) {
self.isPressed = true;
self.isHeld = true;
self.holdStartTime = Date.now();
LK.getSound('keyHit').play();
keyGraphics.tint = 0x90ee90; // Light green to show it's being held
}
return {
success: true,
isTrap: false
};
} else if (self.type === 'trap') {
self.isActive = false;
LK.getSound('trapHit').play();
// Add fade out animation for trap hit
tween(self, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return {
success: false,
isTrap: true
}; // Trap hit is bad
}
return {
success: false,
isTrap: false
};
};
self.release = function () {
if (self.type === 'hold' && self.isHeld && self.isPressed) {
// Always disappear when released, regardless of hold time
self.isActive = false;
self.isHeld = false;
var holdTime = Date.now() - self.holdStartTime;
// Give points based on how long it was held
LK.setScore(LK.getScore() + Math.floor(holdTime / 100)); // Partial points based on hold time
keyGraphics.tint = 0xff6666; // Red tint for release
// Add fade out animation for hold release
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Game variables
var lanes = [];
var keys = [];
var lives = 2;
var dangerLineY = 2400;
var spawnTimer = 0;
var spawnInterval = 60; // frames between spawns
var heldKeys = [];
var particles = [];
// Particle creation function
function createParticles(x, y, color, count) {
for (var i = 0; i < count; i++) {
var particle = LK.getAsset('normalKey', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 50,
y: y + (Math.random() - 0.5) * 50,
scaleX: 0.2,
scaleY: 0.2,
tint: color
});
game.addChild(particle);
particles.push(particle);
// Animate particle
tween(particle, {
alpha: 0,
x: particle.x + (Math.random() - 0.5) * 200,
y: particle.y - Math.random() * 150,
rotation: Math.random() * Math.PI * 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
var index = particles.indexOf(particle);
if (index > -1) {
particles.splice(index, 1);
}
}
});
}
}
// Lane positions
var lanePositions = [2048 * 0.1, 2048 * 0.3, 2048 * 0.5, 2048 * 0.7, 2048 * 0.9];
// Create lane backgrounds
for (var i = 0; i < 5; i++) {
var lane = game.addChild(LK.getAsset('keyLane', {
anchorX: 0.5,
anchorY: 0,
x: lanePositions[i],
y: 0,
alpha: 0.3
}));
lanes.push(lane);
}
// Create danger line
var dangerLine = game.addChild(LK.getAsset('dangerLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: dangerLineY
}));
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: '#ffffff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var livesTxt = new Text2('Lives: 2', {
size: 60,
fill: '#ff4444'
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
// Touch handling
var currentTouches = {};
function getLaneFromX(x) {
for (var i = 0; i < lanePositions.length; i++) {
if (Math.abs(x - lanePositions[i]) < 100) {
return i;
}
}
return -1;
}
function getKeyInLane(lane, y) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.lane === lane && Math.abs(key.y - y) < 100 && key.isActive) {
return key;
}
}
return null;
}
game.down = function (x, y, obj) {
var lane = getLaneFromX(x);
if (lane === -1) return;
var key = getKeyInLane(lane, y);
if (key) {
var result = key.hit();
if (key.type === 'hold' && result.success) {
heldKeys.push({
key: key,
lane: lane
});
} else if (result.isTrap) {
lives--;
livesTxt.setText('Lives: ' + lives);
LK.effects.flashScreen(0xff0000, 500);
if (lives <= 0) {
LK.showGameOver();
}
}
}
currentTouches[lane] = true;
};
game.up = function (x, y, obj) {
var lane = getLaneFromX(x);
if (lane === -1) return;
// Release held keys in this lane
for (var i = heldKeys.length - 1; i >= 0; i--) {
if (heldKeys[i].lane === lane) {
heldKeys[i].key.release();
heldKeys.splice(i, 1);
}
}
currentTouches[lane] = false;
};
function spawnKey() {
var lane = Math.floor(Math.random() * 5);
var keyTypes = ['normal', 'normal', 'normal', 'hold', 'trap']; // Weighted towards normal
var type = keyTypes[Math.floor(Math.random() * keyTypes.length)];
var newKey = new PianoKey(type, lane);
newKey.x = lanePositions[lane];
newKey.y = -50;
newKey.lastY = -50; // Initialize lastY for transition tracking
keys.push(newKey);
game.addChild(newKey);
}
game.update = function () {
// Update score display
scoreTxt.setText('Score: ' + LK.getScore());
// Spawn new keys
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnKey();
spawnTimer = 0;
// Gradually increase difficulty
if (spawnInterval > 20) {
spawnInterval--;
}
}
// Update and check keys
for (var i = keys.length - 1; i >= 0; i--) {
var key = keys[i];
// Check if key crossed danger line for the first time
if (key.lastY <= dangerLineY && key.y > dangerLineY && key.isActive) {
if ((key.type === 'normal' || key.type === 'hold') && !key.hasBeenHit) {
// Missing normal/hold keys is bad
lives--;
livesTxt.setText('Lives: ' + lives);
LK.getSound('keyMiss').play();
LK.effects.flashScreen(0xff0000, 300);
if (lives <= 0) {
LK.showGameOver();
return;
}
// Don't deactivate - let them continue falling and dealing damage
}
// For trap keys, don't deactivate them - let them continue falling
}
// Update lastY for transition tracking
key.lastY = key.y;
// Remove keys that are off screen or destroyed
if (key.y > 2800 || key.destroyed) {
if (!key.destroyed) {
key.destroy();
}
keys.splice(i, 1);
// Remove from held keys if present
for (var j = heldKeys.length - 1; j >= 0; j--) {
if (heldKeys[j].key === key) {
heldKeys.splice(j, 1);
}
}
}
}
// Clean up completed hold keys from heldKeys array
for (var h = heldKeys.length - 1; h >= 0; h--) {
var heldKey = heldKeys[h].key;
if (heldKey.isHoldComplete || heldKey.destroyed) {
heldKeys.splice(h, 1);
}
}
// Win condition at score 100000
if (LK.getScore() >= 100000) {
LK.showYouWin();
}
};
make me a trap key for piano 80x180. In-Game asset. 2d. High contrast. No shadows
make me a trap key for piano and color is red 80x180. In-Game asset. 2d. High contrast. No shadows
a piano key with a line and a circle in the middle. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat