User prompt
Add score animation text display on center circle with blue color and tween animation. Bunu beyaz renk yap fontu da biraz büyüt ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add score animation text display on center circle with blue color and tween animation bunu Mavi renk yap ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Center circle assetinim üstünde her bir seferinde kazanılan puanı +1, +2,+4,+8,+16 şeklinde animasyon şeklinde yaz. Rengi mavi olsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Seviye 3 ve seviye 4e arka plana uzak boşluğu animasyonu ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Arcleft arcright arcup arcdown bunlara tıklayınca arka plan renkleri değişiyor. Onların asetlerini ekle değiştirmek istiyorum
User prompt
Seviye 2 seviye 3 seviye 4 için farklı animasyonlar koyalım arka plana ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Soundlarda sesleri göster göremiyorum ben de değiştireyim
User prompt
Oyun game over olunca Final score bölümünde skor yazmıyor onu düzlet
User prompt
Soundlarda bu sesleri göster göremiyorum ben de değiştireyim
User prompt
Ortadaki arcup arcdown arcleft arcrightlara basıldığında bir ses çıkıyor, ben 15-20 adet ses eklesem her birine basıldığında karışık çıksa? Örneğin 3 adet piyano 3 adet bateri 3 adet gitar 3 adet obua 3 adet arp sesi eklesem bunlar bu arclara tıklandığında rastgele çalsa? Olur mu sence fikrin nedir? Oyun müzik temalı olduğu için farklı müzik aletlerini de kullanmış oluruz diye düşünüyorum
User prompt
Dördüncü seviye için müzik ekle üçüncü seviyeden daha hızlı olsun
User prompt
Oyunda seviyeler arası geçişte müziği değiştirsek? İkinci seviyede birinci seviyeden daha hızlı üçüncüde de ikinci seviyeden daha hızlı bir müzik koysak.
User prompt
efekti beğenmedim tam anlaşılmıyor sesli birşey olsun sevinen alkışlayan insanlar tarzı
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Light (note) class var Light = Container.expand(function () { var self = Container.call(this); // Properties to be set on creation: // self.direction: 'up' | 'down' | 'left' | 'right' // self.color: 'red' | 'blue' | 'green' | 'yellow' // self.speed: px per tick // self.targetX, self.targetY: where to move toward // Attach note type-specific asset as the light symbol // Always use the correct asset for the note type, matching noteTypes array var noteAssetId; if (self.noteAsset) { noteAssetId = self.noteAsset; } else { // Fallback: find asset from noteTypes array by name noteAssetId = "note_quarter"; for (var i = 0; i < noteTypes.length; ++i) { if (noteTypes[i].name === self.noteType) { noteAssetId = noteTypes[i].asset; break; } } } var light = self.attachAsset(noteAssetId, { anchorX: 0.5, anchorY: 0.5 }); // For hit detection self.radius = light.width * 0.5; // For animation self.active = true; // If false, ignore update // Called every tick self.update = function () { if (!self.active) return; // Move towards center of white circle (never stop at target, just keep moving in that direction) var dx = centerX - self.x; var dy = centerY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) dist = 0.0001; // avoid division by zero self.x += self.speed * dx / dist; self.y += self.speed * dy / dist; // Lights never stop moving, so do not clamp or halt at target // --- Add glowing effect: pulse scale and alpha --- var pulse = 0.15 * Math.sin(LK.ticks * 0.25 + self.x + self.y) + 1.0; self.scaleX = pulse; self.scaleY = pulse; self.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.5 + self.x); // --- Add rotation effect --- // Slowed down rotation for a smoother effect self.rotation += 0.045; // rotate slower for a more pleasant effect }; // Animate and destroy on hit/miss self.hit = function (_onFinish) { self.active = false; // Flash the light with a white overlay before animating out LK.effects.flashObject(self, 0xffffff, 120); tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); if (_onFinish) _onFinish(); } }); }; return self; }); // StepLadderBar: Asymmetric, playful step-only ladder (no rails), using a step asset var StepLadderBar = Container.expand(function () { var self = Container.call(this); self.stepCount = 30; // 30 steps self.notes = []; self.stepPositions = []; // Step asset: must be in asset library as 'ladderStep' var stepAsset = LK.getAsset('ladderStep', { anchorX: 0.5, anchorY: 0.5 }); var stepW = stepAsset.width; var stepH = stepAsset.height; var ladderHeight = 2732 - 320; // leave 160px margin top/bottom var startY = 2732 - 160 - stepH / 2; var endY = 160 + stepH / 2; var stepSpacing = (startY - endY) / (self.stepCount - 1); var centerX = 2048 / 2; // Asymmetry parameters var maxOffset = 520; // much larger horizontal offset from center for more dramatic zig-zag for (var i = 0; i < self.stepCount; ++i) { var y = startY - i * stepSpacing; // Alternate left/right, but with more organic, non-uniform pattern var direction = i % 2 === 0 ? 1 : -1; // Stronger sine wave for zig-zag, plus a secondary wave for more organic feel var zigzag = Math.sin(i * Math.PI / 5.2) * maxOffset * 0.85 + Math.sin(i * Math.PI / 2.7) * maxOffset * 0.25; // Larger, more varied jitter for playful irregularity var jitter = (Math.random() - 0.5) * 120 + Math.sin(i * 1.7) * 30; // Occasionally flip direction for extra asymmetry if (i % 7 === 0) direction *= -1; var x = centerX + direction * (maxOffset * 0.6) + zigzag + jitter; self.stepPositions.push({ x: x, y: y }); // Draw step (horizontal bar) var step = self.attachAsset('ladderStep', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }); } // Add a note to the next available step, animating it in self.addNote = function (color) { var idx = self.notes.length; if (idx >= self.stepCount) return; // No more steps var pos = self.stepPositions[idx]; // Use note type-specific asset for step ladder bar notes // Find the noteType from the last hit light (if available) var noteType = 'quarter'; if (lights.length > 0 && lights[0].lastHitNoteType) { noteType = lights[0].lastHitNoteType; } else if (typeof self.lastAddedNoteType === "string") { noteType = self.lastAddedNoteType; } // Always use the correct asset for the note type, matching noteTypes array var noteAssetId; if (lights.length > 0 && lights[0].noteAsset) { noteAssetId = lights[0].noteAsset; } else { noteAssetId = "note_quarter"; for (var i = 0; i < noteTypes.length; ++i) { if (noteTypes[i].name === noteType) { noteAssetId = noteTypes[i].asset; break; } } } var note = self.attachAsset(noteAssetId, { anchorX: 0.5, anchorY: 0.5, x: pos.x, y: pos.y, scaleX: 1.0, scaleY: 1.0, tint: color === 'red' ? 0xff4b4b : color === 'blue' ? 0x4bafff : color === 'green' ? 0x4bff4b : color === 'yellow' ? 0xffe14b : 0xffffff, alpha: 0.0 }); self.notes.push(note); // Animate in: fade and pop tween(note, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 180, easing: tween.easeOut, onFinish: function onFinish() { tween(note, { scaleX: 1.0, scaleY: 1.0 }, { duration: 120, easing: tween.easeIn }); } }); }; return self; }); // Animated falling treble clefs background var TrebleClefBackground = Container.expand(function () { var self = Container.call(this); self.clefs = []; self.spawnInterval = 60; // spawn every 60 ticks (1s) self.lastSpawnTick = 0; self.maxClefs = 10; // Precompute asset size var clefAsset = LK.getAsset('trebleClefStaff', { anchorX: 0.5, anchorY: 0.5 }); var clefW = clefAsset.width; var clefH = clefAsset.height; // Spawn a new clef at random X self.spawnClef = function () { if (self.clefs.length >= self.maxClefs) return; var x = 100 + Math.random() * (2048 - 200); var y = -clefH / 2 - Math.random() * 200; var speed = 2.5 + Math.random() * 2.5; var alpha = 0.18 + Math.random() * 0.12; var scale = 0.7 + Math.random() * 0.5; var rot = (Math.random() - 0.5) * 0.2; var clef = self.attachAsset('trebleClefStaff', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: scale, scaleY: scale, alpha: alpha, rotation: rot }); clef._vy = speed; clef._vr = (Math.random() - 0.5) * 0.01; self.clefs.push(clef); }; self.update = function () { // Spawn new clefs at interval if (LK.ticks - self.lastSpawnTick > self.spawnInterval) { self.spawnClef(); self.lastSpawnTick = LK.ticks; } // Animate all clefs for (var i = self.clefs.length - 1; i >= 0; --i) { var clef = self.clefs[i]; clef.y += clef._vy; clef.rotation += clef._vr; // Fade in/out at top/bottom if (clef.y < 200) { clef.alpha = Math.min(0.3, 0.12 + (clef.y + clef.height / 2) / 400); } else if (clef.y > 2732 - 200) { clef.alpha = Math.max(0, 0.3 - (clef.y - (2732 - 200)) / 400); } // Remove if out of screen if (clef.y - clef.height / 2 > 2732 + 40) { self.removeChild(clef); self.clefs.splice(i, 1); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Zayıf zil sesi (right) // Güçlü zil sesi (left) // Soft davul sesi (down) // Tok davul sesi (up) // Sound assets for each direction // Quarter-circle arcs (using ellipse, but will be visually quartered by anchor/position) // LK.init.music('bgmusic', {volume: 0.5}); // Music (background, optional, not played in MVP) // Sound effects for each direction // Four colored "light" shapes for incoming notes // Four quarter arcs (quarter circles) for directions: up, down, left, right // --- Layout constants --- // Drum and cymbal sounds for arc taps // Up (red) - main drum // Down (blue) - high-pitched drum // Left (green) - low cymbal // Right (yellow) - high cymbal // Arc and UI shapes // Ladder and clef images // --- Note shape assets for all note types (for easy editing) --- // birlik // ikilik // dörtlük // sekizlik // onaltılık // otuzikilik // altmışdörtlük // Music and sounds var centerX = 2048 / 2; var centerY = 2732 / 2; var centerRadius = 200; // centerCircle radius // --- Animated falling treble clefs background --- var trebleClefBg = new TrebleClefBackground(); trebleClefBg.x = 0; trebleClefBg.y = 0; game.addChild(trebleClefBg); // --- Step ladder bar: starts at bottom, fills screen vertically --- var stepLadderBar = new StepLadderBar(); stepLadderBar.x = 0; stepLadderBar.y = 0; game.addChild(stepLadderBar); // Arc positions (relative to center) // We want the inner edge of each arc to touch the center circle (radius = 200) // Each arc is 300x300, so the distance from center to arc center = centerRadius + arcW/2 = 200 + 150 = 350 var arcDistance = 200 + 150; // 350 var arcOffset = arcDistance; // alias for compatibility with spawn/target logic var arcW = 300, arcH = 300; // all arcs are quarter circles, so 300x300 // --- Create main elements --- // Center circle var centerCircle = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5 }); centerCircle.x = centerX; centerCircle.y = centerY; game.addChild(centerCircle); // Quarter arcs - D-pad style, all arcs directly adjacent to center circle // Up arc: above center, touching at bottom var arcUp = LK.getAsset('arcUp', { anchorX: 0.5, // center horizontally anchorY: 1.0 // bottom }); arcUp.x = centerX; arcUp.y = centerY - centerRadius; game.addChild(arcUp); // Down arc: below center, touching at top var arcDown = LK.getAsset('arcDown', { anchorX: 0.5, // center horizontally anchorY: 0.0 // top }); arcDown.x = centerX; arcDown.y = centerY + centerRadius; game.addChild(arcDown); // Left arc: left of center, touching at right var arcLeft = LK.getAsset('arcLeft', { anchorX: 1.0, // right anchorY: 0.5 // center vertically }); arcLeft.x = centerX - centerRadius; arcLeft.y = centerY; game.addChild(arcLeft); // Right arc: right of center, touching at left var arcRight = LK.getAsset('arcRight', { anchorX: 0.0, // left anchorY: 0.5 // center vertically }); arcRight.x = centerX + centerRadius; arcRight.y = centerY; game.addChild(arcRight); // --- Arc hitboxes for input detection --- var arcHitboxes = [{ dir: 'up', x: centerX, y: centerY - centerRadius - arcH / 2, w: arcW, h: arcH, color: 'red', asset: arcUp }, { dir: 'down', x: centerX, y: centerY + centerRadius + arcH / 2, w: arcW, h: arcH, color: 'blue', asset: arcDown }, { dir: 'left', x: centerX - centerRadius - arcW / 2, y: centerY, w: arcW, h: arcH, color: 'green', asset: arcLeft }, { dir: 'right', x: centerX + centerRadius + arcW / 2, y: centerY, w: arcW, h: arcH, color: 'yellow', asset: arcRight }]; // --- Score, lives and wave display --- var score = 0; var highScore = storage.highScore || 0; // Lives system removed: no lives variable, no display var level = 1; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var highScoreTxt = new Text2('High: ' + highScore, { size: 60, fill: 0xFFD700 }); // Anchor to left (0), top (0) highScoreTxt.anchor.set(0, 0); // Place at (x=110, y=20) to avoid top left menu and keep visible highScoreTxt.x = 110; highScoreTxt.y = 20; LK.gui.top.addChild(highScoreTxt); var levelTxt = new Text2('Level 1', { size: 90, fill: 0xFFD700 }); levelTxt.anchor.set(0.5, 0); levelTxt.y = 110; // place below score LK.gui.top.addChild(levelTxt); // Lives display (top right, away from menu) // --- Light management --- var lights = []; // No lightActive flag needed; allow multiple lights at once // --- Color mapping for directions --- var dirColor = { 'up': 'red', 'down': 'blue', 'left': 'green', 'right': 'yellow' }; var dirSound = { 'up': 'drumRed', 'down': 'drumBlue', 'left': 'drumGreen', 'right': 'drumYellow' }; var dirBg = { 'up': 0xff4b4b, 'down': 0x4bafff, 'left': 0x4bff4b, 'right': 0xffe14b }; // --- Light spawn positions (offscreen, towards arcs) --- function getLightSpawn(dir) { var dist = 700; // distance from center if (dir === 'up') return { x: centerX, y: centerY - arcOffset - dist }; if (dir === 'down') return { x: centerX, y: centerY + arcOffset + dist }; if (dir === 'left') return { x: centerX - arcOffset - dist, y: centerY }; if (dir === 'right') return { x: centerX + arcOffset + dist, y: centerY }; return { x: centerX, y: centerY }; } function getLightTarget(dir) { if (dir === 'up') return { x: centerX, y: centerY - arcOffset }; if (dir === 'down') return { x: centerX, y: centerY + arcOffset }; if (dir === 'left') return { x: centerX - arcOffset, y: centerY }; if (dir === 'right') return { x: centerX + arcOffset, y: centerY }; return { x: centerX, y: centerY }; } // --- Note types and their musical properties --- // Each note type uses its own asset, speed, and point value as described: // - note_whole: slowest, 1 point // - note_half: 10% faster than whole, 2 points // - note_quarter: 10% faster than half, 4 points // - note_eighth: 10% faster than quarter, 8 points // - note_sixteenth: 10% faster than eighth, 16 points var noteTypes = [{ name: "whole", asset: "note_whole", speed: 1.0, points: 1, weight: 1 }, { name: "half", asset: "note_half", speed: 1.1, // 10% faster than whole points: 2, weight: 1 }, { name: "quarter", asset: "note_quarter", speed: 1.1 * 1.1, // 10% faster than half points: 4, weight: 1 }, { name: "eighth", asset: "note_eighth", speed: 1.1 * 1.1 * 1.1, // 10% faster than quarter points: 8, weight: 1 }, { name: "sixteenth", asset: "note_sixteenth", speed: 1.1 * 1.1 * 1.1 * 1.1, // 10% faster than eighth points: 16, weight: 1 }]; // note_whole, note_half, note_quarter ve note_eighth kullanılacak // Precompute weighted note type array for random selection var weightedNoteTypes = []; for (var i = 0; i < noteTypes.length; ++i) { for (var j = 0; j < noteTypes[i].weight; ++j) { weightedNoteTypes.push(noteTypes[i]); } } // note_whole, note_half, note_quarter ve note_eighth eklendi // --- Level and Light speed: base speed for whole note, others are multiples --- var level = 1; function getLevelForScore(score) { // Level 1: 0-49, Level 2: 50-149, Level 3: 150-299, Level 4: 300-499, Level 5: 500-749, etc. // Each new level every +150 points after 50 if (score < 50) return 1; return 2 + Math.floor((score - 50) / 100); } function getLightSpeedForNoteType(noteType) { // Use the new speed property from noteTypes var base = 1.2; var currentLevel = getLevelForScore(score); // Level 2: +120%, Level 3: +200% (multiplicative), then +50% for each further level if (currentLevel === 2) { base *= 2.2; } else if (currentLevel === 3) { base *= 2.2 * 3.0; } else if (currentLevel > 3) { base *= 2.2 * 3.0; for (var i = 4; i <= currentLevel; ++i) { base *= 1.5; } } // noteType can be either a noteType object or a {name: ...} object var speedMultiplier = noteType.speed !== undefined ? noteType.speed : 1.0; return base * speedMultiplier; } // --- Spawn a light (note) with note type --- function spawnLight(dir) { var color = dirColor[dir]; var spawn = getLightSpawn(dir); var target = getLightTarget(dir); // note_whole veya note_half rastgele seçilecek var noteType = weightedNoteTypes[Math.floor(Math.random() * weightedNoteTypes.length)]; var light = new Light(); light.noteType = noteType.name; light.noteAsset = noteType.asset; light.notePoints = noteType.points; light.direction = dir; light.color = color; light.x = spawn.x; light.y = spawn.y; light.targetX = target.x; light.targetY = target.y; light.speed = getLightSpeedForNoteType(noteType); // Asset düzeltmesi: ilgili nota için özel asset kullan if (light.children && light.children.length > 0) { for (var i = light.children.length - 1; i >= 0; --i) { light.removeChild(light.children[i]); } } var noteAssetObj = light.attachAsset(noteType.asset, { anchorX: 0.5, anchorY: 0.5 }); light.radius = noteAssetObj.width * 0.5; lights.push(light); game.addChild(light); // No lightActive flag or logic needed } // --- Continuous light spawning logic --- // Timers for continuous spawning var lightSpawnTimer = null; var showInfoTimer = null; var dirs = ['up', 'down', 'left', 'right']; function clearLightTimers() { if (lightSpawnTimer !== null) { LK.clearTimeout(lightSpawnTimer); lightSpawnTimer = null; } if (showInfoTimer !== null) { LK.clearTimeout(showInfoTimer); showInfoTimer = null; } } // Main continuous light spawning function function startLightSpawning() { clearLightTimers(); // Always spawn at random intervals, but ensure at least 2 seconds between spawns var baseInterval, randomRange, minInterval; if (score < 20) { baseInterval = 320; randomRange = 260; minInterval = 2000; } else { // After score 20, make spawn intervals more irregular and sometimes much faster baseInterval = 80 + Math.floor(Math.random() * 120); // base between 80-200ms randomRange = 400 + Math.floor(Math.random() * 200); // random up to 600ms minInterval = 1200 + Math.floor(Math.random() * 600); // min interval 1200-1800ms } // Spawn a light var dir = dirs[Math.floor(Math.random() * 4)]; spawnLight(dir); // Schedule next light var nextInterval = baseInterval; if (randomRange > 0) { nextInterval += Math.floor(Math.random() * randomRange); } // Enforce minimum interval of 1 second (1000ms) if (nextInterval < minInterval) { nextInterval = minInterval; } lightSpawnTimer = LK.setTimeout(function () { startLightSpawning(); }, nextInterval); } // Call this to (re)start spawning after all lights cleared function startWave() { startLightSpawning(); } // --- Handle tap input --- function getDirectionFromPoint(x, y) { // Returns 'up', 'down', 'left', 'right' or null for (var i = 0; i < arcHitboxes.length; ++i) { var arc = arcHitboxes[i]; // Use bounding box for MVP var dx = x - arc.x; var dy = y - arc.y; if (arc.dir === 'up' || arc.dir === 'down') { if (Math.abs(dx) < arc.w / 2 && Math.abs(dy) < arc.h / 2) return arc.dir; } else { if (Math.abs(dx) < arc.w / 2 && Math.abs(dy) < arc.h / 2) return arc.dir; } } return null; } // --- Handle tap (down) --- game.down = function (x, y, obj) { // Use the provided x, y directly as they are already in game coordinates var dir = getDirectionFromPoint(x, y); if (!dir) return; // Always play the arc's sound on tap, even if no light is present var soundPlayed = false; LK.getSound(dirSound[dir]).play(); soundPlayed = true; // --- Beat vibration/tween effect for arc images (hoparlör titreşim) --- var arcObj = null; if (dir === 'up') arcObj = arcUp;else if (dir === 'down') arcObj = arcDown;else if (dir === 'left') arcObj = arcLeft;else if (dir === 'right') arcObj = arcRight; if (arcObj) { // Stop any previous tweens on this arc tween.stop(arcObj, { scaleX: true, scaleY: true }); // Animate: quick scale up and back (beat effect) var origScaleX = arcObj.scaleX !== undefined ? arcObj.scaleX : 1; var origScaleY = arcObj.scaleY !== undefined ? arcObj.scaleY : 1; tween(arcObj, { scaleX: origScaleX * 1.18, scaleY: origScaleY * 1.18 }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(arcObj, { scaleX: origScaleX, scaleY: origScaleY }, { duration: 90, easing: tween.easeIn }); } }); } // Find the closest active light matching direction and close enough for (var i = 0; i < lights.length; ++i) { var light = lights[i]; if (!light.active) continue; // If direction matches and light is close enough to arc var target = getLightTarget(dir); var dx = light.x - target.x; var dy = light.y - target.y; var dist = Math.sqrt(dx * dx + dy * dy); // Only allow tap if light has entered arc zone and has NOT reached centerCircle var dxCenter = light.x - centerX; var dyCenter = light.y - centerY; var distCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter); if (light.direction === dir && light.enteredArc === true && distCenter > centerRadius) { // Correct! // Animate note to step ladder bar instead of destroying light.active = false; var startX = light.x; var startY = light.y; var idx = stepLadderBar.notes.length; var endX = stepLadderBar.x + 190; var endY = stepLadderBar.y + (stepLadderBar.stepYs && stepLadderBar.stepYs[idx] !== undefined ? stepLadderBar.stepYs[idx] : 30 + idx * 90); // Track last hit note type for correct asset on step ladder bar stepLadderBar.lastAddedNoteType = light.noteType; light.lastHitNoteType = light.noteType; tween(light, { x: endX, y: endY, scaleX: 1.1, scaleY: 1.1, alpha: 1 }, { duration: 340, easing: tween.easeIn, onFinish: function onFinish() { // Remove from array and destroy light for (var j = 0; j < lights.length; ++j) { if (lights[j] === light) { lights.splice(j, 1); break; } } light.destroy(); // Add a note to the step ladder bar stepLadderBar.addNote(light.color); } }); // Her nota tipi kendi değeri kadar puan kazandırır var noteScoreTable = { whole: 1, half: 2, quarter: 4, eighth: 8, sixteenth: 16 }; // note_whole 1 puan, note_half 2 puan score += noteScoreTable[light.noteType] || 1; scoreTxt.setText(score); // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('High: ' + highScore); } // Update level if needed var newLevel = getLevelForScore(score); if (newLevel !== level) { // Level up effect: only trigger when going from 1 to 2 if (level === 1 && newLevel === 2) { // Flash the whole screen gold, then fade out LK.effects.flashScreen(0xffd700, 900); // Play applause/cheering sound (must be in asset library, e.g. 'applause') LK.getSound('applause').play(); // Animate the level text: pop and glow tween.stop(levelTxt, { scaleX: true, scaleY: true, alpha: true }); var origScaleX = levelTxt.scaleX !== undefined ? levelTxt.scaleX : 1; var origScaleY = levelTxt.scaleY !== undefined ? levelTxt.scaleY : 1; var origAlpha = levelTxt.alpha !== undefined ? levelTxt.alpha : 1; tween(levelTxt, { scaleX: origScaleX * 1.5, scaleY: origScaleY * 1.5, alpha: 1 }, { duration: 220, easing: tween.easeOut, onFinish: function onFinish() { tween(levelTxt, { scaleX: origScaleX, scaleY: origScaleY, alpha: origAlpha }, { duration: 320, easing: tween.easeIn }); } }); // Show celebratory text in the center of the screen var cheerTxt = new Text2('👏 SEVİYE ATLADIN! 👏', { size: 160, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); cheerTxt.anchor.set(0.5, 0.5); cheerTxt.x = centerX; cheerTxt.y = centerY; cheerTxt.alpha = 0; game.addChild(cheerTxt); // Animate text: fade in, pop, then fade out tween(cheerTxt, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 320, easing: tween.easeOut, onFinish: function onFinish() { tween(cheerTxt, { scaleX: 1.0, scaleY: 1.0 }, { duration: 180, easing: tween.easeIn, onFinish: function onFinish() { tween(cheerTxt, { alpha: 0 }, { duration: 700, easing: tween.easeIn, onFinish: function onFinish() { if (cheerTxt && cheerTxt.parent) { cheerTxt.parent.removeChild(cheerTxt); } } }); } }); } }); } level = newLevel; levelTxt.setText('Level ' + level); } // Win condition removed: game continues, level and speed will keep increasing as score grows // Play sound // (Already played above, do not play again) // Flash background tween.stop(game, { backgroundColor: true }); var oldBg = game.backgroundColor; // If blue arc (down) light is tapped, set background to blue and keep it if (dir === 'down') { game.setBackgroundColor(0x4bafff); } else if (dir === 'left') { // If green arc (left) light is tapped, set background to green and keep it game.setBackgroundColor(0x4bff4b); } else if (dir === 'right') { // If yellow arc (right) light is tapped, set background to yellow and keep it game.setBackgroundColor(0xffe14b); } else if (dir === 'up') { // If red arc (up) light is tapped, set background to red and keep it game.setBackgroundColor(0xff4b4b); } else { tween(game, { backgroundColor: dirBg[dir] }, { duration: 120, onFinish: function onFinish() { tween(game, { backgroundColor: oldBg }, { duration: 400 }); } }); } // No need to spawn next light here; continuous spawn is always active return; } } // If tap is on arc but no light is close: miss // Flash red for feedback, but do not decrement lives or end game LK.effects.flashScreen(0xff0000, 500); }; // --- Update loop: check for missed lights --- game.update = function () { // Animate falling treble clefs background if (typeof trebleClefBg !== "undefined" && trebleClefBg.update) { trebleClefBg.update(); } for (var i = lights.length - 1; i >= 0; --i) { var light = lights[i]; if (!light.active) continue; // Calculate distance to arc (where tap is allowed to start) var arcTarget = getLightTarget(light.direction); var dxArc = light.x - arcTarget.x; var dyArc = light.y - arcTarget.y; var distArc = Math.sqrt(dxArc * dxArc + dyArc * dyArc); // Calculate distance to centerCircle (where tap is no longer allowed, and game over if reached) var dxCenter = light.x - centerX; var dyCenter = light.y - centerY; var distCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter); // Track if light has entered the arc zone (first contact) if (light.enteredArc === undefined) { // If light is outside arc zone, not yet entered light.enteredArc = false; } // If not yet entered arc, check if it just entered if (!light.enteredArc && light.lastDistArc !== undefined && light.lastDistArc >= 120 && distArc < 120) { light.enteredArc = true; } light.lastDistArc = distArc; // If light has entered arc, but now reached centerCircle: game over immediately if (light.enteredArc && distCenter < centerRadius) { light.hit(function () { for (var j = 0; j < lights.length; ++j) { if (lights[j] === light) { lights.splice(j, 1); break; } } // Flash red and trigger game over immediately LK.effects.flashScreen(0xff0000, 500); LK.showGameOver(); }); continue; } } // No batch logic needed; continuous spawn is always active }; // --- Start game --- score = 0; level = 1; scoreTxt.setText(score); highScore = storage.highScore || 0; if (typeof highScoreTxt !== "undefined") { highScoreTxt.setText('High: ' + highScore); } if (typeof levelTxt !== "undefined") { levelTxt.setText('Level 1'); } // Play background beat music (loops by default) LK.playMusic('beat'); LK.setTimeout(function () { startWave(); }, 600); ; ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Light (note) class
var Light = Container.expand(function () {
var self = Container.call(this);
// Properties to be set on creation:
// self.direction: 'up' | 'down' | 'left' | 'right'
// self.color: 'red' | 'blue' | 'green' | 'yellow'
// self.speed: px per tick
// self.targetX, self.targetY: where to move toward
// Attach note type-specific asset as the light symbol
// Always use the correct asset for the note type, matching noteTypes array
var noteAssetId;
if (self.noteAsset) {
noteAssetId = self.noteAsset;
} else {
// Fallback: find asset from noteTypes array by name
noteAssetId = "note_quarter";
for (var i = 0; i < noteTypes.length; ++i) {
if (noteTypes[i].name === self.noteType) {
noteAssetId = noteTypes[i].asset;
break;
}
}
}
var light = self.attachAsset(noteAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// For hit detection
self.radius = light.width * 0.5;
// For animation
self.active = true; // If false, ignore update
// Called every tick
self.update = function () {
if (!self.active) return;
// Move towards center of white circle (never stop at target, just keep moving in that direction)
var dx = centerX - self.x;
var dy = centerY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) dist = 0.0001; // avoid division by zero
self.x += self.speed * dx / dist;
self.y += self.speed * dy / dist;
// Lights never stop moving, so do not clamp or halt at target
// --- Add glowing effect: pulse scale and alpha ---
var pulse = 0.15 * Math.sin(LK.ticks * 0.25 + self.x + self.y) + 1.0;
self.scaleX = pulse;
self.scaleY = pulse;
self.alpha = 0.85 + 0.15 * Math.sin(LK.ticks * 0.5 + self.x);
// --- Add rotation effect ---
// Slowed down rotation for a smoother effect
self.rotation += 0.045; // rotate slower for a more pleasant effect
};
// Animate and destroy on hit/miss
self.hit = function (_onFinish) {
self.active = false;
// Flash the light with a white overlay before animating out
LK.effects.flashObject(self, 0xffffff, 120);
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
if (_onFinish) _onFinish();
}
});
};
return self;
});
// StepLadderBar: Asymmetric, playful step-only ladder (no rails), using a step asset
var StepLadderBar = Container.expand(function () {
var self = Container.call(this);
self.stepCount = 30; // 30 steps
self.notes = [];
self.stepPositions = [];
// Step asset: must be in asset library as 'ladderStep'
var stepAsset = LK.getAsset('ladderStep', {
anchorX: 0.5,
anchorY: 0.5
});
var stepW = stepAsset.width;
var stepH = stepAsset.height;
var ladderHeight = 2732 - 320; // leave 160px margin top/bottom
var startY = 2732 - 160 - stepH / 2;
var endY = 160 + stepH / 2;
var stepSpacing = (startY - endY) / (self.stepCount - 1);
var centerX = 2048 / 2;
// Asymmetry parameters
var maxOffset = 520; // much larger horizontal offset from center for more dramatic zig-zag
for (var i = 0; i < self.stepCount; ++i) {
var y = startY - i * stepSpacing;
// Alternate left/right, but with more organic, non-uniform pattern
var direction = i % 2 === 0 ? 1 : -1;
// Stronger sine wave for zig-zag, plus a secondary wave for more organic feel
var zigzag = Math.sin(i * Math.PI / 5.2) * maxOffset * 0.85 + Math.sin(i * Math.PI / 2.7) * maxOffset * 0.25;
// Larger, more varied jitter for playful irregularity
var jitter = (Math.random() - 0.5) * 120 + Math.sin(i * 1.7) * 30;
// Occasionally flip direction for extra asymmetry
if (i % 7 === 0) direction *= -1;
var x = centerX + direction * (maxOffset * 0.6) + zigzag + jitter;
self.stepPositions.push({
x: x,
y: y
});
// Draw step (horizontal bar)
var step = self.attachAsset('ladderStep', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
});
}
// Add a note to the next available step, animating it in
self.addNote = function (color) {
var idx = self.notes.length;
if (idx >= self.stepCount) return; // No more steps
var pos = self.stepPositions[idx];
// Use note type-specific asset for step ladder bar notes
// Find the noteType from the last hit light (if available)
var noteType = 'quarter';
if (lights.length > 0 && lights[0].lastHitNoteType) {
noteType = lights[0].lastHitNoteType;
} else if (typeof self.lastAddedNoteType === "string") {
noteType = self.lastAddedNoteType;
}
// Always use the correct asset for the note type, matching noteTypes array
var noteAssetId;
if (lights.length > 0 && lights[0].noteAsset) {
noteAssetId = lights[0].noteAsset;
} else {
noteAssetId = "note_quarter";
for (var i = 0; i < noteTypes.length; ++i) {
if (noteTypes[i].name === noteType) {
noteAssetId = noteTypes[i].asset;
break;
}
}
}
var note = self.attachAsset(noteAssetId, {
anchorX: 0.5,
anchorY: 0.5,
x: pos.x,
y: pos.y,
scaleX: 1.0,
scaleY: 1.0,
tint: color === 'red' ? 0xff4b4b : color === 'blue' ? 0x4bafff : color === 'green' ? 0x4bff4b : color === 'yellow' ? 0xffe14b : 0xffffff,
alpha: 0.0
});
self.notes.push(note);
// Animate in: fade and pop
tween(note, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 180,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(note, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
return self;
});
// Animated falling treble clefs background
var TrebleClefBackground = Container.expand(function () {
var self = Container.call(this);
self.clefs = [];
self.spawnInterval = 60; // spawn every 60 ticks (1s)
self.lastSpawnTick = 0;
self.maxClefs = 10;
// Precompute asset size
var clefAsset = LK.getAsset('trebleClefStaff', {
anchorX: 0.5,
anchorY: 0.5
});
var clefW = clefAsset.width;
var clefH = clefAsset.height;
// Spawn a new clef at random X
self.spawnClef = function () {
if (self.clefs.length >= self.maxClefs) return;
var x = 100 + Math.random() * (2048 - 200);
var y = -clefH / 2 - Math.random() * 200;
var speed = 2.5 + Math.random() * 2.5;
var alpha = 0.18 + Math.random() * 0.12;
var scale = 0.7 + Math.random() * 0.5;
var rot = (Math.random() - 0.5) * 0.2;
var clef = self.attachAsset('trebleClefStaff', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: scale,
scaleY: scale,
alpha: alpha,
rotation: rot
});
clef._vy = speed;
clef._vr = (Math.random() - 0.5) * 0.01;
self.clefs.push(clef);
};
self.update = function () {
// Spawn new clefs at interval
if (LK.ticks - self.lastSpawnTick > self.spawnInterval) {
self.spawnClef();
self.lastSpawnTick = LK.ticks;
}
// Animate all clefs
for (var i = self.clefs.length - 1; i >= 0; --i) {
var clef = self.clefs[i];
clef.y += clef._vy;
clef.rotation += clef._vr;
// Fade in/out at top/bottom
if (clef.y < 200) {
clef.alpha = Math.min(0.3, 0.12 + (clef.y + clef.height / 2) / 400);
} else if (clef.y > 2732 - 200) {
clef.alpha = Math.max(0, 0.3 - (clef.y - (2732 - 200)) / 400);
}
// Remove if out of screen
if (clef.y - clef.height / 2 > 2732 + 40) {
self.removeChild(clef);
self.clefs.splice(i, 1);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Zayıf zil sesi (right)
// Güçlü zil sesi (left)
// Soft davul sesi (down)
// Tok davul sesi (up)
// Sound assets for each direction
// Quarter-circle arcs (using ellipse, but will be visually quartered by anchor/position)
// LK.init.music('bgmusic', {volume: 0.5});
// Music (background, optional, not played in MVP)
// Sound effects for each direction
// Four colored "light" shapes for incoming notes
// Four quarter arcs (quarter circles) for directions: up, down, left, right
// --- Layout constants ---
// Drum and cymbal sounds for arc taps
// Up (red) - main drum
// Down (blue) - high-pitched drum
// Left (green) - low cymbal
// Right (yellow) - high cymbal
// Arc and UI shapes
// Ladder and clef images
// --- Note shape assets for all note types (for easy editing) ---
// birlik
// ikilik
// dörtlük
// sekizlik
// onaltılık
// otuzikilik
// altmışdörtlük
// Music and sounds
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var centerRadius = 200; // centerCircle radius
// --- Animated falling treble clefs background ---
var trebleClefBg = new TrebleClefBackground();
trebleClefBg.x = 0;
trebleClefBg.y = 0;
game.addChild(trebleClefBg);
// --- Step ladder bar: starts at bottom, fills screen vertically ---
var stepLadderBar = new StepLadderBar();
stepLadderBar.x = 0;
stepLadderBar.y = 0;
game.addChild(stepLadderBar);
// Arc positions (relative to center)
// We want the inner edge of each arc to touch the center circle (radius = 200)
// Each arc is 300x300, so the distance from center to arc center = centerRadius + arcW/2 = 200 + 150 = 350
var arcDistance = 200 + 150; // 350
var arcOffset = arcDistance; // alias for compatibility with spawn/target logic
var arcW = 300,
arcH = 300; // all arcs are quarter circles, so 300x300
// --- Create main elements ---
// Center circle
var centerCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5
});
centerCircle.x = centerX;
centerCircle.y = centerY;
game.addChild(centerCircle);
// Quarter arcs - D-pad style, all arcs directly adjacent to center circle
// Up arc: above center, touching at bottom
var arcUp = LK.getAsset('arcUp', {
anchorX: 0.5,
// center horizontally
anchorY: 1.0 // bottom
});
arcUp.x = centerX;
arcUp.y = centerY - centerRadius;
game.addChild(arcUp);
// Down arc: below center, touching at top
var arcDown = LK.getAsset('arcDown', {
anchorX: 0.5,
// center horizontally
anchorY: 0.0 // top
});
arcDown.x = centerX;
arcDown.y = centerY + centerRadius;
game.addChild(arcDown);
// Left arc: left of center, touching at right
var arcLeft = LK.getAsset('arcLeft', {
anchorX: 1.0,
// right
anchorY: 0.5 // center vertically
});
arcLeft.x = centerX - centerRadius;
arcLeft.y = centerY;
game.addChild(arcLeft);
// Right arc: right of center, touching at left
var arcRight = LK.getAsset('arcRight', {
anchorX: 0.0,
// left
anchorY: 0.5 // center vertically
});
arcRight.x = centerX + centerRadius;
arcRight.y = centerY;
game.addChild(arcRight);
// --- Arc hitboxes for input detection ---
var arcHitboxes = [{
dir: 'up',
x: centerX,
y: centerY - centerRadius - arcH / 2,
w: arcW,
h: arcH,
color: 'red',
asset: arcUp
}, {
dir: 'down',
x: centerX,
y: centerY + centerRadius + arcH / 2,
w: arcW,
h: arcH,
color: 'blue',
asset: arcDown
}, {
dir: 'left',
x: centerX - centerRadius - arcW / 2,
y: centerY,
w: arcW,
h: arcH,
color: 'green',
asset: arcLeft
}, {
dir: 'right',
x: centerX + centerRadius + arcW / 2,
y: centerY,
w: arcW,
h: arcH,
color: 'yellow',
asset: arcRight
}];
// --- Score, lives and wave display ---
var score = 0;
var highScore = storage.highScore || 0;
// Lives system removed: no lives variable, no display
var level = 1;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var highScoreTxt = new Text2('High: ' + highScore, {
size: 60,
fill: 0xFFD700
});
// Anchor to left (0), top (0)
highScoreTxt.anchor.set(0, 0);
// Place at (x=110, y=20) to avoid top left menu and keep visible
highScoreTxt.x = 110;
highScoreTxt.y = 20;
LK.gui.top.addChild(highScoreTxt);
var levelTxt = new Text2('Level 1', {
size: 90,
fill: 0xFFD700
});
levelTxt.anchor.set(0.5, 0);
levelTxt.y = 110; // place below score
LK.gui.top.addChild(levelTxt);
// Lives display (top right, away from menu)
// --- Light management ---
var lights = [];
// No lightActive flag needed; allow multiple lights at once
// --- Color mapping for directions ---
var dirColor = {
'up': 'red',
'down': 'blue',
'left': 'green',
'right': 'yellow'
};
var dirSound = {
'up': 'drumRed',
'down': 'drumBlue',
'left': 'drumGreen',
'right': 'drumYellow'
};
var dirBg = {
'up': 0xff4b4b,
'down': 0x4bafff,
'left': 0x4bff4b,
'right': 0xffe14b
};
// --- Light spawn positions (offscreen, towards arcs) ---
function getLightSpawn(dir) {
var dist = 700; // distance from center
if (dir === 'up') return {
x: centerX,
y: centerY - arcOffset - dist
};
if (dir === 'down') return {
x: centerX,
y: centerY + arcOffset + dist
};
if (dir === 'left') return {
x: centerX - arcOffset - dist,
y: centerY
};
if (dir === 'right') return {
x: centerX + arcOffset + dist,
y: centerY
};
return {
x: centerX,
y: centerY
};
}
function getLightTarget(dir) {
if (dir === 'up') return {
x: centerX,
y: centerY - arcOffset
};
if (dir === 'down') return {
x: centerX,
y: centerY + arcOffset
};
if (dir === 'left') return {
x: centerX - arcOffset,
y: centerY
};
if (dir === 'right') return {
x: centerX + arcOffset,
y: centerY
};
return {
x: centerX,
y: centerY
};
}
// --- Note types and their musical properties ---
// Each note type uses its own asset, speed, and point value as described:
// - note_whole: slowest, 1 point
// - note_half: 10% faster than whole, 2 points
// - note_quarter: 10% faster than half, 4 points
// - note_eighth: 10% faster than quarter, 8 points
// - note_sixteenth: 10% faster than eighth, 16 points
var noteTypes = [{
name: "whole",
asset: "note_whole",
speed: 1.0,
points: 1,
weight: 1
}, {
name: "half",
asset: "note_half",
speed: 1.1,
// 10% faster than whole
points: 2,
weight: 1
}, {
name: "quarter",
asset: "note_quarter",
speed: 1.1 * 1.1,
// 10% faster than half
points: 4,
weight: 1
}, {
name: "eighth",
asset: "note_eighth",
speed: 1.1 * 1.1 * 1.1,
// 10% faster than quarter
points: 8,
weight: 1
}, {
name: "sixteenth",
asset: "note_sixteenth",
speed: 1.1 * 1.1 * 1.1 * 1.1,
// 10% faster than eighth
points: 16,
weight: 1
}];
// note_whole, note_half, note_quarter ve note_eighth kullanılacak
// Precompute weighted note type array for random selection
var weightedNoteTypes = [];
for (var i = 0; i < noteTypes.length; ++i) {
for (var j = 0; j < noteTypes[i].weight; ++j) {
weightedNoteTypes.push(noteTypes[i]);
}
}
// note_whole, note_half, note_quarter ve note_eighth eklendi
// --- Level and Light speed: base speed for whole note, others are multiples ---
var level = 1;
function getLevelForScore(score) {
// Level 1: 0-49, Level 2: 50-149, Level 3: 150-299, Level 4: 300-499, Level 5: 500-749, etc.
// Each new level every +150 points after 50
if (score < 50) return 1;
return 2 + Math.floor((score - 50) / 100);
}
function getLightSpeedForNoteType(noteType) {
// Use the new speed property from noteTypes
var base = 1.2;
var currentLevel = getLevelForScore(score);
// Level 2: +120%, Level 3: +200% (multiplicative), then +50% for each further level
if (currentLevel === 2) {
base *= 2.2;
} else if (currentLevel === 3) {
base *= 2.2 * 3.0;
} else if (currentLevel > 3) {
base *= 2.2 * 3.0;
for (var i = 4; i <= currentLevel; ++i) {
base *= 1.5;
}
}
// noteType can be either a noteType object or a {name: ...} object
var speedMultiplier = noteType.speed !== undefined ? noteType.speed : 1.0;
return base * speedMultiplier;
}
// --- Spawn a light (note) with note type ---
function spawnLight(dir) {
var color = dirColor[dir];
var spawn = getLightSpawn(dir);
var target = getLightTarget(dir);
// note_whole veya note_half rastgele seçilecek
var noteType = weightedNoteTypes[Math.floor(Math.random() * weightedNoteTypes.length)];
var light = new Light();
light.noteType = noteType.name;
light.noteAsset = noteType.asset;
light.notePoints = noteType.points;
light.direction = dir;
light.color = color;
light.x = spawn.x;
light.y = spawn.y;
light.targetX = target.x;
light.targetY = target.y;
light.speed = getLightSpeedForNoteType(noteType);
// Asset düzeltmesi: ilgili nota için özel asset kullan
if (light.children && light.children.length > 0) {
for (var i = light.children.length - 1; i >= 0; --i) {
light.removeChild(light.children[i]);
}
}
var noteAssetObj = light.attachAsset(noteType.asset, {
anchorX: 0.5,
anchorY: 0.5
});
light.radius = noteAssetObj.width * 0.5;
lights.push(light);
game.addChild(light);
// No lightActive flag or logic needed
}
// --- Continuous light spawning logic ---
// Timers for continuous spawning
var lightSpawnTimer = null;
var showInfoTimer = null;
var dirs = ['up', 'down', 'left', 'right'];
function clearLightTimers() {
if (lightSpawnTimer !== null) {
LK.clearTimeout(lightSpawnTimer);
lightSpawnTimer = null;
}
if (showInfoTimer !== null) {
LK.clearTimeout(showInfoTimer);
showInfoTimer = null;
}
}
// Main continuous light spawning function
function startLightSpawning() {
clearLightTimers();
// Always spawn at random intervals, but ensure at least 2 seconds between spawns
var baseInterval, randomRange, minInterval;
if (score < 20) {
baseInterval = 320;
randomRange = 260;
minInterval = 2000;
} else {
// After score 20, make spawn intervals more irregular and sometimes much faster
baseInterval = 80 + Math.floor(Math.random() * 120); // base between 80-200ms
randomRange = 400 + Math.floor(Math.random() * 200); // random up to 600ms
minInterval = 1200 + Math.floor(Math.random() * 600); // min interval 1200-1800ms
}
// Spawn a light
var dir = dirs[Math.floor(Math.random() * 4)];
spawnLight(dir);
// Schedule next light
var nextInterval = baseInterval;
if (randomRange > 0) {
nextInterval += Math.floor(Math.random() * randomRange);
}
// Enforce minimum interval of 1 second (1000ms)
if (nextInterval < minInterval) {
nextInterval = minInterval;
}
lightSpawnTimer = LK.setTimeout(function () {
startLightSpawning();
}, nextInterval);
}
// Call this to (re)start spawning after all lights cleared
function startWave() {
startLightSpawning();
}
// --- Handle tap input ---
function getDirectionFromPoint(x, y) {
// Returns 'up', 'down', 'left', 'right' or null
for (var i = 0; i < arcHitboxes.length; ++i) {
var arc = arcHitboxes[i];
// Use bounding box for MVP
var dx = x - arc.x;
var dy = y - arc.y;
if (arc.dir === 'up' || arc.dir === 'down') {
if (Math.abs(dx) < arc.w / 2 && Math.abs(dy) < arc.h / 2) return arc.dir;
} else {
if (Math.abs(dx) < arc.w / 2 && Math.abs(dy) < arc.h / 2) return arc.dir;
}
}
return null;
}
// --- Handle tap (down) ---
game.down = function (x, y, obj) {
// Use the provided x, y directly as they are already in game coordinates
var dir = getDirectionFromPoint(x, y);
if (!dir) return;
// Always play the arc's sound on tap, even if no light is present
var soundPlayed = false;
LK.getSound(dirSound[dir]).play();
soundPlayed = true;
// --- Beat vibration/tween effect for arc images (hoparlör titreşim) ---
var arcObj = null;
if (dir === 'up') arcObj = arcUp;else if (dir === 'down') arcObj = arcDown;else if (dir === 'left') arcObj = arcLeft;else if (dir === 'right') arcObj = arcRight;
if (arcObj) {
// Stop any previous tweens on this arc
tween.stop(arcObj, {
scaleX: true,
scaleY: true
});
// Animate: quick scale up and back (beat effect)
var origScaleX = arcObj.scaleX !== undefined ? arcObj.scaleX : 1;
var origScaleY = arcObj.scaleY !== undefined ? arcObj.scaleY : 1;
tween(arcObj, {
scaleX: origScaleX * 1.18,
scaleY: origScaleY * 1.18
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(arcObj, {
scaleX: origScaleX,
scaleY: origScaleY
}, {
duration: 90,
easing: tween.easeIn
});
}
});
}
// Find the closest active light matching direction and close enough
for (var i = 0; i < lights.length; ++i) {
var light = lights[i];
if (!light.active) continue;
// If direction matches and light is close enough to arc
var target = getLightTarget(dir);
var dx = light.x - target.x;
var dy = light.y - target.y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Only allow tap if light has entered arc zone and has NOT reached centerCircle
var dxCenter = light.x - centerX;
var dyCenter = light.y - centerY;
var distCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter);
if (light.direction === dir && light.enteredArc === true && distCenter > centerRadius) {
// Correct!
// Animate note to step ladder bar instead of destroying
light.active = false;
var startX = light.x;
var startY = light.y;
var idx = stepLadderBar.notes.length;
var endX = stepLadderBar.x + 190;
var endY = stepLadderBar.y + (stepLadderBar.stepYs && stepLadderBar.stepYs[idx] !== undefined ? stepLadderBar.stepYs[idx] : 30 + idx * 90);
// Track last hit note type for correct asset on step ladder bar
stepLadderBar.lastAddedNoteType = light.noteType;
light.lastHitNoteType = light.noteType;
tween(light, {
x: endX,
y: endY,
scaleX: 1.1,
scaleY: 1.1,
alpha: 1
}, {
duration: 340,
easing: tween.easeIn,
onFinish: function onFinish() {
// Remove from array and destroy light
for (var j = 0; j < lights.length; ++j) {
if (lights[j] === light) {
lights.splice(j, 1);
break;
}
}
light.destroy();
// Add a note to the step ladder bar
stepLadderBar.addNote(light.color);
}
});
// Her nota tipi kendi değeri kadar puan kazandırır
var noteScoreTable = {
whole: 1,
half: 2,
quarter: 4,
eighth: 8,
sixteenth: 16
};
// note_whole 1 puan, note_half 2 puan
score += noteScoreTable[light.noteType] || 1;
scoreTxt.setText(score);
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High: ' + highScore);
}
// Update level if needed
var newLevel = getLevelForScore(score);
if (newLevel !== level) {
// Level up effect: only trigger when going from 1 to 2
if (level === 1 && newLevel === 2) {
// Flash the whole screen gold, then fade out
LK.effects.flashScreen(0xffd700, 900);
// Play applause/cheering sound (must be in asset library, e.g. 'applause')
LK.getSound('applause').play();
// Animate the level text: pop and glow
tween.stop(levelTxt, {
scaleX: true,
scaleY: true,
alpha: true
});
var origScaleX = levelTxt.scaleX !== undefined ? levelTxt.scaleX : 1;
var origScaleY = levelTxt.scaleY !== undefined ? levelTxt.scaleY : 1;
var origAlpha = levelTxt.alpha !== undefined ? levelTxt.alpha : 1;
tween(levelTxt, {
scaleX: origScaleX * 1.5,
scaleY: origScaleY * 1.5,
alpha: 1
}, {
duration: 220,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelTxt, {
scaleX: origScaleX,
scaleY: origScaleY,
alpha: origAlpha
}, {
duration: 320,
easing: tween.easeIn
});
}
});
// Show celebratory text in the center of the screen
var cheerTxt = new Text2('👏 SEVİYE ATLADIN! 👏', {
size: 160,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
cheerTxt.anchor.set(0.5, 0.5);
cheerTxt.x = centerX;
cheerTxt.y = centerY;
cheerTxt.alpha = 0;
game.addChild(cheerTxt);
// Animate text: fade in, pop, then fade out
tween(cheerTxt, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 320,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(cheerTxt, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 180,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(cheerTxt, {
alpha: 0
}, {
duration: 700,
easing: tween.easeIn,
onFinish: function onFinish() {
if (cheerTxt && cheerTxt.parent) {
cheerTxt.parent.removeChild(cheerTxt);
}
}
});
}
});
}
});
}
level = newLevel;
levelTxt.setText('Level ' + level);
}
// Win condition removed: game continues, level and speed will keep increasing as score grows
// Play sound
// (Already played above, do not play again)
// Flash background
tween.stop(game, {
backgroundColor: true
});
var oldBg = game.backgroundColor;
// If blue arc (down) light is tapped, set background to blue and keep it
if (dir === 'down') {
game.setBackgroundColor(0x4bafff);
} else if (dir === 'left') {
// If green arc (left) light is tapped, set background to green and keep it
game.setBackgroundColor(0x4bff4b);
} else if (dir === 'right') {
// If yellow arc (right) light is tapped, set background to yellow and keep it
game.setBackgroundColor(0xffe14b);
} else if (dir === 'up') {
// If red arc (up) light is tapped, set background to red and keep it
game.setBackgroundColor(0xff4b4b);
} else {
tween(game, {
backgroundColor: dirBg[dir]
}, {
duration: 120,
onFinish: function onFinish() {
tween(game, {
backgroundColor: oldBg
}, {
duration: 400
});
}
});
}
// No need to spawn next light here; continuous spawn is always active
return;
}
}
// If tap is on arc but no light is close: miss
// Flash red for feedback, but do not decrement lives or end game
LK.effects.flashScreen(0xff0000, 500);
};
// --- Update loop: check for missed lights ---
game.update = function () {
// Animate falling treble clefs background
if (typeof trebleClefBg !== "undefined" && trebleClefBg.update) {
trebleClefBg.update();
}
for (var i = lights.length - 1; i >= 0; --i) {
var light = lights[i];
if (!light.active) continue;
// Calculate distance to arc (where tap is allowed to start)
var arcTarget = getLightTarget(light.direction);
var dxArc = light.x - arcTarget.x;
var dyArc = light.y - arcTarget.y;
var distArc = Math.sqrt(dxArc * dxArc + dyArc * dyArc);
// Calculate distance to centerCircle (where tap is no longer allowed, and game over if reached)
var dxCenter = light.x - centerX;
var dyCenter = light.y - centerY;
var distCenter = Math.sqrt(dxCenter * dxCenter + dyCenter * dyCenter);
// Track if light has entered the arc zone (first contact)
if (light.enteredArc === undefined) {
// If light is outside arc zone, not yet entered
light.enteredArc = false;
}
// If not yet entered arc, check if it just entered
if (!light.enteredArc && light.lastDistArc !== undefined && light.lastDistArc >= 120 && distArc < 120) {
light.enteredArc = true;
}
light.lastDistArc = distArc;
// If light has entered arc, but now reached centerCircle: game over immediately
if (light.enteredArc && distCenter < centerRadius) {
light.hit(function () {
for (var j = 0; j < lights.length; ++j) {
if (lights[j] === light) {
lights.splice(j, 1);
break;
}
}
// Flash red and trigger game over immediately
LK.effects.flashScreen(0xff0000, 500);
LK.showGameOver();
});
continue;
}
}
// No batch logic needed; continuous spawn is always active
};
// --- Start game ---
score = 0;
level = 1;
scoreTxt.setText(score);
highScore = storage.highScore || 0;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText('High: ' + highScore);
}
if (typeof levelTxt !== "undefined") {
levelTxt.setText('Level 1');
}
// Play background beat music (loops by default)
LK.playMusic('beat');
LK.setTimeout(function () {
startWave();
}, 600);
;
;
beat
Music
drumRed
Sound effect
drumGreen
Sound effect
beat2
Music
beat3
Music
beat4
Music
harp4
Sound effect
oboe4
Sound effect
harp1
Sound effect
harp2
Sound effect
harp3
Sound effect
piano1
Sound effect
piano2
Sound effect
piano3
Sound effect
piano4
Sound effect
drum4
Sound effect
oboe1
Sound effect
oboe2
Sound effect
oboe3
Sound effect