/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bonus class var Bonus = Container.expand(function () { var self = Container.call(this); var bonusGfx = self.attachAsset('bonus', { anchorX: 0.5, anchorY: 0.5 }); // Update: move down (relative to world) self.update = function (speed) { self.y += speed; }; return self; }); // Climber (player) class var Climber = Container.expand(function () { var self = Container.call(this); var climberGfx = self.attachAsset('climber', { anchorX: 0.5, anchorY: 0.5 }); // --- Trail logic --- // Store last position for trail self.lastTrailX = undefined; self.lastTrailY = undefined; self.trailCooldown = 0; // Helper: spawn a single orange trail segment at (x, y) self.spawnTrail = function (x, y) { // Use a simple orange ellipse as the trail segment var trail = LK.getAsset('trail_orange', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.7, scaleY: 0.4, alpha: 0.5 }); // If asset doesn't exist yet, create it as a shape if (!trail) { trail = LK.getAsset('trail_orange', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.7, scaleY: 0.4, alpha: 0.5 }); } // Add to game if (game) game.addChild(trail); // Animate fade out and shrink, then destroy tween(trail, { alpha: 0, scaleX: 0.2, scaleY: 0.1 }, { duration: 320, easing: tween.quadIn, onFinish: function onFinish() { if (trail && trail.parent) trail.parent.removeChild(trail); } }); }; // State: which wall (0 = left, 1 = right) self.wallSide = 0; // Vertical speed (pixels per tick) self.vy = 10; // Horizontal jump speed (pixels per tick) self.vx = 0; // Is currently jumping self.isJumping = false; // Target wall x self.targetX = 0; // Jump to the other wall self.jump = function () { // If already jumping, allow to interrupt and jump to the other wall if (self.isJumping) { // Instantly stop current tween by setting isJumping to false self.isJumping = false; } self.wallSide = 1 - self.wallSide; self.isJumping = true; // Set target x (climber sits just outside the wall, not inside) if (self.wallSide === 0) { self.targetX = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE; } else { self.targetX = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE; } // Add a diagonal jump: also move y a bit up, then back to fixed y var jumpPeakY = self.y - 180 * GAME_SCALE; // jump up by 180px, scaled var originalY = self.y; // Determine tilt direction: right jump = tilt left, left jump = tilt right var tiltAngle = 0; if (self.wallSide === 0) { // Jumping to left wall, tilt slightly right tiltAngle = Math.PI / 16; } else { // Jumping to right wall, tilt slightly left tiltAngle = -Math.PI / 16; } // Animate jump with smooth physics using easing for both X and Y // Also animate rotation for tilt tween(self, { x: self.targetX, y: jumpPeakY, rotation: tiltAngle }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { // Fall back to original y and reset tilt with smooth ease tween(self, { y: originalY, rotation: 0 }, { duration: 180, easing: tween.bounceOut, onFinish: function onFinish() { self.x = self.targetX; self.isJumping = false; } }); } }); }; // Update: move up and spawn trail self.update = function () { // Move up self.y -= self.vy; // Trail: spawn a segment if moved enough if (self.lastTrailX === undefined || self.lastTrailY === undefined) { self.lastTrailX = self.x; self.lastTrailY = self.y; } // Only spawn trail if moved at least 18px (vertical) or 8px (horizontal) var dx = self.x - self.lastTrailX; var dy = self.y - self.lastTrailY; if (dx * dx + dy * dy > 64 * GAME_SCALE * GAME_SCALE) { self.spawnTrail(self.x, self.y + climberHeight * 0.18); self.lastTrailX = self.x; self.lastTrailY = self.y; } }; return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); var obsGfx = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Update: move down (relative to world) self.update = function (speed) { self.y += speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1e2a38 }); /**** * Game Code ****/ // Add two background images for infinite vertical scroll var backgroundImage1 = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 2048 / 2048, scaleY: 2732 / 2732 }); var backgroundImage2 = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: -3640.89, // Place second image right above the first scaleX: 2048 / 2048, scaleY: 2732 / 2732 }); game.addChild(backgroundImage1); game.addChild(backgroundImage2); // Play background music LK.playMusic('bgmusic'); // --- Fail screen state --- // --- Constants --- // Scale factor for all gameplay elements var failScreenActive = false; var failScreenTimeout = null; var failScreenContainer = null; var GAME_SCALE = 1.2; var wallWidth = 180 * GAME_SCALE; var climberWidth = 120 * GAME_SCALE; var climberHeight = 120 * GAME_SCALE; // Make the gap between walls much narrower and ensure climber fits var wallGap = 420 * 1.4 * GAME_SCALE; // Increased by 40% and scaled by 1.2x var wallLeftX = (2048 - wallGap) / 2 - wallWidth / 2; var wallRightX = (2048 + wallGap) / 2 + wallWidth / 2; var playTopMargin = 100 * GAME_SCALE; // leave top 100px for menu, scaled var playBottomMargin = 0; // --- State --- var climber; var obstacles = []; var bonuses = []; var distance = 0; // in pixels var bonusScore = 0; var scrollSpeed = 18 * GAME_SCALE; // initial vertical speed (pixels per tick) - scaled var speedIncreaseEvery = 400 * GAME_SCALE; // increase speed more frequently, scaled var speedIncreaseAmount = 1.2; // increase speed more per step (no scaling needed) var maxScrollSpeed = 48 * GAME_SCALE; // allow higher max speed, scaled var lastObstacleY = 0; var lastBonusY = 0; var obstacleSpacing = 420 * 1.1 * GAME_SCALE; // min vertical distance between obstacles, increased by 10% and scaled var bonusSpacing = 900 * GAME_SCALE; // min vertical distance between bonuses, scaled var scoreTxt; // --- Obstacle damage protection & opacity --- var obstacleDamageEnabled = false; var obstacleOpacity = 0.35; // Start with 35% opacity // Set all current obstacles to given opacity function setAllObstacleOpacity(alpha) { for (var i = 0; i < obstacles.length; i++) { if (obstacles[i] && obstacles[i].children && obstacles[i].children[0]) { obstacles[i].children[0].alpha = alpha; } } } // Set initial opacity setAllObstacleOpacity(obstacleOpacity); // At 1s: blink between 0.35 and 0.6 for 1s, then set to 0.6 LK.setTimeout(function () { var blinkCount = 0; var blinkInterval = LK.setInterval(function () { blinkCount++; // Alternate between 0.35 and 0.6 every 100ms obstacleOpacity = blinkCount % 2 === 0 ? 0.35 : 0.6; setAllObstacleOpacity(obstacleOpacity); if (blinkCount >= 10) { // 1s total (10 blinks) LK.clearInterval(blinkInterval); obstacleOpacity = 0.6; setAllObstacleOpacity(obstacleOpacity); } }, 100); }, 1000); // At 2s: set to 1.0 and enable damage LK.setTimeout(function () { obstacleDamageEnabled = true; obstacleOpacity = 1.0; setAllObstacleOpacity(obstacleOpacity); // After 2s, always force obstacleOpacity to 1.0 for all future obstacles setAllObstacleOpacity = function setAllObstacleOpacity(alpha) { for (var i = 0; i < obstacles.length; i++) { if (obstacles[i] && obstacles[i].children && obstacles[i].children[0]) { obstacles[i].children[0].alpha = 1.0; } } }; }, 2000); // --- Walls --- // Walls removed: no wall graphics are added to the game // --- Climber --- climber = new Climber(); climber.x = wallLeftX + wallWidth / 2 + climberWidth / 2; climber.y = 2732 / 2; // start at vertical center of the screen (not scaled) game.addChild(climber); // --- Score Display --- scoreTxt = new Text2('0 m', { size: 120 * GAME_SCALE * 0.7, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); // anchor left scoreTxt.y = 310 * GAME_SCALE - 200; // move 100 units lower than before, scaled LK.gui.topLeft.addChild(scoreTxt); // --- Bonus Count Display (yellow ball icon + count) --- var bonusIcon = LK.getAsset('bonus', { anchorX: 0, anchorY: 0, // Reduce icon size by 20% (relative to previous) scaleX: 0.5 * GAME_SCALE * 0.7 * 3 * 0.8, scaleY: 0.5 * GAME_SCALE * 0.7 * 3 * 0.8, x: 0, y: scoreTxt.y + scoreTxt.height + 10 * GAME_SCALE }); LK.gui.topLeft.addChild(bonusIcon); var bonusCountTxt = new Text2('0', { size: 90 * GAME_SCALE * 0.7 * 1.4, // Increase by 40% fill: 0xFFF700 }); bonusCountTxt.anchor.set(0, 0); bonusCountTxt.x = bonusIcon.x + bonusIcon.width + 16 * GAME_SCALE; // Move count 20 units lower bonusCountTxt.y = bonusIcon.y + (bonusIcon.height - bonusCountTxt.height) / 2; LK.gui.topLeft.addChild(bonusCountTxt); // Helper to update bonus count text function updateBonusCountText() { bonusCountTxt.setText(bonusScore + ""); } updateBonusCountText(); // Heart (life) display using custom asset var maxLives = 3; var lives = maxLives; var heartIcons = []; for (var i = 0; i < maxLives; i++) { // Use your custom heart asset here. Make sure to add it in the Assets section, e.g.: // LK.init.image('heart', {width:100, height:100, id:'your_heart_asset_id'}) var heart = LK.getAsset('heart', { anchorX: 1, anchorY: 0, scaleX: 0.7 * GAME_SCALE, scaleY: 0.7 * GAME_SCALE, x: -50, y: 100 + i * 120 * GAME_SCALE * 0.7 }); LK.gui.topRight.addChild(heart); heartIcons.push(heart); } function updateHearts() { for (var i = 0; i < heartIcons.length; i++) { heartIcons[i].alpha = i < lives ? 1 : 0.25; } } updateHearts(); // --- Helper: format distance --- function formatDistance(px) { var meters = Math.floor(px / 10); if (meters < 1000) return meters + " m"; return (meters / 1000).toFixed(2) + " km"; } // --- Helper: update score text --- function updateScoreText() { var meters = Math.floor(distance / 10); scoreTxt.setText(meters + " m"); } // --- Helper: spawn obstacle --- function spawnObstacle() { // Randomly pick left or right wall var side = Math.random() < 0.5 ? 0 : 1; var obs = new Obstacle(); if (side === 0) { obs.x = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE; obs.children[0].flipX = 1; // Mirror horizontally for left-side obstacles } else { obs.x = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE; obs.children[0].flipX = 0; // No flip for right-side obstacles } // Set opacity according to current global value if (obs.children && obs.children[0]) { // After 800m, always set to 1.0 if (distance >= 8000) { obs.children[0].alpha = 1.0; } else { obs.children[0].alpha = obstacleOpacity; } } obs.y = lastObstacleY - obstacleSpacing - Math.random() * 220 * GAME_SCALE; obstacles.push(obs); game.addChild(obs); lastObstacleY = obs.y; } // --- Helper: spawn bonus --- function spawnBonus() { var bonus = new Bonus(); bonus.x = 2048 / 2; bonus.y = lastBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE; bonuses.push(bonus); game.addChild(bonus); lastBonusY = bonus.y; } // --- Initial obstacles/bonuses --- // Start obstacles and bonuses from the bottom of the screen lastObstacleY = 2732 - climberHeight / 2; lastBonusY = 2732 - climberHeight / 2; // (2732 is not scaled, as it's the screen size) // Delay obstacle and bonus spawning by 1 second, then spawn at 2 second intervals LK.setTimeout(function () { for (var i = 0; i < 6; i++) { spawnObstacle(); } for (var i = 0; i < 2; i++) { spawnBonus(); } // Set interval to spawn obstacles every 2 seconds (2000ms) LK.setInterval(function () { spawnObstacle(); }, 2000); }, 1000); // --- Input: tap to jump --- game.down = function (x, y, obj) { // Allow jump if not already moving to the same wall (ignore isJumping) var nextWall = 1 - climber.wallSide; // Only allow jump if not already moving to that wall if (!(climber.isJumping && climber.targetX === (nextWall === 0 ? wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 : wallRightX - wallWidth / 2 - climberWidth / 2 - 2))) { climber.jump(); } }; // --- Move handler (not used for drag, but required by LK) --- game.move = function (x, y, obj) {}; // --- Main update loop --- game.update = function () { if (failScreenActive) { // Block gameplay update while fail screen is shown return; } // --- Infinite vertical background scroll --- // Move both backgrounds down at 1/20th of scrollSpeed var bgScrollSpeed = scrollSpeed / 20; backgroundImage1.y += bgScrollSpeed; backgroundImage2.y += bgScrollSpeed; // If a background image moves off the bottom, move it above the other if (backgroundImage1.y >= 3640.89) { backgroundImage1.y = backgroundImage2.y - 3640.89; } if (backgroundImage2.y >= 3640.89) { backgroundImage2.y = backgroundImage1.y - 3640.89; } // Increase/decrease/stop speed at milestones if (!game.lastSpeedMilestone) game.lastSpeedMilestone = 0; var metersNow = Math.floor(distance / 10); // --- NEW: At 500m, increase speed by 0.3 only once --- if (!game.did500mSpeedup && metersNow >= 500) { scrollSpeed += 0.3; climber.vy = scrollSpeed; game.did500mSpeedup = true; } // 0-4k: increase speed every 1000m, then stop increasing after 4000m if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow <= 4000) { scrollSpeed *= 1.2; if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed; climber.vy = scrollSpeed; // Increase obstacleSpacing by 0.1x (10%) every 1000m obstacleSpacing *= 1.1; game.lastSpeedMilestone += 1000; } // 10k-15k: decrease speed by 0.08 per 1000m else if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow > 10000 && metersNow <= 15000) { scrollSpeed -= 0.08; if (scrollSpeed < 1) scrollSpeed = 1; // Prevent negative/zero speed climber.vy = scrollSpeed; game.lastSpeedMilestone += 1000; } // 15k-20k: increase speed by 0.08 per 1000m else if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow > 15000 && metersNow <= 20000) { scrollSpeed += 0.08; if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed; climber.vy = scrollSpeed; game.lastSpeedMilestone += 1000; } // After 15k: every 500m, increase speed by 0.1 if (!game.last500mSpeedMilestone) game.last500mSpeedMilestone = 15000; if (metersNow >= 15000 && metersNow - game.last500mSpeedMilestone >= 500) { scrollSpeed += 0.1; if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed; climber.vy = scrollSpeed; game.last500mSpeedMilestone += 500; } // After 20k: stop increasing speed, no further changes // Keep climber fixed at the vertical center of the screen, move obstacles/bonuses down by scrollSpeed var screenClimberY = 2732 / 2; // fixed Y position for climber at vertical center (not scaled) climber.y = screenClimberY; // Move all obstacles and bonuses down by scrollSpeed for (var i = 0; i < obstacles.length; i++) { obstacles[i].y += scrollSpeed; // After 800m (8000px), always force obstacle opacity to 1.0 if (distance >= 8000 && obstacles[i].children && obstacles[i].children[0]) { obstacles[i].children[0].alpha = 1.0; } } for (var j = 0; j < bonuses.length; j++) { bonuses[j].y += scrollSpeed; } // Walls removed: no wall movement needed // Update distance distance += scrollSpeed; updateScoreText(); // Move obstacles and check collision for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; // If obstacle goes off the bottom, move it back to the top with new random Y and side if (obs.y > 2732 + 100 * GAME_SCALE) { // Randomly pick left or right wall var side = Math.random() < 0.5 ? 0 : 1; if (side === 0) { obs.x = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE; obs.children[0].flipX = 1; // Mirror horizontally for left-side obstacles } else { obs.x = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE; obs.children[0].flipX = 0; // No flip for right-side obstacles } // Set opacity according to current global value if (obs.children && obs.children[0]) { // After 800m, always set to 1.0 if (distance >= 8000) { obs.children[0].alpha = 1.0; } else { obs.children[0].alpha = obstacleOpacity; } } // Place above the highest obstacle var highestY = climber.y - 400 * GAME_SCALE; for (var k = 0; k < obstacles.length; k++) { if (obstacles[k].y < highestY) highestY = obstacles[k].y; } obs.y = highestY - obstacleSpacing - Math.random() * 220 * GAME_SCALE; lastObstacleY = obs.y; } // Collision: only if climber is on same wall and overlaps in Y if (Math.abs(obs.x - climber.x) < climberWidth / 2 + 10 * GAME_SCALE && Math.abs(obs.y - screenClimberY) < climberHeight / 2 + 30 * GAME_SCALE) { if (obstacleDamageEnabled) { LK.effects.flashScreen(0xff0000, 800); // Play obstacle hit sound LK.getSound('obstacle_hit').play(); lives--; updateHearts(); if (lives <= 0) { // Play fail sound LK.getSound('fail').play(); // Show fail screen for 2 seconds with score failScreenActive = true; failScreenContainer = new Container(); // Semi-transparent black background var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2, alpha: 0.7, tint: 0x000000 }); failScreenContainer.addChild(bg); // "Failed!" text var failTxt = new Text2("Failed!", { size: 180 * GAME_SCALE, fill: 0xFF4444 }); failTxt.anchor.set(0.5, 0.5); failTxt.x = 2048 / 2; failTxt.y = 2732 / 2 - 180 * GAME_SCALE; failScreenContainer.addChild(failTxt); // Only distance value (no 'Skor:' label) var scoreFailTxt = new Text2(Math.floor(distance / 10) + " m", { size: 120 * GAME_SCALE, fill: "#fff" }); scoreFailTxt.anchor.set(0.5, 0.5); scoreFailTxt.x = 2048 / 2; scoreFailTxt.y = 2732 / 2 + 20 * GAME_SCALE; failScreenContainer.addChild(scoreFailTxt); // Bonus icon and count (centered, icon above, count below) var bonusFailIcon = LK.getAsset('bonus', { anchorX: 0.5, anchorY: 0.5, // %5 büyüt scaleX: 0.7 * GAME_SCALE * 1.05, scaleY: 0.7 * GAME_SCALE * 1.05, x: 2048 / 2, // 10 birim aşağıya indir y: 2732 / 2 + 120 * GAME_SCALE + 10 }); failScreenContainer.addChild(bonusFailIcon); var bonusFailCount = new Text2(bonusScore + "", { size: 100 * GAME_SCALE, fill: 0xFFF700 }); bonusFailCount.anchor.set(0.5, 0); bonusFailCount.x = 2048 / 2; bonusFailCount.y = bonusFailIcon.y + bonusFailIcon.height / 2 + 10 * GAME_SCALE; failScreenContainer.addChild(bonusFailCount); game.addChild(failScreenContainer); // After 2 seconds, remove fail screen and show game over failScreenTimeout = LK.setTimeout(function () { if (failScreenContainer && failScreenContainer.parent) { failScreenContainer.parent.removeChild(failScreenContainer); } failScreenActive = false; LK.showGameOver(); }, 2000); return; } // Temporary invulnerability for 1s after hit obstacleDamageEnabled = false; setAllObstacleOpacity(0.5); LK.setTimeout(function () { obstacleDamageEnabled = true; setAllObstacleOpacity(1.0); }, 1000); } } } // Move bonuses and check collection for (var j = bonuses.length - 1; j >= 0; j--) { var b = bonuses[j]; // If bonus goes off the bottom, move it back to the top (like obstacles) if (b.y > 2732 + 100 * GAME_SCALE) { // Place above the highest bonus var highestBonusY = climber.y - 800 * GAME_SCALE; for (var k = 0; k < bonuses.length; k++) { if (bonuses[k].y < highestBonusY) highestBonusY = bonuses[k].y; } b.y = highestBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE; lastBonusY = b.y; continue; } // Collect if (Math.abs(b.x - climber.x) < climberWidth / 2 + 40 * GAME_SCALE && Math.abs(b.y - screenClimberY) < climberHeight / 2 + 40 * GAME_SCALE) { bonusScore += 1; // Add 50 units to distance when collecting a yellow bonus distance += 50; updateScoreText(); updateBonusCountText(); LK.effects.flashObject(b, 0xffff00, 400); // Play bonus collect sound LK.getSound('bonus_collect').play(); // Move collected bonus to top (infinite) var highestBonusY = climber.y - 800 * GAME_SCALE; for (var k = 0; k < bonuses.length; k++) { if (bonuses[k].y < highestBonusY) highestBonusY = bonuses[k].y; } b.y = highestBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE; lastBonusY = b.y; continue; } } // Spawn new obstacles/bonuses as needed for infinite runner while (lastObstacleY > climber.y - 1200 * GAME_SCALE) { spawnObstacle(); } while (lastBonusY > climber.y - 1800 * GAME_SCALE) { spawnBonus(); } }; // --- Reset state on game over --- game.on('gameover', function () { // All state will be reset by LK, so nothing to do here bonusScore = 0; updateBonusCountText && updateBonusCountText(); // Remove fail screen if still present if (failScreenTimeout) { LK.clearTimeout(failScreenTimeout); failScreenTimeout = null; } if (failScreenContainer && failScreenContainer.parent) { failScreenContainer.parent.removeChild(failScreenContainer); failScreenContainer = null; } failScreenActive = false; }); // --- You win (not used in endless) --- game.on('youwin', function () { // Not used bonusScore = 0; updateBonusCountText && updateBonusCountText(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bonus class
var Bonus = Container.expand(function () {
var self = Container.call(this);
var bonusGfx = self.attachAsset('bonus', {
anchorX: 0.5,
anchorY: 0.5
});
// Update: move down (relative to world)
self.update = function (speed) {
self.y += speed;
};
return self;
});
// Climber (player) class
var Climber = Container.expand(function () {
var self = Container.call(this);
var climberGfx = self.attachAsset('climber', {
anchorX: 0.5,
anchorY: 0.5
});
// --- Trail logic ---
// Store last position for trail
self.lastTrailX = undefined;
self.lastTrailY = undefined;
self.trailCooldown = 0;
// Helper: spawn a single orange trail segment at (x, y)
self.spawnTrail = function (x, y) {
// Use a simple orange ellipse as the trail segment
var trail = LK.getAsset('trail_orange', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.7,
scaleY: 0.4,
alpha: 0.5
});
// If asset doesn't exist yet, create it as a shape
if (!trail) {
trail = LK.getAsset('trail_orange', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.7,
scaleY: 0.4,
alpha: 0.5
});
}
// Add to game
if (game) game.addChild(trail);
// Animate fade out and shrink, then destroy
tween(trail, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.1
}, {
duration: 320,
easing: tween.quadIn,
onFinish: function onFinish() {
if (trail && trail.parent) trail.parent.removeChild(trail);
}
});
};
// State: which wall (0 = left, 1 = right)
self.wallSide = 0;
// Vertical speed (pixels per tick)
self.vy = 10;
// Horizontal jump speed (pixels per tick)
self.vx = 0;
// Is currently jumping
self.isJumping = false;
// Target wall x
self.targetX = 0;
// Jump to the other wall
self.jump = function () {
// If already jumping, allow to interrupt and jump to the other wall
if (self.isJumping) {
// Instantly stop current tween by setting isJumping to false
self.isJumping = false;
}
self.wallSide = 1 - self.wallSide;
self.isJumping = true;
// Set target x (climber sits just outside the wall, not inside)
if (self.wallSide === 0) {
self.targetX = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE;
} else {
self.targetX = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE;
}
// Add a diagonal jump: also move y a bit up, then back to fixed y
var jumpPeakY = self.y - 180 * GAME_SCALE; // jump up by 180px, scaled
var originalY = self.y;
// Determine tilt direction: right jump = tilt left, left jump = tilt right
var tiltAngle = 0;
if (self.wallSide === 0) {
// Jumping to left wall, tilt slightly right
tiltAngle = Math.PI / 16;
} else {
// Jumping to right wall, tilt slightly left
tiltAngle = -Math.PI / 16;
}
// Animate jump with smooth physics using easing for both X and Y
// Also animate rotation for tilt
tween(self, {
x: self.targetX,
y: jumpPeakY,
rotation: tiltAngle
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Fall back to original y and reset tilt with smooth ease
tween(self, {
y: originalY,
rotation: 0
}, {
duration: 180,
easing: tween.bounceOut,
onFinish: function onFinish() {
self.x = self.targetX;
self.isJumping = false;
}
});
}
});
};
// Update: move up and spawn trail
self.update = function () {
// Move up
self.y -= self.vy;
// Trail: spawn a segment if moved enough
if (self.lastTrailX === undefined || self.lastTrailY === undefined) {
self.lastTrailX = self.x;
self.lastTrailY = self.y;
}
// Only spawn trail if moved at least 18px (vertical) or 8px (horizontal)
var dx = self.x - self.lastTrailX;
var dy = self.y - self.lastTrailY;
if (dx * dx + dy * dy > 64 * GAME_SCALE * GAME_SCALE) {
self.spawnTrail(self.x, self.y + climberHeight * 0.18);
self.lastTrailX = self.x;
self.lastTrailY = self.y;
}
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsGfx = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Update: move down (relative to world)
self.update = function (speed) {
self.y += speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1e2a38
});
/****
* Game Code
****/
// Add two background images for infinite vertical scroll
var backgroundImage1 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 2048 / 2048,
scaleY: 2732 / 2732
});
var backgroundImage2 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: -3640.89,
// Place second image right above the first
scaleX: 2048 / 2048,
scaleY: 2732 / 2732
});
game.addChild(backgroundImage1);
game.addChild(backgroundImage2);
// Play background music
LK.playMusic('bgmusic');
// --- Fail screen state ---
// --- Constants ---
// Scale factor for all gameplay elements
var failScreenActive = false;
var failScreenTimeout = null;
var failScreenContainer = null;
var GAME_SCALE = 1.2;
var wallWidth = 180 * GAME_SCALE;
var climberWidth = 120 * GAME_SCALE;
var climberHeight = 120 * GAME_SCALE;
// Make the gap between walls much narrower and ensure climber fits
var wallGap = 420 * 1.4 * GAME_SCALE; // Increased by 40% and scaled by 1.2x
var wallLeftX = (2048 - wallGap) / 2 - wallWidth / 2;
var wallRightX = (2048 + wallGap) / 2 + wallWidth / 2;
var playTopMargin = 100 * GAME_SCALE; // leave top 100px for menu, scaled
var playBottomMargin = 0;
// --- State ---
var climber;
var obstacles = [];
var bonuses = [];
var distance = 0; // in pixels
var bonusScore = 0;
var scrollSpeed = 18 * GAME_SCALE; // initial vertical speed (pixels per tick) - scaled
var speedIncreaseEvery = 400 * GAME_SCALE; // increase speed more frequently, scaled
var speedIncreaseAmount = 1.2; // increase speed more per step (no scaling needed)
var maxScrollSpeed = 48 * GAME_SCALE; // allow higher max speed, scaled
var lastObstacleY = 0;
var lastBonusY = 0;
var obstacleSpacing = 420 * 1.1 * GAME_SCALE; // min vertical distance between obstacles, increased by 10% and scaled
var bonusSpacing = 900 * GAME_SCALE; // min vertical distance between bonuses, scaled
var scoreTxt;
// --- Obstacle damage protection & opacity ---
var obstacleDamageEnabled = false;
var obstacleOpacity = 0.35; // Start with 35% opacity
// Set all current obstacles to given opacity
function setAllObstacleOpacity(alpha) {
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] && obstacles[i].children && obstacles[i].children[0]) {
obstacles[i].children[0].alpha = alpha;
}
}
}
// Set initial opacity
setAllObstacleOpacity(obstacleOpacity);
// At 1s: blink between 0.35 and 0.6 for 1s, then set to 0.6
LK.setTimeout(function () {
var blinkCount = 0;
var blinkInterval = LK.setInterval(function () {
blinkCount++;
// Alternate between 0.35 and 0.6 every 100ms
obstacleOpacity = blinkCount % 2 === 0 ? 0.35 : 0.6;
setAllObstacleOpacity(obstacleOpacity);
if (blinkCount >= 10) {
// 1s total (10 blinks)
LK.clearInterval(blinkInterval);
obstacleOpacity = 0.6;
setAllObstacleOpacity(obstacleOpacity);
}
}, 100);
}, 1000);
// At 2s: set to 1.0 and enable damage
LK.setTimeout(function () {
obstacleDamageEnabled = true;
obstacleOpacity = 1.0;
setAllObstacleOpacity(obstacleOpacity);
// After 2s, always force obstacleOpacity to 1.0 for all future obstacles
setAllObstacleOpacity = function setAllObstacleOpacity(alpha) {
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] && obstacles[i].children && obstacles[i].children[0]) {
obstacles[i].children[0].alpha = 1.0;
}
}
};
}, 2000);
// --- Walls ---
// Walls removed: no wall graphics are added to the game
// --- Climber ---
climber = new Climber();
climber.x = wallLeftX + wallWidth / 2 + climberWidth / 2;
climber.y = 2732 / 2; // start at vertical center of the screen (not scaled)
game.addChild(climber);
// --- Score Display ---
scoreTxt = new Text2('0 m', {
size: 120 * GAME_SCALE * 0.7,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0); // anchor left
scoreTxt.y = 310 * GAME_SCALE - 200; // move 100 units lower than before, scaled
LK.gui.topLeft.addChild(scoreTxt);
// --- Bonus Count Display (yellow ball icon + count) ---
var bonusIcon = LK.getAsset('bonus', {
anchorX: 0,
anchorY: 0,
// Reduce icon size by 20% (relative to previous)
scaleX: 0.5 * GAME_SCALE * 0.7 * 3 * 0.8,
scaleY: 0.5 * GAME_SCALE * 0.7 * 3 * 0.8,
x: 0,
y: scoreTxt.y + scoreTxt.height + 10 * GAME_SCALE
});
LK.gui.topLeft.addChild(bonusIcon);
var bonusCountTxt = new Text2('0', {
size: 90 * GAME_SCALE * 0.7 * 1.4,
// Increase by 40%
fill: 0xFFF700
});
bonusCountTxt.anchor.set(0, 0);
bonusCountTxt.x = bonusIcon.x + bonusIcon.width + 16 * GAME_SCALE;
// Move count 20 units lower
bonusCountTxt.y = bonusIcon.y + (bonusIcon.height - bonusCountTxt.height) / 2;
LK.gui.topLeft.addChild(bonusCountTxt);
// Helper to update bonus count text
function updateBonusCountText() {
bonusCountTxt.setText(bonusScore + "");
}
updateBonusCountText();
// Heart (life) display using custom asset
var maxLives = 3;
var lives = maxLives;
var heartIcons = [];
for (var i = 0; i < maxLives; i++) {
// Use your custom heart asset here. Make sure to add it in the Assets section, e.g.:
// LK.init.image('heart', {width:100, height:100, id:'your_heart_asset_id'})
var heart = LK.getAsset('heart', {
anchorX: 1,
anchorY: 0,
scaleX: 0.7 * GAME_SCALE,
scaleY: 0.7 * GAME_SCALE,
x: -50,
y: 100 + i * 120 * GAME_SCALE * 0.7
});
LK.gui.topRight.addChild(heart);
heartIcons.push(heart);
}
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].alpha = i < lives ? 1 : 0.25;
}
}
updateHearts();
// --- Helper: format distance ---
function formatDistance(px) {
var meters = Math.floor(px / 10);
if (meters < 1000) return meters + " m";
return (meters / 1000).toFixed(2) + " km";
}
// --- Helper: update score text ---
function updateScoreText() {
var meters = Math.floor(distance / 10);
scoreTxt.setText(meters + " m");
}
// --- Helper: spawn obstacle ---
function spawnObstacle() {
// Randomly pick left or right wall
var side = Math.random() < 0.5 ? 0 : 1;
var obs = new Obstacle();
if (side === 0) {
obs.x = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE;
obs.children[0].flipX = 1; // Mirror horizontally for left-side obstacles
} else {
obs.x = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE;
obs.children[0].flipX = 0; // No flip for right-side obstacles
}
// Set opacity according to current global value
if (obs.children && obs.children[0]) {
// After 800m, always set to 1.0
if (distance >= 8000) {
obs.children[0].alpha = 1.0;
} else {
obs.children[0].alpha = obstacleOpacity;
}
}
obs.y = lastObstacleY - obstacleSpacing - Math.random() * 220 * GAME_SCALE;
obstacles.push(obs);
game.addChild(obs);
lastObstacleY = obs.y;
}
// --- Helper: spawn bonus ---
function spawnBonus() {
var bonus = new Bonus();
bonus.x = 2048 / 2;
bonus.y = lastBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE;
bonuses.push(bonus);
game.addChild(bonus);
lastBonusY = bonus.y;
}
// --- Initial obstacles/bonuses ---
// Start obstacles and bonuses from the bottom of the screen
lastObstacleY = 2732 - climberHeight / 2;
lastBonusY = 2732 - climberHeight / 2;
// (2732 is not scaled, as it's the screen size)
// Delay obstacle and bonus spawning by 1 second, then spawn at 2 second intervals
LK.setTimeout(function () {
for (var i = 0; i < 6; i++) {
spawnObstacle();
}
for (var i = 0; i < 2; i++) {
spawnBonus();
}
// Set interval to spawn obstacles every 2 seconds (2000ms)
LK.setInterval(function () {
spawnObstacle();
}, 2000);
}, 1000);
// --- Input: tap to jump ---
game.down = function (x, y, obj) {
// Allow jump if not already moving to the same wall (ignore isJumping)
var nextWall = 1 - climber.wallSide;
// Only allow jump if not already moving to that wall
if (!(climber.isJumping && climber.targetX === (nextWall === 0 ? wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 : wallRightX - wallWidth / 2 - climberWidth / 2 - 2))) {
climber.jump();
}
};
// --- Move handler (not used for drag, but required by LK) ---
game.move = function (x, y, obj) {};
// --- Main update loop ---
game.update = function () {
if (failScreenActive) {
// Block gameplay update while fail screen is shown
return;
}
// --- Infinite vertical background scroll ---
// Move both backgrounds down at 1/20th of scrollSpeed
var bgScrollSpeed = scrollSpeed / 20;
backgroundImage1.y += bgScrollSpeed;
backgroundImage2.y += bgScrollSpeed;
// If a background image moves off the bottom, move it above the other
if (backgroundImage1.y >= 3640.89) {
backgroundImage1.y = backgroundImage2.y - 3640.89;
}
if (backgroundImage2.y >= 3640.89) {
backgroundImage2.y = backgroundImage1.y - 3640.89;
}
// Increase/decrease/stop speed at milestones
if (!game.lastSpeedMilestone) game.lastSpeedMilestone = 0;
var metersNow = Math.floor(distance / 10);
// --- NEW: At 500m, increase speed by 0.3 only once ---
if (!game.did500mSpeedup && metersNow >= 500) {
scrollSpeed += 0.3;
climber.vy = scrollSpeed;
game.did500mSpeedup = true;
}
// 0-4k: increase speed every 1000m, then stop increasing after 4000m
if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow <= 4000) {
scrollSpeed *= 1.2;
if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed;
climber.vy = scrollSpeed;
// Increase obstacleSpacing by 0.1x (10%) every 1000m
obstacleSpacing *= 1.1;
game.lastSpeedMilestone += 1000;
}
// 10k-15k: decrease speed by 0.08 per 1000m
else if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow > 10000 && metersNow <= 15000) {
scrollSpeed -= 0.08;
if (scrollSpeed < 1) scrollSpeed = 1; // Prevent negative/zero speed
climber.vy = scrollSpeed;
game.lastSpeedMilestone += 1000;
}
// 15k-20k: increase speed by 0.08 per 1000m
else if (metersNow - game.lastSpeedMilestone >= 1000 && metersNow > 15000 && metersNow <= 20000) {
scrollSpeed += 0.08;
if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed;
climber.vy = scrollSpeed;
game.lastSpeedMilestone += 1000;
}
// After 15k: every 500m, increase speed by 0.1
if (!game.last500mSpeedMilestone) game.last500mSpeedMilestone = 15000;
if (metersNow >= 15000 && metersNow - game.last500mSpeedMilestone >= 500) {
scrollSpeed += 0.1;
if (scrollSpeed > maxScrollSpeed) scrollSpeed = maxScrollSpeed;
climber.vy = scrollSpeed;
game.last500mSpeedMilestone += 500;
}
// After 20k: stop increasing speed, no further changes
// Keep climber fixed at the vertical center of the screen, move obstacles/bonuses down by scrollSpeed
var screenClimberY = 2732 / 2; // fixed Y position for climber at vertical center (not scaled)
climber.y = screenClimberY;
// Move all obstacles and bonuses down by scrollSpeed
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].y += scrollSpeed;
// After 800m (8000px), always force obstacle opacity to 1.0
if (distance >= 8000 && obstacles[i].children && obstacles[i].children[0]) {
obstacles[i].children[0].alpha = 1.0;
}
}
for (var j = 0; j < bonuses.length; j++) {
bonuses[j].y += scrollSpeed;
}
// Walls removed: no wall movement needed
// Update distance
distance += scrollSpeed;
updateScoreText();
// Move obstacles and check collision
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
// If obstacle goes off the bottom, move it back to the top with new random Y and side
if (obs.y > 2732 + 100 * GAME_SCALE) {
// Randomly pick left or right wall
var side = Math.random() < 0.5 ? 0 : 1;
if (side === 0) {
obs.x = wallLeftX + wallWidth / 2 + climberWidth / 2 + 2 * GAME_SCALE;
obs.children[0].flipX = 1; // Mirror horizontally for left-side obstacles
} else {
obs.x = wallRightX - wallWidth / 2 - climberWidth / 2 - 2 * GAME_SCALE;
obs.children[0].flipX = 0; // No flip for right-side obstacles
}
// Set opacity according to current global value
if (obs.children && obs.children[0]) {
// After 800m, always set to 1.0
if (distance >= 8000) {
obs.children[0].alpha = 1.0;
} else {
obs.children[0].alpha = obstacleOpacity;
}
}
// Place above the highest obstacle
var highestY = climber.y - 400 * GAME_SCALE;
for (var k = 0; k < obstacles.length; k++) {
if (obstacles[k].y < highestY) highestY = obstacles[k].y;
}
obs.y = highestY - obstacleSpacing - Math.random() * 220 * GAME_SCALE;
lastObstacleY = obs.y;
}
// Collision: only if climber is on same wall and overlaps in Y
if (Math.abs(obs.x - climber.x) < climberWidth / 2 + 10 * GAME_SCALE && Math.abs(obs.y - screenClimberY) < climberHeight / 2 + 30 * GAME_SCALE) {
if (obstacleDamageEnabled) {
LK.effects.flashScreen(0xff0000, 800);
// Play obstacle hit sound
LK.getSound('obstacle_hit').play();
lives--;
updateHearts();
if (lives <= 0) {
// Play fail sound
LK.getSound('fail').play();
// Show fail screen for 2 seconds with score
failScreenActive = true;
failScreenContainer = new Container();
// Semi-transparent black background
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 20,
scaleY: 20,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.7,
tint: 0x000000
});
failScreenContainer.addChild(bg);
// "Failed!" text
var failTxt = new Text2("Failed!", {
size: 180 * GAME_SCALE,
fill: 0xFF4444
});
failTxt.anchor.set(0.5, 0.5);
failTxt.x = 2048 / 2;
failTxt.y = 2732 / 2 - 180 * GAME_SCALE;
failScreenContainer.addChild(failTxt);
// Only distance value (no 'Skor:' label)
var scoreFailTxt = new Text2(Math.floor(distance / 10) + " m", {
size: 120 * GAME_SCALE,
fill: "#fff"
});
scoreFailTxt.anchor.set(0.5, 0.5);
scoreFailTxt.x = 2048 / 2;
scoreFailTxt.y = 2732 / 2 + 20 * GAME_SCALE;
failScreenContainer.addChild(scoreFailTxt);
// Bonus icon and count (centered, icon above, count below)
var bonusFailIcon = LK.getAsset('bonus', {
anchorX: 0.5,
anchorY: 0.5,
// %5 büyüt
scaleX: 0.7 * GAME_SCALE * 1.05,
scaleY: 0.7 * GAME_SCALE * 1.05,
x: 2048 / 2,
// 10 birim aşağıya indir
y: 2732 / 2 + 120 * GAME_SCALE + 10
});
failScreenContainer.addChild(bonusFailIcon);
var bonusFailCount = new Text2(bonusScore + "", {
size: 100 * GAME_SCALE,
fill: 0xFFF700
});
bonusFailCount.anchor.set(0.5, 0);
bonusFailCount.x = 2048 / 2;
bonusFailCount.y = bonusFailIcon.y + bonusFailIcon.height / 2 + 10 * GAME_SCALE;
failScreenContainer.addChild(bonusFailCount);
game.addChild(failScreenContainer);
// After 2 seconds, remove fail screen and show game over
failScreenTimeout = LK.setTimeout(function () {
if (failScreenContainer && failScreenContainer.parent) {
failScreenContainer.parent.removeChild(failScreenContainer);
}
failScreenActive = false;
LK.showGameOver();
}, 2000);
return;
}
// Temporary invulnerability for 1s after hit
obstacleDamageEnabled = false;
setAllObstacleOpacity(0.5);
LK.setTimeout(function () {
obstacleDamageEnabled = true;
setAllObstacleOpacity(1.0);
}, 1000);
}
}
}
// Move bonuses and check collection
for (var j = bonuses.length - 1; j >= 0; j--) {
var b = bonuses[j];
// If bonus goes off the bottom, move it back to the top (like obstacles)
if (b.y > 2732 + 100 * GAME_SCALE) {
// Place above the highest bonus
var highestBonusY = climber.y - 800 * GAME_SCALE;
for (var k = 0; k < bonuses.length; k++) {
if (bonuses[k].y < highestBonusY) highestBonusY = bonuses[k].y;
}
b.y = highestBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE;
lastBonusY = b.y;
continue;
}
// Collect
if (Math.abs(b.x - climber.x) < climberWidth / 2 + 40 * GAME_SCALE && Math.abs(b.y - screenClimberY) < climberHeight / 2 + 40 * GAME_SCALE) {
bonusScore += 1;
// Add 50 units to distance when collecting a yellow bonus
distance += 50;
updateScoreText();
updateBonusCountText();
LK.effects.flashObject(b, 0xffff00, 400);
// Play bonus collect sound
LK.getSound('bonus_collect').play();
// Move collected bonus to top (infinite)
var highestBonusY = climber.y - 800 * GAME_SCALE;
for (var k = 0; k < bonuses.length; k++) {
if (bonuses[k].y < highestBonusY) highestBonusY = bonuses[k].y;
}
b.y = highestBonusY - bonusSpacing - Math.random() * 400 * GAME_SCALE;
lastBonusY = b.y;
continue;
}
}
// Spawn new obstacles/bonuses as needed for infinite runner
while (lastObstacleY > climber.y - 1200 * GAME_SCALE) {
spawnObstacle();
}
while (lastBonusY > climber.y - 1800 * GAME_SCALE) {
spawnBonus();
}
};
// --- Reset state on game over ---
game.on('gameover', function () {
// All state will be reset by LK, so nothing to do here
bonusScore = 0;
updateBonusCountText && updateBonusCountText();
// Remove fail screen if still present
if (failScreenTimeout) {
LK.clearTimeout(failScreenTimeout);
failScreenTimeout = null;
}
if (failScreenContainer && failScreenContainer.parent) {
failScreenContainer.parent.removeChild(failScreenContainer);
failScreenContainer = null;
}
failScreenActive = false;
});
// --- You win (not used in endless) ---
game.on('youwin', function () {
// Not used
bonusScore = 0;
updateBonusCountText && updateBonusCountText();
});
make cyberpunk retro, neon wall. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Make pixelart neon wall obstacle. In-Game asset. 2d. High contrast. No shadows
toony basic flame. In-Game asset. 2d. High contrast. No shadows
mavileri parlat daha renkli olsun
arkasına parıltı ekle
kalbi retro neon yap