User prompt
bloklar müziğin ritmine göre düşsün
User prompt
arkadan tıkladığımız notalara göre bir müzik çalsın
Code edit (1 edits merged)
Please save this source code
User prompt
Piano Rhythm Tap
Initial prompt
bir piyano oyunu yapalım arkadan bir müzik çalsın arkadaki müziğe göre bloklar düşsün bloklara tam zamanında tıklayalım
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Block class: falling piano key var Block = Container.expand(function () { var self = Container.call(this); // Attach block asset var blockAsset = self.attachAsset('block', { anchorX: 0.5, anchorY: 0.5 }); // Track if block has been hit or missed self.hit = false; self.missed = false; // For hit effect self.showHitEffect = function () { var effect = LK.getAsset('hitEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); self.addChild(effect); tween(effect, { alpha: 0 }, { duration: 300, easing: tween.linear, onFinish: function onFinish() { effect.destroy(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Background asset covering the entire game area // --- FULLSCREEN BACKGROUND --- // Create a background image that covers the entire game area var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 }); game.addChild(background); // Piano note sounds for each lane (C, D, E, G as example) // Music (looping, but we will play it once per game) // Sound for miss // Sound for block hit // Block hit effect // Target line // Falling block (piano key) // --- GAME CONSTANTS --- // Piano note sounds for each lane // Piano note sounds for each lane (C, D, E, G as example) var NUM_LANES = 4; var LANE_WIDTH = 400; // 2048/4 = 512, but leave some margin var BLOCK_WIDTH = 300; var BLOCK_HEIGHT = 120; var BLOCK_SPEED_START = 12; // px per frame (60fps) var BLOCK_SPEED_MAX = 32; var BLOCK_SPEED_INCREMENT = 0.5; // per level var BLOCK_SPAWN_INTERVAL = 48; // frames between blocks (will decrease as tempo increases) var BLOCK_SPAWN_INTERVAL_MIN = 18; var TARGET_LINE_Y = 2732 - 320; // 320px from bottom var HIT_WINDOW = 80; // px window for perfect hit var MISS_WINDOW = 120; // px window for miss // --- GAME STATE --- var blocks = []; var score = 0; var combo = 0; var bestCombo = 0; var blockSpeed = BLOCK_SPEED_START; var blockSpawnInterval = BLOCK_SPAWN_INTERVAL; var ticksSinceLastBlock = 0; var isPlaying = false; var songTicks = 0; var songLengthTicks = 60 * 60; // 60 seconds at 60fps (will end game if song ends) var nextTempoIncrease = 600; // every 10 seconds // --- LANE POSITIONS --- var laneXs = []; for (var i = 0; i < NUM_LANES; i++) { // Center blocks in each lane laneXs[i] = LANE_WIDTH / 2 + i * LANE_WIDTH + (2048 - NUM_LANES * LANE_WIDTH) / 2; } // --- GUI ELEMENTS --- // Score text var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Combo text var comboTxt = new Text2('', { size: 80, fill: 0xFFE066 }); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 130; // Best combo text var bestComboTxt = new Text2('', { size: 60, fill: 0xCCCCCC }); bestComboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bestComboTxt); bestComboTxt.y = 210; // --- TARGET LINE --- var targetLine = LK.getAsset('targetLine', { anchorX: 0.5, // center horizontally anchorY: 0.5, // center vertically x: 2048 / 2, // center of screen y: 2732 / 2, // exactly center vertically scaleX: 1, scaleY: 1 }); game.addChild(targetLine); // Make it a long, thin horizontal blue bar (yanlamasına uzatılmış mavi çubuk) at the center of the screen targetLine.width = 2000; targetLine.height = 80; // Kalınlaştırıldı targetLine.rotation = 0; // 0 degrees, horizontal (yan) targetLine.x = 2048 / 2; targetLine.y = TARGET_LINE_Y; // targetLine.tint = 0x2196F3; // --- GAME START --- function startGame() { // Reset state for (var i = blocks.length - 1; i >= 0; i--) { blocks[i].destroy(); blocks.splice(i, 1); } score = 0; combo = 0; bestCombo = 0; blockSpeed = BLOCK_SPEED_START; blockSpawnInterval = BLOCK_SPAWN_INTERVAL; ticksSinceLastBlock = 0; isPlaying = true; songTicks = 0; nextTempoIncrease = 600; scoreTxt.setText('0'); comboTxt.setText(''); bestComboTxt.setText(''); // Start music LK.playMusic('pianoTrack', { loop: false }); } startGame(); // --- BLOCK SCHEDULING --- // For MVP, blocks are spawned randomly in lanes, but now in sync with the music beat using LK.music.getBeat API function spawnBlock() { var lane = Math.floor(Math.random() * NUM_LANES); var block = new Block(); block.x = laneXs[lane]; block.y = -BLOCK_HEIGHT / 2; block.lane = lane; block.hit = false; block.missed = false; blocks.push(block); game.addChild(block); } // --- BEAT SYNC SCHEDULING --- var lastBeat = -1; // --- GAME UPDATE --- game.update = function () { if (!isPlaying) return; songTicks++; ticksSinceLastBlock++; // Increase tempo every 10 seconds, but do it smoothly every frame for gradual acceleration if (songTicks >= nextTempoIncrease) { if (blockSpeed < BLOCK_SPEED_MAX) blockSpeed += BLOCK_SPEED_INCREMENT; if (blockSpawnInterval > BLOCK_SPAWN_INTERVAL_MIN) blockSpawnInterval--; nextTempoIncrease += 600; } // Gradually increase block speed and decrease spawn interval every frame for smooth acceleration if (blockSpeed < BLOCK_SPEED_MAX) { blockSpeed += 0.01; // very slow, smooth increase if (blockSpeed > BLOCK_SPEED_MAX) blockSpeed = BLOCK_SPEED_MAX; } if (blockSpawnInterval > BLOCK_SPAWN_INTERVAL_MIN) { blockSpawnInterval -= 0.01; if (blockSpawnInterval < BLOCK_SPAWN_INTERVAL_MIN) blockSpawnInterval = BLOCK_SPAWN_INTERVAL_MIN; } // Spawn new block exactly on music beat using LK.music.getBeat API var currentBeat = LK.music && LK.music.getBeat ? LK.music.getBeat('pianoTrack') : undefined; if (typeof currentBeat === 'number' && currentBeat !== lastBeat) { spawnBlock(); lastBeat = currentBeat; ticksSinceLastBlock = 0; } else if (ticksSinceLastBlock >= blockSpawnInterval && (!(LK.music && LK.music.getBeat) || typeof currentBeat !== 'number')) { // fallback: spawn at interval if beat API not available spawnBlock(); ticksSinceLastBlock = 0; } // Move blocks for (var i = blocks.length - 1; i >= 0; i--) { var block = blocks[i]; if (block.hit || block.missed) continue; block.y += blockSpeed; // Missed block (passed target line + window) if (block.y - TARGET_LINE_Y > MISS_WINDOW) { block.missed = true; combo = 0; comboTxt.setText(''); LK.getSound('miss').play(); // Flash screen red LK.effects.flashScreen(0xff0000, 600); // End game isPlaying = false; LK.stopMusic(); LK.showGameOver(); break; } } // End game if song ends if (songTicks > songLengthTicks) { isPlaying = false; LK.stopMusic(); LK.showYouWin(); } }; // --- INPUT HANDLING --- // Tap/click on falling block at the right time game.down = function (x, y, obj) { if (!isPlaying) return; // Only allow taps below the target line (to avoid accidental taps) if (y < TARGET_LINE_Y - BLOCK_HEIGHT) return; // Find the block closest to the target line in each lane var tapped = false; for (var i = 0; i < NUM_LANES; i++) { // Check if tap is in this lane var laneLeft = laneXs[i] - LANE_WIDTH / 2; var laneRight = laneXs[i] + LANE_WIDTH / 2; if (x >= laneLeft && x < laneRight) { // Find the first block in this lane within the hit window var bestBlock = null; var bestDist = 9999; for (var j = 0; j < blocks.length; j++) { var block = blocks[j]; if (block.lane !== i || block.hit || block.missed) continue; var dist = Math.abs(block.y - TARGET_LINE_Y); if (dist < HIT_WINDOW && dist < bestDist) { bestBlock = block; bestDist = dist; } } if (bestBlock) { // Hit! bestBlock.hit = true; tapped = true; score += 1; combo += 1; if (combo > bestCombo) bestCombo = combo; scoreTxt.setText(score + ''); comboTxt.setText(combo > 1 ? combo + ' combo!' : ''); bestComboTxt.setText('Best: ' + bestCombo); bestBlock.showHitEffect(); // Play a different piano note sound for each lane, randomize note for each tap var noteLane = bestBlock.lane; var noteOptions = ['piano_note_0', 'piano_note_1', 'piano_note_2', 'piano_note_3']; // Pick a random note different from the lane's default var noteId = noteOptions[Math.floor(Math.random() * noteOptions.length)]; LK.getSound(noteId).play(); // Animate block fade out tween(bestBlock, { alpha: 0 }, { duration: 180, onFinish: function onFinish() { bestBlock.destroy(); } }); } break; } } if (!tapped) { // Missed tap (tapped empty lane or wrong time) combo = 0; comboTxt.setText(''); LK.getSound('miss').play(); LK.effects.flashScreen(0xff0000, 400); isPlaying = false; LK.stopMusic(); LK.showGameOver(); } }; // --- GAME OVER / WIN HANDLING --- // (Handled by LK engine, game will be reset automatically) // --- MUSIC RESTART ON GAME RESET --- LK.on('gameReset', function () { startGame(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Block class: falling piano key
var Block = Container.expand(function () {
var self = Container.call(this);
// Attach block asset
var blockAsset = self.attachAsset('block', {
anchorX: 0.5,
anchorY: 0.5
});
// Track if block has been hit or missed
self.hit = false;
self.missed = false;
// For hit effect
self.showHitEffect = function () {
var effect = LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
self.addChild(effect);
tween(effect, {
alpha: 0
}, {
duration: 300,
easing: tween.linear,
onFinish: function onFinish() {
effect.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Background asset covering the entire game area
// --- FULLSCREEN BACKGROUND ---
// Create a background image that covers the entire game area
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
game.addChild(background);
// Piano note sounds for each lane (C, D, E, G as example)
// Music (looping, but we will play it once per game)
// Sound for miss
// Sound for block hit
// Block hit effect
// Target line
// Falling block (piano key)
// --- GAME CONSTANTS ---
// Piano note sounds for each lane
// Piano note sounds for each lane (C, D, E, G as example)
var NUM_LANES = 4;
var LANE_WIDTH = 400; // 2048/4 = 512, but leave some margin
var BLOCK_WIDTH = 300;
var BLOCK_HEIGHT = 120;
var BLOCK_SPEED_START = 12; // px per frame (60fps)
var BLOCK_SPEED_MAX = 32;
var BLOCK_SPEED_INCREMENT = 0.5; // per level
var BLOCK_SPAWN_INTERVAL = 48; // frames between blocks (will decrease as tempo increases)
var BLOCK_SPAWN_INTERVAL_MIN = 18;
var TARGET_LINE_Y = 2732 - 320; // 320px from bottom
var HIT_WINDOW = 80; // px window for perfect hit
var MISS_WINDOW = 120; // px window for miss
// --- GAME STATE ---
var blocks = [];
var score = 0;
var combo = 0;
var bestCombo = 0;
var blockSpeed = BLOCK_SPEED_START;
var blockSpawnInterval = BLOCK_SPAWN_INTERVAL;
var ticksSinceLastBlock = 0;
var isPlaying = false;
var songTicks = 0;
var songLengthTicks = 60 * 60; // 60 seconds at 60fps (will end game if song ends)
var nextTempoIncrease = 600; // every 10 seconds
// --- LANE POSITIONS ---
var laneXs = [];
for (var i = 0; i < NUM_LANES; i++) {
// Center blocks in each lane
laneXs[i] = LANE_WIDTH / 2 + i * LANE_WIDTH + (2048 - NUM_LANES * LANE_WIDTH) / 2;
}
// --- GUI ELEMENTS ---
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Combo text
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFE066
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 130;
// Best combo text
var bestComboTxt = new Text2('', {
size: 60,
fill: 0xCCCCCC
});
bestComboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(bestComboTxt);
bestComboTxt.y = 210;
// --- TARGET LINE ---
var targetLine = LK.getAsset('targetLine', {
anchorX: 0.5,
// center horizontally
anchorY: 0.5,
// center vertically
x: 2048 / 2,
// center of screen
y: 2732 / 2,
// exactly center vertically
scaleX: 1,
scaleY: 1
});
game.addChild(targetLine);
// Make it a long, thin horizontal blue bar (yanlamasına uzatılmış mavi çubuk) at the center of the screen
targetLine.width = 2000;
targetLine.height = 80; // Kalınlaştırıldı
targetLine.rotation = 0; // 0 degrees, horizontal (yan)
targetLine.x = 2048 / 2;
targetLine.y = TARGET_LINE_Y;
// targetLine.tint = 0x2196F3;
// --- GAME START ---
function startGame() {
// Reset state
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].destroy();
blocks.splice(i, 1);
}
score = 0;
combo = 0;
bestCombo = 0;
blockSpeed = BLOCK_SPEED_START;
blockSpawnInterval = BLOCK_SPAWN_INTERVAL;
ticksSinceLastBlock = 0;
isPlaying = true;
songTicks = 0;
nextTempoIncrease = 600;
scoreTxt.setText('0');
comboTxt.setText('');
bestComboTxt.setText('');
// Start music
LK.playMusic('pianoTrack', {
loop: false
});
}
startGame();
// --- BLOCK SCHEDULING ---
// For MVP, blocks are spawned randomly in lanes, but now in sync with the music beat using LK.music.getBeat API
function spawnBlock() {
var lane = Math.floor(Math.random() * NUM_LANES);
var block = new Block();
block.x = laneXs[lane];
block.y = -BLOCK_HEIGHT / 2;
block.lane = lane;
block.hit = false;
block.missed = false;
blocks.push(block);
game.addChild(block);
}
// --- BEAT SYNC SCHEDULING ---
var lastBeat = -1;
// --- GAME UPDATE ---
game.update = function () {
if (!isPlaying) return;
songTicks++;
ticksSinceLastBlock++;
// Increase tempo every 10 seconds, but do it smoothly every frame for gradual acceleration
if (songTicks >= nextTempoIncrease) {
if (blockSpeed < BLOCK_SPEED_MAX) blockSpeed += BLOCK_SPEED_INCREMENT;
if (blockSpawnInterval > BLOCK_SPAWN_INTERVAL_MIN) blockSpawnInterval--;
nextTempoIncrease += 600;
}
// Gradually increase block speed and decrease spawn interval every frame for smooth acceleration
if (blockSpeed < BLOCK_SPEED_MAX) {
blockSpeed += 0.01; // very slow, smooth increase
if (blockSpeed > BLOCK_SPEED_MAX) blockSpeed = BLOCK_SPEED_MAX;
}
if (blockSpawnInterval > BLOCK_SPAWN_INTERVAL_MIN) {
blockSpawnInterval -= 0.01;
if (blockSpawnInterval < BLOCK_SPAWN_INTERVAL_MIN) blockSpawnInterval = BLOCK_SPAWN_INTERVAL_MIN;
}
// Spawn new block exactly on music beat using LK.music.getBeat API
var currentBeat = LK.music && LK.music.getBeat ? LK.music.getBeat('pianoTrack') : undefined;
if (typeof currentBeat === 'number' && currentBeat !== lastBeat) {
spawnBlock();
lastBeat = currentBeat;
ticksSinceLastBlock = 0;
} else if (ticksSinceLastBlock >= blockSpawnInterval && (!(LK.music && LK.music.getBeat) || typeof currentBeat !== 'number')) {
// fallback: spawn at interval if beat API not available
spawnBlock();
ticksSinceLastBlock = 0;
}
// Move blocks
for (var i = blocks.length - 1; i >= 0; i--) {
var block = blocks[i];
if (block.hit || block.missed) continue;
block.y += blockSpeed;
// Missed block (passed target line + window)
if (block.y - TARGET_LINE_Y > MISS_WINDOW) {
block.missed = true;
combo = 0;
comboTxt.setText('');
LK.getSound('miss').play();
// Flash screen red
LK.effects.flashScreen(0xff0000, 600);
// End game
isPlaying = false;
LK.stopMusic();
LK.showGameOver();
break;
}
}
// End game if song ends
if (songTicks > songLengthTicks) {
isPlaying = false;
LK.stopMusic();
LK.showYouWin();
}
};
// --- INPUT HANDLING ---
// Tap/click on falling block at the right time
game.down = function (x, y, obj) {
if (!isPlaying) return;
// Only allow taps below the target line (to avoid accidental taps)
if (y < TARGET_LINE_Y - BLOCK_HEIGHT) return;
// Find the block closest to the target line in each lane
var tapped = false;
for (var i = 0; i < NUM_LANES; i++) {
// Check if tap is in this lane
var laneLeft = laneXs[i] - LANE_WIDTH / 2;
var laneRight = laneXs[i] + LANE_WIDTH / 2;
if (x >= laneLeft && x < laneRight) {
// Find the first block in this lane within the hit window
var bestBlock = null;
var bestDist = 9999;
for (var j = 0; j < blocks.length; j++) {
var block = blocks[j];
if (block.lane !== i || block.hit || block.missed) continue;
var dist = Math.abs(block.y - TARGET_LINE_Y);
if (dist < HIT_WINDOW && dist < bestDist) {
bestBlock = block;
bestDist = dist;
}
}
if (bestBlock) {
// Hit!
bestBlock.hit = true;
tapped = true;
score += 1;
combo += 1;
if (combo > bestCombo) bestCombo = combo;
scoreTxt.setText(score + '');
comboTxt.setText(combo > 1 ? combo + ' combo!' : '');
bestComboTxt.setText('Best: ' + bestCombo);
bestBlock.showHitEffect();
// Play a different piano note sound for each lane, randomize note for each tap
var noteLane = bestBlock.lane;
var noteOptions = ['piano_note_0', 'piano_note_1', 'piano_note_2', 'piano_note_3'];
// Pick a random note different from the lane's default
var noteId = noteOptions[Math.floor(Math.random() * noteOptions.length)];
LK.getSound(noteId).play();
// Animate block fade out
tween(bestBlock, {
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
bestBlock.destroy();
}
});
}
break;
}
}
if (!tapped) {
// Missed tap (tapped empty lane or wrong time)
combo = 0;
comboTxt.setText('');
LK.getSound('miss').play();
LK.effects.flashScreen(0xff0000, 400);
isPlaying = false;
LK.stopMusic();
LK.showGameOver();
}
};
// --- GAME OVER / WIN HANDLING ---
// (Handled by LK engine, game will be reset automatically)
// --- MUSIC RESTART ON GAME RESET ---
LK.on('gameReset', function () {
startGame();
});