/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- MovingPlatform Class ---
var MovingPlatform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Use a distinct color for moving platforms
var movingColor = 0xffe066;
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: movingColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Movement bounds
self.minX = 0;
self.maxX = 0;
self.speed = 6 + Math.random() * 4; // random speed
self.direction = Math.random() < 0.5 ? 1 : -1;
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
// Set movement bounds
self.setMoveBounds = function (minX, maxX) {
self.minX = minX;
self.maxX = maxX;
};
// Update method for moving
self.update = function () {
if (self.minX === self.maxX) {
return;
}
if (self.lastX === undefined) {
self.lastX = self.x;
}
self.x += self.speed * self.direction;
if (self.x < self.minX) {
self.x = self.minX;
self.direction *= -1;
} else if (self.x > self.maxX) {
self.x = self.maxX;
self.direction *= -1;
}
self.lastX = self.x;
};
return self;
});
// Do not generate platforms or add player until after character is selected and game is started
// All gameplay setup is now handled in resetGame, which is only called after character selection
// --- Platform Class ---
var Platform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Platform visual
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: PLATFORM_COLOR,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
return self;
});
// --- Player Class ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Use selected character asset, fallback to 'player'
var charId = selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player';
var asset = self.attachAsset(charId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.vx = 0;
self.vy = 0;
self.ax = 0; // acceleration x
self.ay = 0; // acceleration y
self.lastVx = 0;
self.lastVy = 0;
self.lastAx = 0;
self.lastAy = 0;
self.isJumping = false;
// Move left
self.moveLeft = function () {
self.vx = -playerMoveSpeed;
};
// Move right
self.moveRight = function () {
self.vx = playerMoveSpeed;
};
// Stop movement
self.stopMove = function () {
self.vx = 0;
};
// Jump
self.jump = function () {
self.vy = -playerJumpVelocity;
self.isJumping = true;
LK.getSound('jump').play();
};
// Update method for velocity/acceleration tracking and dynamic triggers
self.update = function () {
// Calculate acceleration
self.ax = self.vx - self.lastVx;
self.ay = self.vy - self.lastVy;
// Example: Trigger effect if horizontal velocity changes rapidly
if (self.lastVx !== undefined && Math.abs(self.vx - self.lastVx) > 10) {
// Hız çizgisi efekti veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashObject(self, 0x00ffff, 200);
}
// Example: Trigger effect if vertical acceleration exceeds threshold (e.g. falling fast)
if (self.lastAy !== undefined && Math.abs(self.ay) > 20) {
// Tehlike uyarısı veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashScreen(0xffe066, 100);
}
// Store last values for next update
self.lastVx = self.vx;
self.lastVy = self.vy;
self.lastAx = self.ax;
self.lastAy = self.ay;
};
return self;
});
// --- Powerup Class ---
var Powerup = Container.expand(function () {
var self = Container.call(this);
// Use the 'powerup' asset, centered
var asset = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.collected = false;
self.visible = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Constants ---
// analog image asset for analog control
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLATFORM_MIN_WIDTH = 300;
var PLATFORM_MAX_WIDTH = 600;
var PLATFORM_HEIGHT = 40;
var PLATFORM_MIN_GAP = 250;
var PLATFORM_MAX_GAP = 600;
var PLATFORM_SIDE_MARGIN = 100;
var PLATFORM_COLOR = 0x4ecdc4;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 400;
var playerMoveSpeed = 18;
var playerJumpVelocity = 75;
var gravity = 3.2;
var maxFallSpeed = 60;
var scrollThreshold = GAME_HEIGHT / 2;
var platformScrollSpeed = 0; // will be set dynamically
// --- State ---
var platforms = [];
var player;
var score = 0;
var scoreTxt;
var highestY = PLAYER_START_Y;
var dragDir = 0; // -1: left, 1: right, 0: none
var isTouching = false;
var lastTouchX = 0;
var lastTouchY = 0;
var gameOver = false;
// --- Trail State ---
var playerTrail = [];
var maxTrailLength = 16; // Number of trail dots
var trailDotAlphaStart = 0.35; // Starting alpha for the oldest dot
var trailDotAlphaEnd = 0.8; // Ending alpha for the newest dot
// --- Powerup State ---
var powerups = [];
var jumpBoostActive = false;
var jumpBoostTimeout = null;
// --- Helper: Spawn Powerup ---
function spawnPowerup(x, y) {
var p = new Powerup();
p.x = x;
p.y = y - 80; // float above platform
p.visible = true; // ensure powerup is visible when spawned
powerups.push(p);
game.addChild(p);
return p;
}
// --- GUI ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper: Platform Generation ---
function randomPlatformWidth() {
return PLATFORM_MIN_WIDTH + Math.floor(Math.random() * (PLATFORM_MAX_WIDTH - PLATFORM_MIN_WIDTH));
}
function randomPlatformX(width) {
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
return minX + Math.random() * (maxX - minX);
}
function randomPlatformGap() {
return PLATFORM_MIN_GAP + Math.random() * (PLATFORM_MAX_GAP - PLATFORM_MIN_GAP);
}
// --- Helper: Platform Creation ---
function createPlatform(x, y, width, type) {
// type: "moving" or undefined for normal
var plat;
if (type === "moving" || type === undefined && Math.random() < 0.25 && y < PLAYER_START_Y + 100) {
plat = new MovingPlatform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
// Set movement bounds so it doesn't go off screen
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
plat.setMoveBounds(minX, maxX);
} else {
plat = new Platform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
}
platforms.push(plat);
game.addChild(plat);
// Powerup: spawn with 10% chance on top of platform
if (Math.random() < 0.10) {
spawnPowerup(x, y - PLATFORM_HEIGHT / 2);
}
return plat;
}
// --- Helper: Remove platform ---
function removePlatform(plat) {
var idx = platforms.indexOf(plat);
if (idx !== -1) {
platforms.splice(idx, 1);
}
plat.destroy();
}
// --- Helper: Collision Detection (AABB) ---
function playerOnPlatform(player, plat) {
// Only check if player is falling
if (player.vy < 0) {
return false;
}
// Player bottom
var px = player.x;
var py = player.y + player.radius * 0.8;
// Platform bounds
var left = plat.x - plat.width / 2;
var right = plat.x + plat.width / 2;
var top = plat.y - plat.height / 2;
var bottom = plat.y + plat.height / 2;
// Require player's feet to be above the platform top in the previous frame and now inside platform
if (player.lastY !== undefined) {
var lastPy = player.lastY + player.radius * 0.8;
// Only allow landing if last frame was above platform and now inside
if (lastPy <= top &&
// last frame above platform
py > top && py < bottom &&
// now inside platform vertical bounds
px >= left && px <= right // inclusive horizontal bounds for small platforms
) {
return true;
}
}
// For very small platforms, allow a little tolerance (±1px) to ensure collision is detected
if (plat.width < 40) {
if (player.lastY !== undefined && lastPy <= top && py > top && py < bottom && px >= left - 1 && px <= right + 1) {
return true;
}
}
return false;
}
// --- Helper: Generate Initial Platforms ---
function generateInitialPlatforms() {
var y = PLAYER_START_Y + 200;
// First platform: wide, centered, at bottom
createPlatform(GAME_WIDTH / 2, y, 600);
// Remove circular platform creation, just continue with normal platforms
y -= 350;
// Generate upwards, but with fewer platforms (reduce density)
// Ensure all platforms are reachable by limiting vertical gap to jump height
var platformCount = 0;
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // s = v^2/(2g)
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP); // 90% of max jump height for margin
// --- Calculate reachable circle for the player ---
var jumpRadius = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // max jump height
var jumpCircleRadius = jumpRadius * 0.9; // 90% for margin
var circleCenterX = GAME_WIDTH / 2;
var circleCenterY = PLAYER_START_Y;
// Place at least one platform inside the jump circle and above the player
var mustHavePlatformY = PLAYER_START_Y - jumpCircleRadius * 0.7; // 70% up the circle
var mustHavePlatformX = circleCenterX + (Math.random() - 0.5) * jumpCircleRadius * 0.7; // random X inside circle
var mustHavePlatformWidth = randomPlatformWidth();
createPlatform(mustHavePlatformX, mustHavePlatformY, mustHavePlatformWidth);
// Now fill the rest of the platforms as before, but avoid overlapping the must-have platform
while (y > 400 && platformCount < 7) {
// Always use a gap that is not more than safeGap, so player can always reach
var gap = Math.min(randomPlatformGap(), safeGap);
y -= gap + 80; // slightly increase gap for fewer platforms
var width = randomPlatformWidth();
var x = randomPlatformX(width);
// Avoid placing a platform too close to the must-have platform
if (Math.abs(y - mustHavePlatformY) < 100) {
continue;
}
createPlatform(x, y, width);
platformCount++;
}
}
// --- Helper: Generate New Platforms Above ---
function generatePlatformsIfNeeded() {
// Find highest platform
var highestPlatY = GAME_HEIGHT;
for (var i = 0; i < platforms.length; i++) {
if (platforms[i].y < highestPlatY) {
highestPlatY = platforms[i].y;
}
}
// Limit to 12 platforms on screen
// Restore to original logic: just fill up with normal platforms, no must-have guarantee
while (highestPlatY > -200 && platforms.length < 12) {
var width = randomPlatformWidth();
var x = randomPlatformX(width);
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity);
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP);
var gap = Math.min(randomPlatformGap(), safeGap);
highestPlatY -= gap;
// Ensure no two platforms have the same Y coordinate (within 1px tolerance)
var yUnique = true;
for (var j = 0; j < platforms.length; j++) {
if (Math.abs(platforms[j].y - highestPlatY) < 1) {
yUnique = false;
break;
}
}
if (yUnique) {
createPlatform(x, highestPlatY, width);
} else {
// If not unique, try a slightly different gap
highestPlatY -= 10 + Math.random() * 20;
}
}
}
// --- Helper: Remove Offscreen Platforms ---
function removeOffscreenPlatforms() {
for (var i = platforms.length - 1; i >= 0; i--) {
if (platforms[i].y > GAME_HEIGHT + 200) {
removePlatform(platforms[i]);
}
}
}
// --- Helper: Update Score ---
function updateScore() {
// Score is highestY reached (inverted, since y decreases as we go up)
var newScore = Math.max(0, Math.floor((PLAYER_START_Y - highestY) / 10));
if (newScore > score) {
score = newScore;
scoreTxt.setText(score);
// Play sound at every 1000 and multiples of 1000
if (score > 0 && score % 1000 === 0) {
LK.getSound('jump').play();
}
}
}
// --- Character Selection ---
var characterOptions = [{
id: 'player',
color: 0xf67280,
label: 'Kırmızı'
}, {
id: 'player2',
color: 0x4ecdc4,
label: 'Mavi'
}, {
id: 'player3',
color: 0xffe066,
label: 'Sarı'
}];
var selectedCharacter = null;
var characterSelectNodes = [];
var characterSelectActive = false;
// --- Language State ---
var LANG_TR = 'tr';
var LANG_EN = 'en';
var currentLang = LANG_EN; // Default language is English
// --- Language Strings ---
var langStrings = {
tr: {
title: 'Zıpla!',
info: 'En yükseğe zıpla, güçlendiricileri topla!',
start: 'Başla',
lang_tr: 'Türkçe',
lang_en: 'İngilizce'
},
en: {
title: 'Jump!',
info: 'Jump as high as you can, collect powerups!',
start: 'Start',
lang_tr: 'Turkish',
lang_en: 'English'
}
};
// --- Start Menu ---
var startMenuNodes = [];
var startMenuActive = true;
var langBtnTr, langBtnEn;
function showStartMenu() {
startMenuActive = true;
// Remove any previous menu nodes
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
// Add a white background covering the whole screen using a dedicated menu background asset
var menuBg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBg);
startMenuNodes.push(menuBg);
// --- Language Toggle Buttons (Top Right) ---
// Language buttons: stack vertically, each with a colored background for visibility
var langBtnY = 80;
var langBtnSpacingY = 130;
var langBtnSize = 110;
// Türkçe button background
var langBgTr = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_TR ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY
});
game.addChild(langBgTr);
startMenuNodes.push(langBgTr);
langBtnTr = new Text2(langStrings.tr.lang_tr, {
size: 54,
fill: currentLang === LANG_TR ? "#ff9900" : "#22223B"
});
langBtnTr.anchor.set(0.5, 0.5);
langBtnTr.x = GAME_WIDTH - 140;
langBtnTr.y = langBtnY;
langBtnTr.interactive = true;
langBtnTr.buttonMode = true;
langBtnTr._lang = LANG_TR;
game.addChild(langBtnTr);
startMenuNodes.push(langBtnTr);
// English button background
var langBgEn = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_EN ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY + langBtnSpacingY
});
game.addChild(langBgEn);
startMenuNodes.push(langBgEn);
langBtnEn = new Text2(langStrings.tr.lang_en, {
size: 54,
fill: currentLang === LANG_EN ? "#ff9900" : "#22223B"
});
langBtnEn.anchor.set(0.5, 0.5);
langBtnEn.x = GAME_WIDTH - 140;
langBtnEn.y = langBtnY + langBtnSpacingY;
langBtnEn.interactive = true;
langBtnEn.buttonMode = true;
langBtnEn._lang = LANG_EN;
game.addChild(langBtnEn);
startMenuNodes.push(langBtnEn);
// --- Animated Game Title ---
var titleY = 320;
var title = new Text2(langStrings[currentLang].title, {
size: 220,
fill: 0xF67280,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.x = GAME_WIDTH / 2;
title.y = titleY;
game.addChild(title);
startMenuNodes.push(title);
// Animate title with a gentle up-down floating effect
tween(title, {
y: titleY + 40
}, {
duration: 1200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// --- Info Text ---
var infoTxt = new Text2(langStrings[currentLang].info, {
size: 70,
fill: 0x22223B
});
infoTxt.anchor.set(0.5, 0.5);
infoTxt.x = GAME_WIDTH / 2;
infoTxt.y = titleY + 160;
game.addChild(infoTxt);
startMenuNodes.push(infoTxt);
// --- Character Selection with Visual Highlight ---
var charY = GAME_HEIGHT * 0.75;
var charSpacing = 320;
var charStartX = GAME_WIDTH / 2 - (characterOptions.length - 1) / 2 * charSpacing;
for (var i = 0; i < characterOptions.length; i++) {
var opt = characterOptions[i];
// Add a highlight ring behind the character if selected
var highlight = LK.getAsset('platform', {
width: 200,
height: 200,
color: 0xfaf3dd,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
highlight.visible = selectedCharacter && selectedCharacter.id === opt.id;
highlight._charIndex = i;
game.addChild(highlight);
startMenuNodes.push(highlight);
var node = LK.getAsset(opt.id, {
width: 160,
height: 160,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
node.interactive = true;
node.buttonMode = true;
// Add colored background behind character node for visibility (instead of label)
var charBg = LK.getAsset('platform', {
width: 180,
height: 180,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
game.addChild(charBg);
startMenuNodes.push(charBg);
node._charIndex = i;
game.addChild(node);
startMenuNodes.push(node);
}
// --- Başla Button with Glow Effect ---
var btnWidth = 600;
var btnHeight = 180;
var btnY = charY - 350;
// Başla button background (removed image asset, use only colored ellipse asset)
var btnBg = LK.getAsset('baslaBtnBg', {
width: btnWidth + 40,
height: btnHeight + 40,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnBg);
startMenuNodes.push(btnBg);
var btn = LK.getAsset('baslaBtnBg', {
width: btnWidth,
height: btnHeight,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
btn.interactive = true;
btn.buttonMode = true;
// Add a glowing effect to Başla button (pulsing scale)
tween(btn, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 900,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// Add an asset inside the Başla button (e.g. a star icon, using 'powerup' asset as example)
var btnInnerAsset = LK.getAsset('powerup', {
width: 90,
height: 90,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnInnerAsset);
startMenuNodes.push(btnInnerAsset);
// Başla button label (will be updated on language change)
var btnLabel = new Text2(langStrings[currentLang].start, {
size: 110,
fill: "#fff"
});
btnLabel.anchor.set(0.5, 0.5);
btnLabel.x = GAME_WIDTH / 2;
btnLabel.y = btnY;
btn._btnLabel = btnLabel; // reference for dynamic update
game.addChild(btn);
game.addChild(btnLabel);
startMenuNodes.push(btn, btnLabel);
// --- Language Button Event Handlers ---
langBtnTr.down = function () {
if (currentLang !== LANG_TR) {
currentLang = LANG_TR;
showStartMenu();
}
};
langBtnEn.down = function () {
if (currentLang !== LANG_EN) {
currentLang = LANG_EN;
showStartMenu();
}
};
// Update Başla button label if present (for language switch)
if (btn && btn._btnLabel) {
btn._btnLabel.setText(langStrings[currentLang].start);
}
}
function hideStartMenu() {
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
startMenuActive = false;
}
// Show character selection UI
// character select is now part of start menu, so this is not needed
function showCharacterSelect() {}
// character select is now part of start menu, so this is not needed
function hideCharacterSelect() {}
// Listen for down event to select character or start game
// --- Game Setup ---
function resetGame() {
// Remove old platforms
for (var i = 0; i < platforms.length; i++) {
platforms[i].destroy();
}
platforms = [];
// Remove old powerups
for (var i = 0; i < powerups.length; i++) {
powerups[i].destroy();
}
powerups = [];
// Remove player if exists
if (player) {
player.destroy();
}
// Reset state
score = 0;
scoreTxt.setText(score);
highestY = PLAYER_START_Y;
dragDir = 0;
isTouching = false;
lastTouchX = 0;
lastTouchY = 0;
gameOver = false;
// Reset trail
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].parent) {
playerTrail[i].parent.removeChild(playerTrail[i]);
}
}
playerTrail = [];
jumpBoostActive = false;
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
jumpBoostTimeout = null;
}
// Reset platform count for powerup spawn
createPlatform.platformCount = 0;
// Generate platforms
generateInitialPlatforms();
// Create player
player = new Player();
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
player.vx = 0;
player.vy = 0;
player.isJumping = false;
game.addChild(player);
// (No circular platform creation here; handled only in generateInitialPlatforms)
}
// --- Analog Button for Left/Right Movement ---
var analogBase = null,
analogKnob = null;
var analogActive = false;
var analogStartX = 0,
analogStartY = 0;
var analogDir = 0; // -1: left, 1: right, 0: none
var analogRadius = 180 * 1.2; // 1.2x larger
var analogKnobRadius = 70 * 1.2; // 1.2x larger
var analogCenterX = 0;
var analogCenterY = 0;
// Helper to create analog at a given position
function showAnalogAt(x, y) {
// Clamp analog so it doesn't go off screen (and not in top left 100x100)
var minX = 100 + analogRadius;
var maxX = GAME_WIDTH - analogRadius;
var minY = analogRadius;
var maxY = GAME_HEIGHT - analogRadius;
analogCenterX = Math.max(minX, Math.min(maxX, x));
analogCenterY = Math.max(minY, Math.min(maxY, y));
// Remove previous analog if exists
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = LK.getAsset('analog', {
width: analogRadius * 2,
height: analogRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogBase.alpha = 0.35;
analogBase.interactive = true;
analogBase.buttonMode = true;
game.addChild(analogBase);
analogKnob = LK.getAsset('analog', {
width: analogKnobRadius * 2,
height: analogKnobRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogKnob.alpha = 0.7;
analogKnob.interactive = true;
analogKnob.buttonMode = true;
game.addChild(analogKnob);
// Attach handlers
analogBase.down = analogKnob.down = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
}
// Helper to update analog direction and player movement
function updateAnalogDir(knobX, knobY) {
var dx = knobX - analogCenterX;
// Only consider horizontal movement
if (dx < -30) {
analogDir = -1;
player.moveLeft();
} else if (dx > 30) {
analogDir = 1;
player.moveRight();
} else {
analogDir = 0;
player.stopMove();
}
}
// Analog input handlers
game.down = function (x, y, obj) {
// Block all gameplay input if start menu or character select is active
if (startMenuActive || characterSelectActive) {
// If start menu is active, handle menu logic
if (startMenuActive) {
var charSelected = false;
// Check if a character was tapped
for (var i = 0; i < startMenuNodes.length; i++) {
var node = startMenuNodes[i];
if (node._charIndex !== undefined) {
var dx = x - node.x;
var dy = y - node.y;
if (dx * dx + dy * dy < 80 * 80) {
selectedCharacter = characterOptions[node._charIndex];
charSelected = true;
// Visually highlight the selected character by updating highlight rings
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = n._charIndex === node._charIndex;
}
}
}
}
}
// Check if Başla button pressed (always last two nodes)
var btn = startMenuNodes[startMenuNodes.length - 2];
var btnLabel = startMenuNodes[startMenuNodes.length - 1];
var dx = x - btn.x;
var dy = y - btn.y;
var baslaPressed = dx * dx / (btn.width * btn.width * 0.25) + dy * dy / (btn.height * btn.height * 0.25) < 1;
if (baslaPressed && selectedCharacter) {
// Show highlight for selected character before starting
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = selectedCharacter && characterOptions[n._charIndex].id === selectedCharacter.id;
}
}
// Prevent multiple presses
if (btn._pressed) {
return;
}
btn._pressed = true;
// Animate Başla button to scale up to 1.1x, then back to 1.0x, then start game after animation
tween(btn, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 120,
easing: tween.easeOutBack,
onFinish: function onFinish() {
tween(btn, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 80,
easing: tween.easeInBack,
onFinish: function onFinish() {
hideStartMenu();
resetGame();
btn._pressed = false;
}
});
}
});
return;
}
return;
}
return;
}
if (gameOver) {
return;
}
// Always remove any existing analog before creating a new one
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
analogActive = false;
// Show analog at touch location
showAnalogAt(x, y);
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
game.move = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
if (!analogActive || !analogBase || !analogKnob) {
return;
}
// Clamp knob within analog base
var dx = x - analogCenterX;
var dy = y - analogCenterY;
var dist = Math.sqrt(dx * dx + dy * dy);
var maxDist = analogRadius - analogKnobRadius;
if (dist > maxDist) {
dx = dx * maxDist / dist;
dy = dy * maxDist / dist;
}
analogKnob.x = analogCenterX + dx;
analogKnob.y = analogCenterY + dy;
updateAnalogDir(analogKnob.x, analogKnob.y);
};
game.up = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = false;
analogDir = 0;
player.stopMove();
// Hide analog after release
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
};
// --- Input Handling (Touch/Drag) ---
// This input handler is now only used for gameplay, not for menu/character select
// --- Main Game Loop ---
game.update = function () {
// Block all game updates if start menu or character select is active
if (startMenuActive || characterSelectActive) {
return;
}
if (gameOver) {
return;
}
// --- Player Physics ---
// Call player update for velocity/acceleration tracking and dynamic triggers
if (typeof player.update === "function") {
player.update();
}
// Show player only when jumping
if (player.isJumping && !player.parent) {
game.addChild(player);
} else if (!player.isJumping && player.parent) {
player.parent.removeChild(player);
}
// --- Player Trail Effect ---
// Add current player position to trail
if (!gameOver && player.parent) {
playerTrail.push({
x: player.x,
y: player.y
});
if (playerTrail.length > maxTrailLength) {
playerTrail.shift();
}
}
// Remove old trail dots if any
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].node && playerTrail[i].node.parent) {
playerTrail[i].node.parent.removeChild(playerTrail[i].node);
playerTrail[i].node = null;
}
}
// Draw trail dots (skip if not enough points)
for (var i = 0; i < playerTrail.length; i++) {
var t = i / (playerTrail.length - 1);
var alpha = trailDotAlphaStart + (trailDotAlphaEnd - trailDotAlphaStart) * t;
var dot = LK.getAsset(selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: playerTrail[i].x,
y: playerTrail[i].y
});
dot.alpha = alpha;
// Place behind player
game.addChildAt(dot, 0);
playerTrail[i].node = dot;
}
// Horizontal movement
player.x += player.vx;
// Wrap player horizontally: if player goes off right, appear at left; if off left, appear at right
if (player.x > GAME_WIDTH + player.radius) {
player.x = -player.radius;
}
if (player.x < -player.radius) {
player.x = GAME_WIDTH + player.radius;
}
// Track lastY for platform collision logic
player.lastY = player.y;
// Vertical movement
player.y += player.vy;
player.vy += gravity;
if (player.vy > maxFallSpeed) {
player.vy = maxFallSpeed;
}
// --- Update moving platforms ---
for (var i = 0; i < platforms.length; i++) {
if (typeof platforms[i].update === "function" && platforms[i] instanceof MovingPlatform) {
platforms[i].update();
}
}
// --- Platform Collision ---
var landed = false;
for (var i = 0; i < platforms.length; i++) {
if (playerOnPlatform(player, platforms[i])) {
// Only allow landing if falling
if (player.vy >= 0) {
player.y = platforms[i].y - platforms[i].height / 2 - player.radius * 0.8;
player.jump();
landed = true;
break;
}
}
}
// --- Scrolling ---
// If player is above scroll threshold, move everything down
if (player.y < scrollThreshold) {
var dy = scrollThreshold - player.y;
player.y = scrollThreshold;
// Move all platforms down
for (var i = 0; i < platforms.length; i++) {
platforms[i].y += dy;
}
// Move all powerups down (so they behave like platforms)
for (var i = 0; i < powerups.length; i++) {
powerups[i].y += dy;
}
// Move backgrounds down
bg1.y += dy;
bg2.y += dy;
// If a background moves completely below the screen, move it above the other for seamless repeat
if (bg1.y >= GAME_HEIGHT) {
bg1.y = bg2.y - BG_HEIGHT;
}
if (bg2.y >= GAME_HEIGHT) {
bg2.y = bg1.y - BG_HEIGHT;
}
// Track highestY
highestY -= dy;
} else {
// Track highestY
if (player.y < highestY) {
highestY = player.y;
}
// Also update backgrounds to follow player if not scrolling
// (keeps backgrounds in sync if player falls)
// This ensures backgrounds always cover the screen
if (bg1.y > 0 && bg2.y > 0) {
// If both are above, move the one further down above the other
if (bg1.y > bg2.y) {
bg1.y = bg2.y - BG_HEIGHT;
} else {
bg2.y = bg1.y - BG_HEIGHT;
}
}
if (bg1.y < -BG_HEIGHT) {
bg1.y = bg2.y + BG_HEIGHT;
}
if (bg2.y < -BG_HEIGHT) {
bg2.y = bg1.y + BG_HEIGHT;
}
}
// --- Remove Offscreen Platforms & Generate New Ones ---
removeOffscreenPlatforms();
generatePlatformsIfNeeded();
// --- Powerup Collision ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (!p.collected) {
// Simple circle collision
var dx = player.x - p.x;
var dy = player.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.radius + p.radius) {
// Collect powerup
p.collected = true;
p.visible = false;
// Activate jump boost
jumpBoostActive = true;
playerJumpVelocity = 90; // 1.5x original (60)
// Clear previous timeout if any
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
}
jumpBoostTimeout = LK.setTimeout(function () {
jumpBoostActive = false;
playerJumpVelocity = 60;
jumpBoostTimeout = null;
}, 3000);
}
}
}
// --- Remove collected/offscreen powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (p.collected || p.y > GAME_HEIGHT + 200) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Update Score ---
updateScore();
// --- Game Over: Fell below screen ---
if (player.y > GAME_HEIGHT + 200) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
};
// --- Start Game ---
// Only show start menu at very beginning, do not start/reset game until after character is selected
showStartMenu();
// --- Repeating Background Setup ---
var BG_HEIGHT = GAME_HEIGHT;
var BG_WIDTH = GAME_WIDTH;
// Create two background assets for seamless vertical repeat
var bg1 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: BG_WIDTH,
height: BG_HEIGHT
});
var bg2 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: -BG_HEIGHT,
width: BG_WIDTH,
height: BG_HEIGHT
});
game.addChildAt(bg1, 0);
game.addChildAt(bg2, 0);
// --- Background Repeat Logic ---
// Move this logic into game.update; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- MovingPlatform Class ---
var MovingPlatform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Use a distinct color for moving platforms
var movingColor = 0xffe066;
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: movingColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Movement bounds
self.minX = 0;
self.maxX = 0;
self.speed = 6 + Math.random() * 4; // random speed
self.direction = Math.random() < 0.5 ? 1 : -1;
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
// Set movement bounds
self.setMoveBounds = function (minX, maxX) {
self.minX = minX;
self.maxX = maxX;
};
// Update method for moving
self.update = function () {
if (self.minX === self.maxX) {
return;
}
if (self.lastX === undefined) {
self.lastX = self.x;
}
self.x += self.speed * self.direction;
if (self.x < self.minX) {
self.x = self.minX;
self.direction *= -1;
} else if (self.x > self.maxX) {
self.x = self.maxX;
self.direction *= -1;
}
self.lastX = self.x;
};
return self;
});
// Do not generate platforms or add player until after character is selected and game is started
// All gameplay setup is now handled in resetGame, which is only called after character selection
// --- Platform Class ---
var Platform = Container.expand(function () {
var self = Container.call(this);
// Default size
self.width = 400;
self.height = 40;
// Platform visual
var platAsset = self.attachAsset('platform', {
width: self.width,
height: self.height,
color: PLATFORM_COLOR,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Set platform size
self.setSize = function (w, h) {
self.width = w;
self.height = h;
platAsset.width = w;
platAsset.height = h;
};
return self;
});
// --- Player Class ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Use selected character asset, fallback to 'player'
var charId = selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player';
var asset = self.attachAsset(charId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.vx = 0;
self.vy = 0;
self.ax = 0; // acceleration x
self.ay = 0; // acceleration y
self.lastVx = 0;
self.lastVy = 0;
self.lastAx = 0;
self.lastAy = 0;
self.isJumping = false;
// Move left
self.moveLeft = function () {
self.vx = -playerMoveSpeed;
};
// Move right
self.moveRight = function () {
self.vx = playerMoveSpeed;
};
// Stop movement
self.stopMove = function () {
self.vx = 0;
};
// Jump
self.jump = function () {
self.vy = -playerJumpVelocity;
self.isJumping = true;
LK.getSound('jump').play();
};
// Update method for velocity/acceleration tracking and dynamic triggers
self.update = function () {
// Calculate acceleration
self.ax = self.vx - self.lastVx;
self.ay = self.vy - self.lastVy;
// Example: Trigger effect if horizontal velocity changes rapidly
if (self.lastVx !== undefined && Math.abs(self.vx - self.lastVx) > 10) {
// Hız çizgisi efekti veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashObject(self, 0x00ffff, 200);
}
// Example: Trigger effect if vertical acceleration exceeds threshold (e.g. falling fast)
if (self.lastAy !== undefined && Math.abs(self.ay) > 20) {
// Tehlike uyarısı veya başka bir efekt tetiklenebilir
// Örnek: LK.effects.flashScreen(0xffe066, 100);
}
// Store last values for next update
self.lastVx = self.vx;
self.lastVy = self.vy;
self.lastAx = self.ax;
self.lastAy = self.ay;
};
return self;
});
// --- Powerup Class ---
var Powerup = Container.expand(function () {
var self = Container.call(this);
// Use the 'powerup' asset, centered
var asset = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 50; // for collision, matches asset size
self.collected = false;
self.visible = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Constants ---
// analog image asset for analog control
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLATFORM_MIN_WIDTH = 300;
var PLATFORM_MAX_WIDTH = 600;
var PLATFORM_HEIGHT = 40;
var PLATFORM_MIN_GAP = 250;
var PLATFORM_MAX_GAP = 600;
var PLATFORM_SIDE_MARGIN = 100;
var PLATFORM_COLOR = 0x4ecdc4;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 400;
var playerMoveSpeed = 18;
var playerJumpVelocity = 75;
var gravity = 3.2;
var maxFallSpeed = 60;
var scrollThreshold = GAME_HEIGHT / 2;
var platformScrollSpeed = 0; // will be set dynamically
// --- State ---
var platforms = [];
var player;
var score = 0;
var scoreTxt;
var highestY = PLAYER_START_Y;
var dragDir = 0; // -1: left, 1: right, 0: none
var isTouching = false;
var lastTouchX = 0;
var lastTouchY = 0;
var gameOver = false;
// --- Trail State ---
var playerTrail = [];
var maxTrailLength = 16; // Number of trail dots
var trailDotAlphaStart = 0.35; // Starting alpha for the oldest dot
var trailDotAlphaEnd = 0.8; // Ending alpha for the newest dot
// --- Powerup State ---
var powerups = [];
var jumpBoostActive = false;
var jumpBoostTimeout = null;
// --- Helper: Spawn Powerup ---
function spawnPowerup(x, y) {
var p = new Powerup();
p.x = x;
p.y = y - 80; // float above platform
p.visible = true; // ensure powerup is visible when spawned
powerups.push(p);
game.addChild(p);
return p;
}
// --- GUI ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Helper: Platform Generation ---
function randomPlatformWidth() {
return PLATFORM_MIN_WIDTH + Math.floor(Math.random() * (PLATFORM_MAX_WIDTH - PLATFORM_MIN_WIDTH));
}
function randomPlatformX(width) {
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
return minX + Math.random() * (maxX - minX);
}
function randomPlatformGap() {
return PLATFORM_MIN_GAP + Math.random() * (PLATFORM_MAX_GAP - PLATFORM_MIN_GAP);
}
// --- Helper: Platform Creation ---
function createPlatform(x, y, width, type) {
// type: "moving" or undefined for normal
var plat;
if (type === "moving" || type === undefined && Math.random() < 0.25 && y < PLAYER_START_Y + 100) {
plat = new MovingPlatform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
// Set movement bounds so it doesn't go off screen
var minX = PLATFORM_SIDE_MARGIN + width / 2;
var maxX = GAME_WIDTH - PLATFORM_SIDE_MARGIN - width / 2;
plat.setMoveBounds(minX, maxX);
} else {
plat = new Platform();
plat.setSize(width, PLATFORM_HEIGHT);
plat.x = x;
plat.y = y;
}
platforms.push(plat);
game.addChild(plat);
// Powerup: spawn with 10% chance on top of platform
if (Math.random() < 0.10) {
spawnPowerup(x, y - PLATFORM_HEIGHT / 2);
}
return plat;
}
// --- Helper: Remove platform ---
function removePlatform(plat) {
var idx = platforms.indexOf(plat);
if (idx !== -1) {
platforms.splice(idx, 1);
}
plat.destroy();
}
// --- Helper: Collision Detection (AABB) ---
function playerOnPlatform(player, plat) {
// Only check if player is falling
if (player.vy < 0) {
return false;
}
// Player bottom
var px = player.x;
var py = player.y + player.radius * 0.8;
// Platform bounds
var left = plat.x - plat.width / 2;
var right = plat.x + plat.width / 2;
var top = plat.y - plat.height / 2;
var bottom = plat.y + plat.height / 2;
// Require player's feet to be above the platform top in the previous frame and now inside platform
if (player.lastY !== undefined) {
var lastPy = player.lastY + player.radius * 0.8;
// Only allow landing if last frame was above platform and now inside
if (lastPy <= top &&
// last frame above platform
py > top && py < bottom &&
// now inside platform vertical bounds
px >= left && px <= right // inclusive horizontal bounds for small platforms
) {
return true;
}
}
// For very small platforms, allow a little tolerance (±1px) to ensure collision is detected
if (plat.width < 40) {
if (player.lastY !== undefined && lastPy <= top && py > top && py < bottom && px >= left - 1 && px <= right + 1) {
return true;
}
}
return false;
}
// --- Helper: Generate Initial Platforms ---
function generateInitialPlatforms() {
var y = PLAYER_START_Y + 200;
// First platform: wide, centered, at bottom
createPlatform(GAME_WIDTH / 2, y, 600);
// Remove circular platform creation, just continue with normal platforms
y -= 350;
// Generate upwards, but with fewer platforms (reduce density)
// Ensure all platforms are reachable by limiting vertical gap to jump height
var platformCount = 0;
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // s = v^2/(2g)
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP); // 90% of max jump height for margin
// --- Calculate reachable circle for the player ---
var jumpRadius = playerJumpVelocity * playerJumpVelocity / (2 * gravity); // max jump height
var jumpCircleRadius = jumpRadius * 0.9; // 90% for margin
var circleCenterX = GAME_WIDTH / 2;
var circleCenterY = PLAYER_START_Y;
// Place at least one platform inside the jump circle and above the player
var mustHavePlatformY = PLAYER_START_Y - jumpCircleRadius * 0.7; // 70% up the circle
var mustHavePlatformX = circleCenterX + (Math.random() - 0.5) * jumpCircleRadius * 0.7; // random X inside circle
var mustHavePlatformWidth = randomPlatformWidth();
createPlatform(mustHavePlatformX, mustHavePlatformY, mustHavePlatformWidth);
// Now fill the rest of the platforms as before, but avoid overlapping the must-have platform
while (y > 400 && platformCount < 7) {
// Always use a gap that is not more than safeGap, so player can always reach
var gap = Math.min(randomPlatformGap(), safeGap);
y -= gap + 80; // slightly increase gap for fewer platforms
var width = randomPlatformWidth();
var x = randomPlatformX(width);
// Avoid placing a platform too close to the must-have platform
if (Math.abs(y - mustHavePlatformY) < 100) {
continue;
}
createPlatform(x, y, width);
platformCount++;
}
}
// --- Helper: Generate New Platforms Above ---
function generatePlatformsIfNeeded() {
// Find highest platform
var highestPlatY = GAME_HEIGHT;
for (var i = 0; i < platforms.length; i++) {
if (platforms[i].y < highestPlatY) {
highestPlatY = platforms[i].y;
}
}
// Limit to 12 platforms on screen
// Restore to original logic: just fill up with normal platforms, no must-have guarantee
while (highestPlatY > -200 && platforms.length < 12) {
var width = randomPlatformWidth();
var x = randomPlatformX(width);
var maxJumpHeight = playerJumpVelocity * playerJumpVelocity / (2 * gravity);
var safeGap = Math.min(maxJumpHeight * 0.9, PLATFORM_MAX_GAP);
var gap = Math.min(randomPlatformGap(), safeGap);
highestPlatY -= gap;
// Ensure no two platforms have the same Y coordinate (within 1px tolerance)
var yUnique = true;
for (var j = 0; j < platforms.length; j++) {
if (Math.abs(platforms[j].y - highestPlatY) < 1) {
yUnique = false;
break;
}
}
if (yUnique) {
createPlatform(x, highestPlatY, width);
} else {
// If not unique, try a slightly different gap
highestPlatY -= 10 + Math.random() * 20;
}
}
}
// --- Helper: Remove Offscreen Platforms ---
function removeOffscreenPlatforms() {
for (var i = platforms.length - 1; i >= 0; i--) {
if (platforms[i].y > GAME_HEIGHT + 200) {
removePlatform(platforms[i]);
}
}
}
// --- Helper: Update Score ---
function updateScore() {
// Score is highestY reached (inverted, since y decreases as we go up)
var newScore = Math.max(0, Math.floor((PLAYER_START_Y - highestY) / 10));
if (newScore > score) {
score = newScore;
scoreTxt.setText(score);
// Play sound at every 1000 and multiples of 1000
if (score > 0 && score % 1000 === 0) {
LK.getSound('jump').play();
}
}
}
// --- Character Selection ---
var characterOptions = [{
id: 'player',
color: 0xf67280,
label: 'Kırmızı'
}, {
id: 'player2',
color: 0x4ecdc4,
label: 'Mavi'
}, {
id: 'player3',
color: 0xffe066,
label: 'Sarı'
}];
var selectedCharacter = null;
var characterSelectNodes = [];
var characterSelectActive = false;
// --- Language State ---
var LANG_TR = 'tr';
var LANG_EN = 'en';
var currentLang = LANG_EN; // Default language is English
// --- Language Strings ---
var langStrings = {
tr: {
title: 'Zıpla!',
info: 'En yükseğe zıpla, güçlendiricileri topla!',
start: 'Başla',
lang_tr: 'Türkçe',
lang_en: 'İngilizce'
},
en: {
title: 'Jump!',
info: 'Jump as high as you can, collect powerups!',
start: 'Start',
lang_tr: 'Turkish',
lang_en: 'English'
}
};
// --- Start Menu ---
var startMenuNodes = [];
var startMenuActive = true;
var langBtnTr, langBtnEn;
function showStartMenu() {
startMenuActive = true;
// Remove any previous menu nodes
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
// Add a white background covering the whole screen using a dedicated menu background asset
var menuBg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBg);
startMenuNodes.push(menuBg);
// --- Language Toggle Buttons (Top Right) ---
// Language buttons: stack vertically, each with a colored background for visibility
var langBtnY = 80;
var langBtnSpacingY = 130;
var langBtnSize = 110;
// Türkçe button background
var langBgTr = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_TR ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY
});
game.addChild(langBgTr);
startMenuNodes.push(langBgTr);
langBtnTr = new Text2(langStrings.tr.lang_tr, {
size: 54,
fill: currentLang === LANG_TR ? "#ff9900" : "#22223B"
});
langBtnTr.anchor.set(0.5, 0.5);
langBtnTr.x = GAME_WIDTH - 140;
langBtnTr.y = langBtnY;
langBtnTr.interactive = true;
langBtnTr.buttonMode = true;
langBtnTr._lang = LANG_TR;
game.addChild(langBtnTr);
startMenuNodes.push(langBtnTr);
// English button background
var langBgEn = LK.getAsset('platform', {
width: 220,
height: 90,
color: currentLang === LANG_EN ? 0xffe066 : 0xfaf3dd,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH - 140,
y: langBtnY + langBtnSpacingY
});
game.addChild(langBgEn);
startMenuNodes.push(langBgEn);
langBtnEn = new Text2(langStrings.tr.lang_en, {
size: 54,
fill: currentLang === LANG_EN ? "#ff9900" : "#22223B"
});
langBtnEn.anchor.set(0.5, 0.5);
langBtnEn.x = GAME_WIDTH - 140;
langBtnEn.y = langBtnY + langBtnSpacingY;
langBtnEn.interactive = true;
langBtnEn.buttonMode = true;
langBtnEn._lang = LANG_EN;
game.addChild(langBtnEn);
startMenuNodes.push(langBtnEn);
// --- Animated Game Title ---
var titleY = 320;
var title = new Text2(langStrings[currentLang].title, {
size: 220,
fill: 0xF67280,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0.5);
title.x = GAME_WIDTH / 2;
title.y = titleY;
game.addChild(title);
startMenuNodes.push(title);
// Animate title with a gentle up-down floating effect
tween(title, {
y: titleY + 40
}, {
duration: 1200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// --- Info Text ---
var infoTxt = new Text2(langStrings[currentLang].info, {
size: 70,
fill: 0x22223B
});
infoTxt.anchor.set(0.5, 0.5);
infoTxt.x = GAME_WIDTH / 2;
infoTxt.y = titleY + 160;
game.addChild(infoTxt);
startMenuNodes.push(infoTxt);
// --- Character Selection with Visual Highlight ---
var charY = GAME_HEIGHT * 0.75;
var charSpacing = 320;
var charStartX = GAME_WIDTH / 2 - (characterOptions.length - 1) / 2 * charSpacing;
for (var i = 0; i < characterOptions.length; i++) {
var opt = characterOptions[i];
// Add a highlight ring behind the character if selected
var highlight = LK.getAsset('platform', {
width: 200,
height: 200,
color: 0xfaf3dd,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
highlight.visible = selectedCharacter && selectedCharacter.id === opt.id;
highlight._charIndex = i;
game.addChild(highlight);
startMenuNodes.push(highlight);
var node = LK.getAsset(opt.id, {
width: 160,
height: 160,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
node.interactive = true;
node.buttonMode = true;
// Add colored background behind character node for visibility (instead of label)
var charBg = LK.getAsset('platform', {
width: 180,
height: 180,
color: opt.color,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: charStartX + i * charSpacing,
y: charY
});
game.addChild(charBg);
startMenuNodes.push(charBg);
node._charIndex = i;
game.addChild(node);
startMenuNodes.push(node);
}
// --- Başla Button with Glow Effect ---
var btnWidth = 600;
var btnHeight = 180;
var btnY = charY - 350;
// Başla button background (removed image asset, use only colored ellipse asset)
var btnBg = LK.getAsset('baslaBtnBg', {
width: btnWidth + 40,
height: btnHeight + 40,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnBg);
startMenuNodes.push(btnBg);
var btn = LK.getAsset('baslaBtnBg', {
width: btnWidth,
height: btnHeight,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
btn.interactive = true;
btn.buttonMode = true;
// Add a glowing effect to Başla button (pulsing scale)
tween(btn, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 900,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOutSine
});
// Add an asset inside the Başla button (e.g. a star icon, using 'powerup' asset as example)
var btnInnerAsset = LK.getAsset('powerup', {
width: 90,
height: 90,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: btnY
});
game.addChild(btnInnerAsset);
startMenuNodes.push(btnInnerAsset);
// Başla button label (will be updated on language change)
var btnLabel = new Text2(langStrings[currentLang].start, {
size: 110,
fill: "#fff"
});
btnLabel.anchor.set(0.5, 0.5);
btnLabel.x = GAME_WIDTH / 2;
btnLabel.y = btnY;
btn._btnLabel = btnLabel; // reference for dynamic update
game.addChild(btn);
game.addChild(btnLabel);
startMenuNodes.push(btn, btnLabel);
// --- Language Button Event Handlers ---
langBtnTr.down = function () {
if (currentLang !== LANG_TR) {
currentLang = LANG_TR;
showStartMenu();
}
};
langBtnEn.down = function () {
if (currentLang !== LANG_EN) {
currentLang = LANG_EN;
showStartMenu();
}
};
// Update Başla button label if present (for language switch)
if (btn && btn._btnLabel) {
btn._btnLabel.setText(langStrings[currentLang].start);
}
}
function hideStartMenu() {
for (var i = 0; i < startMenuNodes.length; i++) {
if (startMenuNodes[i].parent) {
startMenuNodes[i].parent.removeChild(startMenuNodes[i]);
}
}
startMenuNodes = [];
startMenuActive = false;
}
// Show character selection UI
// character select is now part of start menu, so this is not needed
function showCharacterSelect() {}
// character select is now part of start menu, so this is not needed
function hideCharacterSelect() {}
// Listen for down event to select character or start game
// --- Game Setup ---
function resetGame() {
// Remove old platforms
for (var i = 0; i < platforms.length; i++) {
platforms[i].destroy();
}
platforms = [];
// Remove old powerups
for (var i = 0; i < powerups.length; i++) {
powerups[i].destroy();
}
powerups = [];
// Remove player if exists
if (player) {
player.destroy();
}
// Reset state
score = 0;
scoreTxt.setText(score);
highestY = PLAYER_START_Y;
dragDir = 0;
isTouching = false;
lastTouchX = 0;
lastTouchY = 0;
gameOver = false;
// Reset trail
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].parent) {
playerTrail[i].parent.removeChild(playerTrail[i]);
}
}
playerTrail = [];
jumpBoostActive = false;
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
jumpBoostTimeout = null;
}
// Reset platform count for powerup spawn
createPlatform.platformCount = 0;
// Generate platforms
generateInitialPlatforms();
// Create player
player = new Player();
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
player.vx = 0;
player.vy = 0;
player.isJumping = false;
game.addChild(player);
// (No circular platform creation here; handled only in generateInitialPlatforms)
}
// --- Analog Button for Left/Right Movement ---
var analogBase = null,
analogKnob = null;
var analogActive = false;
var analogStartX = 0,
analogStartY = 0;
var analogDir = 0; // -1: left, 1: right, 0: none
var analogRadius = 180 * 1.2; // 1.2x larger
var analogKnobRadius = 70 * 1.2; // 1.2x larger
var analogCenterX = 0;
var analogCenterY = 0;
// Helper to create analog at a given position
function showAnalogAt(x, y) {
// Clamp analog so it doesn't go off screen (and not in top left 100x100)
var minX = 100 + analogRadius;
var maxX = GAME_WIDTH - analogRadius;
var minY = analogRadius;
var maxY = GAME_HEIGHT - analogRadius;
analogCenterX = Math.max(minX, Math.min(maxX, x));
analogCenterY = Math.max(minY, Math.min(maxY, y));
// Remove previous analog if exists
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = LK.getAsset('analog', {
width: analogRadius * 2,
height: analogRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogBase.alpha = 0.35;
analogBase.interactive = true;
analogBase.buttonMode = true;
game.addChild(analogBase);
analogKnob = LK.getAsset('analog', {
width: analogKnobRadius * 2,
height: analogKnobRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
x: analogCenterX,
y: analogCenterY
});
analogKnob.alpha = 0.7;
analogKnob.interactive = true;
analogKnob.buttonMode = true;
game.addChild(analogKnob);
// Attach handlers
analogBase.down = analogKnob.down = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
}
// Helper to update analog direction and player movement
function updateAnalogDir(knobX, knobY) {
var dx = knobX - analogCenterX;
// Only consider horizontal movement
if (dx < -30) {
analogDir = -1;
player.moveLeft();
} else if (dx > 30) {
analogDir = 1;
player.moveRight();
} else {
analogDir = 0;
player.stopMove();
}
}
// Analog input handlers
game.down = function (x, y, obj) {
// Block all gameplay input if start menu or character select is active
if (startMenuActive || characterSelectActive) {
// If start menu is active, handle menu logic
if (startMenuActive) {
var charSelected = false;
// Check if a character was tapped
for (var i = 0; i < startMenuNodes.length; i++) {
var node = startMenuNodes[i];
if (node._charIndex !== undefined) {
var dx = x - node.x;
var dy = y - node.y;
if (dx * dx + dy * dy < 80 * 80) {
selectedCharacter = characterOptions[node._charIndex];
charSelected = true;
// Visually highlight the selected character by updating highlight rings
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = n._charIndex === node._charIndex;
}
}
}
}
}
// Check if Başla button pressed (always last two nodes)
var btn = startMenuNodes[startMenuNodes.length - 2];
var btnLabel = startMenuNodes[startMenuNodes.length - 1];
var dx = x - btn.x;
var dy = y - btn.y;
var baslaPressed = dx * dx / (btn.width * btn.width * 0.25) + dy * dy / (btn.height * btn.height * 0.25) < 1;
if (baslaPressed && selectedCharacter) {
// Show highlight for selected character before starting
for (var j = 0; j < startMenuNodes.length; j++) {
var n = startMenuNodes[j];
if (n._charIndex !== undefined && n.width === 200 && n.height === 200) {
n.visible = selectedCharacter && characterOptions[n._charIndex].id === selectedCharacter.id;
}
}
// Prevent multiple presses
if (btn._pressed) {
return;
}
btn._pressed = true;
// Animate Başla button to scale up to 1.1x, then back to 1.0x, then start game after animation
tween(btn, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 120,
easing: tween.easeOutBack,
onFinish: function onFinish() {
tween(btn, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 80,
easing: tween.easeInBack,
onFinish: function onFinish() {
hideStartMenu();
resetGame();
btn._pressed = false;
}
});
}
});
return;
}
return;
}
return;
}
if (gameOver) {
return;
}
// Always remove any existing analog before creating a new one
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
analogActive = false;
// Show analog at touch location
showAnalogAt(x, y);
analogActive = true;
analogStartX = x;
analogStartY = y;
analogKnob.x = x;
analogKnob.y = y;
updateAnalogDir(x, y);
};
game.move = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
if (!analogActive || !analogBase || !analogKnob) {
return;
}
// Clamp knob within analog base
var dx = x - analogCenterX;
var dy = y - analogCenterY;
var dist = Math.sqrt(dx * dx + dy * dy);
var maxDist = analogRadius - analogKnobRadius;
if (dist > maxDist) {
dx = dx * maxDist / dist;
dy = dy * maxDist / dist;
}
analogKnob.x = analogCenterX + dx;
analogKnob.y = analogCenterY + dy;
updateAnalogDir(analogKnob.x, analogKnob.y);
};
game.up = function (x, y, obj) {
if (startMenuActive || characterSelectActive || gameOver) {
return;
}
analogActive = false;
analogDir = 0;
player.stopMove();
// Hide analog after release
if (analogBase && analogBase.parent) {
analogBase.parent.removeChild(analogBase);
}
if (analogKnob && analogKnob.parent) {
analogKnob.parent.removeChild(analogKnob);
}
analogBase = null;
analogKnob = null;
};
// --- Input Handling (Touch/Drag) ---
// This input handler is now only used for gameplay, not for menu/character select
// --- Main Game Loop ---
game.update = function () {
// Block all game updates if start menu or character select is active
if (startMenuActive || characterSelectActive) {
return;
}
if (gameOver) {
return;
}
// --- Player Physics ---
// Call player update for velocity/acceleration tracking and dynamic triggers
if (typeof player.update === "function") {
player.update();
}
// Show player only when jumping
if (player.isJumping && !player.parent) {
game.addChild(player);
} else if (!player.isJumping && player.parent) {
player.parent.removeChild(player);
}
// --- Player Trail Effect ---
// Add current player position to trail
if (!gameOver && player.parent) {
playerTrail.push({
x: player.x,
y: player.y
});
if (playerTrail.length > maxTrailLength) {
playerTrail.shift();
}
}
// Remove old trail dots if any
for (var i = 0; i < playerTrail.length; i++) {
if (playerTrail[i].node && playerTrail[i].node.parent) {
playerTrail[i].node.parent.removeChild(playerTrail[i].node);
playerTrail[i].node = null;
}
}
// Draw trail dots (skip if not enough points)
for (var i = 0; i < playerTrail.length; i++) {
var t = i / (playerTrail.length - 1);
var alpha = trailDotAlphaStart + (trailDotAlphaEnd - trailDotAlphaStart) * t;
var dot = LK.getAsset(selectedCharacter && selectedCharacter.id ? selectedCharacter.id : 'player', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: playerTrail[i].x,
y: playerTrail[i].y
});
dot.alpha = alpha;
// Place behind player
game.addChildAt(dot, 0);
playerTrail[i].node = dot;
}
// Horizontal movement
player.x += player.vx;
// Wrap player horizontally: if player goes off right, appear at left; if off left, appear at right
if (player.x > GAME_WIDTH + player.radius) {
player.x = -player.radius;
}
if (player.x < -player.radius) {
player.x = GAME_WIDTH + player.radius;
}
// Track lastY for platform collision logic
player.lastY = player.y;
// Vertical movement
player.y += player.vy;
player.vy += gravity;
if (player.vy > maxFallSpeed) {
player.vy = maxFallSpeed;
}
// --- Update moving platforms ---
for (var i = 0; i < platforms.length; i++) {
if (typeof platforms[i].update === "function" && platforms[i] instanceof MovingPlatform) {
platforms[i].update();
}
}
// --- Platform Collision ---
var landed = false;
for (var i = 0; i < platforms.length; i++) {
if (playerOnPlatform(player, platforms[i])) {
// Only allow landing if falling
if (player.vy >= 0) {
player.y = platforms[i].y - platforms[i].height / 2 - player.radius * 0.8;
player.jump();
landed = true;
break;
}
}
}
// --- Scrolling ---
// If player is above scroll threshold, move everything down
if (player.y < scrollThreshold) {
var dy = scrollThreshold - player.y;
player.y = scrollThreshold;
// Move all platforms down
for (var i = 0; i < platforms.length; i++) {
platforms[i].y += dy;
}
// Move all powerups down (so they behave like platforms)
for (var i = 0; i < powerups.length; i++) {
powerups[i].y += dy;
}
// Move backgrounds down
bg1.y += dy;
bg2.y += dy;
// If a background moves completely below the screen, move it above the other for seamless repeat
if (bg1.y >= GAME_HEIGHT) {
bg1.y = bg2.y - BG_HEIGHT;
}
if (bg2.y >= GAME_HEIGHT) {
bg2.y = bg1.y - BG_HEIGHT;
}
// Track highestY
highestY -= dy;
} else {
// Track highestY
if (player.y < highestY) {
highestY = player.y;
}
// Also update backgrounds to follow player if not scrolling
// (keeps backgrounds in sync if player falls)
// This ensures backgrounds always cover the screen
if (bg1.y > 0 && bg2.y > 0) {
// If both are above, move the one further down above the other
if (bg1.y > bg2.y) {
bg1.y = bg2.y - BG_HEIGHT;
} else {
bg2.y = bg1.y - BG_HEIGHT;
}
}
if (bg1.y < -BG_HEIGHT) {
bg1.y = bg2.y + BG_HEIGHT;
}
if (bg2.y < -BG_HEIGHT) {
bg2.y = bg1.y + BG_HEIGHT;
}
}
// --- Remove Offscreen Platforms & Generate New Ones ---
removeOffscreenPlatforms();
generatePlatformsIfNeeded();
// --- Powerup Collision ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (!p.collected) {
// Simple circle collision
var dx = player.x - p.x;
var dy = player.y - p.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.radius + p.radius) {
// Collect powerup
p.collected = true;
p.visible = false;
// Activate jump boost
jumpBoostActive = true;
playerJumpVelocity = 90; // 1.5x original (60)
// Clear previous timeout if any
if (jumpBoostTimeout) {
LK.clearTimeout(jumpBoostTimeout);
}
jumpBoostTimeout = LK.setTimeout(function () {
jumpBoostActive = false;
playerJumpVelocity = 60;
jumpBoostTimeout = null;
}, 3000);
}
}
}
// --- Remove collected/offscreen powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
if (p.collected || p.y > GAME_HEIGHT + 200) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Update Score ---
updateScore();
// --- Game Over: Fell below screen ---
if (player.y > GAME_HEIGHT + 200) {
gameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
};
// --- Start Game ---
// Only show start menu at very beginning, do not start/reset game until after character is selected
showStartMenu();
// --- Repeating Background Setup ---
var BG_HEIGHT = GAME_HEIGHT;
var BG_WIDTH = GAME_WIDTH;
// Create two background assets for seamless vertical repeat
var bg1 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: BG_WIDTH,
height: BG_HEIGHT
});
var bg2 = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: -BG_HEIGHT,
width: BG_WIDTH,
height: BG_HEIGHT
});
game.addChildAt(bg1, 0);
game.addChildAt(bg2, 0);
// --- Background Repeat Logic ---
// Move this logic into game.update;
Cartoon bir astronot. In-Game asset. 2d. High contrast. No shadows
Cartoon bir astronot. In-Game asset. 2d. High contrast. No shadows
İskender kebap. In-Game asset. 2d. High contrast. No shadows
Nebula uzay arkaplan nebula oranı 3/10 olsun. In-Game asset. 2d. High contrast. No shadows
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Jump Up: Sonsuz Platform Macerası" and with the description astronot in the moon"Karakterin otomatik zıpladığı, sağ-sol yönlendirme ile platformlarda yükseldiğin sonsuz bir parkur oyunu. Platformlar rastgele ve benzersiz şekilde oluşur.". No text on banner!