/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highestScore: 0
});
/****
* Classes
****/
// Bulut (Cloud) background object class
var Bulut = Container.expand(function () {
var self = Container.call(this);
var bulutImg = self.attachAsset('bulut', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = bulutImg.width;
self.height = bulutImg.height;
// Set a random speed for parallax effect
self.speed = 2 + Math.random() * 2;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
// Car body
var carBody = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 1
});
// Physics
self.vy = 0; // vertical velocity
self.isJumping = false;
// Car size for collision
self.bodyWidth = carBody.width;
self.bodyHeight = carBody.height;
// Update method
self.update = function () {
// Gravity and jump physics
self.y += self.vy;
if (self.y < groundY) {
self.vy += gravity;
if (self.y > groundY) {
self.y = groundY;
self.vy = 0;
self.isJumping = false;
}
} else if (self.y > groundY) {
self.y = groundY;
self.vy = 0;
self.isJumping = false;
}
};
// Jump method
self.jump = function () {
if (!self.isJumping && self.y >= groundY) {
self.vy = jumpVelocity;
self.isJumping = true;
LK.getSound('jump').play();
// Tilt the car slightly when jumping, then return to normal after 300ms
if (typeof tween !== "undefined" && typeof tween.create === "function") {
// Animate tilt to -0.25
var tiltTween = tween.create(carBody, {
rotation: -0.25
}, 120, {
easing: "easeOutCubic"
});
tiltTween.then(function () {
// Animate back to 0
var resetTween = tween.create(carBody, {
rotation: 0
}, 180, {
easing: "easeInCubic"
});
// No need to do anything after reset
});
} else {
carBody.rotation = -0.25;
// Fallback: reset after 300ms
LK.setTimeout(function () {
carBody.rotation = 0;
}, 300);
}
}
};
return self;
});
// Coin collectible class
var CoinCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a yellow coin emoji for collectible
var coinTxt = new Text2('🪙', {
size: 110,
fill: 0xffe066 // gold/yellow
});
coinTxt.anchor.set(0.5, 1);
self.addChild(coinTxt);
self.width = coinTxt.width;
self.height = coinTxt.height;
// For collision
self.bodyWidth = coinTxt.width;
self.bodyHeight = coinTxt.height;
self.update = function () {
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Gold heart collectible class
var GoldHeartCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a text heart for collectible, gold color
var goldHeartTxt = new Text2('❤', {
size: 120,
fill: 0xffd700 // gold
});
goldHeartTxt.anchor.set(0.5, 1);
self.addChild(goldHeartTxt);
self.width = goldHeartTxt.width;
self.height = goldHeartTxt.height;
// For collision
self.bodyWidth = goldHeartTxt.width;
self.bodyHeight = goldHeartTxt.height;
self.update = function () {
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Heart collectible class
var HeartCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a text heart for collectible
var heartTxt = new Text2('❤', {
size: 92,
fill: 0xff69b4
});
heartTxt.anchor.set(0.5, 1);
self.addChild(heartTxt);
self.width = heartTxt.width;
self.height = heartTxt.height;
// For collision
self.bodyWidth = heartTxt.width;
self.bodyHeight = heartTxt.height;
// Update method
self.update = function () {
// Use effectiveGameSpeed if defined, else fallback to gameSpeed
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.width = obs.width;
self.height = obs.height;
// For collision
self.bodyWidth = obs.width;
self.bodyHeight = obs.height;
// Update method
self.update = function () {
// Use effectiveGameSpeed if defined, else fallback to gameSpeed
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // sky blue
});
/****
* Game Code
****/
// Jump sound
// Road: dark gray box
// Obstacle: red box
// Wheel: black ellipse
// Car: blue box
// Constants
// Left-right moving obstacle asset
var groundY = 2200; // y position of the road top
var gravity = 4.5;
var jumpVelocity = -80;
var initialGameSpeed = 24;
var maxGameSpeed = 60;
var gameSpeed = initialGameSpeed;
var obstacleMinGap = 500;
var obstacleMaxGap = 1100;
var obstacleMinY = groundY;
var obstacleMaxY = groundY;
var minObstacleHeight = 120;
var maxObstacleHeight = 220;
// Road
var road = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY
});
game.addChild(road);
// Car
var car = new Car();
car.x = 400;
// Move the car visually closer to the road (simulate being on the road, not floating above)
car.y = groundY + 20; // 20px below the road top, adjust as needed for best look
game.addChild(car);
// --- Slow effect state ---
var carIsSlowed = false;
var carSlowTicks = 0;
var carSlowDuration = 120; // 2 seconds at 60fps
var carSlowSpeedFactor = 0.45; // 45% speed when slowed
// Obstacles
var obstacles = [];
var nextObstacleX = 2048 + 400;
// Heart collectibles
var heartCollectibles = [];
var nextHeartX = 2048 + 1200; // Start further out
// Gold heart collectibles
var goldHeartCollectibles = [];
var goldHeartActive = false;
var goldHeartTimer = 0;
var goldHeartDuration = 8 * 60; // 8 seconds at 60fps
var goldHeartDisplay = null;
var goldHeartDisplayLives = 0;
// --- Coin system ---
// Persistent coin count (load from storage, default 0)
var coins = typeof storage.coins === "number" ? storage.coins : 0;
// Coin collectibles
var coinCollectibles = [];
// Coin display
var coinTxt = new Text2('', {
size: 90,
fill: 0xffe066
});
coinTxt.anchor.set(1, 1);
coinTxt.x = -40;
coinTxt.y = -40;
LK.gui.bottomRight.addChild(coinTxt);
function updateCoinDisplay() {
coinTxt.setText("Jeton: " + coins);
}
updateCoinDisplay();
// Score
var score = 0;
var lastScore = 0;
var lives = 3; // Start with 3 lives
var maxLives = 3; // This can grow up to 9 as hearts are collected
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lives display
var livesTxt = null;
var goldHeartDisplay = null;
function updateLivesDisplay() {
if (!gameStarted) {
// Hide lives display on start screen
if (livesTxt) livesTxt.setText("");
if (goldHeartDisplay) goldHeartDisplay.setText("");
return;
}
if (!livesTxt) {
livesTxt = new Text2('', {
size: 90,
fill: 0xff4444
});
livesTxt.anchor.set(0, 1);
// Shift a bit more left to avoid coin overlap
livesTxt.x = 10;
livesTxt.y = -40;
LK.gui.bottomLeft.addChild(livesTxt);
}
if (goldHeartActive) {
// Hide normal lives, show gold hearts
if (!goldHeartDisplay) {
goldHeartDisplay = new Text2('', {
size: 76,
fill: 0xffd700
});
goldHeartDisplay.anchor.set(0, 1);
goldHeartDisplay.x = 18;
goldHeartDisplay.y = -40;
LK.gui.bottomLeft.addChild(goldHeartDisplay);
}
var hearts = '';
// Always cap display to 9 hearts maximum to prevent overflow
var displayMax = maxLives > 9 ? 9 : maxLives;
var displayLives = goldHeartDisplayLives > 9 ? 9 : goldHeartDisplayLives;
for (var i = 0; i < displayLives; i++) hearts += '❤ ';
for (var i = displayLives; i < displayMax; i++) hearts += '♡ ';
goldHeartDisplay.setText("Altın Can: " + hearts.trim());
livesTxt.setText(""); // Hide normal
} else {
// Show normal lives, hide gold
if (goldHeartDisplay) goldHeartDisplay.setText("");
var hearts = '';
var displayMax = maxLives > 9 ? 9 : maxLives;
for (var i = 0; i < lives && i < 9; i++) hearts += '❤ ';
for (var i = lives; i < displayMax; i++) hearts += '♡ ';
livesTxt.setText("Can: " + hearts.trim());
}
}
updateLivesDisplay();
// Last score display
var lastScoreTxt = new Text2('', {
size: 70,
fill: 0xFFD700
});
lastScoreTxt.anchor.set(0.5, 0);
lastScoreTxt.y = scoreTxt.height + 10;
LK.gui.top.addChild(lastScoreTxt);
// Highest score display (below leaderboard)
var highestScore = storage.highestScore || 0;
var highestScoreTxt = new Text2('', {
size: 70,
fill: 0x00ffcc
});
highestScoreTxt.anchor.set(0.5, 0);
highestScoreTxt.y = lastScoreTxt.y + lastScoreTxt.height + 10;
LK.gui.top.addChild(highestScoreTxt);
// Difficulty
var ticksSinceStart = 0;
// --- Start Screen Overlay ---
var gameStarted = false;
var startOverlay = new Container();
var startBg = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
startBg.alpha = 0.82;
startOverlay.addChild(startBg);
// Add a visual image to the start overlay
var startVisual = LK.getAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 700,
scaleX: 2.2,
scaleY: 2.2
});
startOverlay.addChild(startVisual);
var startText = new Text2("Araba Koşusu", {
size: 180,
fill: 0xffffff
});
startText.anchor.set(0.5, 0.5);
startText.x = 2048 / 2;
startText.y = 900;
startOverlay.addChild(startText);
var tapText = new Text2("Başlamak için ekrana dokun", {
size: 90,
fill: 0xffd700
});
tapText.anchor.set(0.5, 0.5);
tapText.x = 2048 / 2;
tapText.y = 1200;
startOverlay.addChild(tapText);
// --- Garage Button ---
var garageBtn = new Text2("Garaj", {
size: 100,
fill: 0x00bfff
});
garageBtn.anchor.set(0.5, 0.5);
// Place below the start text, centered
garageBtn.x = 2048 / 2;
garageBtn.y = 1400;
garageBtn.interactive = true;
garageBtn.buttonMode = true;
// --- Garage Overlay ---
var garageOverlay = new Container();
garageOverlay.visible = false;
// Add a semi-transparent background
var garageBg = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
garageBg.alpha = 0.92;
garageOverlay.addChild(garageBg);
// Garage title
var garageTitle = new Text2("Garaj", {
size: 160,
fill: 0xffffff
});
garageTitle.anchor.set(0.5, 0.5);
garageTitle.x = 2048 / 2;
garageTitle.y = 400;
garageOverlay.addChild(garageTitle);
// --- Car selection state ---
// Persistent unlock state for cars (all unlocked)
var unlockedCars = {
car: true,
araba2: true,
araba3: true
};
var selectedCarId = storage.selectedCarId || 'car';
// Show araba (main car) character
var araba1 = LK.getAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 500,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba1);
// Show araba2 character
var araba2 = LK.getAsset('araba2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba2);
// Show araba3 character
var araba3 = LK.getAsset('araba3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 500,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba3);
// --- Car unlock/lock overlays and buy buttons ---
var araba2LockTxt = null;
var araba2BuyBtn = null;
var araba3LockTxt = null;
var araba3BuyBtn = null;
function updateCarLockStates() {
// All cars are unlocked, so remove any lock/buy overlays if present
if (araba2LockTxt) {
garageOverlay.removeChild(araba2LockTxt);
araba2LockTxt = null;
}
if (araba2BuyBtn) {
garageOverlay.removeChild(araba2BuyBtn);
araba2BuyBtn = null;
}
if (araba3LockTxt) {
garageOverlay.removeChild(araba3LockTxt);
araba3LockTxt = null;
}
if (araba3BuyBtn) {
garageOverlay.removeChild(araba3BuyBtn);
araba3BuyBtn = null;
}
}
updateCarLockStates();
// --- Car selection highlight ---
var araba1Border = new Container();
var araba2Border = new Container();
var araba3Border = new Container();
function updateCarSelectionHighlight() {
// Remove all children first
araba1Border.removeChildren();
araba2Border.removeChildren();
araba3Border.removeChildren();
// Draw a simple border using a colored rectangle asset
if (selectedCarId === 'car') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba1.width + 30,
height: araba1.height + 30,
x: araba1.x,
y: araba1.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba1Border.addChild(border);
}
if (selectedCarId === 'araba2') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba2.width + 30,
height: araba2.height + 30,
x: araba2.x,
y: araba2.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba2Border.addChild(border);
}
if (selectedCarId === 'araba3') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba3.width + 30,
height: araba3.height + 30,
x: araba3.x,
y: araba3.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba3Border.addChild(border);
}
}
araba1Border.x = 0;
araba1Border.y = 0;
araba2Border.x = 0;
araba2Border.y = 0;
araba3Border.x = 0;
araba3Border.y = 0;
garageOverlay.addChild(araba1Border);
garageOverlay.addChild(araba2Border);
garageOverlay.addChild(araba3Border);
updateCarSelectionHighlight();
// --- Car selection interaction ---
araba1.interactive = true;
araba1.buttonMode = true;
araba1.down = function (x, y, obj) {
selectedCarId = 'car';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
};
araba2.interactive = true;
araba2.buttonMode = true;
araba2.down = function (x, y, obj) {
if (unlockedCars.araba2) {
selectedCarId = 'araba2';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
}
};
araba3.interactive = true;
araba3.buttonMode = true;
araba3.down = function (x, y, obj) {
if (unlockedCars.araba3) {
selectedCarId = 'araba3';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
}
};
// Add a close button to return to start screen
var closeGarageBtn = new Text2("Kapat", {
size: 90,
fill: 0xff4444
});
closeGarageBtn.anchor.set(0.5, 0.5);
closeGarageBtn.x = 2048 / 2;
closeGarageBtn.y = 1800;
closeGarageBtn.interactive = true;
closeGarageBtn.buttonMode = true;
closeGarageBtn.down = function (x, y, obj) {
garageOverlay.visible = false;
startOverlay.visible = true;
};
garageOverlay.addChild(closeGarageBtn);
game.addChild(garageOverlay);
garageBtn.down = function (x, y, obj) {
// Only allow navigation if still on start screen
if (!gameStarted) {
startOverlay.visible = false;
garageOverlay.visible = true;
}
};
startOverlay.addChild(garageBtn);
game.addChild(startOverlay);
// Play 'arkaplan' music on the start screen overlay
LK.playMusic('arkaplan');
// Touch handler: start game if on start screen and not tapping garage, otherwise jump
game.down = function (x, y, obj) {
if (!gameStarted) {
// If garage overlay is visible, ignore all taps except closeGarageBtn (handled by its own .down)
if (garageOverlay.visible) {
return;
}
// If tap is on the garage button, let its own .down handle it
// Use global coordinates for tap and garageBtn
var btnGlobalX = garageBtn.x;
var btnGlobalY = garageBtn.y;
var btnBounds = {
x: btnGlobalX - garageBtn.width * garageBtn.anchor.x,
y: btnGlobalY - garageBtn.height * garageBtn.anchor.y,
width: garageBtn.width,
height: garageBtn.height
};
if (x >= btnBounds.x && x <= btnBounds.x + btnBounds.width && y >= btnBounds.y && y <= btnBounds.y + btnBounds.height) {
// Let garageBtn.down handle it, do NOT start the game
return;
}
// Otherwise, start the game ONLY if not tapping garage button
if (!garageOverlay.visible) {
startOverlay.visible = false;
gameStarted = true;
// --- Set car asset based on selection ---
if (selectedCarId === 'araba2' && unlockedCars.araba2) {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('araba2', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
// Fix araba2 to be visually on top of the road
car.y = groundY + 5; // Adjust as needed for best look
} else if (selectedCarId === 'araba3' && unlockedCars.araba3) {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('araba3', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
// Fix araba3 to be visually on top of the road
car.y = groundY + 10; // Adjust as needed for best look
} else {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('car', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
}
updateLivesDisplay();
// Stop menu music when game starts
LK.stopMusic();
return;
}
}
car.jump();
};
// --- Bulut (cloud) background logic ---
// Only define bulutlar array here, spawn logic will be in game.update after gameStarted
if (typeof bulutlar === "undefined") {
var bulutlar = [];
}
// Main update loop
game.update = function () {
if (!gameStarted) return;
// --- Bulut (cloud) background logic (spawn/update only after gameStarted) ---
if (bulutlar.length === 0) {
// Spawn initial clouds
for (var i = 0; i < 4; i++) {
var bulut = new Bulut();
bulut.x = 400 + i * 500 + Math.random() * 200;
bulut.y = 400 + Math.random() * 600;
bulut.speed = 1.2 + Math.random() * 1.8;
game.addChild(bulut);
bulutlar.push(bulut);
}
}
for (var i = bulutlar.length - 1; i >= 0; i--) {
var bulut = bulutlar[i];
bulut.update();
if (bulut.x < -bulut.width / 2) {
bulut.destroy();
bulutlar.splice(i, 1);
}
}
// Spawn new bulut if needed
if (bulutlar.length < 4) {
var bulut = new Bulut();
bulut.x = 2048 + bulut.width / 2 + Math.random() * 200;
bulut.y = 300 + Math.random() * 800;
bulut.speed = 1.2 + Math.random() * 1.8;
game.addChild(bulut);
bulutlar.push(bulut);
}
ticksSinceStart++;
// Increase game speed smoothly over time for gradual acceleration
if (gameSpeed < maxGameSpeed) {
// Accelerate slowly at first, then faster as time goes on
// The divisor controls how quickly speed ramps up (higher = slower ramp)
var speedup = ticksSinceStart / 60 * 0.18; // 0.18 px/frame/sec, adjust for feel
gameSpeed = initialGameSpeed + speedup;
if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed;
}
// --- Car slow effect logic ---
if (carIsSlowed) {
carSlowTicks++;
if (carSlowTicks >= carSlowDuration) {
carIsSlowed = false;
carSlowTicks = 0;
}
}
var effectiveGameSpeed = carIsSlowed ? gameSpeed * carSlowSpeedFactor : gameSpeed;
// Update car
car.update();
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
// If car is slowed, move obstacles at slowed speed for 2 seconds
if (carIsSlowed) {
var speed = gameSpeed * carSlowSpeedFactor;
obs.x -= speed - (typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed);
}
obs.update();
// Remove if off screen
if (obs.x < -200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var obsLeft = obs.x - obs.bodyWidth / 2;
var obsRight = obs.x + obs.bodyWidth / 2;
var obsTop = obs.y - obs.bodyHeight;
var obsBottom = obs.y;
var intersect = !(carRight < obsLeft || carLeft > obsRight || carBottom < obsTop + 10 || carTop > obsBottom - 10);
if (intersect) {
if (goldHeartActive) {
// No damage, just animate obstacle tumble and remove after
if (typeof tween !== "undefined" && typeof tween.create === "function") {
// Animate: rotate 1.5 turns, fall down
tween.create(obs, {
rotation: obs.rotation + Math.PI * 3,
y: obs.y + 400,
alpha: 0.2
}, 700, {
easing: "easeInCubic"
}).then(function () {
obs.destroy();
});
} else {
obs.destroy();
}
obstacles.splice(i, 1);
continue;
}
// Play 'engellenmek' sound on collision
LK.getSound('engellenmek').play();
// Removed LK.effects.flashScreen(0xff0000, 800);
lives--;
updateLivesDisplay();
// --- Trigger car slow effect for 2 seconds ---
carIsSlowed = true;
carSlowTicks = 0;
if (lives <= 0) {
// Stop car movement and animate tilt to indicate a crash
car.vy = 0;
car.isJumping = false;
// Animate car tilt to crash angle (0.35 radians) smoothly
if (typeof tween !== "undefined" && typeof tween.create === "function") {
tween.create(car.children[0], {
rotation: 0.35
}, 320, {
easing: "easeOutCubic"
});
} else {
car.children[0].rotation = 0.35;
}
LK.setScore(score); // Ensure latest score is set
lastScore = score;
lastScoreTxt.setText("Son Skor: " + lastScore);
// Update highest score if needed
if (score > highestScore) {
highestScore = score;
storage.highestScore = highestScore;
highestScoreTxt.setText("En Yüksek Skor: " + highestScore);
} else {
// Always show the stored value if not beaten
highestScoreTxt.setText("En Yüksek Skor: " + (storage.highestScore || highestScore));
}
LK.showGameOver();
return;
} else {
// Animate obstacle tumble and falling to ground, then remove after
if (typeof tween !== "undefined" && typeof tween.create === "function") {
tween.create(obs, {
rotation: obs.rotation + Math.PI * 3,
y: groundY + obs.height,
alpha: 0.2
}, 700, {
easing: "easeInCubic"
}).then(function () {
obs.destroy();
});
} else {
obs.destroy();
}
obstacles.splice(i, 1);
continue;
}
}
// Score: passed obstacle
if (!obs.passed && obs.x + obs.bodyWidth / 2 < car.x - car.bodyWidth / 2) {
obs.passed = true;
score++;
scoreTxt.setText(score);
}
}
// --- Heart collectibles update and collision ---
for (var i = heartCollectibles.length - 1; i >= 0; i--) {
var heart = heartCollectibles[i];
// If car is slowed, move hearts at slowed speed for 2 seconds
if (carIsSlowed) {
var speed = gameSpeed * carSlowSpeedFactor;
heart.x -= speed - (typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed);
}
heart.update();
// Remove if off screen
if (heart.x < -200) {
heart.destroy();
heartCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var heartLeft = heart.x - heart.bodyWidth / 2;
var heartRight = heart.x + heart.bodyWidth / 2;
var heartTop = heart.y - heart.bodyHeight;
var heartBottom = heart.y;
var intersect = !(carRight < heartLeft || carLeft > heartRight || carBottom < heartTop + 10 || carTop > heartBottom - 10);
if (intersect) {
if (!goldHeartActive) {
// Always increase lives, up to a hard cap of 9
if (lives < 9) {
lives++;
// If we go above maxLives, update maxLives to match (so display shows more hearts)
if (lives > maxLives) maxLives = lives;
updateLivesDisplay();
}
}
// Remove the heart collectible
heart.destroy();
heartCollectibles.splice(i, 1);
continue;
}
}
// --- Gold heart collectibles update and collision ---
for (var i = goldHeartCollectibles.length - 1; i >= 0; i--) {
var goldHeart = goldHeartCollectibles[i];
goldHeart.update();
if (goldHeart.x < -200) {
goldHeart.destroy();
goldHeartCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var goldLeft = goldHeart.x - goldHeart.bodyWidth / 2;
var goldRight = goldHeart.x + goldHeart.bodyWidth / 2;
var goldTop = goldHeart.y - goldHeart.bodyHeight;
var goldBottom = goldHeart.y;
var intersect = !(carRight < goldLeft || carLeft > goldRight || carBottom < goldTop + 10 || carTop > goldBottom - 10);
if (intersect) {
// Activate gold heart mode
goldHeartActive = true;
goldHeartTimer = 0;
// Fill lives to max when gold heart is collected
lives = maxLives;
// Cap goldHeartDisplayLives to 9 to prevent overflow
goldHeartDisplayLives = lives > 9 ? 9 : lives;
updateLivesDisplay();
// Remove the gold heart collectible
goldHeart.destroy();
goldHeartCollectibles.splice(i, 1);
continue;
}
}
// --- Coin collectibles update and collision ---
for (var i = coinCollectibles.length - 1; i >= 0; i--) {
var coin = coinCollectibles[i];
coin.update();
if (coin.x < -200) {
coin.destroy();
coinCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var coinLeft = coin.x - coin.bodyWidth / 2;
var coinRight = coin.x + coin.bodyWidth / 2;
var coinTop = coin.y - coin.bodyHeight;
var coinBottom = coin.y;
var intersect = !(carRight < coinLeft || carLeft > coinRight || carBottom < coinTop + 10 || carTop > coinBottom - 10);
if (intersect) {
// Add coin, persistently
coins++;
storage.coins = coins;
updateCoinDisplay();
coin.destroy();
coinCollectibles.splice(i, 1);
continue;
}
}
// Gold heart timer logic
if (goldHeartActive) {
goldHeartTimer++;
if (goldHeartTimer >= goldHeartDuration) {
goldHeartActive = false;
goldHeartTimer = 0;
updateLivesDisplay();
}
}
// Spawn new obstacles
if (obstacles.length === 0 || obstacles.length > 0 && obstacles[obstacles.length - 1].x < 2048 - getNextGap()) {
var obs = new Obstacle();
obs.x = 2048 + 100;
obs.y = groundY;
game.addChild(obs);
obstacles.push(obs);
}
// --- Spawn heart collectibles and coins occasionally ---
if ((heartCollectibles.length === 0 || heartCollectibles.length > 0 && heartCollectibles[heartCollectibles.length - 1].x < 2048 - getNextHeartGap()) && goldHeartCollectibles.length === 0 // Only one gold heart at a time
) {
var rand = Math.random();
if (rand < 0.02) {
// 2% chance: spawn gold heart
var goldHeart = new GoldHeartCollectible();
goldHeart.x = 2048 + 200 + Math.floor(Math.random() * 600);
goldHeart.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(goldHeart);
goldHeartCollectibles.push(goldHeart);
} else if (rand < 0.44) {
// 39% chance: spawn normal heart (reduced by 1%)
var heart = new HeartCollectible();
heart.x = 2048 + 200 + Math.floor(Math.random() * 600);
heart.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(heart);
heartCollectibles.push(heart);
} else if (rand < 1.00) {
// 56% chance: spawn coin (increased by 15%)
var coin = new CoinCollectible();
coin.x = 2048 + 200 + Math.floor(Math.random() * 600);
coin.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(coin);
coinCollectibles.push(coin);
}
}
};
// Helper: get next gap (randomized, gets smaller as speed increases)
function getNextGap() {
var minGap = obstacleMinGap + 400 - Math.floor((gameSpeed - initialGameSpeed) * 10);
var maxGap = obstacleMaxGap + 600 - Math.floor((gameSpeed - initialGameSpeed) * 12);
if (minGap < 720) minGap = 720;
if (maxGap < 1000) maxGap = 1000;
// 45% chance to make a much wider gap, otherwise normal
if (Math.random() < 0.45) {
// Extra wide gap (make even wider)
var extraMin = maxGap + 700;
var extraMax = maxGap + 1600;
return extraMin + Math.floor(Math.random() * (extraMax - extraMin));
} else {
// Normal gap
return minGap + Math.floor(Math.random() * (maxGap - minGap));
}
}
// Helper: get next heart collectible gap (randomized, not too frequent)
function getNextHeartGap() {
// Hearts are less frequent than obstacles
var minGap = 1200;
var maxGap = 2200;
return minGap + Math.floor(Math.random() * (maxGap - minGap));
}
// Reset score and lives on game start
LK.setScore(0);
score = 0;
// On game start, reset maxLives to 3, but if lives was higher, keep it (up to 9)
if (lives > 3) {
maxLives = lives > 9 ? 9 : lives;
} else {
maxLives = 3;
}
lives = maxLives;
scoreTxt.setText(score);
lastScoreTxt.setText("");
updateLivesDisplay();
heartCollectibles = [];
goldHeartCollectibles = [];
coinCollectibles = [];
goldHeartActive = false;
goldHeartTimer = 0;
goldHeartDisplayLives = 0;
updateCoinDisplay();
if ((storage.highestScore || highestScore) > 0) {
highestScoreTxt.setText("En Yüksek Skor: " + (storage.highestScore || highestScore));
} else {
highestScoreTxt.setText("");
}
// Show leaderboard button in the top GUI, right side
var leaderboardBtn = new Text2("🏆", {
size: 110,
fill: 0xffff00
});
leaderboardBtn.anchor.set(1, 0); // right-top
leaderboardBtn.x = -40; // offset from right edge
leaderboardBtn.y = 0;
leaderboardBtn.interactive = true;
leaderboardBtn.buttonMode = true;
leaderboardBtn.down = function (x, y, obj) {
LK.showLeaderboard();
};
LK.gui.topRight.addChild(leaderboardBtn); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highestScore: 0
});
/****
* Classes
****/
// Bulut (Cloud) background object class
var Bulut = Container.expand(function () {
var self = Container.call(this);
var bulutImg = self.attachAsset('bulut', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = bulutImg.width;
self.height = bulutImg.height;
// Set a random speed for parallax effect
self.speed = 2 + Math.random() * 2;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
// Car body
var carBody = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 1
});
// Physics
self.vy = 0; // vertical velocity
self.isJumping = false;
// Car size for collision
self.bodyWidth = carBody.width;
self.bodyHeight = carBody.height;
// Update method
self.update = function () {
// Gravity and jump physics
self.y += self.vy;
if (self.y < groundY) {
self.vy += gravity;
if (self.y > groundY) {
self.y = groundY;
self.vy = 0;
self.isJumping = false;
}
} else if (self.y > groundY) {
self.y = groundY;
self.vy = 0;
self.isJumping = false;
}
};
// Jump method
self.jump = function () {
if (!self.isJumping && self.y >= groundY) {
self.vy = jumpVelocity;
self.isJumping = true;
LK.getSound('jump').play();
// Tilt the car slightly when jumping, then return to normal after 300ms
if (typeof tween !== "undefined" && typeof tween.create === "function") {
// Animate tilt to -0.25
var tiltTween = tween.create(carBody, {
rotation: -0.25
}, 120, {
easing: "easeOutCubic"
});
tiltTween.then(function () {
// Animate back to 0
var resetTween = tween.create(carBody, {
rotation: 0
}, 180, {
easing: "easeInCubic"
});
// No need to do anything after reset
});
} else {
carBody.rotation = -0.25;
// Fallback: reset after 300ms
LK.setTimeout(function () {
carBody.rotation = 0;
}, 300);
}
}
};
return self;
});
// Coin collectible class
var CoinCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a yellow coin emoji for collectible
var coinTxt = new Text2('🪙', {
size: 110,
fill: 0xffe066 // gold/yellow
});
coinTxt.anchor.set(0.5, 1);
self.addChild(coinTxt);
self.width = coinTxt.width;
self.height = coinTxt.height;
// For collision
self.bodyWidth = coinTxt.width;
self.bodyHeight = coinTxt.height;
self.update = function () {
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Gold heart collectible class
var GoldHeartCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a text heart for collectible, gold color
var goldHeartTxt = new Text2('❤', {
size: 120,
fill: 0xffd700 // gold
});
goldHeartTxt.anchor.set(0.5, 1);
self.addChild(goldHeartTxt);
self.width = goldHeartTxt.width;
self.height = goldHeartTxt.height;
// For collision
self.bodyWidth = goldHeartTxt.width;
self.bodyHeight = goldHeartTxt.height;
self.update = function () {
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Heart collectible class
var HeartCollectible = Container.expand(function () {
var self = Container.call(this);
// Use a text heart for collectible
var heartTxt = new Text2('❤', {
size: 92,
fill: 0xff69b4
});
heartTxt.anchor.set(0.5, 1);
self.addChild(heartTxt);
self.width = heartTxt.width;
self.height = heartTxt.height;
// For collision
self.bodyWidth = heartTxt.width;
self.bodyHeight = heartTxt.height;
// Update method
self.update = function () {
// Use effectiveGameSpeed if defined, else fallback to gameSpeed
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.width = obs.width;
self.height = obs.height;
// For collision
self.bodyWidth = obs.width;
self.bodyHeight = obs.height;
// Update method
self.update = function () {
// Use effectiveGameSpeed if defined, else fallback to gameSpeed
var speed = typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed;
self.x -= speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // sky blue
});
/****
* Game Code
****/
// Jump sound
// Road: dark gray box
// Obstacle: red box
// Wheel: black ellipse
// Car: blue box
// Constants
// Left-right moving obstacle asset
var groundY = 2200; // y position of the road top
var gravity = 4.5;
var jumpVelocity = -80;
var initialGameSpeed = 24;
var maxGameSpeed = 60;
var gameSpeed = initialGameSpeed;
var obstacleMinGap = 500;
var obstacleMaxGap = 1100;
var obstacleMinY = groundY;
var obstacleMaxY = groundY;
var minObstacleHeight = 120;
var maxObstacleHeight = 220;
// Road
var road = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY
});
game.addChild(road);
// Car
var car = new Car();
car.x = 400;
// Move the car visually closer to the road (simulate being on the road, not floating above)
car.y = groundY + 20; // 20px below the road top, adjust as needed for best look
game.addChild(car);
// --- Slow effect state ---
var carIsSlowed = false;
var carSlowTicks = 0;
var carSlowDuration = 120; // 2 seconds at 60fps
var carSlowSpeedFactor = 0.45; // 45% speed when slowed
// Obstacles
var obstacles = [];
var nextObstacleX = 2048 + 400;
// Heart collectibles
var heartCollectibles = [];
var nextHeartX = 2048 + 1200; // Start further out
// Gold heart collectibles
var goldHeartCollectibles = [];
var goldHeartActive = false;
var goldHeartTimer = 0;
var goldHeartDuration = 8 * 60; // 8 seconds at 60fps
var goldHeartDisplay = null;
var goldHeartDisplayLives = 0;
// --- Coin system ---
// Persistent coin count (load from storage, default 0)
var coins = typeof storage.coins === "number" ? storage.coins : 0;
// Coin collectibles
var coinCollectibles = [];
// Coin display
var coinTxt = new Text2('', {
size: 90,
fill: 0xffe066
});
coinTxt.anchor.set(1, 1);
coinTxt.x = -40;
coinTxt.y = -40;
LK.gui.bottomRight.addChild(coinTxt);
function updateCoinDisplay() {
coinTxt.setText("Jeton: " + coins);
}
updateCoinDisplay();
// Score
var score = 0;
var lastScore = 0;
var lives = 3; // Start with 3 lives
var maxLives = 3; // This can grow up to 9 as hearts are collected
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lives display
var livesTxt = null;
var goldHeartDisplay = null;
function updateLivesDisplay() {
if (!gameStarted) {
// Hide lives display on start screen
if (livesTxt) livesTxt.setText("");
if (goldHeartDisplay) goldHeartDisplay.setText("");
return;
}
if (!livesTxt) {
livesTxt = new Text2('', {
size: 90,
fill: 0xff4444
});
livesTxt.anchor.set(0, 1);
// Shift a bit more left to avoid coin overlap
livesTxt.x = 10;
livesTxt.y = -40;
LK.gui.bottomLeft.addChild(livesTxt);
}
if (goldHeartActive) {
// Hide normal lives, show gold hearts
if (!goldHeartDisplay) {
goldHeartDisplay = new Text2('', {
size: 76,
fill: 0xffd700
});
goldHeartDisplay.anchor.set(0, 1);
goldHeartDisplay.x = 18;
goldHeartDisplay.y = -40;
LK.gui.bottomLeft.addChild(goldHeartDisplay);
}
var hearts = '';
// Always cap display to 9 hearts maximum to prevent overflow
var displayMax = maxLives > 9 ? 9 : maxLives;
var displayLives = goldHeartDisplayLives > 9 ? 9 : goldHeartDisplayLives;
for (var i = 0; i < displayLives; i++) hearts += '❤ ';
for (var i = displayLives; i < displayMax; i++) hearts += '♡ ';
goldHeartDisplay.setText("Altın Can: " + hearts.trim());
livesTxt.setText(""); // Hide normal
} else {
// Show normal lives, hide gold
if (goldHeartDisplay) goldHeartDisplay.setText("");
var hearts = '';
var displayMax = maxLives > 9 ? 9 : maxLives;
for (var i = 0; i < lives && i < 9; i++) hearts += '❤ ';
for (var i = lives; i < displayMax; i++) hearts += '♡ ';
livesTxt.setText("Can: " + hearts.trim());
}
}
updateLivesDisplay();
// Last score display
var lastScoreTxt = new Text2('', {
size: 70,
fill: 0xFFD700
});
lastScoreTxt.anchor.set(0.5, 0);
lastScoreTxt.y = scoreTxt.height + 10;
LK.gui.top.addChild(lastScoreTxt);
// Highest score display (below leaderboard)
var highestScore = storage.highestScore || 0;
var highestScoreTxt = new Text2('', {
size: 70,
fill: 0x00ffcc
});
highestScoreTxt.anchor.set(0.5, 0);
highestScoreTxt.y = lastScoreTxt.y + lastScoreTxt.height + 10;
LK.gui.top.addChild(highestScoreTxt);
// Difficulty
var ticksSinceStart = 0;
// --- Start Screen Overlay ---
var gameStarted = false;
var startOverlay = new Container();
var startBg = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
startBg.alpha = 0.82;
startOverlay.addChild(startBg);
// Add a visual image to the start overlay
var startVisual = LK.getAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 700,
scaleX: 2.2,
scaleY: 2.2
});
startOverlay.addChild(startVisual);
var startText = new Text2("Araba Koşusu", {
size: 180,
fill: 0xffffff
});
startText.anchor.set(0.5, 0.5);
startText.x = 2048 / 2;
startText.y = 900;
startOverlay.addChild(startText);
var tapText = new Text2("Başlamak için ekrana dokun", {
size: 90,
fill: 0xffd700
});
tapText.anchor.set(0.5, 0.5);
tapText.x = 2048 / 2;
tapText.y = 1200;
startOverlay.addChild(tapText);
// --- Garage Button ---
var garageBtn = new Text2("Garaj", {
size: 100,
fill: 0x00bfff
});
garageBtn.anchor.set(0.5, 0.5);
// Place below the start text, centered
garageBtn.x = 2048 / 2;
garageBtn.y = 1400;
garageBtn.interactive = true;
garageBtn.buttonMode = true;
// --- Garage Overlay ---
var garageOverlay = new Container();
garageOverlay.visible = false;
// Add a semi-transparent background
var garageBg = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
garageBg.alpha = 0.92;
garageOverlay.addChild(garageBg);
// Garage title
var garageTitle = new Text2("Garaj", {
size: 160,
fill: 0xffffff
});
garageTitle.anchor.set(0.5, 0.5);
garageTitle.x = 2048 / 2;
garageTitle.y = 400;
garageOverlay.addChild(garageTitle);
// --- Car selection state ---
// Persistent unlock state for cars (all unlocked)
var unlockedCars = {
car: true,
araba2: true,
araba3: true
};
var selectedCarId = storage.selectedCarId || 'car';
// Show araba (main car) character
var araba1 = LK.getAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 500,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba1);
// Show araba2 character
var araba2 = LK.getAsset('araba2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba2);
// Show araba3 character
var araba3 = LK.getAsset('araba3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 500,
y: 1000,
scaleX: 1.2,
scaleY: 1.2
});
garageOverlay.addChild(araba3);
// --- Car unlock/lock overlays and buy buttons ---
var araba2LockTxt = null;
var araba2BuyBtn = null;
var araba3LockTxt = null;
var araba3BuyBtn = null;
function updateCarLockStates() {
// All cars are unlocked, so remove any lock/buy overlays if present
if (araba2LockTxt) {
garageOverlay.removeChild(araba2LockTxt);
araba2LockTxt = null;
}
if (araba2BuyBtn) {
garageOverlay.removeChild(araba2BuyBtn);
araba2BuyBtn = null;
}
if (araba3LockTxt) {
garageOverlay.removeChild(araba3LockTxt);
araba3LockTxt = null;
}
if (araba3BuyBtn) {
garageOverlay.removeChild(araba3BuyBtn);
araba3BuyBtn = null;
}
}
updateCarLockStates();
// --- Car selection highlight ---
var araba1Border = new Container();
var araba2Border = new Container();
var araba3Border = new Container();
function updateCarSelectionHighlight() {
// Remove all children first
araba1Border.removeChildren();
araba2Border.removeChildren();
araba3Border.removeChildren();
// Draw a simple border using a colored rectangle asset
if (selectedCarId === 'car') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba1.width + 30,
height: araba1.height + 30,
x: araba1.x,
y: araba1.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba1Border.addChild(border);
}
if (selectedCarId === 'araba2') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba2.width + 30,
height: araba2.height + 30,
x: araba2.x,
y: araba2.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba2Border.addChild(border);
}
if (selectedCarId === 'araba3') {
var border = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: araba3.width + 30,
height: araba3.height + 30,
x: araba3.x,
y: araba3.y
});
border.alpha = 0.18;
border.tint = 0x00ff00;
araba3Border.addChild(border);
}
}
araba1Border.x = 0;
araba1Border.y = 0;
araba2Border.x = 0;
araba2Border.y = 0;
araba3Border.x = 0;
araba3Border.y = 0;
garageOverlay.addChild(araba1Border);
garageOverlay.addChild(araba2Border);
garageOverlay.addChild(araba3Border);
updateCarSelectionHighlight();
// --- Car selection interaction ---
araba1.interactive = true;
araba1.buttonMode = true;
araba1.down = function (x, y, obj) {
selectedCarId = 'car';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
};
araba2.interactive = true;
araba2.buttonMode = true;
araba2.down = function (x, y, obj) {
if (unlockedCars.araba2) {
selectedCarId = 'araba2';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
}
};
araba3.interactive = true;
araba3.buttonMode = true;
araba3.down = function (x, y, obj) {
if (unlockedCars.araba3) {
selectedCarId = 'araba3';
storage.selectedCarId = selectedCarId;
updateCarSelectionHighlight();
}
};
// Add a close button to return to start screen
var closeGarageBtn = new Text2("Kapat", {
size: 90,
fill: 0xff4444
});
closeGarageBtn.anchor.set(0.5, 0.5);
closeGarageBtn.x = 2048 / 2;
closeGarageBtn.y = 1800;
closeGarageBtn.interactive = true;
closeGarageBtn.buttonMode = true;
closeGarageBtn.down = function (x, y, obj) {
garageOverlay.visible = false;
startOverlay.visible = true;
};
garageOverlay.addChild(closeGarageBtn);
game.addChild(garageOverlay);
garageBtn.down = function (x, y, obj) {
// Only allow navigation if still on start screen
if (!gameStarted) {
startOverlay.visible = false;
garageOverlay.visible = true;
}
};
startOverlay.addChild(garageBtn);
game.addChild(startOverlay);
// Play 'arkaplan' music on the start screen overlay
LK.playMusic('arkaplan');
// Touch handler: start game if on start screen and not tapping garage, otherwise jump
game.down = function (x, y, obj) {
if (!gameStarted) {
// If garage overlay is visible, ignore all taps except closeGarageBtn (handled by its own .down)
if (garageOverlay.visible) {
return;
}
// If tap is on the garage button, let its own .down handle it
// Use global coordinates for tap and garageBtn
var btnGlobalX = garageBtn.x;
var btnGlobalY = garageBtn.y;
var btnBounds = {
x: btnGlobalX - garageBtn.width * garageBtn.anchor.x,
y: btnGlobalY - garageBtn.height * garageBtn.anchor.y,
width: garageBtn.width,
height: garageBtn.height
};
if (x >= btnBounds.x && x <= btnBounds.x + btnBounds.width && y >= btnBounds.y && y <= btnBounds.y + btnBounds.height) {
// Let garageBtn.down handle it, do NOT start the game
return;
}
// Otherwise, start the game ONLY if not tapping garage button
if (!garageOverlay.visible) {
startOverlay.visible = false;
gameStarted = true;
// --- Set car asset based on selection ---
if (selectedCarId === 'araba2' && unlockedCars.araba2) {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('araba2', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
// Fix araba2 to be visually on top of the road
car.y = groundY + 5; // Adjust as needed for best look
} else if (selectedCarId === 'araba3' && unlockedCars.araba3) {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('araba3', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
// Fix araba3 to be visually on top of the road
car.y = groundY + 10; // Adjust as needed for best look
} else {
// Remove old car asset
if (car.children.length > 0) car.removeChild(car.children[0]);
var carBody = car.attachAsset('car', {
anchorX: 0.5,
anchorY: 1
});
car.bodyWidth = carBody.width;
car.bodyHeight = carBody.height;
}
updateLivesDisplay();
// Stop menu music when game starts
LK.stopMusic();
return;
}
}
car.jump();
};
// --- Bulut (cloud) background logic ---
// Only define bulutlar array here, spawn logic will be in game.update after gameStarted
if (typeof bulutlar === "undefined") {
var bulutlar = [];
}
// Main update loop
game.update = function () {
if (!gameStarted) return;
// --- Bulut (cloud) background logic (spawn/update only after gameStarted) ---
if (bulutlar.length === 0) {
// Spawn initial clouds
for (var i = 0; i < 4; i++) {
var bulut = new Bulut();
bulut.x = 400 + i * 500 + Math.random() * 200;
bulut.y = 400 + Math.random() * 600;
bulut.speed = 1.2 + Math.random() * 1.8;
game.addChild(bulut);
bulutlar.push(bulut);
}
}
for (var i = bulutlar.length - 1; i >= 0; i--) {
var bulut = bulutlar[i];
bulut.update();
if (bulut.x < -bulut.width / 2) {
bulut.destroy();
bulutlar.splice(i, 1);
}
}
// Spawn new bulut if needed
if (bulutlar.length < 4) {
var bulut = new Bulut();
bulut.x = 2048 + bulut.width / 2 + Math.random() * 200;
bulut.y = 300 + Math.random() * 800;
bulut.speed = 1.2 + Math.random() * 1.8;
game.addChild(bulut);
bulutlar.push(bulut);
}
ticksSinceStart++;
// Increase game speed smoothly over time for gradual acceleration
if (gameSpeed < maxGameSpeed) {
// Accelerate slowly at first, then faster as time goes on
// The divisor controls how quickly speed ramps up (higher = slower ramp)
var speedup = ticksSinceStart / 60 * 0.18; // 0.18 px/frame/sec, adjust for feel
gameSpeed = initialGameSpeed + speedup;
if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed;
}
// --- Car slow effect logic ---
if (carIsSlowed) {
carSlowTicks++;
if (carSlowTicks >= carSlowDuration) {
carIsSlowed = false;
carSlowTicks = 0;
}
}
var effectiveGameSpeed = carIsSlowed ? gameSpeed * carSlowSpeedFactor : gameSpeed;
// Update car
car.update();
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
// If car is slowed, move obstacles at slowed speed for 2 seconds
if (carIsSlowed) {
var speed = gameSpeed * carSlowSpeedFactor;
obs.x -= speed - (typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed);
}
obs.update();
// Remove if off screen
if (obs.x < -200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var obsLeft = obs.x - obs.bodyWidth / 2;
var obsRight = obs.x + obs.bodyWidth / 2;
var obsTop = obs.y - obs.bodyHeight;
var obsBottom = obs.y;
var intersect = !(carRight < obsLeft || carLeft > obsRight || carBottom < obsTop + 10 || carTop > obsBottom - 10);
if (intersect) {
if (goldHeartActive) {
// No damage, just animate obstacle tumble and remove after
if (typeof tween !== "undefined" && typeof tween.create === "function") {
// Animate: rotate 1.5 turns, fall down
tween.create(obs, {
rotation: obs.rotation + Math.PI * 3,
y: obs.y + 400,
alpha: 0.2
}, 700, {
easing: "easeInCubic"
}).then(function () {
obs.destroy();
});
} else {
obs.destroy();
}
obstacles.splice(i, 1);
continue;
}
// Play 'engellenmek' sound on collision
LK.getSound('engellenmek').play();
// Removed LK.effects.flashScreen(0xff0000, 800);
lives--;
updateLivesDisplay();
// --- Trigger car slow effect for 2 seconds ---
carIsSlowed = true;
carSlowTicks = 0;
if (lives <= 0) {
// Stop car movement and animate tilt to indicate a crash
car.vy = 0;
car.isJumping = false;
// Animate car tilt to crash angle (0.35 radians) smoothly
if (typeof tween !== "undefined" && typeof tween.create === "function") {
tween.create(car.children[0], {
rotation: 0.35
}, 320, {
easing: "easeOutCubic"
});
} else {
car.children[0].rotation = 0.35;
}
LK.setScore(score); // Ensure latest score is set
lastScore = score;
lastScoreTxt.setText("Son Skor: " + lastScore);
// Update highest score if needed
if (score > highestScore) {
highestScore = score;
storage.highestScore = highestScore;
highestScoreTxt.setText("En Yüksek Skor: " + highestScore);
} else {
// Always show the stored value if not beaten
highestScoreTxt.setText("En Yüksek Skor: " + (storage.highestScore || highestScore));
}
LK.showGameOver();
return;
} else {
// Animate obstacle tumble and falling to ground, then remove after
if (typeof tween !== "undefined" && typeof tween.create === "function") {
tween.create(obs, {
rotation: obs.rotation + Math.PI * 3,
y: groundY + obs.height,
alpha: 0.2
}, 700, {
easing: "easeInCubic"
}).then(function () {
obs.destroy();
});
} else {
obs.destroy();
}
obstacles.splice(i, 1);
continue;
}
}
// Score: passed obstacle
if (!obs.passed && obs.x + obs.bodyWidth / 2 < car.x - car.bodyWidth / 2) {
obs.passed = true;
score++;
scoreTxt.setText(score);
}
}
// --- Heart collectibles update and collision ---
for (var i = heartCollectibles.length - 1; i >= 0; i--) {
var heart = heartCollectibles[i];
// If car is slowed, move hearts at slowed speed for 2 seconds
if (carIsSlowed) {
var speed = gameSpeed * carSlowSpeedFactor;
heart.x -= speed - (typeof effectiveGameSpeed !== "undefined" ? effectiveGameSpeed : gameSpeed);
}
heart.update();
// Remove if off screen
if (heart.x < -200) {
heart.destroy();
heartCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var heartLeft = heart.x - heart.bodyWidth / 2;
var heartRight = heart.x + heart.bodyWidth / 2;
var heartTop = heart.y - heart.bodyHeight;
var heartBottom = heart.y;
var intersect = !(carRight < heartLeft || carLeft > heartRight || carBottom < heartTop + 10 || carTop > heartBottom - 10);
if (intersect) {
if (!goldHeartActive) {
// Always increase lives, up to a hard cap of 9
if (lives < 9) {
lives++;
// If we go above maxLives, update maxLives to match (so display shows more hearts)
if (lives > maxLives) maxLives = lives;
updateLivesDisplay();
}
}
// Remove the heart collectible
heart.destroy();
heartCollectibles.splice(i, 1);
continue;
}
}
// --- Gold heart collectibles update and collision ---
for (var i = goldHeartCollectibles.length - 1; i >= 0; i--) {
var goldHeart = goldHeartCollectibles[i];
goldHeart.update();
if (goldHeart.x < -200) {
goldHeart.destroy();
goldHeartCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var goldLeft = goldHeart.x - goldHeart.bodyWidth / 2;
var goldRight = goldHeart.x + goldHeart.bodyWidth / 2;
var goldTop = goldHeart.y - goldHeart.bodyHeight;
var goldBottom = goldHeart.y;
var intersect = !(carRight < goldLeft || carLeft > goldRight || carBottom < goldTop + 10 || carTop > goldBottom - 10);
if (intersect) {
// Activate gold heart mode
goldHeartActive = true;
goldHeartTimer = 0;
// Fill lives to max when gold heart is collected
lives = maxLives;
// Cap goldHeartDisplayLives to 9 to prevent overflow
goldHeartDisplayLives = lives > 9 ? 9 : lives;
updateLivesDisplay();
// Remove the gold heart collectible
goldHeart.destroy();
goldHeartCollectibles.splice(i, 1);
continue;
}
}
// --- Coin collectibles update and collision ---
for (var i = coinCollectibles.length - 1; i >= 0; i--) {
var coin = coinCollectibles[i];
coin.update();
if (coin.x < -200) {
coin.destroy();
coinCollectibles.splice(i, 1);
continue;
}
// Collision detection (AABB)
var carLeft = car.x - car.bodyWidth / 2 + 20;
var carRight = car.x + car.bodyWidth / 2 - 20;
var carTop = car.y - car.bodyHeight;
var carBottom = car.y;
var coinLeft = coin.x - coin.bodyWidth / 2;
var coinRight = coin.x + coin.bodyWidth / 2;
var coinTop = coin.y - coin.bodyHeight;
var coinBottom = coin.y;
var intersect = !(carRight < coinLeft || carLeft > coinRight || carBottom < coinTop + 10 || carTop > coinBottom - 10);
if (intersect) {
// Add coin, persistently
coins++;
storage.coins = coins;
updateCoinDisplay();
coin.destroy();
coinCollectibles.splice(i, 1);
continue;
}
}
// Gold heart timer logic
if (goldHeartActive) {
goldHeartTimer++;
if (goldHeartTimer >= goldHeartDuration) {
goldHeartActive = false;
goldHeartTimer = 0;
updateLivesDisplay();
}
}
// Spawn new obstacles
if (obstacles.length === 0 || obstacles.length > 0 && obstacles[obstacles.length - 1].x < 2048 - getNextGap()) {
var obs = new Obstacle();
obs.x = 2048 + 100;
obs.y = groundY;
game.addChild(obs);
obstacles.push(obs);
}
// --- Spawn heart collectibles and coins occasionally ---
if ((heartCollectibles.length === 0 || heartCollectibles.length > 0 && heartCollectibles[heartCollectibles.length - 1].x < 2048 - getNextHeartGap()) && goldHeartCollectibles.length === 0 // Only one gold heart at a time
) {
var rand = Math.random();
if (rand < 0.02) {
// 2% chance: spawn gold heart
var goldHeart = new GoldHeartCollectible();
goldHeart.x = 2048 + 200 + Math.floor(Math.random() * 600);
goldHeart.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(goldHeart);
goldHeartCollectibles.push(goldHeart);
} else if (rand < 0.44) {
// 39% chance: spawn normal heart (reduced by 1%)
var heart = new HeartCollectible();
heart.x = 2048 + 200 + Math.floor(Math.random() * 600);
heart.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(heart);
heartCollectibles.push(heart);
} else if (rand < 1.00) {
// 56% chance: spawn coin (increased by 15%)
var coin = new CoinCollectible();
coin.x = 2048 + 200 + Math.floor(Math.random() * 600);
coin.y = groundY - 180 - Math.floor(Math.random() * 200);
game.addChild(coin);
coinCollectibles.push(coin);
}
}
};
// Helper: get next gap (randomized, gets smaller as speed increases)
function getNextGap() {
var minGap = obstacleMinGap + 400 - Math.floor((gameSpeed - initialGameSpeed) * 10);
var maxGap = obstacleMaxGap + 600 - Math.floor((gameSpeed - initialGameSpeed) * 12);
if (minGap < 720) minGap = 720;
if (maxGap < 1000) maxGap = 1000;
// 45% chance to make a much wider gap, otherwise normal
if (Math.random() < 0.45) {
// Extra wide gap (make even wider)
var extraMin = maxGap + 700;
var extraMax = maxGap + 1600;
return extraMin + Math.floor(Math.random() * (extraMax - extraMin));
} else {
// Normal gap
return minGap + Math.floor(Math.random() * (maxGap - minGap));
}
}
// Helper: get next heart collectible gap (randomized, not too frequent)
function getNextHeartGap() {
// Hearts are less frequent than obstacles
var minGap = 1200;
var maxGap = 2200;
return minGap + Math.floor(Math.random() * (maxGap - minGap));
}
// Reset score and lives on game start
LK.setScore(0);
score = 0;
// On game start, reset maxLives to 3, but if lives was higher, keep it (up to 9)
if (lives > 3) {
maxLives = lives > 9 ? 9 : lives;
} else {
maxLives = 3;
}
lives = maxLives;
scoreTxt.setText(score);
lastScoreTxt.setText("");
updateLivesDisplay();
heartCollectibles = [];
goldHeartCollectibles = [];
coinCollectibles = [];
goldHeartActive = false;
goldHeartTimer = 0;
goldHeartDisplayLives = 0;
updateCoinDisplay();
if ((storage.highestScore || highestScore) > 0) {
highestScoreTxt.setText("En Yüksek Skor: " + (storage.highestScore || highestScore));
} else {
highestScoreTxt.setText("");
}
// Show leaderboard button in the top GUI, right side
var leaderboardBtn = new Text2("🏆", {
size: 110,
fill: 0xffff00
});
leaderboardBtn.anchor.set(1, 0); // right-top
leaderboardBtn.x = -40; // offset from right edge
leaderboardBtn.y = 0;
leaderboardBtn.interactive = true;
leaderboardBtn.buttonMode = true;
leaderboardBtn.down = function (x, y, obj) {
LK.showLeaderboard();
};
LK.gui.topRight.addChild(leaderboardBtn);
red car and driver. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
sarı renkli "Stop" tabelası. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a human. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
altın para, berrak. In-Game asset. 2d. High contrast. No shadows
pixelart, cloud. In-Game asset. 2d. High contrast. No shadows
dark green car and driver. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
blue car and driver. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat