User prompt
centercircle'daki knob hangi yöne doğru gidiyorsa karakterin oraya kadar her zaman gitmesini sağla şuan shadow karakteri biraz takılıyor gibi gözüküyor.
User prompt
centercircle büyüdü fakat içindeki knob yeterince haraket edemiyor
User prompt
centercicle'ı 2 katı büyüt
User prompt
karakterin ekranın herhangi bir yerine tıklayınca ortaya çıkan bir karakter yürütme alanı ve imleciyle harake ettirilsin
User prompt
agent shadow ekranın herhangi bir yerine dokunulduğunda ortaya çıkan sürüklenebilir bir panel ile yürütülebilsin dokunduğumuz yere ışınlanıyor şuan karakter bunun olmaması gerek
Code edit (1 edits merged)
Please save this source code
User prompt
Agent Shadow: Işık Kaçışı
Initial prompt
agent shadow 2d bir beceri oyunudur. karakter karaktere basılı tutarak haraket ettirilir. karakterimiz bir gölge şeklindeki insandır. ve amacı etraftan gelen ışık mermilerinden kaçması gerekiyor.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { chapter: 1, endlessHighScore: 0 }); /**** * Classes ****/ // Takipçi ışık mermisi (ChasingLightBullet) sınıfı var ChasingLightBullet = Container.expand(function () { var self = Container.call(this); // Mermi grafiği var bulletAsset = self.attachAsset('lightBullet', { anchorX: 0.5, anchorY: 0.5 }); // Hız vektörü self.speed = 10 + Math.random() * 3; // Çarpışma kontrolü için son intersect durumu self.lastIntersecting = false; // Mermi boyutu self.width = bulletAsset.width; self.height = bulletAsset.height; // Chasing Bullets mode: No pulse/yanıp sönme effect bulletAsset.alpha = 1.0; // Güncelleme fonksiyonu self.update = function () { // Son pozisyonları güncelle if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } // --- Bullet trail effect (effectwhite) --- if (self._destroyed !== true) { var trail = LK.getAsset('effectwhite', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.25, scaleY: 0.25, alpha: 0.18 }); if (self.parent && typeof self.parent.addChild === "function") { self.parent.addChild(trail); } else if (typeof game !== "undefined" && typeof game.addChild === "function") { game.addChild(trail); } tween(trail, { alpha: 0 }, { duration: 320, onFinish: function onFinish() { if (trail && typeof trail.destroy === "function") { trail.destroy(); } } }); } // Hedefe doğru yönel (Chasing Bullets modunda gecikmeli ve yumuşak dönüş) // Delay ve smooth rotate için state değişkenleri if (self._turnDelay === undefined) { self._turnDelay = 0; // Kaç frame sonra tekrar hedefe dönecek self._targetAngle = self.rotation - Math.PI / 2; // Son hedef açı self._currentAngle = self.rotation - Math.PI / 2; // Şu anki açı } if (typeof shadow !== "undefined") { // Chasing Bullets modunda smooth turning // Delay ayarı: her 12 frame'de bir yeni hedef açıya bak (yaklaşık 0.2s) var turnDelayFrames = 12; if (self._turnDelay <= 0) { var dx = shadow.x - self.x; var dy = shadow.y - self.y; var newTargetAngle = Math.atan2(dy, dx); // Ani açı değişimini engelle: sadece küçük farklarda güncelle // (ör: 90 dereceden fazla ise hemen dönmesin) var angleDiff = newTargetAngle - self._currentAngle; // -PI ile PI arası normalize et while (angleDiff > Math.PI) { angleDiff -= 2 * Math.PI; } while (angleDiff < -Math.PI) { angleDiff += 2 * Math.PI; } // Maksimum bir açı değişimi uygula (ör: 60 derece/frame) var maxTurn = Math.PI / 3; // 60 derece if (Math.abs(angleDiff) > maxTurn) { newTargetAngle = self._currentAngle + (angleDiff > 0 ? maxTurn : -maxTurn); } self._targetAngle = newTargetAngle; self._turnDelay = turnDelayFrames; } else { self._turnDelay--; } // Yavaşça hedef açıya yaklaş (smooth rotate) var rotateSpeed = 0.18; // 0.0-1.0 arası, 1.0 anında döner, 0.1 çok yavaş var diff = self._targetAngle - self._currentAngle; // -PI ile PI arası normalize et while (diff > Math.PI) { diff -= 2 * Math.PI; } while (diff < -Math.PI) { diff += 2 * Math.PI; } self._currentAngle += diff * rotateSpeed; // Pozisyonu güncelle self.x += Math.cos(self._currentAngle) * self.speed; self.y += Math.sin(self._currentAngle) * self.speed; // Merminin ucunu oyuncuya bakacak şekilde döndür self.rotation = self._currentAngle + Math.PI / 2; } self.lastX = self.x; self.lastY = self.y; }; // Ekran dışına çıktı mı? self.isOutOfBounds = function () { return self.x < -self.width || self.x > 2048 + self.width || self.y < -self.height || self.y > 2732 + self.height; }; // Yok edilirken animasyonu durdur var _destroy = self.destroy; // pulseTween is not used in this class, but to prevent ReferenceError, define it as null var pulseTween = null; self.destroy = function () { if (pulseTween) { pulseTween.stop(); } _destroy.call(self); }; return self; }); // Işık mermisi (LightBullet) sınıfı var LightBullet = Container.expand(function () { var self = Container.call(this); // Mermi grafiği var bulletAsset = self.attachAsset('lightBullet', { anchorX: 0.5, anchorY: 0.5 }); // Hız vektörü self.speedX = 0; self.speedY = 0; // Çarpışma kontrolü için son intersect durumu self.lastIntersecting = false; // Mermi boyutu self.width = bulletAsset.width; self.height = bulletAsset.height; // Mermi güncelleme fonksiyonu self.update = function () { // Son pozisyonları güncelle if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } // --- Bullet trail effect (effectwhite) --- if (self._destroyed !== true) { // Her frame bir iz bırak (isteğe göre seyrekleştirilebilir) var trail = LK.getAsset('effectwhite', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, scaleX: 0.25, scaleY: 0.25, alpha: 0.18 }); // Trail'i ana sahneye ekle (self.parent varsa oraya ekle, yoksa game'e ekle) if (self.parent && typeof self.parent.addChild === "function") { self.parent.addChild(trail); } else if (typeof game !== "undefined" && typeof game.addChild === "function") { game.addChild(trail); } // Trail'i fade out ile yok et tween(trail, { alpha: 0 }, { duration: 320, onFinish: function onFinish() { if (trail && typeof trail.destroy === "function") { trail.destroy(); } } }); } self.x += self.speedX; self.y += self.speedY; self.lastX = self.x; self.lastY = self.y; }; // Ekran dışına çıktı mı? self.isOutOfBounds = function () { return self.x < -self.width || self.x > 2048 + self.width || self.y < -self.height || self.y > 2732 + self.height; }; return self; }); // Gölge karakter (Shadow) sınıfı - Gerçekçi gölge efekti için yumuşak, bulanık elipsler ve dinamik hareket var Shadow = Container.expand(function () { var self = Container.call(this); // Gölge için 3 büyük eliptik, yarı saydam, üst üste bindirilmiş layer var ellipseLayers = []; var ellipseParams = [ // scaleX, scaleY, alpha, blur { scaleX: 1.0, scaleY: 0.7, alpha: 0.22 }, { scaleX: 0.8, scaleY: 0.5, alpha: 0.16 }, { scaleX: 0.6, scaleY: 0.35, alpha: 0.10 }]; var baseSizeW = 160; var baseSizeH = 220; var baseColor = 0x222222; for (var i = 0; i < ellipseParams.length; i++) { var p = ellipseParams[i]; var ell = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, scaleX: p.scaleX, scaleY: p.scaleY, alpha: p.alpha }); ell.x = 0; ell.y = 0; self.addChild(ell); ellipseLayers.push(ell); } // Hafif yanal ve dikey dalgalanma için parametreler var animTick = Math.floor(Math.random() * 1000); var waveSpeed = 0.018; var waveRadiusX = 18; var waveRadiusY = 10; // Gölgeye yumuşak bir "blur" efekti için üst üste bindirilmiş küçük gölge kutuları (soft edge) var softEdgeBoxes = []; var softBoxCount = 8; var softBoxRadius = 70; for (var i = 0; i < softBoxCount; i++) { var angle = Math.PI * 2 / softBoxCount * i; var box = LK.getAsset('shadowbox', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.45 + Math.random() * 0.12, scaleY: 0.45 + Math.random() * 0.12, alpha: 0.08 + Math.random() * 0.06 }); // Başlangıçta merkezde box.x = Math.cos(angle) * softBoxRadius; box.y = Math.sin(angle) * softBoxRadius * 0.7; self.addChild(box); softEdgeBoxes.push({ box: box, baseAngle: angle }); } // Gözler: shadowredeye assetini ekle (en üstte) var eyeAsset = LK.getAsset('shadowredeye', { anchorX: 0.5, anchorY: 0.5 }); eyeAsset.x = 0; eyeAsset.y = -baseSizeH * 0.18; self.addChild(eyeAsset); // Yarıçapı döndür (kenar çarpışma için) self.getRadius = function () { // En büyük elipsin genişliğinin yarısı return baseSizeW * 0.5; }; // --- Realistic shadow trail effect --- self._shadowTrailTick = 0; self._shadowTrailInterval = 2 + Math.floor(Math.random() * 2); // every 2-3 frames self._lastTrailX = undefined; self._lastTrailY = undefined; self.update = function () { animTick++; // Elips katmanlarını hafifçe dalgalandır for (var i = 0; i < ellipseLayers.length; i++) { var ell = ellipseLayers[i]; var phase = animTick * waveSpeed + i * 0.7; ell.x = Math.cos(phase) * (waveRadiusX * (1 - i * 0.3)); ell.y = Math.sin(phase * 0.8) * (waveRadiusY * (1 - i * 0.3)); // Hafifçe scale animasyonu var scalePulse = 1 + Math.sin(phase * 1.2) * 0.04; ell.scaleX = ellipseParams[i].scaleX * scalePulse; ell.scaleY = ellipseParams[i].scaleY * scalePulse; // Hafif alpha dalgalanması ell.alpha = ellipseParams[i].alpha + Math.abs(Math.sin(phase * 0.7)) * 0.04; } // Soft edge kutuları yumuşakça döndür ve titreştir for (var i = 0; i < softEdgeBoxes.length; i++) { var obj = softEdgeBoxes[i]; var box = obj.box; var baseAngle = obj.baseAngle; var phase = animTick * 0.012 + baseAngle * 1.2; var r = softBoxRadius + Math.sin(phase * 1.1 + i) * 8; box.x = Math.cos(baseAngle + Math.sin(phase) * 0.18) * r; box.y = Math.sin(baseAngle + Math.cos(phase) * 0.18) * r * 0.7; // Hafifçe scale ve alpha animasyonu var scalePulse = 1 + Math.sin(phase * 1.5) * 0.09; box.scaleX = (0.45 + i * 0.01) * scalePulse; box.scaleY = (0.45 + i * 0.01) * scalePulse; box.alpha = 0.08 + Math.abs(Math.sin(phase * 0.7 + i)) * 0.07; } // Gözler kutunun merkezine sabitlenir eyeAsset.x = 0; eyeAsset.y = -baseSizeH * 0.18; // --- Realistic shadow trail effect --- self._shadowTrailTick++; // Only create trail if moved enough (avoid stacking on same spot) var minTrailDist = 12; if (self._lastTrailX === undefined) { self._lastTrailX = self.x; } if (self._lastTrailY === undefined) { self._lastTrailY = self.y; } var dx = self.x - self._lastTrailX; var dy = self.y - self._lastTrailY; var dist = Math.sqrt(dx * dx + dy * dy); if (self._shadowTrailTick % self._shadowTrailInterval === 0 && dist > minTrailDist * 0.5) { // Trail asset: use shadow asset, blurred and faded var tScale = 0.85 + Math.random() * 0.18; var tAlpha = 0.13 + Math.random() * 0.07; var tSkew = (Math.random() - 0.5) * 0.18; var tY = self.y + (Math.random() - 0.5) * 12; var tX = self.x + (Math.random() - 0.5) * 12; var trail = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, x: tX, y: tY, scaleX: tScale * (1 + tSkew), scaleY: tScale * (0.7 + tSkew * 0.5), alpha: tAlpha }); // Trail should be behind the shadow, so add to parent if possible if (self.parent && typeof self.parent.addChild === "function") { self.parent.addChild(trail); // Move trail behind shadow if (typeof self.parent.children !== "undefined" && self.parent.children.length > 1) { // Move trail just before shadow in display list var idx = self.parent.children.indexOf(self); if (idx > 0) { // Remove and insert at idx-1 self.parent.children.splice(self.parent.children.length - 1, 1); // remove last (trail) self.parent.children.splice(idx - 1, 0, trail); } } } else if (typeof game !== "undefined" && typeof game.addChild === "function") { game.addChild(trail); } // Fade out and destroy tween(trail, { alpha: 0 }, { duration: 420 + Math.random() * 180, onFinish: function onFinish() { if (trail && typeof trail.destroy === "function") { trail.destroy(); } } }); self._lastTrailX = self.x; self._lastTrailY = self.y; } }; return self; }); // ShadowSkill: Expanding shadow skill effect (destroys bullets on contact) var ShadowSkill = Container.expand(function () { var self = Container.call(this); // Main shadow effect (ellipse) var effect = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 0.7, alpha: 0.22 }); self.addChild(effect); // Initial radius and max radius self.radius = 180; self.maxRadius = 900; self.duration = 700; // ms self._startTime = Date.now(); self._destroyed = false; // Set initial scale effect.scaleX = self.radius / 80; effect.scaleY = self.radius * 0.7 / 110; // Center on shadow self.x = shadow ? shadow.x : GAME_W / 2; self.y = shadow ? shadow.y : GAME_H * 0.8; // Animate expansion tween(effect, { scaleX: self.maxRadius / 80, scaleY: self.maxRadius * 0.7 / 110, alpha: 0.0 }, { duration: self.duration, onFinish: function onFinish() { self._destroyed = true; if (typeof self.destroy === "function") { self.destroy(); } } }); // Update: check collision with bullets self.update = function () { if (self._destroyed) { return; } // Do NOT follow shadow position; keep at activation position // Current radius (approximate from scale) var currRadius = effect.scaleX * 80; // Destroy bullets that touch the effect for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; if (b._destroyed) { continue; } var dx = b.x - self.x; var dy = b.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < currRadius + (b.width ? b.width * 0.5 : 30)) { b.destroy(); bullets.splice(i, 1); } } // Destroy chasing bullets that touch the effect for (var i = chasingBullets.length - 1; i >= 0; i--) { var cb = chasingBullets[i]; if (cb._destroyed) { continue; } var dx = cb.x - self.x; var dy = cb.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < currRadius + (cb.width ? cb.width * 0.5 : 30)) { cb.destroy(); chasingBullets.splice(i, 1); } } }; // Defensive destroy var _destroy = self.destroy; self.destroy = function () { self._destroyed = true; _destroy.call(self); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181a1b }); /**** * Game Code ****/ // Oyun alanı boyutları var GAME_W = 2048; var GAME_H = 2732; // Storage eklentisini başlat // Menü ekranı için değişkenler var menuContainer = null; var menuButtons = []; var menuActive = true; var currentMode = null; // "chapter" veya "endless" // Oyun içi değişkenler var shadow = null; var bullets = []; var chasingBullets = []; var score = 0; var scoreTxt = null; var dragNode = null; var isGameActive = false; var controlPanel = null; var joystick = null; var joystickRadius = 180 * 2; var joystickKnobRadius = 60 * 1.2; var isPanelActive = false; var panelStartX = 0; var panelStartY = 0; var knobStartX = 0; var knobStartY = 0; var bulletInterval = 700; var bulletTimer = null; var chasingBulletInterval = 1200; var chasingBulletTimer = null; // --- Shadow Skill State --- var shadowSkillLastUsed = 0; var shadowSkillCooldown = 30000; // 30 seconds var shadowSkillActive = false; var shadowSkillEffect = null; var shadowSkillTapTimes = []; var shadowSkillTapWindow = 400; // ms (double tap max interval) var shadowSkillGuiText = null; // Menü ekranı oluşturma fonksiyonu function showMenu() { menuActive = true; isGameActive = false; // Menü zaten varsa kaldır if (menuContainer) { menuContainer.destroy(); menuContainer = null; menuButtons = []; } menuContainer = new Container(); // --- Main menu background (fills screen) --- var bg = LK.getAsset('mainmenubackground', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: GAME_W, height: GAME_H }); menuContainer.addChild(bg); // --- Animated white bar (mainmenuwhite) --- var whiteBar = LK.getAsset('mainmenuwhite', { anchorX: 0.5, anchorY: 0, x: GAME_W / 2, y: 0, width: GAME_W * 0.9, height: 220 }); menuContainer.addChild(whiteBar); // Animate whiteBar up and down forever function animateWhiteBarDown() { tween(whiteBar, { y: GAME_H - whiteBar.height }, { duration: 2200, easing: tween.easeInOut, onFinish: animateWhiteBarUp }); } function animateWhiteBarUp() { tween(whiteBar, { y: 0 }, { duration: 2200, easing: tween.easeInOut, onFinish: animateWhiteBarDown }); } animateWhiteBarDown(); // --- Logo (original scale, moved 400px down) --- var logo = LK.getAsset('agentshadowlogo', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: 800, scaleX: 1, scaleY: 1 }); menuContainer.addChild(logo); // Butonlar arası dikey boşluk var btnSpacing = 80; var btnW = 600; var btnH = 180; var startY = 1300; // 900 + 400 // Buton isimleri ve fonksiyonları var btnDefs = [{ label: "Story Mode", locked: true, onClick: function onClick() { // Show locked message instead of starting the game if (game._storyLockedText) { game._storyLockedText.destroy(); game._storyLockedText = null; } var lockedText = new Text2("Locked until v0.2", { size: 80, fill: "#fff" }); lockedText.anchor.set(0.5, 0.5); lockedText.x = GAME_W / 2; lockedText.y = 1200; // Animate alpha pulse function pulseUp() { tween(lockedText, { alpha: 1.0 }, { duration: 500, onFinish: pulseDown }); } function pulseDown() { tween(lockedText, { alpha: 0.3 }, { duration: 500, onFinish: pulseUp }); } lockedText.alpha = 0.3; pulseUp(); menuContainer.addChild(lockedText); game._storyLockedText = lockedText; // Remove after 2.5s LK.setTimeout(function () { if (game._storyLockedText) { game._storyLockedText.destroy(); game._storyLockedText = null; } }, 2500); } }, { label: "Endless Run", onClick: function onClick() { currentMode = "endless"; menuActive = false; menuContainer.visible = false; startEndlessRun(); } }, { label: "Reset Story", onClick: function onClick() { storage.chapter = 1; storage.section = 1; // Geri bildirim için kısa bir animasyon veya renk değişimi yapılabilir if (menuButtons[2] && menuButtons[2].bg) { LK.effects.flashObject(menuButtons[2].bg, 0xff0000, 300); } } }]; // Butonları oluştur for (var i = 0; i < btnDefs.length; i++) { var btnY = startY + i * (btnH + btnSpacing); // Buton arka planı var btnBg = LK.getAsset('blackbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: btnY, width: btnW, height: btnH }); // Buton metni var btnText = new Text2(btnDefs[i].label, { size: 80, fill: "#fff" }); btnText.anchor.set(0.5, 0.5); btnText.x = GAME_W / 2; btnText.y = btnY; // Buton Container'ı var btnContainer = new Container(); btnContainer.bg = btnBg; btnContainer.addChild(btnBg); btnContainer.addChild(btnText); // Story Mode artık kilitli değil, lock icon eklenmiyor // Add lock icon for locked buttons (Story Mode) if (btnDefs[i].locked) { var lockIcon = LK.getAsset('lockicon', { anchorX: 1, anchorY: 0.5, x: GAME_W / 2 + btnW / 2 - 40, y: btnY, scaleX: 1.1, scaleY: 1.1 }); btnContainer.addChild(lockIcon); // Reduce button alpha to indicate locked btnContainer.alpha = 0.55; // Still allow interaction to show locked message btnContainer.interactive = true; btnContainer.buttonMode = false; } else { btnContainer.interactive = true; btnContainer.buttonMode = true; } // Basit dokunma olayı (function (def, btn) { btnContainer.down = function (x, y, obj) { if (!menuActive) { return; } def.onClick(); }; })(btnDefs[i], btnContainer); menuContainer.addChild(btnContainer); menuButtons.push(btnContainer); } // Menü sahneye ekle game.addChild(menuContainer); } // Menüden chapter modunda oyunu başlat function startGameFromChapter() { // Temizle clearGameObjects(); // Son kaydedilen chapter ve bölüm bilgisini storage'dan al var chapter = storage.chapter || 1; var section = storage.section || 1; // Karakteri oluştur ve ortala shadow = new Shadow(); shadow.x = GAME_W / 2; shadow.y = GAME_H * 0.8; game.addChild(shadow); // Skor (chapter modunda skor kullanılmaz, sadece kalan mermi sayısı gösterilir) score = 0; if (scoreTxt) { scoreTxt.destroy(); } // Chapter 1 bullet sayacı (skor yerine) -- bu skor metni yeni sistemde de güncelleniyor var chapter1BulletCount = 50; scoreTxt = new Text2(chapter1BulletCount + "", { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); scoreTxt.visible = false; // Geri sayım bitene kadar görünmez LK.gui.top.addChild(scoreTxt); isGameActive = false; // Oyun başlatılmadan önce false // --- YENİ CHAPTER 1: 5 aşamalı sistem ile başlat --- // Bölüm ilerlemesine göre başlat if (chapter === 1 && section === 2) { startChapter1_2Bullets(); } else { // Varsayılan: chapter 1 bölüm 1'den başlat startChapter1Bullets(); } } // Diyalog ekranı fonksiyonu (geri getirildi) function showDialogue(text, onClose) { // Önce eski diyalog varsa kaldır if (game._dialogueContainer) { game._dialogueContainer.destroy(); game._dialogueContainer = null; } var dialogueContainer = new Container(); // Arka plan var bg = LK.getAsset('dialoguebackground', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: GAME_H / 2, width: 1500, height: 750 }); dialogueContainer.addChild(bg); // Sol üstte ??? etiketi var whoTxt = new Text2("???", { size: 110, fill: "#222" }); whoTxt.anchor.set(0, 0); // 50 piksel sağa kaydır whoTxt.x = Math.max((GAME_W - 1500) / 2 + 60 + 50, 40); whoTxt.y = Math.max((GAME_H - 750) / 2 + 40, 40); dialogueContainer.addChild(whoTxt); // Sağda walkie talkie (daha büyük ve 300px sağa kaydır) var walkie = LK.getAsset('walkietalkie', { anchorX: 1, anchorY: 1, x: (GAME_W + 1500) / 2 - 60 + 300, y: (GAME_H + 750) / 2 - 60, scaleX: 1.15, scaleY: 1.15 }); dialogueContainer.addChild(walkie); // Kullanıcı manuel olarak satırları ayırabilsin: Eğer text bir dizi ise her eleman bir satır olarak alınır. // Eğer string ise, tek satır olarak gösterilir. var lines = []; var dialogueFontSize = 54; if (typeof text === "string") { lines = [text]; } else if (Array.isArray(text)) { lines = text.slice(0); } var textNodes = []; // Diyalog kutusunun üst kısmından biraz aşağıda başlasın, satır aralığı daha rahat var baseY = GAME_H / 2 - 750 / 2 + 170; var lineSpacing = 70; // Tüm satırları aynı anda göster, her biri kendi satırında for (var i = 0; i < lines.length; i++) { var line = lines[i]; var txt = new Text2(line, { size: dialogueFontSize, fill: "#222" }); // Sola yaslı: anchorX = 0, anchorY = 0 txt.anchor.set(0, 0); // Diyalog kutusunun sol kenarına hizala (arka planın solundan biraz içeride başlasın) txt.x = (GAME_W - 1500) / 2 + 80; txt.y = baseY + i * lineSpacing; dialogueContainer.addChild(txt); textNodes.push(txt); } // Altta click to continue... var ctc = new Text2("click to continue...", { size: 70, fill: "#222" }); ctc.anchor.set(0.5, 1); ctc.x = GAME_W / 2; ctc.y = (GAME_H + 750) / 2 - 60; ctc.alpha = 0.3; dialogueContainer.addChild(ctc); // Alpha animasyonu (0.3 <-> 1.0) function pulseUp() { tween(ctc, { alpha: 1.0 }, { duration: 600, onFinish: pulseDown }); } function pulseDown() { tween(ctc, { alpha: 0.3 }, { duration: 600, onFinish: pulseUp }); } pulseUp(); // Kapatma için dokunma dialogueContainer.interactive = true; dialogueContainer.down = function (x, y, obj) { if (game._dialogueContainer) { game._dialogueContainer.destroy(); game._dialogueContainer = null; } if (typeof onClose === "function") { onClose(); } }; game.addChild(dialogueContainer); game._dialogueContainer = dialogueContainer; } // Chapter 1'in ilk aşamasını başlatan fonksiyon (50 mermilik, her 5 mermide bir hızlanan bölüm) function startChapter1Bullets() { // Diyalog ekranı göster showDialogue(["Agent Shadow, survive the incoming", "barrage. Move to dodge the light bullets."], function () { // Diyalog kapandıktan sonra üstte başlık ve geri sayım göster var chapterTitleContainer = new Container(); // Başlık yazısı var titleText = new Text2("Chapter 1: Meet With ???", { size: 110, fill: "#fff" }); titleText.anchor.set(0.5, 0); titleText.x = GAME_W / 2; titleText.y = 120; chapterTitleContainer.addChild(titleText); // Geri sayım yazısı var countdownText = new Text2("3", { size: 180, fill: "#fff" }); countdownText.anchor.set(0.5, 0); countdownText.x = GAME_W / 2; countdownText.y = titleText.y + titleText.height + 40; chapterTitleContainer.addChild(countdownText); // Ekrana ekle game.addChild(chapterTitleContainer); // Geri sayım başlat var countdownVals = ["3", "2", "1"]; var countdownIdx = 0; function doCountdown() { if (countdownIdx < countdownVals.length) { countdownText.setText(countdownVals[countdownIdx]); countdownIdx++; LK.setTimeout(doCountdown, 900); } else { // Mermi spawn fonksiyonu var _fireNextBullet = function fireNextBullet() { if (!isGameActive) { // Oyun biterse timerı temizle if (chapter1BulletTimer) { LK.clearTimeout(chapter1BulletTimer); chapter1BulletTimer = null; } return; } // SADECE YUKARIDAN DİKİNE GELEN MERMİ OLUŞTUR (OYUNCUYA DOĞRU DEĞİL, DİKİNE) // chapter 1-1'de sadece üstten aşağıya, sidespawn yok! var bullet = new LightBullet(); // Reduce bullet size by 50% for chapter 1-1 if (bullet && bullet.children && bullet.children.length > 0) { var bulletAsset = bullet.children[0]; bulletAsset.scaleX = 0.5; bulletAsset.scaleY = 0.5; // Update width/height for collision/out-of-bounds logic bullet.width = bulletAsset.width * 0.5; bullet.height = bulletAsset.height * 0.5; } // Sadece üstten rastgele X'e spawnla var startX = Math.random() * GAME_W; var startY = -bullet.height; // Hız (her 5 mermide bir hızlanacak şekilde ayarlanacak) var baseSpeed = 13; var speedStep = 2.2; var speed = baseSpeed + Math.floor(bulletsFired / 5) * speedStep + Math.random() * 2.5; bullet.x = startX; bullet.y = startY; bullet.speedX = 0; bullet.speedY = speed; // Merminin ucunu aşağıya bakacak şekilde döndür (PI radian = aşağı) bullet.rotation = Math.PI; bullets.push(bullet); game.addChild(bullet); bulletsFired++; // Skor göstergesini güncelle (kalan mermi) if (scoreTxt) { scoreTxt.setText(totalBullets - bulletsFired + ""); } // 50 mermiye ulaşıldıysa bitir if (bulletsFired >= totalBullets) { // Son mermi atıldıktan sonra kısa bir süre bekle, sonra outro diyaloğu göster LK.setTimeout(function () { isGameActive = false; // CHAPTER 1-1 OUTRO DİYALOĞU // --- Bölüm geçildi, ilerlemeyi kaydet --- storage.chapter = 1; storage.section = 2; showDialogue(["You’re alive… For now. But the", "enemy will hit harder. Prepare."], function () { // CHAPTER 1-2 INTRO DİYALOĞU showDialogue("The enemy’s tracking you, Shadow. Trick", "them with your Shadow Clone. Move.", function () { // CHAPTER 1-2 BAŞLAT startChapter1_2Bullets(); }); }); }, 900); return; } // Her 5 mermide bir hızlan if (bulletsFired % 5 === 0 && currentInterval > minInterval) { currentInterval = Math.max(minInterval, currentInterval - intervalStep); } // Sonraki mermiyi zamanla chapter1BulletTimer = LK.setTimeout(_fireNextBullet, currentInterval); }; // İlk mermiyi başlat // Geri sayım bitti, başlığı kaldır ve oyunu başlat chapterTitleContainer.destroy(); // Geri sayım bittiğinde kalan mermi sayısını göster if (scoreTxt) { scoreTxt.visible = true; } // 50 mermilik bölüm başlasın var totalBullets = 50; var bulletsFired = 0; var baseInterval = 900; // ilk mermi aralığı (ms) var minInterval = 250; // minimum aralık var intervalStep = 90; // her 5 mermide bir azaltılacak miktar var currentInterval = baseInterval; var chapter1BulletTimer = null; // Oyun aktif isGameActive = true; menuActive = false; // Skor göstergesi: kalan mermi sayısı if (scoreTxt) { scoreTxt.setText(totalBullets + ""); } _fireNextBullet(); } } doCountdown(); }); } // Chapter 1-2 bullet pattern and logic function startChapter1_2Bullets() { // Skor metni sıfırla ve göster if (scoreTxt) { scoreTxt.setText("50"); scoreTxt.visible = true; } // Tüm eski mermileri temizle for (var i = 0; i < bullets.length; i++) { if (bullets[i]) { bullets[i].destroy(); } } bullets = []; // Ayarlar var totalBullets = 50; var bulletsFired = 0; var interval = 1000; // 1 saniyede bir var chapter1_2BulletTimer = null; isGameActive = true; menuActive = false; // Mermi spawn fonksiyonu function fireNextBullet_1_2() { if (!isGameActive) { if (chapter1_2BulletTimer) { LK.clearTimeout(chapter1_2BulletTimer); chapter1_2BulletTimer = null; } return; } // Rastgele sağdan veya soldan gelsin var fromLeft = Math.random() < 0.5; var bullet = new LightBullet(); var startY = shadow ? shadow.y : GAME_H * 0.5; // Clamp startY to visible area if (startY < bullet.height / 2) { startY = bullet.height / 2; } if (startY > GAME_H - bullet.height / 2) { startY = GAME_H - bullet.height / 2; } var startX = fromLeft ? -bullet.width : GAME_W + bullet.width; bullet.x = startX; bullet.y = startY; // Hedef oyuncunun o anki konumu var targetX = shadow ? shadow.x : GAME_W / 2; var targetY = shadow ? shadow.y : GAME_H * 0.8; var dx = targetX - startX; var dy = targetY - startY; var angle = Math.atan2(dy, dx); var speed = 15 + Math.random() * 3; bullet.speedX = Math.cos(angle) * speed; bullet.speedY = Math.sin(angle) * speed; // Merminin ucunu oyuncuya bakacak şekilde döndür bullet.rotation = angle + Math.PI / 2; bullets.push(bullet); game.addChild(bullet); bulletsFired++; // Skor göstergesini güncelle (kalan mermi) if (scoreTxt) { scoreTxt.setText(totalBullets - bulletsFired + ""); } // 50 mermiye ulaşıldıysa bitir if (bulletsFired >= totalBullets) { LK.setTimeout(function () { isGameActive = false; // CHAPTER 1-2 OUTRO DİYALOĞU // --- Bölüm geçildi, ilerlemeyi kaydet --- storage.chapter = 2; storage.section = 1; showDialogue("Your clone worked. But these enemies don’t quit. More are coming.", function () { // Sonraki bölüme geçiş veya menüye dönüş burada yapılabilir LK.showYouWin(); }); }, 900); return; } // Sonraki mermiyi zamanla chapter1_2BulletTimer = LK.setTimeout(fireNextBullet_1_2, interval); } // İlk mermiyi başlat fireNextBullet_1_2(); } // --- Endless Run Bullet Modes --- // Global endless run bullet speed multiplier (starts slow, increases with score) var endlessBulletSpeedMultiplier = 1.0; // Helper to update endlessBulletSpeedMultiplier based on score function updateEndlessBulletSpeedMultiplier() { // At score 0: 1.0 (slowest), at score 30: 1.03, at score 36: 1.036, etc. // 3% per 30, 3.6% per 36, 10% per 100, etc. if (typeof score === "number" && score >= 0) { endlessBulletSpeedMultiplier = 1.0 + score * 0.001; } else { endlessBulletSpeedMultiplier = 1.0; } } var ENDLESS_BULLET_MODES = [{ name: "Vertical Bullets", desc: "Bullets from Top", spawn: function spawn() { updateEndlessBulletSpeedMultiplier(); var bullet = new LightBullet(); var startX = Math.random() * GAME_W; var startY = -bullet.height; var baseSpeed = 7.5; // much slower than before var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier; bullet.x = startX; bullet.y = startY; bullet.speedX = 0; bullet.speedY = speed; bullet.rotation = Math.PI; bullets.push(bullet); game.addChild(bullet); } }, { name: "Side Bullets", desc: "Bullets from Sides", spawn: function spawn() { updateEndlessBulletSpeedMultiplier(); var fromLeft = Math.random() < 0.5; var bullet = new LightBullet(); var startY = Math.random() * GAME_H; var startX = fromLeft ? -bullet.width : GAME_W + bullet.width; var baseSpeed = 7.5; var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier; bullet.x = startX; bullet.y = startY; bullet.speedX = fromLeft ? speed : -speed; bullet.speedY = 0; bullet.rotation = fromLeft ? Math.PI / 2 : -Math.PI / 2; bullets.push(bullet); game.addChild(bullet); } }, { name: "Chasing Bullets", desc: "Chasing Bullets", spawn: function spawn() { updateEndlessBulletSpeedMultiplier(); var edge = Math.floor(Math.random() * 4); var bullet = new ChasingLightBullet(); var startX, startY; if (edge === 0) { startX = Math.random() * GAME_W; startY = -bullet.height; } else if (edge === 1) { startX = GAME_W + bullet.width; startY = Math.random() * GAME_H; } else if (edge === 2) { startX = Math.random() * GAME_W; startY = GAME_H + bullet.height; } else { startX = -bullet.width; startY = Math.random() * GAME_H; } bullet.x = startX; bullet.y = startY; // Set chasing bullet speed to scale with endlessBulletSpeedMultiplier // Lower base speed for chasing bullets in endless mode for fairness var baseChaseSpeed = 4.5; bullet.speed = (baseChaseSpeed + Math.random() * 1.8) * endlessBulletSpeedMultiplier; chasingBullets.push(bullet); game.addChild(bullet); } }, { name: "Targeted Bullets", desc: "Bullets to Player", spawn: function spawn() { updateEndlessBulletSpeedMultiplier(); if (!shadow) { return; } var bullet = new LightBullet(); var edge = Math.floor(Math.random() * 4); var startX, startY; if (edge === 0) { startX = Math.random() * GAME_W; startY = -bullet.height; } else if (edge === 1) { startX = GAME_W + bullet.width; startY = Math.random() * GAME_H; } else if (edge === 2) { startX = Math.random() * GAME_W; startY = GAME_H + bullet.height; } else { startX = -bullet.width; startY = Math.random() * GAME_H; } var dx = shadow.x - startX; var dy = shadow.y - startY; var angle = Math.atan2(dy, dx); var baseSpeed = 7.5; var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier; bullet.x = startX; bullet.y = startY; bullet.speedX = Math.cos(angle) * speed; bullet.speedY = Math.sin(angle) * speed; bullet.rotation = angle + Math.PI / 2; bullets.push(bullet); game.addChild(bullet); } }]; // --- Endless Run Mode State --- var endlessCurrentModeIndex = 0; var endlessCurrentModeScore = 0; var endlessModeNameText = null; function pickRandomEndlessMode(excludeIndex) { var idx; do { idx = Math.floor(Math.random() * ENDLESS_BULLET_MODES.length); } while (ENDLESS_BULLET_MODES.length > 1 && idx === excludeIndex); return idx; } // Endless Run başlat function startEndlessRun() { clearGameObjects(); // --- Endless Run Mod State --- window._endlessRunState = { bulletSpeedBase: 13, bulletSpeedStep: 0, bulletSizeScale: 1, chasingBulletSpeedBase: 10, chasingBulletSpeedStep: 0, chasingBulletActive: true }; // Reset endless run bullet speed multiplier at the start endlessBulletSpeedMultiplier = 1.0; // --- Shadow Skill: Reset state --- shadowSkillLastUsed = 0; shadowSkillActive = false; shadowSkillTapTimes = []; if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") { shadowSkillEffect.destroy(); shadowSkillEffect = null; } if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") { shadowSkillGuiText.destroy(); shadowSkillGuiText = null; } // Remove old shadow skill asset and bar if any if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset && typeof shadowSkillGuiAsset.destroy === "function") { shadowSkillGuiAsset.destroy(); shadowSkillGuiAsset = null; } if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillBar.destroy === "function") { shadowSkillBar.destroy(); shadowSkillBar = null; } // Set cooldown to 15 seconds shadowSkillCooldown = 15000; // Add shadow skill asset to bottom left (avoid 100x100 px top left menu area) shadowSkillGuiAsset = LK.getAsset('shadowskill', { anchorX: 0, anchorY: 1, x: 60, y: GAME_H - 60, scaleX: 1.5, scaleY: 1.5, alpha: 1 }); game.addChild(shadowSkillGuiAsset); // Add loading bar (as a box) above the asset, but initially invisible shadowSkillBar = LK.getAsset('shadowbox', { anchorX: 0.5, anchorY: 1, x: shadowSkillGuiAsset.x + shadowSkillGuiAsset.width * 0.75, y: shadowSkillGuiAsset.y - shadowSkillGuiAsset.height * 1.1, width: 180, height: 32, alpha: 0, scaleX: 1, scaleY: 1 }); game.addChild(shadowSkillBar); // Store for update shadowSkillBarBaseWidth = shadowSkillBar.width; // Karakteri oluştur ve ortala shadow = new Shadow(); shadow.x = GAME_W / 2; shadow.y = GAME_H * 0.8; game.addChild(shadow); // Skor (endless modda skor gösterilir) score = 0; if (scoreTxt) { scoreTxt.destroy(); } scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); isGameActive = false; menuActive = false; // --- Shadow Skill: Show cooldown text (endless only) --- if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") { shadowSkillGuiText.destroy(); shadowSkillGuiText = null; } shadowSkillGuiText = new Text2("Skill Ready", { size: 60, fill: 0xAAFFFF }); shadowSkillGuiText.anchor.set(0.5, 0); shadowSkillGuiText.x = GAME_W / 2; shadowSkillGuiText.y = 120 + 60; LK.gui.top.addChild(shadowSkillGuiText); // --- Endless Run: Show unlock info if any --- if (game._endlessUnlockText) { game._endlessUnlockText.destroy(); game._endlessUnlockText = null; } // --- Endless Run: 3-2-1 geri sayım ekle --- if (game._endlessCountdownContainer) { game._endlessCountdownContainer.destroy(); game._endlessCountdownContainer = null; } var countdownContainer = new Container(); var countdownText = new Text2("3", { size: 220, fill: "#fff" }); countdownText.anchor.set(0.5, 0.5); countdownText.x = GAME_W / 2; countdownText.y = GAME_H / 2; countdownContainer.addChild(countdownText); game.addChild(countdownContainer); game._endlessCountdownContainer = countdownContainer; var countdownVals = ["3", "2", "1"]; var countdownIdx = 0; function doEndlessCountdown() { if (countdownIdx < countdownVals.length) { countdownText.setText(countdownVals[countdownIdx]); countdownIdx++; LK.setTimeout(doEndlessCountdown, 900); } else { // Geri sayım bittiğinde container'ı kaldır ve oyunu başlat if (game._endlessCountdownContainer) { game._endlessCountdownContainer.destroy(); game._endlessCountdownContainer = null; } // Rastgele bir mod seç endlessCurrentModeIndex = pickRandomEndlessMode(-1); endlessCurrentModeScore = 0; // Mod adını göster if (endlessModeNameText) { endlessModeNameText.destroy(); endlessModeNameText = null; } endlessModeNameText = new Text2(ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name, { size: 140, fill: "#fff" }); endlessModeNameText.anchor.set(0.5, 0.5); endlessModeNameText.x = GAME_W / 2; endlessModeNameText.y = GAME_H / 2; game.addChild(endlessModeNameText); // Hemen başlat, 3 saniye bekleme isGameActive = true; startBulletTimer(); startChasingBulletTimer(); // Mod adını 3 saniye sonra kaldır LK.setTimeout(function () { if (endlessModeNameText) { endlessModeNameText.destroy(); endlessModeNameText = null; } }, 3000); } } doEndlessCountdown(); } // Oyun objelerini temizle function clearGameObjects() { // Karakteri sil if (shadow) { shadow.destroy(); shadow = null; } // Mermileri sil for (var i = 0; i < bullets.length; i++) { if (bullets[i]) { bullets[i].destroy(); } } bullets = []; for (var i = 0; i < chasingBullets.length; i++) { if (chasingBullets[i]) { chasingBullets[i].destroy(); } } chasingBullets = []; // Skor metni sil if (scoreTxt) { scoreTxt.destroy(); scoreTxt = null; } // Joystick ve paneli sil if (controlPanel) { controlPanel.destroy(); controlPanel = null; } joystick = null; isPanelActive = false; dragNode = null; joystickDirX = 0; joystickDirY = 0; joystickActive = false; // --- Shadow Skill: Cleanup effect, text, asset, and bar --- if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") { shadowSkillEffect.destroy(); shadowSkillEffect = null; } if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") { shadowSkillGuiText.destroy(); shadowSkillGuiText = null; } if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset && typeof shadowSkillGuiAsset.destroy === "function") { shadowSkillGuiAsset.destroy(); shadowSkillGuiAsset = null; } if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillBar.destroy === "function") { shadowSkillBar.destroy(); shadowSkillBar = null; } shadowSkillActive = false; shadowSkillTapTimes = []; } // Oyun ilk açıldığında menüyü göster showMenu(); // Mermi oluşturma fonksiyonu function spawnBullet() { // Only apply endless run balancing if in endless mode var isEndless = currentMode === "endless"; if (isEndless) { // Use endless run bullet mode system if (typeof endlessCurrentModeIndex !== "number" || endlessCurrentModeIndex < 0) { endlessCurrentModeIndex = 0; } // Only spawn the current mode's bullet type for 25 bullets, never mixing ENDLESS_BULLET_MODES[endlessCurrentModeIndex].spawn(); // Skor artır updateScore(score + 1); endlessCurrentModeScore++; // Change mode only after 25 bullets of the same type if (endlessCurrentModeScore >= 25) { endlessCurrentModeScore = 0; var prevIndex = endlessCurrentModeIndex; endlessCurrentModeIndex = pickRandomEndlessMode(prevIndex); // Show mode name if (endlessModeNameText) { endlessModeNameText.destroy(); endlessModeNameText = null; } endlessModeNameText = new Text2(ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name, { size: 140, fill: "#fff" }); endlessModeNameText.anchor.set(0.5, 0.5); endlessModeNameText.x = GAME_W / 2; endlessModeNameText.y = GAME_H / 2; game.addChild(endlessModeNameText); LK.setTimeout(function () { if (endlessModeNameText) { endlessModeNameText.destroy(); endlessModeNameText = null; } }, 3000); } } else { // Orijinal mantık (chapter mode) var bullet = new LightBullet(); var angle, startX, startY, targetX, targetY, speed; // Kenara göre başlangıç pozisyonu ve yönü belirle var edge = Math.floor(Math.random() * 4); if (edge === 0) { startX = Math.random() * GAME_W; startY = -bullet.height; } else if (edge === 1) { startX = GAME_W + bullet.width; startY = Math.random() * GAME_H; } else if (edge === 2) { startX = Math.random() * GAME_W; startY = GAME_H + bullet.height; } else { startX = -bullet.width; startY = Math.random() * GAME_H; } // Hedef karakterin anlık pozisyonu targetX = shadow.x; targetY = shadow.y; var dx = targetX - startX; var dy = targetY - startY; angle = Math.atan2(dy, dx); // Hız (her mermi için rastgele küçük bir varyasyon) speed = 13 + Math.random() * 5; bullet.x = startX; bullet.y = startY; bullet.speedX = Math.cos(angle) * speed; bullet.speedY = Math.sin(angle) * speed; // Merminin ucunu oyuncuya bakacak şekilde döndür bullet.rotation = angle + Math.PI / 2; bullets.push(bullet); game.addChild(bullet); } } // Takipçi mermi oluşturma fonksiyonu function spawnChasingBullet() { var isEndless = currentMode === "endless"; // Only allow chasing bullets in endless mode if current mode is "Chasing Bullets" if (isEndless && typeof endlessCurrentModeIndex === "number" && ENDLESS_BULLET_MODES[endlessCurrentModeIndex]) { if (ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name !== "Chasing Bullets") { return; } } // --- Endless Run: Chasing bullets always enabled, no unlocks --- // Kenardan rastgele bir yerden başlat var edge = Math.floor(Math.random() * 4); var bullet = new ChasingLightBullet(); var startX, startY; if (edge === 0) { startX = Math.random() * GAME_W; startY = -bullet.height; } else if (edge === 1) { startX = GAME_W + bullet.width; startY = Math.random() * GAME_H; } else if (edge === 2) { startX = Math.random() * GAME_W; startY = GAME_H + bullet.height; } else { startX = -bullet.width; startY = Math.random() * GAME_H; } bullet.x = startX; bullet.y = startY; // --- Endless Run: Dynamic chasing bullet speed/size --- if (isEndless) { updateEndlessBulletSpeedMultiplier(); // Lower base speed for chasing bullets in endless mode for fairness var baseChaseSpeed = 4.5; bullet.speed = (baseChaseSpeed + Math.random() * 1.8) * endlessBulletSpeedMultiplier; } // Başlangıçta oyuncuya bakacak şekilde döndür var dx = shadow.x - bullet.x; var dy = shadow.y - bullet.y; bullet.rotation = Math.atan2(dy, dx) + Math.PI / 2; chasingBullets.push(bullet); game.addChild(bullet); // Endless run modunda skor mermi sayısı olarak güncellensin if (currentMode === "endless") { updateScore(score + 1); } } // Mermi oluşturma zamanlayıcısı başlat function startBulletTimer() { if (bulletTimer) { LK.clearInterval(bulletTimer); } bulletTimer = LK.setInterval(function () { if (isGameActive) { spawnBullet(); } }, bulletInterval); } // startBulletTimer(); // Chapter 1'de otomatik mermi spawn edilmesin // Takipçi mermi zamanlayıcısı başlat function startChasingBulletTimer() { if (chasingBulletTimer) { LK.clearInterval(chasingBulletTimer); } // Chasing bullets spawn at most one per second in endless run mode chasingBulletTimer = LK.setInterval(function () { // Only spawn chasing bullets if current endless mode is 'Chasing Bullets' var isEndless = currentMode === "endless"; var chasingModeActive = false; if (isEndless && typeof endlessCurrentModeIndex === "number" && ENDLESS_BULLET_MODES[endlessCurrentModeIndex]) { chasingModeActive = ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name === "Chasing Bullets"; } if (isGameActive && score >= 10 && chasingModeActive) { // Limit chasing bullets on screen to 2 at a time for fairness if (chasingBullets.length < 2) { spawnChasingBullet(); } } }, 1000); // 1000ms = 1 second } // startChasingBulletTimer(); // Chapter 1'de otomatik takipçi mermi spawn edilmesin // Skoru güncelle function updateScore(val) { // Sadece endless modda skor güncellensin if (currentMode === "endless") { score = val; if (scoreTxt) { scoreTxt.setText(score); } } } // Oyun bittiğinde function gameOver() { isGameActive = false; // Ekranı kırmızıya flashla LK.effects.flashScreen(0xff2222, 800); // Chapter modunda ise ilerlemeyi kaydet if (currentMode === "chapter") { // Skora göre chapter ilerletme örneği (ileride özelleştirilebilir) if (score > 0) { var nextChapter = (storage.chapter || 1) + 1; storage.chapter = nextChapter; } } // Endless modda high score zaten update'de kaydediliyor // Oyun bitişini göster LK.showGameOver(); // Kısa bir gecikmeden sonra menüyü tekrar göster LK.setTimeout(function () { showMenu(); }, 1200); } // Sürükleme işlemleri // Joystick yönünü ve mesafesini globalde tut var joystickDirX = 0; var joystickDirY = 0; var joystickActive = false; function handleMove(x, y, obj) { if (!isGameActive || menuActive) { return; } if (isPanelActive && controlPanel && joystick) { // Joystick merkezine göre delta var dx = x - panelStartX; var dy = y - panelStartY; var dist = Math.sqrt(dx * dx + dy * dy); // Knob'u panel sınırında tut var maxDist = joystickRadius - joystickKnobRadius; if (dist > maxDist) { var angle = Math.atan2(dy, dx); dx = Math.cos(angle) * maxDist; dy = Math.sin(angle) * maxDist; } joystick.x = dx; joystick.y = dy; // Joystick yönünü ve oranını kaydet if (dist > 10) { // ölü bölge joystickDirX = dx / maxDist; joystickDirY = dy / maxDist; joystickActive = true; } else { joystickDirX = 0; joystickDirY = 0; joystickActive = false; } } } game.move = handleMove; game.down = function (x, y, obj) { if (!isGameActive || menuActive) { return; } // --- Shadow Skill: Double-tap detection (endless only) --- if (currentMode === "endless") { var now = Date.now(); // Remove old taps for (var i = shadowSkillTapTimes.length - 1; i >= 0; i--) { if (now - shadowSkillTapTimes[i] > shadowSkillTapWindow) { shadowSkillTapTimes.splice(i, 1); } } shadowSkillTapTimes.push(now); // If two taps within window, trigger skill if (shadowSkillTapTimes.length >= 2 && !shadowSkillActive) { var lastUsedAgo = now - shadowSkillLastUsed; if (lastUsedAgo >= shadowSkillCooldown) { // Activate skill shadowSkillActive = true; shadowSkillLastUsed = now; shadowSkillTapTimes = []; // Remove old effect if any if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") { shadowSkillEffect.destroy(); shadowSkillEffect = null; } // Create and add skill effect shadowSkillEffect = new ShadowSkill(); game.addChild(shadowSkillEffect); // Optional: flash effect LK.effects.flashObject(shadow, 0x00ffff, 300); } } } // Kontrol paneli zaten varsa kaldır if (controlPanel) { controlPanel.destroy(); controlPanel = null; joystick = null; isPanelActive = false; } // Kontrol panelini oluştur controlPanel = new Container(); controlPanel.x = x; controlPanel.y = y; // Panel arka planı (şeffaf daire) var panelBg = LK.getAsset('centerCircle', { width: joystickRadius * 2, height: joystickRadius * 2, color: 0x222222, anchorX: 0.5, anchorY: 0.5, alpha: 0.25 }); controlPanel.addChild(panelBg); // Joystick knob'u (küçük daire) joystick = LK.getAsset('centerCircle', { width: joystickKnobRadius * 2, height: joystickKnobRadius * 2, color: 0xffffff, anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); joystick.x = 0; joystick.y = 0; controlPanel.addChild(joystick); // Paneli oyuna ekle game.addChild(controlPanel); isPanelActive = true; panelStartX = x; panelStartY = y; knobStartX = 0; knobStartY = 0; }; game.up = function (x, y, obj) { if (menuActive) { return; } // Kontrol panelini kaldır if (controlPanel) { controlPanel.destroy(); controlPanel = null; joystick = null; isPanelActive = false; } dragNode = null; // Joystick bırakıldığında hareketi durdur joystickDirX = 0; joystickDirY = 0; joystickActive = false; }; // Oyun güncelleme döngüsü game.update = function () { if (!isGameActive || menuActive) { return; } // Karakteri joystick yönünde sürekli hareket ettir if (joystickActive) { var moveSpeed = 22; shadow.x += joystickDirX * moveSpeed; shadow.y += joystickDirY * moveSpeed; var r = shadow.getRadius(); if (shadow.x < r) { shadow.x = r; } if (shadow.x > GAME_W - r) { shadow.x = GAME_W - r; } if (shadow.y < r) { shadow.y = r; } if (shadow.y > GAME_H - r) { shadow.y = GAME_H - r; } } // --- Shadow Skill: Update effect, asset alpha, and loading bar in endless run --- if (currentMode === "endless") { // Update skill effect if (shadowSkillEffect && typeof shadowSkillEffect.update === "function") { shadowSkillEffect.update(); if (shadowSkillEffect._destroyed) { shadowSkillEffect = null; shadowSkillActive = false; } } // Update shadow skill asset alpha and loading bar if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset) { var now = Date.now(); var cdMs = Math.max(0, shadowSkillLastUsed + shadowSkillCooldown - now); if (shadowSkillActive) { shadowSkillGuiAsset.alpha = 0.4; } else if (cdMs <= 0) { shadowSkillGuiAsset.alpha = 1.0; } else { shadowSkillGuiAsset.alpha = 0.4; } } // Update loading bar position and visibility above the skill asset if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset) { var now = Date.now(); var cdMs = Math.max(0, shadowSkillLastUsed + shadowSkillCooldown - now); var fill = 1 - cdMs / shadowSkillCooldown; if (fill < 0) { fill = 0; } if (fill > 1) { fill = 1; } // Position bar 50px higher and 50px more to the left above the skill asset, left-aligned var barFullWidth = typeof shadowSkillBarBaseWidth !== "undefined" ? shadowSkillBarBaseWidth : 180; // Place bar centered horizontally above the skill asset, but 50px higher and 50px more to the left shadowSkillBar.x = shadowSkillGuiAsset.x + shadowSkillGuiAsset.width * 0.5 - 50; shadowSkillBar.y = shadowSkillGuiAsset.y - shadowSkillGuiAsset.height * 1.1 - 50; shadowSkillBar.anchorX = 0; // left align for fill // Only show bar if skill is recharging if (cdMs > 0) { shadowSkillBar.alpha = 0.85; shadowSkillBar.width = barFullWidth * fill; } else { shadowSkillBar.alpha = 0; shadowSkillBar.width = barFullWidth; } } // Remove cooldown text (no longer needed) if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") { shadowSkillGuiText.destroy(); shadowSkillGuiText = null; } } // Mermileri güncelle for (var i = bullets.length - 1; i >= 0; i--) { var b = bullets[i]; b.update(); if (b.isOutOfBounds()) { b.destroy(); bullets.splice(i, 1); continue; } var intersecting = b.intersects(shadow); if (!b.lastIntersecting && intersecting) { // Oyuncu mermiye çarptıysa, oyunu durdur ve "Yeniden Başla" butonu göster isGameActive = false; // Tüm mermileri temizle for (var k = 0; k < bullets.length; k++) { if (bullets[k]) { bullets[k].destroy(); } } bullets = []; for (var k = 0; k < chasingBullets.length; k++) { if (chasingBullets[k]) { chasingBullets[k].destroy(); } } chasingBullets = []; // Karakteri sil if (shadow) { shadow.destroy(); shadow = null; } // Skor metni sil if (scoreTxt) { scoreTxt.destroy(); scoreTxt = null; } // Joystick ve paneli sil if (controlPanel) { controlPanel.destroy(); controlPanel = null; } joystick = null; isPanelActive = false; dragNode = null; joystickDirX = 0; joystickDirY = 0; joystickActive = false; // --- YENİDEN BAŞLA VE ANA MENÜ BUTONLARI EKLE --- if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } var retryButtonContainer = new Container(); // Ortalanmış konumlar var btnW = 600; var btnH = 180; var btnSpacing = 80; var centerY = GAME_H / 2; var restartY = centerY - btnH / 2 - btnSpacing / 2; var menuY = centerY + btnH / 2 + btnSpacing / 2; // --- Endless Run: Skor ve süreyi göster --- if (currentMode === "endless") { // Skor var scoreLabel = new Text2("Score: " + score, { size: 110, fill: "#fff" }); scoreLabel.anchor.set(0.5, 1); scoreLabel.x = GAME_W / 2; scoreLabel.y = restartY - 120; retryButtonContainer.addChild(scoreLabel); // Hayatta kalma süresi (saniye ve dakika) // Her 30 tick = 0.5 saniye, 60 tick = 1 saniye (FPS 60) // Skor her 30 tickte 1 artıyor, yani skor = yarım saniye // Oyun update'inde: updateScore(score + 1); if (LK.ticks % 30 === 0) // Yani score/2 = saniye var survivedSeconds = Math.floor(score / 2); var min = Math.floor(survivedSeconds / 60); var sec = survivedSeconds % 60; var timeStr = min > 0 ? min + "m " + (sec < 10 ? "0" : "") + sec + "s" : sec + "s"; var timeLabel = new Text2("Survived: " + timeStr, { size: 90, fill: "#fff" }); timeLabel.anchor.set(0.5, 1); timeLabel.x = GAME_W / 2; timeLabel.y = scoreLabel.y - 100; retryButtonContainer.addChild(timeLabel); } // Restart butonu arka planı var btnBgRestart = LK.getAsset('blackbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: restartY, width: btnW, height: btnH }); retryButtonContainer.addChild(btnBgRestart); // Restart buton metni var btnTextRestart = new Text2("Restart", { size: 80, fill: "#fff" }); btnTextRestart.anchor.set(0.5, 0.5); btnTextRestart.x = GAME_W / 2; btnTextRestart.y = restartY; retryButtonContainer.addChild(btnTextRestart); // Main Menu butonu arka planı var btnBgMenu = LK.getAsset('blackbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: menuY, width: btnW, height: btnH }); retryButtonContainer.addChild(btnBgMenu); // Main Menu buton metni var btnTextMenu = new Text2("Main Menu", { size: 80, fill: "#fff" }); btnTextMenu.anchor.set(0.5, 0.5); btnTextMenu.x = GAME_W / 2; btnTextMenu.y = menuY; retryButtonContainer.addChild(btnTextMenu); // Restart butonu etkileşimi btnBgRestart.interactive = true; btnBgRestart.buttonMode = true; btnBgRestart.down = function (x, y, obj) { if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } if (currentMode === "endless") { startEndlessRun(); } else { startGameFromChapter(); } }; // Main Menu butonu etkileşimi btnBgMenu.interactive = true; btnBgMenu.buttonMode = true; btnBgMenu.down = function (x, y, obj) { if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } showMenu(); }; game.addChild(retryButtonContainer); game._retryButtonContainer = retryButtonContainer; return; } b.lastIntersecting = intersecting; } // Takipçi mermileri güncelle for (var i = chasingBullets.length - 1; i >= 0; i--) { var cb = chasingBullets[i]; cb.update(); if (cb.isOutOfBounds()) { cb.destroy(); chasingBullets.splice(i, 1); continue; } var intersecting = cb.intersects(shadow); if (!cb.lastIntersecting && intersecting) { // Oyuncu takipçi mermiye çarptıysa, oyunu durdur ve "Yeniden Başla" butonu göster isGameActive = false; // Tüm mermileri temizle for (var k = 0; k < bullets.length; k++) { if (bullets[k]) { bullets[k].destroy(); } } bullets = []; for (var k = 0; k < chasingBullets.length; k++) { if (chasingBullets[k]) { chasingBullets[k].destroy(); } } chasingBullets = []; // Karakteri sil if (shadow) { shadow.destroy(); shadow = null; } // Skor metni sil if (scoreTxt) { scoreTxt.destroy(); scoreTxt = null; } // Joystick ve paneli sil if (controlPanel) { controlPanel.destroy(); controlPanel = null; } joystick = null; isPanelActive = false; dragNode = null; joystickDirX = 0; joystickDirY = 0; joystickActive = false; // --- YENİDEN BAŞLA VE ANA MENÜ BUTONLARI EKLE --- if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } var retryButtonContainer = new Container(); // Ortalanmış konumlar var btnW = 600; var btnH = 180; var btnSpacing = 80; var centerY = GAME_H / 2; var restartY = centerY - btnH / 2 - btnSpacing / 2; var menuY = centerY + btnH / 2 + btnSpacing / 2; // --- Endless Run: Skor ve süreyi göster --- if (currentMode === "endless") { // Skor var scoreLabel = new Text2("Score: " + score, { size: 110, fill: "#fff" }); scoreLabel.anchor.set(0.5, 1); scoreLabel.x = GAME_W / 2; scoreLabel.y = restartY - 120; retryButtonContainer.addChild(scoreLabel); // Hayatta kalma süresi (saniye ve dakika) var survivedSeconds = Math.floor(score / 2); var min = Math.floor(survivedSeconds / 60); var sec = survivedSeconds % 60; var timeStr = min > 0 ? min + "m " + (sec < 10 ? "0" : "") + sec + "s" : sec + "s"; var timeLabel = new Text2("Survived: " + timeStr, { size: 90, fill: "#fff" }); timeLabel.anchor.set(0.5, 1); timeLabel.x = GAME_W / 2; timeLabel.y = scoreLabel.y - 100; retryButtonContainer.addChild(timeLabel); } // Restart butonu arka planı var btnBgRestart = LK.getAsset('blackbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: restartY, width: btnW, height: btnH }); retryButtonContainer.addChild(btnBgRestart); // Restart buton metni var btnTextRestart = new Text2("Restart", { size: 80, fill: "#fff" }); btnTextRestart.anchor.set(0.5, 0.5); btnTextRestart.x = GAME_W / 2; btnTextRestart.y = restartY; retryButtonContainer.addChild(btnTextRestart); // Main Menu butonu arka planı var btnBgMenu = LK.getAsset('blackbutton', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: menuY, width: btnW, height: btnH }); retryButtonContainer.addChild(btnBgMenu); // Main Menu buton metni var btnTextMenu = new Text2("Main Menu", { size: 80, fill: "#fff" }); btnTextMenu.anchor.set(0.5, 0.5); btnTextMenu.x = GAME_W / 2; btnTextMenu.y = menuY; retryButtonContainer.addChild(btnTextMenu); // Restart butonu etkileşimi btnBgRestart.interactive = true; btnBgRestart.buttonMode = true; btnBgRestart.down = function (x, y, obj) { if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } if (currentMode === "endless") { startEndlessRun(); } else { startGameFromChapter(); } }; // Main Menu butonu etkileşimi btnBgMenu.interactive = true; btnBgMenu.buttonMode = true; btnBgMenu.down = function (x, y, obj) { if (game._retryButtonContainer) { game._retryButtonContainer.destroy(); game._retryButtonContainer = null; } showMenu(); }; game.addChild(retryButtonContainer); game._retryButtonContainer = retryButtonContainer; return; } cb.lastIntersecting = intersecting; } // Işık mermileri birbirine çarptı mı kontrol et ve patlat for (var i = bullets.length - 1; i >= 0; i--) { var b1 = bullets[i]; for (var j = i - 1; j >= 0; j--) { var b2 = bullets[j]; if (!b1._destroyed && !b2._destroyed && b1.intersects(b2)) { b1.destroy(); b2.destroy(); bullets.splice(i, 1); bullets.splice(j, 1); i--; break; } } } // Takipçi mermiler ile normal mermiler çarpışırsa patlat for (var i = chasingBullets.length - 1; i >= 0; i--) { var cb = chasingBullets[i]; for (var j = bullets.length - 1; j >= 0; j--) { var b = bullets[j]; if (!cb._destroyed && !b._destroyed && cb.intersects(b)) { cb.destroy(); b.destroy(); chasingBullets.splice(i, 1); bullets.splice(j, 1); i--; break; } } } // Takipçi mermiler birbirine çarparsa patlat for (var i = chasingBullets.length - 1; i >= 0; i--) { var cb1 = chasingBullets[i]; for (var j = i - 1; j >= 0; j--) { var cb2 = chasingBullets[j]; if (!cb1._destroyed && !cb2._destroyed && cb1.intersects(cb2)) { cb1.destroy(); cb2.destroy(); chasingBullets.splice(i, 1); chasingBullets.splice(j, 1); i--; break; } } } // Endless run modunda skor mermi sayısı olarak güncellendiği için burada skor artışı yapılmaz if (currentMode === "endless") { // Endless modda high score kaydet if (score > (storage.endlessHighScore || 0)) { storage.endlessHighScore = score; } // --- Endless Run: Unlock logic removed, difficulty does not increase --- } }; // Oyun yeniden başladığında sıfırla game.on('reset', function () { // Tüm oyun objelerini temizle clearGameObjects(); // Menüye dön showMenu(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
chapter: 1,
endlessHighScore: 0
});
/****
* Classes
****/
// Takipçi ışık mermisi (ChasingLightBullet) sınıfı
var ChasingLightBullet = Container.expand(function () {
var self = Container.call(this);
// Mermi grafiği
var bulletAsset = self.attachAsset('lightBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Hız vektörü
self.speed = 10 + Math.random() * 3;
// Çarpışma kontrolü için son intersect durumu
self.lastIntersecting = false;
// Mermi boyutu
self.width = bulletAsset.width;
self.height = bulletAsset.height;
// Chasing Bullets mode: No pulse/yanıp sönme effect
bulletAsset.alpha = 1.0;
// Güncelleme fonksiyonu
self.update = function () {
// Son pozisyonları güncelle
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
// --- Bullet trail effect (effectwhite) ---
if (self._destroyed !== true) {
var trail = LK.getAsset('effectwhite', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.25,
scaleY: 0.25,
alpha: 0.18
});
if (self.parent && typeof self.parent.addChild === "function") {
self.parent.addChild(trail);
} else if (typeof game !== "undefined" && typeof game.addChild === "function") {
game.addChild(trail);
}
tween(trail, {
alpha: 0
}, {
duration: 320,
onFinish: function onFinish() {
if (trail && typeof trail.destroy === "function") {
trail.destroy();
}
}
});
}
// Hedefe doğru yönel (Chasing Bullets modunda gecikmeli ve yumuşak dönüş)
// Delay ve smooth rotate için state değişkenleri
if (self._turnDelay === undefined) {
self._turnDelay = 0; // Kaç frame sonra tekrar hedefe dönecek
self._targetAngle = self.rotation - Math.PI / 2; // Son hedef açı
self._currentAngle = self.rotation - Math.PI / 2; // Şu anki açı
}
if (typeof shadow !== "undefined") {
// Chasing Bullets modunda smooth turning
// Delay ayarı: her 12 frame'de bir yeni hedef açıya bak (yaklaşık 0.2s)
var turnDelayFrames = 12;
if (self._turnDelay <= 0) {
var dx = shadow.x - self.x;
var dy = shadow.y - self.y;
var newTargetAngle = Math.atan2(dy, dx);
// Ani açı değişimini engelle: sadece küçük farklarda güncelle
// (ör: 90 dereceden fazla ise hemen dönmesin)
var angleDiff = newTargetAngle - self._currentAngle;
// -PI ile PI arası normalize et
while (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI;
}
while (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI;
}
// Maksimum bir açı değişimi uygula (ör: 60 derece/frame)
var maxTurn = Math.PI / 3; // 60 derece
if (Math.abs(angleDiff) > maxTurn) {
newTargetAngle = self._currentAngle + (angleDiff > 0 ? maxTurn : -maxTurn);
}
self._targetAngle = newTargetAngle;
self._turnDelay = turnDelayFrames;
} else {
self._turnDelay--;
}
// Yavaşça hedef açıya yaklaş (smooth rotate)
var rotateSpeed = 0.18; // 0.0-1.0 arası, 1.0 anında döner, 0.1 çok yavaş
var diff = self._targetAngle - self._currentAngle;
// -PI ile PI arası normalize et
while (diff > Math.PI) {
diff -= 2 * Math.PI;
}
while (diff < -Math.PI) {
diff += 2 * Math.PI;
}
self._currentAngle += diff * rotateSpeed;
// Pozisyonu güncelle
self.x += Math.cos(self._currentAngle) * self.speed;
self.y += Math.sin(self._currentAngle) * self.speed;
// Merminin ucunu oyuncuya bakacak şekilde döndür
self.rotation = self._currentAngle + Math.PI / 2;
}
self.lastX = self.x;
self.lastY = self.y;
};
// Ekran dışına çıktı mı?
self.isOutOfBounds = function () {
return self.x < -self.width || self.x > 2048 + self.width || self.y < -self.height || self.y > 2732 + self.height;
};
// Yok edilirken animasyonu durdur
var _destroy = self.destroy;
// pulseTween is not used in this class, but to prevent ReferenceError, define it as null
var pulseTween = null;
self.destroy = function () {
if (pulseTween) {
pulseTween.stop();
}
_destroy.call(self);
};
return self;
});
// Işık mermisi (LightBullet) sınıfı
var LightBullet = Container.expand(function () {
var self = Container.call(this);
// Mermi grafiği
var bulletAsset = self.attachAsset('lightBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Hız vektörü
self.speedX = 0;
self.speedY = 0;
// Çarpışma kontrolü için son intersect durumu
self.lastIntersecting = false;
// Mermi boyutu
self.width = bulletAsset.width;
self.height = bulletAsset.height;
// Mermi güncelleme fonksiyonu
self.update = function () {
// Son pozisyonları güncelle
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
// --- Bullet trail effect (effectwhite) ---
if (self._destroyed !== true) {
// Her frame bir iz bırak (isteğe göre seyrekleştirilebilir)
var trail = LK.getAsset('effectwhite', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.25,
scaleY: 0.25,
alpha: 0.18
});
// Trail'i ana sahneye ekle (self.parent varsa oraya ekle, yoksa game'e ekle)
if (self.parent && typeof self.parent.addChild === "function") {
self.parent.addChild(trail);
} else if (typeof game !== "undefined" && typeof game.addChild === "function") {
game.addChild(trail);
}
// Trail'i fade out ile yok et
tween(trail, {
alpha: 0
}, {
duration: 320,
onFinish: function onFinish() {
if (trail && typeof trail.destroy === "function") {
trail.destroy();
}
}
});
}
self.x += self.speedX;
self.y += self.speedY;
self.lastX = self.x;
self.lastY = self.y;
};
// Ekran dışına çıktı mı?
self.isOutOfBounds = function () {
return self.x < -self.width || self.x > 2048 + self.width || self.y < -self.height || self.y > 2732 + self.height;
};
return self;
});
// Gölge karakter (Shadow) sınıfı - Gerçekçi gölge efekti için yumuşak, bulanık elipsler ve dinamik hareket
var Shadow = Container.expand(function () {
var self = Container.call(this);
// Gölge için 3 büyük eliptik, yarı saydam, üst üste bindirilmiş layer
var ellipseLayers = [];
var ellipseParams = [
// scaleX, scaleY, alpha, blur
{
scaleX: 1.0,
scaleY: 0.7,
alpha: 0.22
}, {
scaleX: 0.8,
scaleY: 0.5,
alpha: 0.16
}, {
scaleX: 0.6,
scaleY: 0.35,
alpha: 0.10
}];
var baseSizeW = 160;
var baseSizeH = 220;
var baseColor = 0x222222;
for (var i = 0; i < ellipseParams.length; i++) {
var p = ellipseParams[i];
var ell = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: p.scaleX,
scaleY: p.scaleY,
alpha: p.alpha
});
ell.x = 0;
ell.y = 0;
self.addChild(ell);
ellipseLayers.push(ell);
}
// Hafif yanal ve dikey dalgalanma için parametreler
var animTick = Math.floor(Math.random() * 1000);
var waveSpeed = 0.018;
var waveRadiusX = 18;
var waveRadiusY = 10;
// Gölgeye yumuşak bir "blur" efekti için üst üste bindirilmiş küçük gölge kutuları (soft edge)
var softEdgeBoxes = [];
var softBoxCount = 8;
var softBoxRadius = 70;
for (var i = 0; i < softBoxCount; i++) {
var angle = Math.PI * 2 / softBoxCount * i;
var box = LK.getAsset('shadowbox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.45 + Math.random() * 0.12,
scaleY: 0.45 + Math.random() * 0.12,
alpha: 0.08 + Math.random() * 0.06
});
// Başlangıçta merkezde
box.x = Math.cos(angle) * softBoxRadius;
box.y = Math.sin(angle) * softBoxRadius * 0.7;
self.addChild(box);
softEdgeBoxes.push({
box: box,
baseAngle: angle
});
}
// Gözler: shadowredeye assetini ekle (en üstte)
var eyeAsset = LK.getAsset('shadowredeye', {
anchorX: 0.5,
anchorY: 0.5
});
eyeAsset.x = 0;
eyeAsset.y = -baseSizeH * 0.18;
self.addChild(eyeAsset);
// Yarıçapı döndür (kenar çarpışma için)
self.getRadius = function () {
// En büyük elipsin genişliğinin yarısı
return baseSizeW * 0.5;
};
// --- Realistic shadow trail effect ---
self._shadowTrailTick = 0;
self._shadowTrailInterval = 2 + Math.floor(Math.random() * 2); // every 2-3 frames
self._lastTrailX = undefined;
self._lastTrailY = undefined;
self.update = function () {
animTick++;
// Elips katmanlarını hafifçe dalgalandır
for (var i = 0; i < ellipseLayers.length; i++) {
var ell = ellipseLayers[i];
var phase = animTick * waveSpeed + i * 0.7;
ell.x = Math.cos(phase) * (waveRadiusX * (1 - i * 0.3));
ell.y = Math.sin(phase * 0.8) * (waveRadiusY * (1 - i * 0.3));
// Hafifçe scale animasyonu
var scalePulse = 1 + Math.sin(phase * 1.2) * 0.04;
ell.scaleX = ellipseParams[i].scaleX * scalePulse;
ell.scaleY = ellipseParams[i].scaleY * scalePulse;
// Hafif alpha dalgalanması
ell.alpha = ellipseParams[i].alpha + Math.abs(Math.sin(phase * 0.7)) * 0.04;
}
// Soft edge kutuları yumuşakça döndür ve titreştir
for (var i = 0; i < softEdgeBoxes.length; i++) {
var obj = softEdgeBoxes[i];
var box = obj.box;
var baseAngle = obj.baseAngle;
var phase = animTick * 0.012 + baseAngle * 1.2;
var r = softBoxRadius + Math.sin(phase * 1.1 + i) * 8;
box.x = Math.cos(baseAngle + Math.sin(phase) * 0.18) * r;
box.y = Math.sin(baseAngle + Math.cos(phase) * 0.18) * r * 0.7;
// Hafifçe scale ve alpha animasyonu
var scalePulse = 1 + Math.sin(phase * 1.5) * 0.09;
box.scaleX = (0.45 + i * 0.01) * scalePulse;
box.scaleY = (0.45 + i * 0.01) * scalePulse;
box.alpha = 0.08 + Math.abs(Math.sin(phase * 0.7 + i)) * 0.07;
}
// Gözler kutunun merkezine sabitlenir
eyeAsset.x = 0;
eyeAsset.y = -baseSizeH * 0.18;
// --- Realistic shadow trail effect ---
self._shadowTrailTick++;
// Only create trail if moved enough (avoid stacking on same spot)
var minTrailDist = 12;
if (self._lastTrailX === undefined) {
self._lastTrailX = self.x;
}
if (self._lastTrailY === undefined) {
self._lastTrailY = self.y;
}
var dx = self.x - self._lastTrailX;
var dy = self.y - self._lastTrailY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (self._shadowTrailTick % self._shadowTrailInterval === 0 && dist > minTrailDist * 0.5) {
// Trail asset: use shadow asset, blurred and faded
var tScale = 0.85 + Math.random() * 0.18;
var tAlpha = 0.13 + Math.random() * 0.07;
var tSkew = (Math.random() - 0.5) * 0.18;
var tY = self.y + (Math.random() - 0.5) * 12;
var tX = self.x + (Math.random() - 0.5) * 12;
var trail = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
x: tX,
y: tY,
scaleX: tScale * (1 + tSkew),
scaleY: tScale * (0.7 + tSkew * 0.5),
alpha: tAlpha
});
// Trail should be behind the shadow, so add to parent if possible
if (self.parent && typeof self.parent.addChild === "function") {
self.parent.addChild(trail);
// Move trail behind shadow
if (typeof self.parent.children !== "undefined" && self.parent.children.length > 1) {
// Move trail just before shadow in display list
var idx = self.parent.children.indexOf(self);
if (idx > 0) {
// Remove and insert at idx-1
self.parent.children.splice(self.parent.children.length - 1, 1); // remove last (trail)
self.parent.children.splice(idx - 1, 0, trail);
}
}
} else if (typeof game !== "undefined" && typeof game.addChild === "function") {
game.addChild(trail);
}
// Fade out and destroy
tween(trail, {
alpha: 0
}, {
duration: 420 + Math.random() * 180,
onFinish: function onFinish() {
if (trail && typeof trail.destroy === "function") {
trail.destroy();
}
}
});
self._lastTrailX = self.x;
self._lastTrailY = self.y;
}
};
return self;
});
// ShadowSkill: Expanding shadow skill effect (destroys bullets on contact)
var ShadowSkill = Container.expand(function () {
var self = Container.call(this);
// Main shadow effect (ellipse)
var effect = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.7,
alpha: 0.22
});
self.addChild(effect);
// Initial radius and max radius
self.radius = 180;
self.maxRadius = 900;
self.duration = 700; // ms
self._startTime = Date.now();
self._destroyed = false;
// Set initial scale
effect.scaleX = self.radius / 80;
effect.scaleY = self.radius * 0.7 / 110;
// Center on shadow
self.x = shadow ? shadow.x : GAME_W / 2;
self.y = shadow ? shadow.y : GAME_H * 0.8;
// Animate expansion
tween(effect, {
scaleX: self.maxRadius / 80,
scaleY: self.maxRadius * 0.7 / 110,
alpha: 0.0
}, {
duration: self.duration,
onFinish: function onFinish() {
self._destroyed = true;
if (typeof self.destroy === "function") {
self.destroy();
}
}
});
// Update: check collision with bullets
self.update = function () {
if (self._destroyed) {
return;
}
// Do NOT follow shadow position; keep at activation position
// Current radius (approximate from scale)
var currRadius = effect.scaleX * 80;
// Destroy bullets that touch the effect
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
if (b._destroyed) {
continue;
}
var dx = b.x - self.x;
var dy = b.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < currRadius + (b.width ? b.width * 0.5 : 30)) {
b.destroy();
bullets.splice(i, 1);
}
}
// Destroy chasing bullets that touch the effect
for (var i = chasingBullets.length - 1; i >= 0; i--) {
var cb = chasingBullets[i];
if (cb._destroyed) {
continue;
}
var dx = cb.x - self.x;
var dy = cb.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < currRadius + (cb.width ? cb.width * 0.5 : 30)) {
cb.destroy();
chasingBullets.splice(i, 1);
}
}
};
// Defensive destroy
var _destroy = self.destroy;
self.destroy = function () {
self._destroyed = true;
_destroy.call(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181a1b
});
/****
* Game Code
****/
// Oyun alanı boyutları
var GAME_W = 2048;
var GAME_H = 2732;
// Storage eklentisini başlat
// Menü ekranı için değişkenler
var menuContainer = null;
var menuButtons = [];
var menuActive = true;
var currentMode = null; // "chapter" veya "endless"
// Oyun içi değişkenler
var shadow = null;
var bullets = [];
var chasingBullets = [];
var score = 0;
var scoreTxt = null;
var dragNode = null;
var isGameActive = false;
var controlPanel = null;
var joystick = null;
var joystickRadius = 180 * 2;
var joystickKnobRadius = 60 * 1.2;
var isPanelActive = false;
var panelStartX = 0;
var panelStartY = 0;
var knobStartX = 0;
var knobStartY = 0;
var bulletInterval = 700;
var bulletTimer = null;
var chasingBulletInterval = 1200;
var chasingBulletTimer = null;
// --- Shadow Skill State ---
var shadowSkillLastUsed = 0;
var shadowSkillCooldown = 30000; // 30 seconds
var shadowSkillActive = false;
var shadowSkillEffect = null;
var shadowSkillTapTimes = [];
var shadowSkillTapWindow = 400; // ms (double tap max interval)
var shadowSkillGuiText = null;
// Menü ekranı oluşturma fonksiyonu
function showMenu() {
menuActive = true;
isGameActive = false;
// Menü zaten varsa kaldır
if (menuContainer) {
menuContainer.destroy();
menuContainer = null;
menuButtons = [];
}
menuContainer = new Container();
// --- Main menu background (fills screen) ---
var bg = LK.getAsset('mainmenubackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_W,
height: GAME_H
});
menuContainer.addChild(bg);
// --- Animated white bar (mainmenuwhite) ---
var whiteBar = LK.getAsset('mainmenuwhite', {
anchorX: 0.5,
anchorY: 0,
x: GAME_W / 2,
y: 0,
width: GAME_W * 0.9,
height: 220
});
menuContainer.addChild(whiteBar);
// Animate whiteBar up and down forever
function animateWhiteBarDown() {
tween(whiteBar, {
y: GAME_H - whiteBar.height
}, {
duration: 2200,
easing: tween.easeInOut,
onFinish: animateWhiteBarUp
});
}
function animateWhiteBarUp() {
tween(whiteBar, {
y: 0
}, {
duration: 2200,
easing: tween.easeInOut,
onFinish: animateWhiteBarDown
});
}
animateWhiteBarDown();
// --- Logo (original scale, moved 400px down) ---
var logo = LK.getAsset('agentshadowlogo', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: 800,
scaleX: 1,
scaleY: 1
});
menuContainer.addChild(logo);
// Butonlar arası dikey boşluk
var btnSpacing = 80;
var btnW = 600;
var btnH = 180;
var startY = 1300; // 900 + 400
// Buton isimleri ve fonksiyonları
var btnDefs = [{
label: "Story Mode",
locked: true,
onClick: function onClick() {
// Show locked message instead of starting the game
if (game._storyLockedText) {
game._storyLockedText.destroy();
game._storyLockedText = null;
}
var lockedText = new Text2("Locked until v0.2", {
size: 80,
fill: "#fff"
});
lockedText.anchor.set(0.5, 0.5);
lockedText.x = GAME_W / 2;
lockedText.y = 1200;
// Animate alpha pulse
function pulseUp() {
tween(lockedText, {
alpha: 1.0
}, {
duration: 500,
onFinish: pulseDown
});
}
function pulseDown() {
tween(lockedText, {
alpha: 0.3
}, {
duration: 500,
onFinish: pulseUp
});
}
lockedText.alpha = 0.3;
pulseUp();
menuContainer.addChild(lockedText);
game._storyLockedText = lockedText;
// Remove after 2.5s
LK.setTimeout(function () {
if (game._storyLockedText) {
game._storyLockedText.destroy();
game._storyLockedText = null;
}
}, 2500);
}
}, {
label: "Endless Run",
onClick: function onClick() {
currentMode = "endless";
menuActive = false;
menuContainer.visible = false;
startEndlessRun();
}
}, {
label: "Reset Story",
onClick: function onClick() {
storage.chapter = 1;
storage.section = 1;
// Geri bildirim için kısa bir animasyon veya renk değişimi yapılabilir
if (menuButtons[2] && menuButtons[2].bg) {
LK.effects.flashObject(menuButtons[2].bg, 0xff0000, 300);
}
}
}];
// Butonları oluştur
for (var i = 0; i < btnDefs.length; i++) {
var btnY = startY + i * (btnH + btnSpacing);
// Buton arka planı
var btnBg = LK.getAsset('blackbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: btnY,
width: btnW,
height: btnH
});
// Buton metni
var btnText = new Text2(btnDefs[i].label, {
size: 80,
fill: "#fff"
});
btnText.anchor.set(0.5, 0.5);
btnText.x = GAME_W / 2;
btnText.y = btnY;
// Buton Container'ı
var btnContainer = new Container();
btnContainer.bg = btnBg;
btnContainer.addChild(btnBg);
btnContainer.addChild(btnText);
// Story Mode artık kilitli değil, lock icon eklenmiyor
// Add lock icon for locked buttons (Story Mode)
if (btnDefs[i].locked) {
var lockIcon = LK.getAsset('lockicon', {
anchorX: 1,
anchorY: 0.5,
x: GAME_W / 2 + btnW / 2 - 40,
y: btnY,
scaleX: 1.1,
scaleY: 1.1
});
btnContainer.addChild(lockIcon);
// Reduce button alpha to indicate locked
btnContainer.alpha = 0.55;
// Still allow interaction to show locked message
btnContainer.interactive = true;
btnContainer.buttonMode = false;
} else {
btnContainer.interactive = true;
btnContainer.buttonMode = true;
}
// Basit dokunma olayı
(function (def, btn) {
btnContainer.down = function (x, y, obj) {
if (!menuActive) {
return;
}
def.onClick();
};
})(btnDefs[i], btnContainer);
menuContainer.addChild(btnContainer);
menuButtons.push(btnContainer);
}
// Menü sahneye ekle
game.addChild(menuContainer);
}
// Menüden chapter modunda oyunu başlat
function startGameFromChapter() {
// Temizle
clearGameObjects();
// Son kaydedilen chapter ve bölüm bilgisini storage'dan al
var chapter = storage.chapter || 1;
var section = storage.section || 1;
// Karakteri oluştur ve ortala
shadow = new Shadow();
shadow.x = GAME_W / 2;
shadow.y = GAME_H * 0.8;
game.addChild(shadow);
// Skor (chapter modunda skor kullanılmaz, sadece kalan mermi sayısı gösterilir)
score = 0;
if (scoreTxt) {
scoreTxt.destroy();
}
// Chapter 1 bullet sayacı (skor yerine) -- bu skor metni yeni sistemde de güncelleniyor
var chapter1BulletCount = 50;
scoreTxt = new Text2(chapter1BulletCount + "", {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false; // Geri sayım bitene kadar görünmez
LK.gui.top.addChild(scoreTxt);
isGameActive = false; // Oyun başlatılmadan önce false
// --- YENİ CHAPTER 1: 5 aşamalı sistem ile başlat ---
// Bölüm ilerlemesine göre başlat
if (chapter === 1 && section === 2) {
startChapter1_2Bullets();
} else {
// Varsayılan: chapter 1 bölüm 1'den başlat
startChapter1Bullets();
}
}
// Diyalog ekranı fonksiyonu (geri getirildi)
function showDialogue(text, onClose) {
// Önce eski diyalog varsa kaldır
if (game._dialogueContainer) {
game._dialogueContainer.destroy();
game._dialogueContainer = null;
}
var dialogueContainer = new Container();
// Arka plan
var bg = LK.getAsset('dialoguebackground', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: GAME_H / 2,
width: 1500,
height: 750
});
dialogueContainer.addChild(bg);
// Sol üstte ??? etiketi
var whoTxt = new Text2("???", {
size: 110,
fill: "#222"
});
whoTxt.anchor.set(0, 0);
// 50 piksel sağa kaydır
whoTxt.x = Math.max((GAME_W - 1500) / 2 + 60 + 50, 40);
whoTxt.y = Math.max((GAME_H - 750) / 2 + 40, 40);
dialogueContainer.addChild(whoTxt);
// Sağda walkie talkie (daha büyük ve 300px sağa kaydır)
var walkie = LK.getAsset('walkietalkie', {
anchorX: 1,
anchorY: 1,
x: (GAME_W + 1500) / 2 - 60 + 300,
y: (GAME_H + 750) / 2 - 60,
scaleX: 1.15,
scaleY: 1.15
});
dialogueContainer.addChild(walkie);
// Kullanıcı manuel olarak satırları ayırabilsin: Eğer text bir dizi ise her eleman bir satır olarak alınır.
// Eğer string ise, tek satır olarak gösterilir.
var lines = [];
var dialogueFontSize = 54;
if (typeof text === "string") {
lines = [text];
} else if (Array.isArray(text)) {
lines = text.slice(0);
}
var textNodes = [];
// Diyalog kutusunun üst kısmından biraz aşağıda başlasın, satır aralığı daha rahat
var baseY = GAME_H / 2 - 750 / 2 + 170;
var lineSpacing = 70;
// Tüm satırları aynı anda göster, her biri kendi satırında
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var txt = new Text2(line, {
size: dialogueFontSize,
fill: "#222"
});
// Sola yaslı: anchorX = 0, anchorY = 0
txt.anchor.set(0, 0);
// Diyalog kutusunun sol kenarına hizala (arka planın solundan biraz içeride başlasın)
txt.x = (GAME_W - 1500) / 2 + 80;
txt.y = baseY + i * lineSpacing;
dialogueContainer.addChild(txt);
textNodes.push(txt);
}
// Altta click to continue...
var ctc = new Text2("click to continue...", {
size: 70,
fill: "#222"
});
ctc.anchor.set(0.5, 1);
ctc.x = GAME_W / 2;
ctc.y = (GAME_H + 750) / 2 - 60;
ctc.alpha = 0.3;
dialogueContainer.addChild(ctc);
// Alpha animasyonu (0.3 <-> 1.0)
function pulseUp() {
tween(ctc, {
alpha: 1.0
}, {
duration: 600,
onFinish: pulseDown
});
}
function pulseDown() {
tween(ctc, {
alpha: 0.3
}, {
duration: 600,
onFinish: pulseUp
});
}
pulseUp();
// Kapatma için dokunma
dialogueContainer.interactive = true;
dialogueContainer.down = function (x, y, obj) {
if (game._dialogueContainer) {
game._dialogueContainer.destroy();
game._dialogueContainer = null;
}
if (typeof onClose === "function") {
onClose();
}
};
game.addChild(dialogueContainer);
game._dialogueContainer = dialogueContainer;
}
// Chapter 1'in ilk aşamasını başlatan fonksiyon (50 mermilik, her 5 mermide bir hızlanan bölüm)
function startChapter1Bullets() {
// Diyalog ekranı göster
showDialogue(["Agent Shadow, survive the incoming", "barrage. Move to dodge the light bullets."], function () {
// Diyalog kapandıktan sonra üstte başlık ve geri sayım göster
var chapterTitleContainer = new Container();
// Başlık yazısı
var titleText = new Text2("Chapter 1: Meet With ???", {
size: 110,
fill: "#fff"
});
titleText.anchor.set(0.5, 0);
titleText.x = GAME_W / 2;
titleText.y = 120;
chapterTitleContainer.addChild(titleText);
// Geri sayım yazısı
var countdownText = new Text2("3", {
size: 180,
fill: "#fff"
});
countdownText.anchor.set(0.5, 0);
countdownText.x = GAME_W / 2;
countdownText.y = titleText.y + titleText.height + 40;
chapterTitleContainer.addChild(countdownText);
// Ekrana ekle
game.addChild(chapterTitleContainer);
// Geri sayım başlat
var countdownVals = ["3", "2", "1"];
var countdownIdx = 0;
function doCountdown() {
if (countdownIdx < countdownVals.length) {
countdownText.setText(countdownVals[countdownIdx]);
countdownIdx++;
LK.setTimeout(doCountdown, 900);
} else {
// Mermi spawn fonksiyonu
var _fireNextBullet = function fireNextBullet() {
if (!isGameActive) {
// Oyun biterse timerı temizle
if (chapter1BulletTimer) {
LK.clearTimeout(chapter1BulletTimer);
chapter1BulletTimer = null;
}
return;
}
// SADECE YUKARIDAN DİKİNE GELEN MERMİ OLUŞTUR (OYUNCUYA DOĞRU DEĞİL, DİKİNE)
// chapter 1-1'de sadece üstten aşağıya, sidespawn yok!
var bullet = new LightBullet();
// Reduce bullet size by 50% for chapter 1-1
if (bullet && bullet.children && bullet.children.length > 0) {
var bulletAsset = bullet.children[0];
bulletAsset.scaleX = 0.5;
bulletAsset.scaleY = 0.5;
// Update width/height for collision/out-of-bounds logic
bullet.width = bulletAsset.width * 0.5;
bullet.height = bulletAsset.height * 0.5;
}
// Sadece üstten rastgele X'e spawnla
var startX = Math.random() * GAME_W;
var startY = -bullet.height;
// Hız (her 5 mermide bir hızlanacak şekilde ayarlanacak)
var baseSpeed = 13;
var speedStep = 2.2;
var speed = baseSpeed + Math.floor(bulletsFired / 5) * speedStep + Math.random() * 2.5;
bullet.x = startX;
bullet.y = startY;
bullet.speedX = 0;
bullet.speedY = speed;
// Merminin ucunu aşağıya bakacak şekilde döndür (PI radian = aşağı)
bullet.rotation = Math.PI;
bullets.push(bullet);
game.addChild(bullet);
bulletsFired++;
// Skor göstergesini güncelle (kalan mermi)
if (scoreTxt) {
scoreTxt.setText(totalBullets - bulletsFired + "");
}
// 50 mermiye ulaşıldıysa bitir
if (bulletsFired >= totalBullets) {
// Son mermi atıldıktan sonra kısa bir süre bekle, sonra outro diyaloğu göster
LK.setTimeout(function () {
isGameActive = false;
// CHAPTER 1-1 OUTRO DİYALOĞU
// --- Bölüm geçildi, ilerlemeyi kaydet ---
storage.chapter = 1;
storage.section = 2;
showDialogue(["You’re alive… For now. But the", "enemy will hit harder. Prepare."], function () {
// CHAPTER 1-2 INTRO DİYALOĞU
showDialogue("The enemy’s tracking you, Shadow. Trick", "them with your Shadow Clone. Move.", function () {
// CHAPTER 1-2 BAŞLAT
startChapter1_2Bullets();
});
});
}, 900);
return;
}
// Her 5 mermide bir hızlan
if (bulletsFired % 5 === 0 && currentInterval > minInterval) {
currentInterval = Math.max(minInterval, currentInterval - intervalStep);
}
// Sonraki mermiyi zamanla
chapter1BulletTimer = LK.setTimeout(_fireNextBullet, currentInterval);
}; // İlk mermiyi başlat
// Geri sayım bitti, başlığı kaldır ve oyunu başlat
chapterTitleContainer.destroy();
// Geri sayım bittiğinde kalan mermi sayısını göster
if (scoreTxt) {
scoreTxt.visible = true;
}
// 50 mermilik bölüm başlasın
var totalBullets = 50;
var bulletsFired = 0;
var baseInterval = 900; // ilk mermi aralığı (ms)
var minInterval = 250; // minimum aralık
var intervalStep = 90; // her 5 mermide bir azaltılacak miktar
var currentInterval = baseInterval;
var chapter1BulletTimer = null;
// Oyun aktif
isGameActive = true;
menuActive = false;
// Skor göstergesi: kalan mermi sayısı
if (scoreTxt) {
scoreTxt.setText(totalBullets + "");
}
_fireNextBullet();
}
}
doCountdown();
});
}
// Chapter 1-2 bullet pattern and logic
function startChapter1_2Bullets() {
// Skor metni sıfırla ve göster
if (scoreTxt) {
scoreTxt.setText("50");
scoreTxt.visible = true;
}
// Tüm eski mermileri temizle
for (var i = 0; i < bullets.length; i++) {
if (bullets[i]) {
bullets[i].destroy();
}
}
bullets = [];
// Ayarlar
var totalBullets = 50;
var bulletsFired = 0;
var interval = 1000; // 1 saniyede bir
var chapter1_2BulletTimer = null;
isGameActive = true;
menuActive = false;
// Mermi spawn fonksiyonu
function fireNextBullet_1_2() {
if (!isGameActive) {
if (chapter1_2BulletTimer) {
LK.clearTimeout(chapter1_2BulletTimer);
chapter1_2BulletTimer = null;
}
return;
}
// Rastgele sağdan veya soldan gelsin
var fromLeft = Math.random() < 0.5;
var bullet = new LightBullet();
var startY = shadow ? shadow.y : GAME_H * 0.5;
// Clamp startY to visible area
if (startY < bullet.height / 2) {
startY = bullet.height / 2;
}
if (startY > GAME_H - bullet.height / 2) {
startY = GAME_H - bullet.height / 2;
}
var startX = fromLeft ? -bullet.width : GAME_W + bullet.width;
bullet.x = startX;
bullet.y = startY;
// Hedef oyuncunun o anki konumu
var targetX = shadow ? shadow.x : GAME_W / 2;
var targetY = shadow ? shadow.y : GAME_H * 0.8;
var dx = targetX - startX;
var dy = targetY - startY;
var angle = Math.atan2(dy, dx);
var speed = 15 + Math.random() * 3;
bullet.speedX = Math.cos(angle) * speed;
bullet.speedY = Math.sin(angle) * speed;
// Merminin ucunu oyuncuya bakacak şekilde döndür
bullet.rotation = angle + Math.PI / 2;
bullets.push(bullet);
game.addChild(bullet);
bulletsFired++;
// Skor göstergesini güncelle (kalan mermi)
if (scoreTxt) {
scoreTxt.setText(totalBullets - bulletsFired + "");
}
// 50 mermiye ulaşıldıysa bitir
if (bulletsFired >= totalBullets) {
LK.setTimeout(function () {
isGameActive = false;
// CHAPTER 1-2 OUTRO DİYALOĞU
// --- Bölüm geçildi, ilerlemeyi kaydet ---
storage.chapter = 2;
storage.section = 1;
showDialogue("Your clone worked. But these enemies don’t quit. More are coming.", function () {
// Sonraki bölüme geçiş veya menüye dönüş burada yapılabilir
LK.showYouWin();
});
}, 900);
return;
}
// Sonraki mermiyi zamanla
chapter1_2BulletTimer = LK.setTimeout(fireNextBullet_1_2, interval);
}
// İlk mermiyi başlat
fireNextBullet_1_2();
}
// --- Endless Run Bullet Modes ---
// Global endless run bullet speed multiplier (starts slow, increases with score)
var endlessBulletSpeedMultiplier = 1.0;
// Helper to update endlessBulletSpeedMultiplier based on score
function updateEndlessBulletSpeedMultiplier() {
// At score 0: 1.0 (slowest), at score 30: 1.03, at score 36: 1.036, etc.
// 3% per 30, 3.6% per 36, 10% per 100, etc.
if (typeof score === "number" && score >= 0) {
endlessBulletSpeedMultiplier = 1.0 + score * 0.001;
} else {
endlessBulletSpeedMultiplier = 1.0;
}
}
var ENDLESS_BULLET_MODES = [{
name: "Vertical Bullets",
desc: "Bullets from Top",
spawn: function spawn() {
updateEndlessBulletSpeedMultiplier();
var bullet = new LightBullet();
var startX = Math.random() * GAME_W;
var startY = -bullet.height;
var baseSpeed = 7.5; // much slower than before
var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier;
bullet.x = startX;
bullet.y = startY;
bullet.speedX = 0;
bullet.speedY = speed;
bullet.rotation = Math.PI;
bullets.push(bullet);
game.addChild(bullet);
}
}, {
name: "Side Bullets",
desc: "Bullets from Sides",
spawn: function spawn() {
updateEndlessBulletSpeedMultiplier();
var fromLeft = Math.random() < 0.5;
var bullet = new LightBullet();
var startY = Math.random() * GAME_H;
var startX = fromLeft ? -bullet.width : GAME_W + bullet.width;
var baseSpeed = 7.5;
var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier;
bullet.x = startX;
bullet.y = startY;
bullet.speedX = fromLeft ? speed : -speed;
bullet.speedY = 0;
bullet.rotation = fromLeft ? Math.PI / 2 : -Math.PI / 2;
bullets.push(bullet);
game.addChild(bullet);
}
}, {
name: "Chasing Bullets",
desc: "Chasing Bullets",
spawn: function spawn() {
updateEndlessBulletSpeedMultiplier();
var edge = Math.floor(Math.random() * 4);
var bullet = new ChasingLightBullet();
var startX, startY;
if (edge === 0) {
startX = Math.random() * GAME_W;
startY = -bullet.height;
} else if (edge === 1) {
startX = GAME_W + bullet.width;
startY = Math.random() * GAME_H;
} else if (edge === 2) {
startX = Math.random() * GAME_W;
startY = GAME_H + bullet.height;
} else {
startX = -bullet.width;
startY = Math.random() * GAME_H;
}
bullet.x = startX;
bullet.y = startY;
// Set chasing bullet speed to scale with endlessBulletSpeedMultiplier
// Lower base speed for chasing bullets in endless mode for fairness
var baseChaseSpeed = 4.5;
bullet.speed = (baseChaseSpeed + Math.random() * 1.8) * endlessBulletSpeedMultiplier;
chasingBullets.push(bullet);
game.addChild(bullet);
}
}, {
name: "Targeted Bullets",
desc: "Bullets to Player",
spawn: function spawn() {
updateEndlessBulletSpeedMultiplier();
if (!shadow) {
return;
}
var bullet = new LightBullet();
var edge = Math.floor(Math.random() * 4);
var startX, startY;
if (edge === 0) {
startX = Math.random() * GAME_W;
startY = -bullet.height;
} else if (edge === 1) {
startX = GAME_W + bullet.width;
startY = Math.random() * GAME_H;
} else if (edge === 2) {
startX = Math.random() * GAME_W;
startY = GAME_H + bullet.height;
} else {
startX = -bullet.width;
startY = Math.random() * GAME_H;
}
var dx = shadow.x - startX;
var dy = shadow.y - startY;
var angle = Math.atan2(dy, dx);
var baseSpeed = 7.5;
var speed = (baseSpeed + Math.random() * 2.5) * endlessBulletSpeedMultiplier;
bullet.x = startX;
bullet.y = startY;
bullet.speedX = Math.cos(angle) * speed;
bullet.speedY = Math.sin(angle) * speed;
bullet.rotation = angle + Math.PI / 2;
bullets.push(bullet);
game.addChild(bullet);
}
}];
// --- Endless Run Mode State ---
var endlessCurrentModeIndex = 0;
var endlessCurrentModeScore = 0;
var endlessModeNameText = null;
function pickRandomEndlessMode(excludeIndex) {
var idx;
do {
idx = Math.floor(Math.random() * ENDLESS_BULLET_MODES.length);
} while (ENDLESS_BULLET_MODES.length > 1 && idx === excludeIndex);
return idx;
}
// Endless Run başlat
function startEndlessRun() {
clearGameObjects();
// --- Endless Run Mod State ---
window._endlessRunState = {
bulletSpeedBase: 13,
bulletSpeedStep: 0,
bulletSizeScale: 1,
chasingBulletSpeedBase: 10,
chasingBulletSpeedStep: 0,
chasingBulletActive: true
};
// Reset endless run bullet speed multiplier at the start
endlessBulletSpeedMultiplier = 1.0;
// --- Shadow Skill: Reset state ---
shadowSkillLastUsed = 0;
shadowSkillActive = false;
shadowSkillTapTimes = [];
if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") {
shadowSkillEffect.destroy();
shadowSkillEffect = null;
}
if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") {
shadowSkillGuiText.destroy();
shadowSkillGuiText = null;
}
// Remove old shadow skill asset and bar if any
if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset && typeof shadowSkillGuiAsset.destroy === "function") {
shadowSkillGuiAsset.destroy();
shadowSkillGuiAsset = null;
}
if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillBar.destroy === "function") {
shadowSkillBar.destroy();
shadowSkillBar = null;
}
// Set cooldown to 15 seconds
shadowSkillCooldown = 15000;
// Add shadow skill asset to bottom left (avoid 100x100 px top left menu area)
shadowSkillGuiAsset = LK.getAsset('shadowskill', {
anchorX: 0,
anchorY: 1,
x: 60,
y: GAME_H - 60,
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
});
game.addChild(shadowSkillGuiAsset);
// Add loading bar (as a box) above the asset, but initially invisible
shadowSkillBar = LK.getAsset('shadowbox', {
anchorX: 0.5,
anchorY: 1,
x: shadowSkillGuiAsset.x + shadowSkillGuiAsset.width * 0.75,
y: shadowSkillGuiAsset.y - shadowSkillGuiAsset.height * 1.1,
width: 180,
height: 32,
alpha: 0,
scaleX: 1,
scaleY: 1
});
game.addChild(shadowSkillBar);
// Store for update
shadowSkillBarBaseWidth = shadowSkillBar.width;
// Karakteri oluştur ve ortala
shadow = new Shadow();
shadow.x = GAME_W / 2;
shadow.y = GAME_H * 0.8;
game.addChild(shadow);
// Skor (endless modda skor gösterilir)
score = 0;
if (scoreTxt) {
scoreTxt.destroy();
}
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
isGameActive = false;
menuActive = false;
// --- Shadow Skill: Show cooldown text (endless only) ---
if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") {
shadowSkillGuiText.destroy();
shadowSkillGuiText = null;
}
shadowSkillGuiText = new Text2("Skill Ready", {
size: 60,
fill: 0xAAFFFF
});
shadowSkillGuiText.anchor.set(0.5, 0);
shadowSkillGuiText.x = GAME_W / 2;
shadowSkillGuiText.y = 120 + 60;
LK.gui.top.addChild(shadowSkillGuiText);
// --- Endless Run: Show unlock info if any ---
if (game._endlessUnlockText) {
game._endlessUnlockText.destroy();
game._endlessUnlockText = null;
}
// --- Endless Run: 3-2-1 geri sayım ekle ---
if (game._endlessCountdownContainer) {
game._endlessCountdownContainer.destroy();
game._endlessCountdownContainer = null;
}
var countdownContainer = new Container();
var countdownText = new Text2("3", {
size: 220,
fill: "#fff"
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = GAME_W / 2;
countdownText.y = GAME_H / 2;
countdownContainer.addChild(countdownText);
game.addChild(countdownContainer);
game._endlessCountdownContainer = countdownContainer;
var countdownVals = ["3", "2", "1"];
var countdownIdx = 0;
function doEndlessCountdown() {
if (countdownIdx < countdownVals.length) {
countdownText.setText(countdownVals[countdownIdx]);
countdownIdx++;
LK.setTimeout(doEndlessCountdown, 900);
} else {
// Geri sayım bittiğinde container'ı kaldır ve oyunu başlat
if (game._endlessCountdownContainer) {
game._endlessCountdownContainer.destroy();
game._endlessCountdownContainer = null;
}
// Rastgele bir mod seç
endlessCurrentModeIndex = pickRandomEndlessMode(-1);
endlessCurrentModeScore = 0;
// Mod adını göster
if (endlessModeNameText) {
endlessModeNameText.destroy();
endlessModeNameText = null;
}
endlessModeNameText = new Text2(ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name, {
size: 140,
fill: "#fff"
});
endlessModeNameText.anchor.set(0.5, 0.5);
endlessModeNameText.x = GAME_W / 2;
endlessModeNameText.y = GAME_H / 2;
game.addChild(endlessModeNameText);
// Hemen başlat, 3 saniye bekleme
isGameActive = true;
startBulletTimer();
startChasingBulletTimer();
// Mod adını 3 saniye sonra kaldır
LK.setTimeout(function () {
if (endlessModeNameText) {
endlessModeNameText.destroy();
endlessModeNameText = null;
}
}, 3000);
}
}
doEndlessCountdown();
}
// Oyun objelerini temizle
function clearGameObjects() {
// Karakteri sil
if (shadow) {
shadow.destroy();
shadow = null;
}
// Mermileri sil
for (var i = 0; i < bullets.length; i++) {
if (bullets[i]) {
bullets[i].destroy();
}
}
bullets = [];
for (var i = 0; i < chasingBullets.length; i++) {
if (chasingBullets[i]) {
chasingBullets[i].destroy();
}
}
chasingBullets = [];
// Skor metni sil
if (scoreTxt) {
scoreTxt.destroy();
scoreTxt = null;
}
// Joystick ve paneli sil
if (controlPanel) {
controlPanel.destroy();
controlPanel = null;
}
joystick = null;
isPanelActive = false;
dragNode = null;
joystickDirX = 0;
joystickDirY = 0;
joystickActive = false;
// --- Shadow Skill: Cleanup effect, text, asset, and bar ---
if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") {
shadowSkillEffect.destroy();
shadowSkillEffect = null;
}
if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") {
shadowSkillGuiText.destroy();
shadowSkillGuiText = null;
}
if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset && typeof shadowSkillGuiAsset.destroy === "function") {
shadowSkillGuiAsset.destroy();
shadowSkillGuiAsset = null;
}
if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillBar.destroy === "function") {
shadowSkillBar.destroy();
shadowSkillBar = null;
}
shadowSkillActive = false;
shadowSkillTapTimes = [];
}
// Oyun ilk açıldığında menüyü göster
showMenu();
// Mermi oluşturma fonksiyonu
function spawnBullet() {
// Only apply endless run balancing if in endless mode
var isEndless = currentMode === "endless";
if (isEndless) {
// Use endless run bullet mode system
if (typeof endlessCurrentModeIndex !== "number" || endlessCurrentModeIndex < 0) {
endlessCurrentModeIndex = 0;
}
// Only spawn the current mode's bullet type for 25 bullets, never mixing
ENDLESS_BULLET_MODES[endlessCurrentModeIndex].spawn();
// Skor artır
updateScore(score + 1);
endlessCurrentModeScore++;
// Change mode only after 25 bullets of the same type
if (endlessCurrentModeScore >= 25) {
endlessCurrentModeScore = 0;
var prevIndex = endlessCurrentModeIndex;
endlessCurrentModeIndex = pickRandomEndlessMode(prevIndex);
// Show mode name
if (endlessModeNameText) {
endlessModeNameText.destroy();
endlessModeNameText = null;
}
endlessModeNameText = new Text2(ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name, {
size: 140,
fill: "#fff"
});
endlessModeNameText.anchor.set(0.5, 0.5);
endlessModeNameText.x = GAME_W / 2;
endlessModeNameText.y = GAME_H / 2;
game.addChild(endlessModeNameText);
LK.setTimeout(function () {
if (endlessModeNameText) {
endlessModeNameText.destroy();
endlessModeNameText = null;
}
}, 3000);
}
} else {
// Orijinal mantık (chapter mode)
var bullet = new LightBullet();
var angle, startX, startY, targetX, targetY, speed;
// Kenara göre başlangıç pozisyonu ve yönü belirle
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
startX = Math.random() * GAME_W;
startY = -bullet.height;
} else if (edge === 1) {
startX = GAME_W + bullet.width;
startY = Math.random() * GAME_H;
} else if (edge === 2) {
startX = Math.random() * GAME_W;
startY = GAME_H + bullet.height;
} else {
startX = -bullet.width;
startY = Math.random() * GAME_H;
}
// Hedef karakterin anlık pozisyonu
targetX = shadow.x;
targetY = shadow.y;
var dx = targetX - startX;
var dy = targetY - startY;
angle = Math.atan2(dy, dx);
// Hız (her mermi için rastgele küçük bir varyasyon)
speed = 13 + Math.random() * 5;
bullet.x = startX;
bullet.y = startY;
bullet.speedX = Math.cos(angle) * speed;
bullet.speedY = Math.sin(angle) * speed;
// Merminin ucunu oyuncuya bakacak şekilde döndür
bullet.rotation = angle + Math.PI / 2;
bullets.push(bullet);
game.addChild(bullet);
}
}
// Takipçi mermi oluşturma fonksiyonu
function spawnChasingBullet() {
var isEndless = currentMode === "endless";
// Only allow chasing bullets in endless mode if current mode is "Chasing Bullets"
if (isEndless && typeof endlessCurrentModeIndex === "number" && ENDLESS_BULLET_MODES[endlessCurrentModeIndex]) {
if (ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name !== "Chasing Bullets") {
return;
}
}
// --- Endless Run: Chasing bullets always enabled, no unlocks ---
// Kenardan rastgele bir yerden başlat
var edge = Math.floor(Math.random() * 4);
var bullet = new ChasingLightBullet();
var startX, startY;
if (edge === 0) {
startX = Math.random() * GAME_W;
startY = -bullet.height;
} else if (edge === 1) {
startX = GAME_W + bullet.width;
startY = Math.random() * GAME_H;
} else if (edge === 2) {
startX = Math.random() * GAME_W;
startY = GAME_H + bullet.height;
} else {
startX = -bullet.width;
startY = Math.random() * GAME_H;
}
bullet.x = startX;
bullet.y = startY;
// --- Endless Run: Dynamic chasing bullet speed/size ---
if (isEndless) {
updateEndlessBulletSpeedMultiplier();
// Lower base speed for chasing bullets in endless mode for fairness
var baseChaseSpeed = 4.5;
bullet.speed = (baseChaseSpeed + Math.random() * 1.8) * endlessBulletSpeedMultiplier;
}
// Başlangıçta oyuncuya bakacak şekilde döndür
var dx = shadow.x - bullet.x;
var dy = shadow.y - bullet.y;
bullet.rotation = Math.atan2(dy, dx) + Math.PI / 2;
chasingBullets.push(bullet);
game.addChild(bullet);
// Endless run modunda skor mermi sayısı olarak güncellensin
if (currentMode === "endless") {
updateScore(score + 1);
}
}
// Mermi oluşturma zamanlayıcısı başlat
function startBulletTimer() {
if (bulletTimer) {
LK.clearInterval(bulletTimer);
}
bulletTimer = LK.setInterval(function () {
if (isGameActive) {
spawnBullet();
}
}, bulletInterval);
}
// startBulletTimer(); // Chapter 1'de otomatik mermi spawn edilmesin
// Takipçi mermi zamanlayıcısı başlat
function startChasingBulletTimer() {
if (chasingBulletTimer) {
LK.clearInterval(chasingBulletTimer);
}
// Chasing bullets spawn at most one per second in endless run mode
chasingBulletTimer = LK.setInterval(function () {
// Only spawn chasing bullets if current endless mode is 'Chasing Bullets'
var isEndless = currentMode === "endless";
var chasingModeActive = false;
if (isEndless && typeof endlessCurrentModeIndex === "number" && ENDLESS_BULLET_MODES[endlessCurrentModeIndex]) {
chasingModeActive = ENDLESS_BULLET_MODES[endlessCurrentModeIndex].name === "Chasing Bullets";
}
if (isGameActive && score >= 10 && chasingModeActive) {
// Limit chasing bullets on screen to 2 at a time for fairness
if (chasingBullets.length < 2) {
spawnChasingBullet();
}
}
}, 1000); // 1000ms = 1 second
}
// startChasingBulletTimer(); // Chapter 1'de otomatik takipçi mermi spawn edilmesin
// Skoru güncelle
function updateScore(val) {
// Sadece endless modda skor güncellensin
if (currentMode === "endless") {
score = val;
if (scoreTxt) {
scoreTxt.setText(score);
}
}
}
// Oyun bittiğinde
function gameOver() {
isGameActive = false;
// Ekranı kırmızıya flashla
LK.effects.flashScreen(0xff2222, 800);
// Chapter modunda ise ilerlemeyi kaydet
if (currentMode === "chapter") {
// Skora göre chapter ilerletme örneği (ileride özelleştirilebilir)
if (score > 0) {
var nextChapter = (storage.chapter || 1) + 1;
storage.chapter = nextChapter;
}
}
// Endless modda high score zaten update'de kaydediliyor
// Oyun bitişini göster
LK.showGameOver();
// Kısa bir gecikmeden sonra menüyü tekrar göster
LK.setTimeout(function () {
showMenu();
}, 1200);
}
// Sürükleme işlemleri
// Joystick yönünü ve mesafesini globalde tut
var joystickDirX = 0;
var joystickDirY = 0;
var joystickActive = false;
function handleMove(x, y, obj) {
if (!isGameActive || menuActive) {
return;
}
if (isPanelActive && controlPanel && joystick) {
// Joystick merkezine göre delta
var dx = x - panelStartX;
var dy = y - panelStartY;
var dist = Math.sqrt(dx * dx + dy * dy);
// Knob'u panel sınırında tut
var maxDist = joystickRadius - joystickKnobRadius;
if (dist > maxDist) {
var angle = Math.atan2(dy, dx);
dx = Math.cos(angle) * maxDist;
dy = Math.sin(angle) * maxDist;
}
joystick.x = dx;
joystick.y = dy;
// Joystick yönünü ve oranını kaydet
if (dist > 10) {
// ölü bölge
joystickDirX = dx / maxDist;
joystickDirY = dy / maxDist;
joystickActive = true;
} else {
joystickDirX = 0;
joystickDirY = 0;
joystickActive = false;
}
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (!isGameActive || menuActive) {
return;
}
// --- Shadow Skill: Double-tap detection (endless only) ---
if (currentMode === "endless") {
var now = Date.now();
// Remove old taps
for (var i = shadowSkillTapTimes.length - 1; i >= 0; i--) {
if (now - shadowSkillTapTimes[i] > shadowSkillTapWindow) {
shadowSkillTapTimes.splice(i, 1);
}
}
shadowSkillTapTimes.push(now);
// If two taps within window, trigger skill
if (shadowSkillTapTimes.length >= 2 && !shadowSkillActive) {
var lastUsedAgo = now - shadowSkillLastUsed;
if (lastUsedAgo >= shadowSkillCooldown) {
// Activate skill
shadowSkillActive = true;
shadowSkillLastUsed = now;
shadowSkillTapTimes = [];
// Remove old effect if any
if (shadowSkillEffect && typeof shadowSkillEffect.destroy === "function") {
shadowSkillEffect.destroy();
shadowSkillEffect = null;
}
// Create and add skill effect
shadowSkillEffect = new ShadowSkill();
game.addChild(shadowSkillEffect);
// Optional: flash effect
LK.effects.flashObject(shadow, 0x00ffff, 300);
}
}
}
// Kontrol paneli zaten varsa kaldır
if (controlPanel) {
controlPanel.destroy();
controlPanel = null;
joystick = null;
isPanelActive = false;
}
// Kontrol panelini oluştur
controlPanel = new Container();
controlPanel.x = x;
controlPanel.y = y;
// Panel arka planı (şeffaf daire)
var panelBg = LK.getAsset('centerCircle', {
width: joystickRadius * 2,
height: joystickRadius * 2,
color: 0x222222,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.25
});
controlPanel.addChild(panelBg);
// Joystick knob'u (küçük daire)
joystick = LK.getAsset('centerCircle', {
width: joystickKnobRadius * 2,
height: joystickKnobRadius * 2,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
joystick.x = 0;
joystick.y = 0;
controlPanel.addChild(joystick);
// Paneli oyuna ekle
game.addChild(controlPanel);
isPanelActive = true;
panelStartX = x;
panelStartY = y;
knobStartX = 0;
knobStartY = 0;
};
game.up = function (x, y, obj) {
if (menuActive) {
return;
}
// Kontrol panelini kaldır
if (controlPanel) {
controlPanel.destroy();
controlPanel = null;
joystick = null;
isPanelActive = false;
}
dragNode = null;
// Joystick bırakıldığında hareketi durdur
joystickDirX = 0;
joystickDirY = 0;
joystickActive = false;
};
// Oyun güncelleme döngüsü
game.update = function () {
if (!isGameActive || menuActive) {
return;
}
// Karakteri joystick yönünde sürekli hareket ettir
if (joystickActive) {
var moveSpeed = 22;
shadow.x += joystickDirX * moveSpeed;
shadow.y += joystickDirY * moveSpeed;
var r = shadow.getRadius();
if (shadow.x < r) {
shadow.x = r;
}
if (shadow.x > GAME_W - r) {
shadow.x = GAME_W - r;
}
if (shadow.y < r) {
shadow.y = r;
}
if (shadow.y > GAME_H - r) {
shadow.y = GAME_H - r;
}
}
// --- Shadow Skill: Update effect, asset alpha, and loading bar in endless run ---
if (currentMode === "endless") {
// Update skill effect
if (shadowSkillEffect && typeof shadowSkillEffect.update === "function") {
shadowSkillEffect.update();
if (shadowSkillEffect._destroyed) {
shadowSkillEffect = null;
shadowSkillActive = false;
}
}
// Update shadow skill asset alpha and loading bar
if (typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset) {
var now = Date.now();
var cdMs = Math.max(0, shadowSkillLastUsed + shadowSkillCooldown - now);
if (shadowSkillActive) {
shadowSkillGuiAsset.alpha = 0.4;
} else if (cdMs <= 0) {
shadowSkillGuiAsset.alpha = 1.0;
} else {
shadowSkillGuiAsset.alpha = 0.4;
}
}
// Update loading bar position and visibility above the skill asset
if (typeof shadowSkillBar !== "undefined" && shadowSkillBar && typeof shadowSkillGuiAsset !== "undefined" && shadowSkillGuiAsset) {
var now = Date.now();
var cdMs = Math.max(0, shadowSkillLastUsed + shadowSkillCooldown - now);
var fill = 1 - cdMs / shadowSkillCooldown;
if (fill < 0) {
fill = 0;
}
if (fill > 1) {
fill = 1;
}
// Position bar 50px higher and 50px more to the left above the skill asset, left-aligned
var barFullWidth = typeof shadowSkillBarBaseWidth !== "undefined" ? shadowSkillBarBaseWidth : 180;
// Place bar centered horizontally above the skill asset, but 50px higher and 50px more to the left
shadowSkillBar.x = shadowSkillGuiAsset.x + shadowSkillGuiAsset.width * 0.5 - 50;
shadowSkillBar.y = shadowSkillGuiAsset.y - shadowSkillGuiAsset.height * 1.1 - 50;
shadowSkillBar.anchorX = 0; // left align for fill
// Only show bar if skill is recharging
if (cdMs > 0) {
shadowSkillBar.alpha = 0.85;
shadowSkillBar.width = barFullWidth * fill;
} else {
shadowSkillBar.alpha = 0;
shadowSkillBar.width = barFullWidth;
}
}
// Remove cooldown text (no longer needed)
if (shadowSkillGuiText && typeof shadowSkillGuiText.destroy === "function") {
shadowSkillGuiText.destroy();
shadowSkillGuiText = null;
}
}
// Mermileri güncelle
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.update();
if (b.isOutOfBounds()) {
b.destroy();
bullets.splice(i, 1);
continue;
}
var intersecting = b.intersects(shadow);
if (!b.lastIntersecting && intersecting) {
// Oyuncu mermiye çarptıysa, oyunu durdur ve "Yeniden Başla" butonu göster
isGameActive = false;
// Tüm mermileri temizle
for (var k = 0; k < bullets.length; k++) {
if (bullets[k]) {
bullets[k].destroy();
}
}
bullets = [];
for (var k = 0; k < chasingBullets.length; k++) {
if (chasingBullets[k]) {
chasingBullets[k].destroy();
}
}
chasingBullets = [];
// Karakteri sil
if (shadow) {
shadow.destroy();
shadow = null;
}
// Skor metni sil
if (scoreTxt) {
scoreTxt.destroy();
scoreTxt = null;
}
// Joystick ve paneli sil
if (controlPanel) {
controlPanel.destroy();
controlPanel = null;
}
joystick = null;
isPanelActive = false;
dragNode = null;
joystickDirX = 0;
joystickDirY = 0;
joystickActive = false;
// --- YENİDEN BAŞLA VE ANA MENÜ BUTONLARI EKLE ---
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
var retryButtonContainer = new Container();
// Ortalanmış konumlar
var btnW = 600;
var btnH = 180;
var btnSpacing = 80;
var centerY = GAME_H / 2;
var restartY = centerY - btnH / 2 - btnSpacing / 2;
var menuY = centerY + btnH / 2 + btnSpacing / 2;
// --- Endless Run: Skor ve süreyi göster ---
if (currentMode === "endless") {
// Skor
var scoreLabel = new Text2("Score: " + score, {
size: 110,
fill: "#fff"
});
scoreLabel.anchor.set(0.5, 1);
scoreLabel.x = GAME_W / 2;
scoreLabel.y = restartY - 120;
retryButtonContainer.addChild(scoreLabel);
// Hayatta kalma süresi (saniye ve dakika)
// Her 30 tick = 0.5 saniye, 60 tick = 1 saniye (FPS 60)
// Skor her 30 tickte 1 artıyor, yani skor = yarım saniye
// Oyun update'inde: updateScore(score + 1); if (LK.ticks % 30 === 0)
// Yani score/2 = saniye
var survivedSeconds = Math.floor(score / 2);
var min = Math.floor(survivedSeconds / 60);
var sec = survivedSeconds % 60;
var timeStr = min > 0 ? min + "m " + (sec < 10 ? "0" : "") + sec + "s" : sec + "s";
var timeLabel = new Text2("Survived: " + timeStr, {
size: 90,
fill: "#fff"
});
timeLabel.anchor.set(0.5, 1);
timeLabel.x = GAME_W / 2;
timeLabel.y = scoreLabel.y - 100;
retryButtonContainer.addChild(timeLabel);
}
// Restart butonu arka planı
var btnBgRestart = LK.getAsset('blackbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: restartY,
width: btnW,
height: btnH
});
retryButtonContainer.addChild(btnBgRestart);
// Restart buton metni
var btnTextRestart = new Text2("Restart", {
size: 80,
fill: "#fff"
});
btnTextRestart.anchor.set(0.5, 0.5);
btnTextRestart.x = GAME_W / 2;
btnTextRestart.y = restartY;
retryButtonContainer.addChild(btnTextRestart);
// Main Menu butonu arka planı
var btnBgMenu = LK.getAsset('blackbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: menuY,
width: btnW,
height: btnH
});
retryButtonContainer.addChild(btnBgMenu);
// Main Menu buton metni
var btnTextMenu = new Text2("Main Menu", {
size: 80,
fill: "#fff"
});
btnTextMenu.anchor.set(0.5, 0.5);
btnTextMenu.x = GAME_W / 2;
btnTextMenu.y = menuY;
retryButtonContainer.addChild(btnTextMenu);
// Restart butonu etkileşimi
btnBgRestart.interactive = true;
btnBgRestart.buttonMode = true;
btnBgRestart.down = function (x, y, obj) {
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
if (currentMode === "endless") {
startEndlessRun();
} else {
startGameFromChapter();
}
};
// Main Menu butonu etkileşimi
btnBgMenu.interactive = true;
btnBgMenu.buttonMode = true;
btnBgMenu.down = function (x, y, obj) {
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
showMenu();
};
game.addChild(retryButtonContainer);
game._retryButtonContainer = retryButtonContainer;
return;
}
b.lastIntersecting = intersecting;
}
// Takipçi mermileri güncelle
for (var i = chasingBullets.length - 1; i >= 0; i--) {
var cb = chasingBullets[i];
cb.update();
if (cb.isOutOfBounds()) {
cb.destroy();
chasingBullets.splice(i, 1);
continue;
}
var intersecting = cb.intersects(shadow);
if (!cb.lastIntersecting && intersecting) {
// Oyuncu takipçi mermiye çarptıysa, oyunu durdur ve "Yeniden Başla" butonu göster
isGameActive = false;
// Tüm mermileri temizle
for (var k = 0; k < bullets.length; k++) {
if (bullets[k]) {
bullets[k].destroy();
}
}
bullets = [];
for (var k = 0; k < chasingBullets.length; k++) {
if (chasingBullets[k]) {
chasingBullets[k].destroy();
}
}
chasingBullets = [];
// Karakteri sil
if (shadow) {
shadow.destroy();
shadow = null;
}
// Skor metni sil
if (scoreTxt) {
scoreTxt.destroy();
scoreTxt = null;
}
// Joystick ve paneli sil
if (controlPanel) {
controlPanel.destroy();
controlPanel = null;
}
joystick = null;
isPanelActive = false;
dragNode = null;
joystickDirX = 0;
joystickDirY = 0;
joystickActive = false;
// --- YENİDEN BAŞLA VE ANA MENÜ BUTONLARI EKLE ---
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
var retryButtonContainer = new Container();
// Ortalanmış konumlar
var btnW = 600;
var btnH = 180;
var btnSpacing = 80;
var centerY = GAME_H / 2;
var restartY = centerY - btnH / 2 - btnSpacing / 2;
var menuY = centerY + btnH / 2 + btnSpacing / 2;
// --- Endless Run: Skor ve süreyi göster ---
if (currentMode === "endless") {
// Skor
var scoreLabel = new Text2("Score: " + score, {
size: 110,
fill: "#fff"
});
scoreLabel.anchor.set(0.5, 1);
scoreLabel.x = GAME_W / 2;
scoreLabel.y = restartY - 120;
retryButtonContainer.addChild(scoreLabel);
// Hayatta kalma süresi (saniye ve dakika)
var survivedSeconds = Math.floor(score / 2);
var min = Math.floor(survivedSeconds / 60);
var sec = survivedSeconds % 60;
var timeStr = min > 0 ? min + "m " + (sec < 10 ? "0" : "") + sec + "s" : sec + "s";
var timeLabel = new Text2("Survived: " + timeStr, {
size: 90,
fill: "#fff"
});
timeLabel.anchor.set(0.5, 1);
timeLabel.x = GAME_W / 2;
timeLabel.y = scoreLabel.y - 100;
retryButtonContainer.addChild(timeLabel);
}
// Restart butonu arka planı
var btnBgRestart = LK.getAsset('blackbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: restartY,
width: btnW,
height: btnH
});
retryButtonContainer.addChild(btnBgRestart);
// Restart buton metni
var btnTextRestart = new Text2("Restart", {
size: 80,
fill: "#fff"
});
btnTextRestart.anchor.set(0.5, 0.5);
btnTextRestart.x = GAME_W / 2;
btnTextRestart.y = restartY;
retryButtonContainer.addChild(btnTextRestart);
// Main Menu butonu arka planı
var btnBgMenu = LK.getAsset('blackbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: menuY,
width: btnW,
height: btnH
});
retryButtonContainer.addChild(btnBgMenu);
// Main Menu buton metni
var btnTextMenu = new Text2("Main Menu", {
size: 80,
fill: "#fff"
});
btnTextMenu.anchor.set(0.5, 0.5);
btnTextMenu.x = GAME_W / 2;
btnTextMenu.y = menuY;
retryButtonContainer.addChild(btnTextMenu);
// Restart butonu etkileşimi
btnBgRestart.interactive = true;
btnBgRestart.buttonMode = true;
btnBgRestart.down = function (x, y, obj) {
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
if (currentMode === "endless") {
startEndlessRun();
} else {
startGameFromChapter();
}
};
// Main Menu butonu etkileşimi
btnBgMenu.interactive = true;
btnBgMenu.buttonMode = true;
btnBgMenu.down = function (x, y, obj) {
if (game._retryButtonContainer) {
game._retryButtonContainer.destroy();
game._retryButtonContainer = null;
}
showMenu();
};
game.addChild(retryButtonContainer);
game._retryButtonContainer = retryButtonContainer;
return;
}
cb.lastIntersecting = intersecting;
}
// Işık mermileri birbirine çarptı mı kontrol et ve patlat
for (var i = bullets.length - 1; i >= 0; i--) {
var b1 = bullets[i];
for (var j = i - 1; j >= 0; j--) {
var b2 = bullets[j];
if (!b1._destroyed && !b2._destroyed && b1.intersects(b2)) {
b1.destroy();
b2.destroy();
bullets.splice(i, 1);
bullets.splice(j, 1);
i--;
break;
}
}
}
// Takipçi mermiler ile normal mermiler çarpışırsa patlat
for (var i = chasingBullets.length - 1; i >= 0; i--) {
var cb = chasingBullets[i];
for (var j = bullets.length - 1; j >= 0; j--) {
var b = bullets[j];
if (!cb._destroyed && !b._destroyed && cb.intersects(b)) {
cb.destroy();
b.destroy();
chasingBullets.splice(i, 1);
bullets.splice(j, 1);
i--;
break;
}
}
}
// Takipçi mermiler birbirine çarparsa patlat
for (var i = chasingBullets.length - 1; i >= 0; i--) {
var cb1 = chasingBullets[i];
for (var j = i - 1; j >= 0; j--) {
var cb2 = chasingBullets[j];
if (!cb1._destroyed && !cb2._destroyed && cb1.intersects(cb2)) {
cb1.destroy();
cb2.destroy();
chasingBullets.splice(i, 1);
chasingBullets.splice(j, 1);
i--;
break;
}
}
}
// Endless run modunda skor mermi sayısı olarak güncellendiği için burada skor artışı yapılmaz
if (currentMode === "endless") {
// Endless modda high score kaydet
if (score > (storage.endlessHighScore || 0)) {
storage.endlessHighScore = score;
}
// --- Endless Run: Unlock logic removed, difficulty does not increase ---
}
};
// Oyun yeniden başladığında sıfırla
game.on('reset', function () {
// Tüm oyun objelerini temizle
clearGameObjects();
// Menüye dön
showMenu();
});
gri bir yuvarlak. In-Game asset. 2d. High contrast. No shadows
white bullet. In-Game asset. 2d. High contrast. No shadows
kırmızı gözler, gözler sadece kırmızı, gözden başka hiçbir şey olmayacak ve 2 tane göz olacak yan yana aynı boyutta, dümdüz kırmızı gözleri ekstra efekt yok, yazı yok, gözler dikine dikdörtgen şeklinde olacak gerçek göz gibi olmayacaklar, gözlerin boşlukları olmayacak gözler köşeli olacak dikine dikdörtgen ve köşeli. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
white color circle. In-Game asset. 2d. High contrast. No shadows
rectangle with rounded edges (color white) (the edges will white too). In-Game asset. 2d. High contrast. No shadows
black walkie talkie. In-Game asset. 2d. High contrast. No shadows
only text no image, agent shadow text logo for agent game. In-Game asset. 2d. High contrast. No shadows
black rectangle (rounded corners). In-Game asset. 2d. High contrast. No shadows
lock icon. no text. only white color. In-Game asset. 2d. High contrast. No shadows
ünlem işareti kırmızı. In-Game asset. 2d. High contrast. No shadows
game skill logo, skill name is "shadow". no text. only image.. In-Game asset. 2d. High contrast. No shadows