/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball (player) class var Ball = Container.expand(function () { var self = Container.call(this); var ballGfx = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.7, scaleY: 1.7 }); self.radius = ballGfx.width / 2; self.vx = 0; self.vy = 0; self.launched = false; self.resting = true; // true if waiting for launch self.bounceCooldown = 0; // prevent double bounces self.health = 5; // Lives per launch self.update = function () { if (self.launched) { // Gravity self.vy += 0.1344; // Move self.x += self.vx; self.y += self.vy; // Friction (air) self.vx *= 0.995; // Clamp to game area if (self.x < self.radius) self.x = self.radius; if (self.x > 2048 - self.radius) self.x = 2048 - self.radius; // If falls below screen, game over if (self.y > 2732 + 200) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } } }; // Reset ball to start self.reset = function () { self.x = 2048 / 2; self.y = typeof ballStartY !== "undefined" ? ballStartY : 2732 - 180; self.vx = 0; self.vy = 0; self.launched = false; self.resting = true; self.bounceCooldown = 0; self.health = 5; // Reset health for new launch }; // Bounce up (from bouncy car) self.bounce = function (strength) { self.vy = -Math.abs(strength || 38 + Math.random() * 8); self.launched = true; self.resting = false; self.bounceCooldown = 10; }; return self; }); // BouncyCar (bouncy vehicle) class var BouncyCar = Container.expand(function () { var self = Container.call(this); var bouncyGfx = self.attachAsset('bouncycar', { anchorX: 0.5, anchorY: 0.5 }); self.scale.set(0.72, 0.72); self.width = bouncyGfx.width * 0.72; self.height = bouncyGfx.height * 0.72; self.speed = 0; self.lane = 0; self.type = 'bouncycar'; self.bouncePhase = Math.random() * Math.PI * 2; self.update = function () { self.x += self.speed; // Bouncy up-down self.y0 = self.y0 || self.y; self.y = self.y0 + Math.sin(LK.ticks / 12 + self.bouncePhase) * 38; if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true; if (self.speed < 0 && self.x < -200) self.destroyed = true; }; return self; }); // Car (normal) class var Car = Container.expand(function () { var self = Container.call(this); var carGfx = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5 }); self.scale.set(0.72, 0.72); self.width = carGfx.width * 0.72; self.height = carGfx.height * 0.72; self.speed = 0; self.lane = 0; self.type = 'car'; self.hitCooldown = 0; // Cooldown after being hit by ball self.update = function () { self.x += self.speed; // Update hit cooldown if (self.hitCooldown > 0) self.hitCooldown--; // Remove if out of screen if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true; if (self.speed < 0 && self.x < -200) self.destroyed = true; }; return self; }); // Motorcycle class var Motorcycle = Container.expand(function () { var self = Container.call(this); var motorcycleGfx = self.attachAsset('moto', { anchorX: 0.5, anchorY: 0.5 }); self.scale.set(0.58, 0.58); // Smaller than cars (0.72 * 0.8) self.width = motorcycleGfx.width * 0.58; self.height = motorcycleGfx.height * 0.58; self.speed = 0; self.lane = 0; self.type = 'motorcycle'; self.hitCooldown = 0; // Cooldown after being hit by ball self.hasJumped = false; // Only jump once self.lastJumpCheck = 0; self.isJumping = false; self.originalY = 0; self.originalScale = 0.58; self.update = function () { self.x += self.speed; // Update hit cooldown if (self.hitCooldown > 0) self.hitCooldown--; // Jump logic - check every second (60 ticks) with 20% chance if (!self.hasJumped && !self.isJumping && LK.ticks - self.lastJumpCheck >= 60) { self.lastJumpCheck = LK.ticks; if (Math.random() < 0.2) { self.performJump(); } } // Remove if out of screen if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true; if (self.speed < 0 && self.x < -200) self.destroyed = true; }; self.performJump = function () { if (self.hasJumped || self.isJumping) return; self.isJumping = true; self.hasJumped = true; self.originalY = self.y; // Reduce speed by 20% during jump self.originalSpeed = self.speed; self.speed = self.speed * 0.8; // Determine target lane based on current lane var targetLane; if (self.lane === 2) { // Lane 3 (index 2) targetLane = Math.random() < 0.5 ? 0 : 4; // Jump to lane 1 or 5 (index 0 or 4) } else if (self.lane === 3) { // Lane 4 (index 3) targetLane = Math.random() < 0.5 ? 1 : 5; // Jump to lane 2 or 6 (index 1 or 5) } var targetY = laneYs[targetLane]; // Calculate rotation angle based on jump direction var rotationAngle = 0; if (targetLane < self.lane) { rotationAngle = self.speed > 0 ? -0.3 : 0.3; // Upward jump } else { rotationAngle = self.speed > 0 ? 0.3 : -0.3; // Downward jump } // Enhanced jump animation with parabolic trajectory and realistic scaling var jumpDuration = 1500; var peakScale = self.originalScale * 2.2; // Increased scale for more dramatic effect var halfDuration = jumpDuration / 2; // First half: jump up with scale increase (ascending arc) tween(self, { y: self.originalY + (targetY - self.originalY) * 0.5, // Halfway point scaleX: peakScale, scaleY: peakScale, rotation: rotationAngle * 0.7 // Partial rotation during ascent }, { duration: halfDuration, easing: tween.easeOut, // Slow down as it reaches peak onFinish: function onFinish() { // Second half: descend with scale decrease (descending arc) tween(self, { y: targetY, scaleX: self.originalScale, scaleY: self.originalScale, rotation: rotationAngle }, { duration: halfDuration, easing: tween.easeIn, // Speed up as it falls onFinish: function onFinish() { // Final landing animation - quick rotation reset tween(self, { rotation: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.isJumping = false; // Restore original speed self.speed = self.originalSpeed; } }); } }); } }); // Update lane self.lane = targetLane; }; return self; }); // Truck (long vehicle) class var Truck = Container.expand(function () { var self = Container.call(this); var truckGfx = self.attachAsset('truck', { anchorX: 0.5, anchorY: 0.5 }); self.scale.set(0.72, 0.72); self.width = truckGfx.width * 0.72; self.height = truckGfx.height * 0.72; self.speed = 0; self.lane = 0; self.type = 'truck'; self.hitCooldown = 0; // Cooldown after being hit by ball self.update = function () { self.x += self.speed; // Update hit cooldown if (self.hitCooldown > 0) self.hitCooldown--; if (self.speed > 0 && self.x > 2048 + 300) self.destroyed = true; if (self.speed < 0 && self.x < -300) self.destroyed = true; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Slingshot band // Bouncy car // Truck // Car // Lane divider // Road background // Stop (target) // Ball (player) // --- Game variables --- var lanes = [0, 1, 2, 3, 4, 5]; var laneYs = []; var laneCount = 6; var verticalOffset = -200; // Move everything up by 200px var roadTop = 600 + verticalOffset; var roadHeight = 1536; var laneGap = roadHeight / laneCount; for (var i = 0; i < laneCount; ++i) { laneYs[i] = roadTop + laneGap / 2 + i * laneGap; } // Place stop (target) at the top of the road, centered horizontally var stopY = roadTop - 100; var stopX = 2048 / 2; // Update stop trigger area to match new stop (target) width var stopRadius = { x: 158, // half of stop image width (316/2), matches texture y: 90 }; // x: horizontal radius, y: vertical radius var level = 1; var vehicles = []; var ball = null; var stop = null; var slingshotBand = null; var isDragging = false; // Place the ball higher up (not at the very bottom) var ballStartY = roadTop + roadHeight + 120; var dragStart = { x: 0, y: 0 }; var dragCurrent = { x: 0, y: 0 }; var launchPower = 0; var launchAngle = 0; var canLaunch = true; var scoreTxt = null; var levelTxt = null; var winTimeout = null; // --- Draw road and lanes --- var road = LK.getAsset('road', { x: 0, y: roadTop, width: 2048, height: roadHeight }); game.addChild(road); // Lane dividers for (var i = 1; i < laneCount; ++i) { var divider = LK.getAsset('divider', { x: 0, y: roadTop + i * laneGap - 12, width: 2048, height: 24 }); game.addChild(divider); } // --- Stop (target) --- stop = LK.getAsset('stop', { anchorX: 0.5, anchorY: 0.5, x: stopX, y: stopY, scaleX: 1.2, scaleY: 1.2 }); game.addChild(stop); // --- Ball (player) --- ball = new Ball(); ball.scale.set(0.9, 0.9); game.addChild(ball); ball.reset(); // --- Slingshot band (visual) --- slingshotBand = LK.getAsset('band', { anchorX: 0.5, anchorY: 1, x: ball.x, y: ball.y, scaleX: 0.7, scaleY: 0.7 }); slingshotBand.visible = false; game.addChild(slingshotBand); // --- GUI: Score and Level --- scoreTxt = new Text2('0', { size: 110, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); levelTxt = new Text2('1', { size: 70, fill: "#fff" }); levelTxt.anchor.set(0.5, 0); LK.gui.topRight.addChild(levelTxt); // Health display var healthTxt = new Text2('♥ 5', { size: 80, fill: 0xFF4444 }); healthTxt.anchor.set(1, 0); LK.gui.topRight.addChild(healthTxt); healthTxt.x = -120; // Position to the left of level text // --- Game state --- function resetGameState() { // Remove vehicles for (var i = 0; i < vehicles.length; ++i) { vehicles[i].destroy(); } vehicles = []; // Reset ball ball.reset(); // Update health display healthTxt.setText('♥ ' + ball.health); // Reset slingshot slingshotBand.visible = false; isDragging = false; canLaunch = true; // Reset score/level LK.setScore(0); scoreTxt.setText('0'); level = 1; levelTxt.setText('1'); } resetGameState(); // --- Vehicle spawner --- function spawnVehicle() { // Check if we should spawn a motorcycle (only in lanes 2 and 3, which are indices 2 and 3) var isMotorcycleSpawn = Math.random() < 0.15; // 15% chance for motorcycle var lane; var y; var v, dir, speed; if (isMotorcycleSpawn) { // Motorcycles only spawn in lanes 3 and 4 (indices 2 and 3) lane = Math.random() < 0.5 ? 2 : 3; y = laneYs[lane]; v = new Motorcycle(); speed = (10 + level * 1.0 + Math.random() * 2) * 0.5 * 0.9; // Slightly slower than cars } else { // Randomly pick lane for other vehicles lane = Math.floor(Math.random() * laneCount); y = laneYs[lane]; // Adjusted spawn probabilities: All vehicle types reduced by 5% // Old: Car 60%, Truck 25%, BouncyCar 15% // New: Car 55%, Truck 20%, BouncyCar 10% var t = Math.random(); if (t < 0.55) { v = new Car(); speed = (12 + level * 1.2 + Math.random() * 2) * 0.5 * 0.95; } else if (t < 0.75) { v = new Truck(); speed = (8 + level * 1.1 + Math.random() * 2) * 0.5 * 0.9 * 0.95; // Truck speed reduced by 10% then by 5% } else { v = new BouncyCar(); speed = (10 + level * 1.3 + Math.random() * 2) * 0.5 * 1.1 * 0.95; // BouncyCar speed increased by 10% then reduced by 5% } } // Direction: even lanes left->right, odd right->left dir = lane % 2 === 0 ? 1 : -1; v.lane = lane; v.y = y; v.y0 = y; v.speed = speed * dir; if (dir > 0) { v.x = -200; } else { v.x = 2048 + 200; // Flip the vehicle horizontally if moving right-to-left if (v.children && v.children.length > 0 && v.children[0]) { v.children[0].scale.x = -1; } } vehicles.push(v); game.addChild(v); } // --- Level progression --- function nextLevel() { level += 1; levelTxt.setText(level + ''); // Remove all vehicles for (var i = 0; i < vehicles.length; ++i) vehicles[i].destroy(); vehicles = []; // Reset ball ball.reset(); // Update health display healthTxt.setText('♥ ' + ball.health); canLaunch = true; slingshotBand.visible = false; } // --- Game update --- game.update = function () { // Ball update ball.update(); // Vehicles update // --- Make vehicles follow the speed of the vehicle in front if they are too close in the same lane --- for (var i = vehicles.length - 1; i >= 0; --i) { var v = vehicles[i]; // Find the closest vehicle ahead in the same lane and direction var minDist = Infinity; var frontVehicle = null; for (var j = 0; j < vehicles.length; ++j) { if (i === j) continue; var v2 = vehicles[j]; if (v2.lane !== v.lane) continue; // Only consider vehicles in the same direction if (v.speed > 0 && v2.x > v.x || v.speed < 0 && v2.x < v.x) { var dist = Math.abs(v2.x - v.x); if (dist < minDist) { minDist = dist; frontVehicle = v2; } } } // If too close, match speed to the front vehicle if (frontVehicle) { // Use the larger of the two widths for minimum gap var minGap = Math.max(v.width, frontVehicle.width) * 0.85; if (minDist < minGap) { v.speed = frontVehicle.speed; // Snap position to avoid overlap if (v.speed > 0) { v.x = Math.min(v.x, frontVehicle.x - minGap + 1); } else { v.x = Math.max(v.x, frontVehicle.x + minGap - 1); } } } v.update(); // Remove destroyed if (v.destroyed) { v.destroy(); vehicles.splice(i, 1); continue; } } // Ball-vehicle collision if (ball.launched && ball.bounceCooldown <= 0) { for (var i = 0; i < vehicles.length; ++i) { var v = vehicles[i]; // Skip if vehicle is in hit cooldown if (v.hitCooldown > 0) continue; // AABB collision var dx = Math.abs(ball.x - v.x); var dy = Math.abs(ball.y - v.y); var overlapX = dx < ball.radius + v.width / 2 - 10; var overlapY = dy < ball.radius + v.height / 2 - 10; if (overlapX && overlapY) { if (v.type === 'bouncycar') { // Calculate the incoming angle and speed var incomingAngle = Math.atan2(ball.vy, ball.vx); var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); // Reverse the angle (go in the exact opposite direction) var baseBounceAngle = incomingAngle + Math.PI; // Add up to ±10% randomness to the angle var randomAngle = (Math.random() - 0.5) * (Math.PI * 0.2); // ±0.1π ≈ ±18° var bounceAngle = baseBounceAngle + randomAngle; // Bounce speed is 120% of the incoming speed var bounceSpeed = speed * 1.2; // Set new velocity ball.vx = Math.cos(bounceAngle) * bounceSpeed; ball.vy = Math.sin(bounceAngle) * bounceSpeed; ball.launched = true; ball.resting = false; ball.bounceCooldown = 12; LK.effects.flashObject(v, 0xffff00, 400); // Do NOT randomize X anymore, keep physics consistent // After bounce, check if ball is immediately on stop (target) var dxStop = ball.x - stop.x; var dyStop = ball.y - stop.y; // Use ellipse collision: (dx/a)^2 + (dy/b)^2 < 1 if (dxStop * dxStop / ((stopRadius.x + ball.radius - 10) * (stopRadius.x + ball.radius - 10)) + dyStop * dyStop / ((stopRadius.y + ball.radius - 10) * (stopRadius.y + ball.radius - 10)) < 1 && Math.abs(ball.vy) < 18) { // Win! LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore() + ''); LK.effects.flashObject(stop, 0x00ff00, 600); canLaunch = false; ball.launched = false; ball.resting = true; // Randomize stop X position (avoid edges) var stopMargin = 180 + stopRadius.x; stop.x = stopMargin + Math.random() * (2048 - 2 * stopMargin); // Next level after short delay if (winTimeout) LK.clearTimeout(winTimeout); winTimeout = LK.setTimeout(function () { nextLevel(); }, 1200); return; } } else { // Vehicle collision - reduce health and deflect ball ball.health -= 1; healthTxt.setText('♥ ' + ball.health); // Set vehicle hit cooldown (2 seconds = 120 ticks at 60fps) v.hitCooldown = 120; // Flash the vehicle red to indicate hit LK.effects.flashObject(v, 0xff0000, 500); // New collision logic: Average of ball's vector and vehicle's vector. // Vehicle's velocity vector is (v.speed, 0) as it only moves horizontally. // Ball's current velocity vector before collision is (ball.vx, ball.vy). // Calculate the average vector components: // new_vx = (ball.vx + vehicle.vx) / 2 // new_vy = (ball.vy + vehicle.vy) / 2 // Since vehicle.vy is 0: // New formula: (ball_speed + 3 * vehicle_speed) / 1.20 var avgVx = (ball.vx + 3 * v.speed) / 1.20; var avgVy = (ball.vy + 3 * 0) / 1.20; // vehicle.vy is 0 // Apply the new averaged velocity to the ball ball.vx = avgVx; ball.vy = avgVy; // Set bounce cooldown to prevent multiple hits ball.bounceCooldown = 8; // Check if health is depleted if (ball.health <= 0) { // Game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } } } } if (ball.bounceCooldown > 0) ball.bounceCooldown--; // Ball-stop (target) collision if (ball.launched && !ball.resting) { var dx = ball.x - stop.x; var dy = ball.y - stop.y; // Use ellipse collision: (dx/a)^2 + (dy/b)^2 < 1 if (dx * dx / ((stopRadius.x + ball.radius - 10) * (stopRadius.x + ball.radius - 10)) + dy * dy / ((stopRadius.y + ball.radius - 10) * (stopRadius.y + ball.radius - 10)) < 1 && Math.abs(ball.vy) < 18) { // Win! LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore() + ''); LK.effects.flashObject(stop, 0x00ff00, 600); canLaunch = false; ball.launched = false; ball.resting = true; // Randomize stop X position (avoid edges) var margin = 180 + stopRadius.x; stop.x = margin + Math.random() * (2048 - 2 * margin); // Next level after short delay if (winTimeout) LK.clearTimeout(winTimeout); winTimeout = LK.setTimeout(function () { nextLevel(); }, 1200); } } // Vehicle spawn logic - reduced by 30% total (20% + 10%) if (LK.ticks % Math.max(52 - Math.min(level * 2, 30), 13) === 0) { spawnVehicle(); } }; // --- Slingshot controls --- function getDragVector() { var dx = dragCurrent.x - dragStart.x; var dy = dragCurrent.y - dragStart.y; return { dx: dx, dy: dy }; } function updateSlingshotBand() { if (!isDragging) { slingshotBand.visible = false; return; } var vec = getDragVector(); var len = Math.sqrt(vec.dx * vec.dx + vec.dy * vec.dy); var maxLen = 420; if (len > maxLen) { vec.dx *= maxLen / len; vec.dy *= maxLen / len; len = maxLen; } // Band position: from ball center to drag point slingshotBand.visible = true; slingshotBand.x = ball.x; slingshotBand.y = ball.y; slingshotBand.height = len; slingshotBand.rotation = Math.atan2(vec.dy, vec.dx) + Math.PI / 2; } // --- Input handlers --- game.down = function (x, y, obj) { // Only allow drag if ball is at rest and not in win state if (!canLaunch || !ball.resting) return; // Only allow drag if touch is near ball var dx = x - ball.x; var dy = y - ball.y; if (dx * dx + dy * dy < ball.radius * ball.radius * 2.2) { isDragging = true; dragStart.x = ball.x; dragStart.y = ball.y; dragCurrent.x = x; dragCurrent.y = y; updateSlingshotBand(); } }; game.move = function (x, y, obj) { if (!isDragging) return; dragCurrent.x = x; dragCurrent.y = y; updateSlingshotBand(); }; game.up = function (x, y, obj) { if (!isDragging) return; isDragging = false; slingshotBand.visible = false; // Launch! var vec = getDragVector(); var len = Math.sqrt(vec.dx * vec.dx + vec.dy * vec.dy); if (len < 60) return; // too short var maxLen = 420; if (len > maxLen) { vec.dx *= maxLen / len; vec.dy *= maxLen / len; len = maxLen; } // Set velocity (scaled) ball.vx = -vec.dx * 0.055; ball.vy = -vec.dy * 0.055; ball.launched = true; ball.resting = false; canLaunch = false; }; // --- Game over/win handling --- LK.on('gameover', function () { resetGameState(); }); LK.on('youwin', function () { resetGameState(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball (player) class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGfx = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7
});
self.radius = ballGfx.width / 2;
self.vx = 0;
self.vy = 0;
self.launched = false;
self.resting = true; // true if waiting for launch
self.bounceCooldown = 0; // prevent double bounces
self.health = 5; // Lives per launch
self.update = function () {
if (self.launched) {
// Gravity
self.vy += 0.1344;
// Move
self.x += self.vx;
self.y += self.vy;
// Friction (air)
self.vx *= 0.995;
// Clamp to game area
if (self.x < self.radius) self.x = self.radius;
if (self.x > 2048 - self.radius) self.x = 2048 - self.radius;
// If falls below screen, game over
if (self.y > 2732 + 200) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
}
};
// Reset ball to start
self.reset = function () {
self.x = 2048 / 2;
self.y = typeof ballStartY !== "undefined" ? ballStartY : 2732 - 180;
self.vx = 0;
self.vy = 0;
self.launched = false;
self.resting = true;
self.bounceCooldown = 0;
self.health = 5; // Reset health for new launch
};
// Bounce up (from bouncy car)
self.bounce = function (strength) {
self.vy = -Math.abs(strength || 38 + Math.random() * 8);
self.launched = true;
self.resting = false;
self.bounceCooldown = 10;
};
return self;
});
// BouncyCar (bouncy vehicle) class
var BouncyCar = Container.expand(function () {
var self = Container.call(this);
var bouncyGfx = self.attachAsset('bouncycar', {
anchorX: 0.5,
anchorY: 0.5
});
self.scale.set(0.72, 0.72);
self.width = bouncyGfx.width * 0.72;
self.height = bouncyGfx.height * 0.72;
self.speed = 0;
self.lane = 0;
self.type = 'bouncycar';
self.bouncePhase = Math.random() * Math.PI * 2;
self.update = function () {
self.x += self.speed;
// Bouncy up-down
self.y0 = self.y0 || self.y;
self.y = self.y0 + Math.sin(LK.ticks / 12 + self.bouncePhase) * 38;
if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true;
if (self.speed < 0 && self.x < -200) self.destroyed = true;
};
return self;
});
// Car (normal) class
var Car = Container.expand(function () {
var self = Container.call(this);
var carGfx = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
self.scale.set(0.72, 0.72);
self.width = carGfx.width * 0.72;
self.height = carGfx.height * 0.72;
self.speed = 0;
self.lane = 0;
self.type = 'car';
self.hitCooldown = 0; // Cooldown after being hit by ball
self.update = function () {
self.x += self.speed;
// Update hit cooldown
if (self.hitCooldown > 0) self.hitCooldown--;
// Remove if out of screen
if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true;
if (self.speed < 0 && self.x < -200) self.destroyed = true;
};
return self;
});
// Motorcycle class
var Motorcycle = Container.expand(function () {
var self = Container.call(this);
var motorcycleGfx = self.attachAsset('moto', {
anchorX: 0.5,
anchorY: 0.5
});
self.scale.set(0.58, 0.58); // Smaller than cars (0.72 * 0.8)
self.width = motorcycleGfx.width * 0.58;
self.height = motorcycleGfx.height * 0.58;
self.speed = 0;
self.lane = 0;
self.type = 'motorcycle';
self.hitCooldown = 0; // Cooldown after being hit by ball
self.hasJumped = false; // Only jump once
self.lastJumpCheck = 0;
self.isJumping = false;
self.originalY = 0;
self.originalScale = 0.58;
self.update = function () {
self.x += self.speed;
// Update hit cooldown
if (self.hitCooldown > 0) self.hitCooldown--;
// Jump logic - check every second (60 ticks) with 20% chance
if (!self.hasJumped && !self.isJumping && LK.ticks - self.lastJumpCheck >= 60) {
self.lastJumpCheck = LK.ticks;
if (Math.random() < 0.2) {
self.performJump();
}
}
// Remove if out of screen
if (self.speed > 0 && self.x > 2048 + 200) self.destroyed = true;
if (self.speed < 0 && self.x < -200) self.destroyed = true;
};
self.performJump = function () {
if (self.hasJumped || self.isJumping) return;
self.isJumping = true;
self.hasJumped = true;
self.originalY = self.y;
// Reduce speed by 20% during jump
self.originalSpeed = self.speed;
self.speed = self.speed * 0.8;
// Determine target lane based on current lane
var targetLane;
if (self.lane === 2) {
// Lane 3 (index 2)
targetLane = Math.random() < 0.5 ? 0 : 4; // Jump to lane 1 or 5 (index 0 or 4)
} else if (self.lane === 3) {
// Lane 4 (index 3)
targetLane = Math.random() < 0.5 ? 1 : 5; // Jump to lane 2 or 6 (index 1 or 5)
}
var targetY = laneYs[targetLane];
// Calculate rotation angle based on jump direction
var rotationAngle = 0;
if (targetLane < self.lane) {
rotationAngle = self.speed > 0 ? -0.3 : 0.3; // Upward jump
} else {
rotationAngle = self.speed > 0 ? 0.3 : -0.3; // Downward jump
}
// Enhanced jump animation with parabolic trajectory and realistic scaling
var jumpDuration = 1500;
var peakScale = self.originalScale * 2.2; // Increased scale for more dramatic effect
var halfDuration = jumpDuration / 2;
// First half: jump up with scale increase (ascending arc)
tween(self, {
y: self.originalY + (targetY - self.originalY) * 0.5,
// Halfway point
scaleX: peakScale,
scaleY: peakScale,
rotation: rotationAngle * 0.7 // Partial rotation during ascent
}, {
duration: halfDuration,
easing: tween.easeOut,
// Slow down as it reaches peak
onFinish: function onFinish() {
// Second half: descend with scale decrease (descending arc)
tween(self, {
y: targetY,
scaleX: self.originalScale,
scaleY: self.originalScale,
rotation: rotationAngle
}, {
duration: halfDuration,
easing: tween.easeIn,
// Speed up as it falls
onFinish: function onFinish() {
// Final landing animation - quick rotation reset
tween(self, {
rotation: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isJumping = false;
// Restore original speed
self.speed = self.originalSpeed;
}
});
}
});
}
});
// Update lane
self.lane = targetLane;
};
return self;
});
// Truck (long vehicle) class
var Truck = Container.expand(function () {
var self = Container.call(this);
var truckGfx = self.attachAsset('truck', {
anchorX: 0.5,
anchorY: 0.5
});
self.scale.set(0.72, 0.72);
self.width = truckGfx.width * 0.72;
self.height = truckGfx.height * 0.72;
self.speed = 0;
self.lane = 0;
self.type = 'truck';
self.hitCooldown = 0; // Cooldown after being hit by ball
self.update = function () {
self.x += self.speed;
// Update hit cooldown
if (self.hitCooldown > 0) self.hitCooldown--;
if (self.speed > 0 && self.x > 2048 + 300) self.destroyed = true;
if (self.speed < 0 && self.x < -300) self.destroyed = true;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Slingshot band
// Bouncy car
// Truck
// Car
// Lane divider
// Road background
// Stop (target)
// Ball (player)
// --- Game variables ---
var lanes = [0, 1, 2, 3, 4, 5];
var laneYs = [];
var laneCount = 6;
var verticalOffset = -200; // Move everything up by 200px
var roadTop = 600 + verticalOffset;
var roadHeight = 1536;
var laneGap = roadHeight / laneCount;
for (var i = 0; i < laneCount; ++i) {
laneYs[i] = roadTop + laneGap / 2 + i * laneGap;
}
// Place stop (target) at the top of the road, centered horizontally
var stopY = roadTop - 100;
var stopX = 2048 / 2;
// Update stop trigger area to match new stop (target) width
var stopRadius = {
x: 158,
// half of stop image width (316/2), matches texture
y: 90
}; // x: horizontal radius, y: vertical radius
var level = 1;
var vehicles = [];
var ball = null;
var stop = null;
var slingshotBand = null;
var isDragging = false;
// Place the ball higher up (not at the very bottom)
var ballStartY = roadTop + roadHeight + 120;
var dragStart = {
x: 0,
y: 0
};
var dragCurrent = {
x: 0,
y: 0
};
var launchPower = 0;
var launchAngle = 0;
var canLaunch = true;
var scoreTxt = null;
var levelTxt = null;
var winTimeout = null;
// --- Draw road and lanes ---
var road = LK.getAsset('road', {
x: 0,
y: roadTop,
width: 2048,
height: roadHeight
});
game.addChild(road);
// Lane dividers
for (var i = 1; i < laneCount; ++i) {
var divider = LK.getAsset('divider', {
x: 0,
y: roadTop + i * laneGap - 12,
width: 2048,
height: 24
});
game.addChild(divider);
}
// --- Stop (target) ---
stop = LK.getAsset('stop', {
anchorX: 0.5,
anchorY: 0.5,
x: stopX,
y: stopY,
scaleX: 1.2,
scaleY: 1.2
});
game.addChild(stop);
// --- Ball (player) ---
ball = new Ball();
ball.scale.set(0.9, 0.9);
game.addChild(ball);
ball.reset();
// --- Slingshot band (visual) ---
slingshotBand = LK.getAsset('band', {
anchorX: 0.5,
anchorY: 1,
x: ball.x,
y: ball.y,
scaleX: 0.7,
scaleY: 0.7
});
slingshotBand.visible = false;
game.addChild(slingshotBand);
// --- GUI: Score and Level ---
scoreTxt = new Text2('0', {
size: 110,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
levelTxt = new Text2('1', {
size: 70,
fill: "#fff"
});
levelTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(levelTxt);
// Health display
var healthTxt = new Text2('♥ 5', {
size: 80,
fill: 0xFF4444
});
healthTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(healthTxt);
healthTxt.x = -120; // Position to the left of level text
// --- Game state ---
function resetGameState() {
// Remove vehicles
for (var i = 0; i < vehicles.length; ++i) {
vehicles[i].destroy();
}
vehicles = [];
// Reset ball
ball.reset();
// Update health display
healthTxt.setText('♥ ' + ball.health);
// Reset slingshot
slingshotBand.visible = false;
isDragging = false;
canLaunch = true;
// Reset score/level
LK.setScore(0);
scoreTxt.setText('0');
level = 1;
levelTxt.setText('1');
}
resetGameState();
// --- Vehicle spawner ---
function spawnVehicle() {
// Check if we should spawn a motorcycle (only in lanes 2 and 3, which are indices 2 and 3)
var isMotorcycleSpawn = Math.random() < 0.15; // 15% chance for motorcycle
var lane;
var y;
var v, dir, speed;
if (isMotorcycleSpawn) {
// Motorcycles only spawn in lanes 3 and 4 (indices 2 and 3)
lane = Math.random() < 0.5 ? 2 : 3;
y = laneYs[lane];
v = new Motorcycle();
speed = (10 + level * 1.0 + Math.random() * 2) * 0.5 * 0.9; // Slightly slower than cars
} else {
// Randomly pick lane for other vehicles
lane = Math.floor(Math.random() * laneCount);
y = laneYs[lane];
// Adjusted spawn probabilities: All vehicle types reduced by 5%
// Old: Car 60%, Truck 25%, BouncyCar 15%
// New: Car 55%, Truck 20%, BouncyCar 10%
var t = Math.random();
if (t < 0.55) {
v = new Car();
speed = (12 + level * 1.2 + Math.random() * 2) * 0.5 * 0.95;
} else if (t < 0.75) {
v = new Truck();
speed = (8 + level * 1.1 + Math.random() * 2) * 0.5 * 0.9 * 0.95; // Truck speed reduced by 10% then by 5%
} else {
v = new BouncyCar();
speed = (10 + level * 1.3 + Math.random() * 2) * 0.5 * 1.1 * 0.95; // BouncyCar speed increased by 10% then reduced by 5%
}
}
// Direction: even lanes left->right, odd right->left
dir = lane % 2 === 0 ? 1 : -1;
v.lane = lane;
v.y = y;
v.y0 = y;
v.speed = speed * dir;
if (dir > 0) {
v.x = -200;
} else {
v.x = 2048 + 200;
// Flip the vehicle horizontally if moving right-to-left
if (v.children && v.children.length > 0 && v.children[0]) {
v.children[0].scale.x = -1;
}
}
vehicles.push(v);
game.addChild(v);
}
// --- Level progression ---
function nextLevel() {
level += 1;
levelTxt.setText(level + '');
// Remove all vehicles
for (var i = 0; i < vehicles.length; ++i) vehicles[i].destroy();
vehicles = [];
// Reset ball
ball.reset();
// Update health display
healthTxt.setText('♥ ' + ball.health);
canLaunch = true;
slingshotBand.visible = false;
}
// --- Game update ---
game.update = function () {
// Ball update
ball.update();
// Vehicles update
// --- Make vehicles follow the speed of the vehicle in front if they are too close in the same lane ---
for (var i = vehicles.length - 1; i >= 0; --i) {
var v = vehicles[i];
// Find the closest vehicle ahead in the same lane and direction
var minDist = Infinity;
var frontVehicle = null;
for (var j = 0; j < vehicles.length; ++j) {
if (i === j) continue;
var v2 = vehicles[j];
if (v2.lane !== v.lane) continue;
// Only consider vehicles in the same direction
if (v.speed > 0 && v2.x > v.x || v.speed < 0 && v2.x < v.x) {
var dist = Math.abs(v2.x - v.x);
if (dist < minDist) {
minDist = dist;
frontVehicle = v2;
}
}
}
// If too close, match speed to the front vehicle
if (frontVehicle) {
// Use the larger of the two widths for minimum gap
var minGap = Math.max(v.width, frontVehicle.width) * 0.85;
if (minDist < minGap) {
v.speed = frontVehicle.speed;
// Snap position to avoid overlap
if (v.speed > 0) {
v.x = Math.min(v.x, frontVehicle.x - minGap + 1);
} else {
v.x = Math.max(v.x, frontVehicle.x + minGap - 1);
}
}
}
v.update();
// Remove destroyed
if (v.destroyed) {
v.destroy();
vehicles.splice(i, 1);
continue;
}
}
// Ball-vehicle collision
if (ball.launched && ball.bounceCooldown <= 0) {
for (var i = 0; i < vehicles.length; ++i) {
var v = vehicles[i];
// Skip if vehicle is in hit cooldown
if (v.hitCooldown > 0) continue;
// AABB collision
var dx = Math.abs(ball.x - v.x);
var dy = Math.abs(ball.y - v.y);
var overlapX = dx < ball.radius + v.width / 2 - 10;
var overlapY = dy < ball.radius + v.height / 2 - 10;
if (overlapX && overlapY) {
if (v.type === 'bouncycar') {
// Calculate the incoming angle and speed
var incomingAngle = Math.atan2(ball.vy, ball.vx);
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
// Reverse the angle (go in the exact opposite direction)
var baseBounceAngle = incomingAngle + Math.PI;
// Add up to ±10% randomness to the angle
var randomAngle = (Math.random() - 0.5) * (Math.PI * 0.2); // ±0.1π ≈ ±18°
var bounceAngle = baseBounceAngle + randomAngle;
// Bounce speed is 120% of the incoming speed
var bounceSpeed = speed * 1.2;
// Set new velocity
ball.vx = Math.cos(bounceAngle) * bounceSpeed;
ball.vy = Math.sin(bounceAngle) * bounceSpeed;
ball.launched = true;
ball.resting = false;
ball.bounceCooldown = 12;
LK.effects.flashObject(v, 0xffff00, 400);
// Do NOT randomize X anymore, keep physics consistent
// After bounce, check if ball is immediately on stop (target)
var dxStop = ball.x - stop.x;
var dyStop = ball.y - stop.y;
// Use ellipse collision: (dx/a)^2 + (dy/b)^2 < 1
if (dxStop * dxStop / ((stopRadius.x + ball.radius - 10) * (stopRadius.x + ball.radius - 10)) + dyStop * dyStop / ((stopRadius.y + ball.radius - 10) * (stopRadius.y + ball.radius - 10)) < 1 && Math.abs(ball.vy) < 18) {
// Win!
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore() + '');
LK.effects.flashObject(stop, 0x00ff00, 600);
canLaunch = false;
ball.launched = false;
ball.resting = true;
// Randomize stop X position (avoid edges)
var stopMargin = 180 + stopRadius.x;
stop.x = stopMargin + Math.random() * (2048 - 2 * stopMargin);
// Next level after short delay
if (winTimeout) LK.clearTimeout(winTimeout);
winTimeout = LK.setTimeout(function () {
nextLevel();
}, 1200);
return;
}
} else {
// Vehicle collision - reduce health and deflect ball
ball.health -= 1;
healthTxt.setText('♥ ' + ball.health);
// Set vehicle hit cooldown (2 seconds = 120 ticks at 60fps)
v.hitCooldown = 120;
// Flash the vehicle red to indicate hit
LK.effects.flashObject(v, 0xff0000, 500);
// New collision logic: Average of ball's vector and vehicle's vector.
// Vehicle's velocity vector is (v.speed, 0) as it only moves horizontally.
// Ball's current velocity vector before collision is (ball.vx, ball.vy).
// Calculate the average vector components:
// new_vx = (ball.vx + vehicle.vx) / 2
// new_vy = (ball.vy + vehicle.vy) / 2
// Since vehicle.vy is 0:
// New formula: (ball_speed + 3 * vehicle_speed) / 1.20
var avgVx = (ball.vx + 3 * v.speed) / 1.20;
var avgVy = (ball.vy + 3 * 0) / 1.20; // vehicle.vy is 0
// Apply the new averaged velocity to the ball
ball.vx = avgVx;
ball.vy = avgVy;
// Set bounce cooldown to prevent multiple hits
ball.bounceCooldown = 8;
// Check if health is depleted
if (ball.health <= 0) {
// Game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
}
}
}
if (ball.bounceCooldown > 0) ball.bounceCooldown--;
// Ball-stop (target) collision
if (ball.launched && !ball.resting) {
var dx = ball.x - stop.x;
var dy = ball.y - stop.y;
// Use ellipse collision: (dx/a)^2 + (dy/b)^2 < 1
if (dx * dx / ((stopRadius.x + ball.radius - 10) * (stopRadius.x + ball.radius - 10)) + dy * dy / ((stopRadius.y + ball.radius - 10) * (stopRadius.y + ball.radius - 10)) < 1 && Math.abs(ball.vy) < 18) {
// Win!
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore() + '');
LK.effects.flashObject(stop, 0x00ff00, 600);
canLaunch = false;
ball.launched = false;
ball.resting = true;
// Randomize stop X position (avoid edges)
var margin = 180 + stopRadius.x;
stop.x = margin + Math.random() * (2048 - 2 * margin);
// Next level after short delay
if (winTimeout) LK.clearTimeout(winTimeout);
winTimeout = LK.setTimeout(function () {
nextLevel();
}, 1200);
}
}
// Vehicle spawn logic - reduced by 30% total (20% + 10%)
if (LK.ticks % Math.max(52 - Math.min(level * 2, 30), 13) === 0) {
spawnVehicle();
}
};
// --- Slingshot controls ---
function getDragVector() {
var dx = dragCurrent.x - dragStart.x;
var dy = dragCurrent.y - dragStart.y;
return {
dx: dx,
dy: dy
};
}
function updateSlingshotBand() {
if (!isDragging) {
slingshotBand.visible = false;
return;
}
var vec = getDragVector();
var len = Math.sqrt(vec.dx * vec.dx + vec.dy * vec.dy);
var maxLen = 420;
if (len > maxLen) {
vec.dx *= maxLen / len;
vec.dy *= maxLen / len;
len = maxLen;
}
// Band position: from ball center to drag point
slingshotBand.visible = true;
slingshotBand.x = ball.x;
slingshotBand.y = ball.y;
slingshotBand.height = len;
slingshotBand.rotation = Math.atan2(vec.dy, vec.dx) + Math.PI / 2;
}
// --- Input handlers ---
game.down = function (x, y, obj) {
// Only allow drag if ball is at rest and not in win state
if (!canLaunch || !ball.resting) return;
// Only allow drag if touch is near ball
var dx = x - ball.x;
var dy = y - ball.y;
if (dx * dx + dy * dy < ball.radius * ball.radius * 2.2) {
isDragging = true;
dragStart.x = ball.x;
dragStart.y = ball.y;
dragCurrent.x = x;
dragCurrent.y = y;
updateSlingshotBand();
}
};
game.move = function (x, y, obj) {
if (!isDragging) return;
dragCurrent.x = x;
dragCurrent.y = y;
updateSlingshotBand();
};
game.up = function (x, y, obj) {
if (!isDragging) return;
isDragging = false;
slingshotBand.visible = false;
// Launch!
var vec = getDragVector();
var len = Math.sqrt(vec.dx * vec.dx + vec.dy * vec.dy);
if (len < 60) return; // too short
var maxLen = 420;
if (len > maxLen) {
vec.dx *= maxLen / len;
vec.dy *= maxLen / len;
len = maxLen;
}
// Set velocity (scaled)
ball.vx = -vec.dx * 0.055;
ball.vy = -vec.dy * 0.055;
ball.launched = true;
ball.resting = false;
canLaunch = false;
};
// --- Game over/win handling ---
LK.on('gameover', function () {
resetGameState();
});
LK.on('youwin', function () {
resetGameState();
});
bir palyanço kafasının arka üsten görünümü çizgi film stili. In-Game asset. 2d. High contrast. No shadows
ileriyi gösteren . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
zıpzıp taşıyan kamyonet üsten görünüm. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
lüks sarı araba üsten görünüş. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
birebir aynı tıs tekerlerin düzeltilmesi gerek sadece
otobuş durağı. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat