/****
* 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.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;
};
// 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(1.2, 1.2);
self.width = bouncyGfx.width * 1.2;
self.height = bouncyGfx.height * 1.2;
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(1.2, 1.2);
self.width = carGfx.width * 1.2;
self.height = carGfx.height * 1.2;
self.speed = 0;
self.lane = 0;
self.type = 'car';
self.update = function () {
self.x += self.speed;
// 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;
});
// 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(1.2, 1.2);
self.width = truckGfx.width * 1.2;
self.height = truckGfx.height * 1.2;
self.speed = 0;
self.lane = 0;
self.type = 'truck';
self.update = function () {
self.x += self.speed;
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
****/
// --- Game variables ---
// Ball (player)
// Stop (target)
// Road background
// Lane divider
// Car
// Truck
// Bouncy car
// Slingshot band
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(1.2, 1.2);
game.addChild(ball);
ball.reset();
// --- Slingshot band (visual) ---
slingshotBand = LK.getAsset('band', {
anchorX: 0.5,
anchorY: 1,
x: ball.x,
y: ball.y
});
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);
// --- Game state ---
function resetGameState() {
// Remove vehicles
for (var i = 0; i < vehicles.length; ++i) {
vehicles[i].destroy();
}
vehicles = [];
// Reset ball
ball.reset();
// 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() {
// Randomly pick lane
var lane = Math.floor(Math.random() * laneCount);
var 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();
var v, dir, speed;
if (t < 0.55) {
v = new Car();
speed = (12 + level * 1.2 + Math.random() * 2) * 0.5;
} else if (t < 0.75) {
v = new Truck();
speed = (8 + level * 1.1 + Math.random() * 2) * 0.5 * 0.9; // Truck speed reduced by 10%
} else {
v = new BouncyCar();
speed = (10 + level * 1.3 + Math.random() * 2) * 0.5 * 1.1; // BouncyCar speed increased by 10%
}
// 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();
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];
// 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 {
// Crash: 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
if (LK.ticks % Math.max(38 - Math.min(level * 2, 30), 10) === 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.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;
};
// 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(1.2, 1.2);
self.width = bouncyGfx.width * 1.2;
self.height = bouncyGfx.height * 1.2;
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(1.2, 1.2);
self.width = carGfx.width * 1.2;
self.height = carGfx.height * 1.2;
self.speed = 0;
self.lane = 0;
self.type = 'car';
self.update = function () {
self.x += self.speed;
// 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;
});
// 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(1.2, 1.2);
self.width = truckGfx.width * 1.2;
self.height = truckGfx.height * 1.2;
self.speed = 0;
self.lane = 0;
self.type = 'truck';
self.update = function () {
self.x += self.speed;
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
****/
// --- Game variables ---
// Ball (player)
// Stop (target)
// Road background
// Lane divider
// Car
// Truck
// Bouncy car
// Slingshot band
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(1.2, 1.2);
game.addChild(ball);
ball.reset();
// --- Slingshot band (visual) ---
slingshotBand = LK.getAsset('band', {
anchorX: 0.5,
anchorY: 1,
x: ball.x,
y: ball.y
});
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);
// --- Game state ---
function resetGameState() {
// Remove vehicles
for (var i = 0; i < vehicles.length; ++i) {
vehicles[i].destroy();
}
vehicles = [];
// Reset ball
ball.reset();
// 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() {
// Randomly pick lane
var lane = Math.floor(Math.random() * laneCount);
var 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();
var v, dir, speed;
if (t < 0.55) {
v = new Car();
speed = (12 + level * 1.2 + Math.random() * 2) * 0.5;
} else if (t < 0.75) {
v = new Truck();
speed = (8 + level * 1.1 + Math.random() * 2) * 0.5 * 0.9; // Truck speed reduced by 10%
} else {
v = new BouncyCar();
speed = (10 + level * 1.3 + Math.random() * 2) * 0.5 * 1.1; // BouncyCar speed increased by 10%
}
// 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();
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];
// 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 {
// Crash: 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
if (LK.ticks % Math.max(38 - Math.min(level * 2, 30), 10) === 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