/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// CarOnlyLeft class
var CarOnlyLeft = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyLeft', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = -16 - Math.random() * 8 - carSpeedBoost; // Speed from right to left
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyLeft) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x + otherCar.width + 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the left
if (self.x < -300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyLeft2 class
var CarOnlyLeft2 = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyLeft2', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = -16 - Math.random() * 8 - carSpeedBoost; // Speed from right to left
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyLeft2) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x + otherCar.width + 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the left
if (self.x < -300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyRight class
var CarOnlyRight = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyRight', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = 16 + Math.random() * 8 + carSpeedBoost; // Speed from left to right
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyRight) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x - self.width - 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the right
if (self.x > 2048 + 300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyRight2 class
var CarOnlyRight2 = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyRight2', {
anchorX: 0.5,
anchorY: 1
});
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = 16 + Math.random() * 8 + carSpeedBoost; // Speed from left to right
self.update = function () {
self.x += self.speed;
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyRight2) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
self.x = otherCar.x - self.width - 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the right
if (self.x > 2048 + 300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.lane = 1;
self.collected = false;
self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 35, self.y - 80, 70, 70);
};
self.update = function () {
self.y += game.speed;
};
return self;
});
// CollectableHealth class
var CollectableHealth = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.timer = 600; // 10 seconds at 60 FPS
self.attachAsset('CollectableHealth', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 50, self.y - 100, 100, 100);
};
self.update = function () {
self.y += game.speed;
if (!self.collected) {
self.timer--;
}
};
return self;
});
// Shield collectable class (spawns like CollectableHealth, but rarer)
var CollectableShield = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.timer = 600; // 10 seconds at 60 FPS
self.attachAsset('Shield', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 50, self.y - 100, 100, 100);
};
self.update = function () {
self.y += game.speed;
if (!self.collected) {
self.timer--;
}
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.type = 'CarOnlyLeft'; // CarOnlyLeft
self.lane = 1;
self.passed = false;
self.setType = function (type) {
self.type = type;
self.removeChildren();
if (type === 'CarOnlyLeft') {
self.attachAsset('CarOnlyLeft', {
anchorX: 0.5,
anchorY: 1
});
self.width = 200;
self.height = 130;
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
self.update = function () {
self.y += game.speed;
};
return self;
});
// Runner class (Penguin for crossing game)
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.width = runnerSprite.width;
self.height = runnerSprite.height;
// For collision box
self.getCollisionRect = function () {
var w = self.width * 0.7;
var h = self.height * 0.9;
return new Rectangle(self.x - w / 2, self.y - h, w, h);
};
self.update = function () {
// No-op for penguin (no auto movement)
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Play relaxMusic as soon as the game starts
LK.playMusic('RelaxMusic');
// New road asset
// Sound effects
// Jungle ground
// Power-up (not yet used, but for future)
// Coin
// Lane obstacles
// Character (runner)
// Penguin crossing game setup
// Define lanes for cars (3 lanes, horizontal) - make lanes closer together
game.laneY = [2732 - 400,
// bottom lane (closest to player)
2732 - 650,
// middle lane
2732 - 900 // top lane (furthest from player)
];
// X positions for cars (off-screen left/right)
game.carStartLeft = -200;
game.carStartRight = 2048 + 200;
// Penguin start position (bottom center, a bit further down)
game.penguinStartX = 2048 / 2;
game.penguinStartY = 2732 - 30; // moved even lower
// Game state
game.ticks = 0;
game.score = 0;
game.isGameOver = false;
// Shield state
var shieldActive = false;
var shieldTimer = 0;
var shieldDuration = 600; // 10 seconds at 60 FPS
// Arrays for cars
var cars = [];
// Add ground to the top of the screen
var topGround = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
game.addChild(topGround);
// Add ground to the bottom of the screen
var bottomGround = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2732
});
game.addChild(bottomGround);
// Add multiple road assets to cover the area strictly between the top and bottom ground, with reduced vertical gaps
var roadHeight = 300; // Height of each road asset
var groundHeight = 300;
var roadStartY = groundHeight; // just below top ground
var roadEndY = 2732 - groundHeight; // just above bottom ground
var numberOfRoads = Math.ceil((roadEndY - roadStartY) / roadHeight);
for (var i = 0; i < numberOfRoads; i++) {
var roadY = roadEndY - i * roadHeight + 10; // Add slight overlap to remove visible gaps
// Clamp roadY so that the bottom of the topmost road does not go above roadStartY
if (roadY < roadStartY + roadHeight) roadY = roadStartY + roadHeight;
// Clamp roadY so that the top of the bottommost road does not go below roadEndY
if (roadY > roadEndY) roadY = roadEndY;
// Only add road if its bottom is below top ground and its top is above bottom ground
if (roadY <= roadEndY && roadY - roadHeight >= roadStartY) {
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: roadY
});
game.addChild(road);
}
}
// Add penguin (player)
var penguin = new Runner();
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
penguin.lane = 1; // start in center
game.addChild(penguin);
// Shield countdown text above penguin
var shieldCountdownTxt = new Text2('', {
size: 60,
fill: 0x00EAFF,
font: "Arial Black"
});
shieldCountdownTxt.anchor.set(0.5, 1);
shieldCountdownTxt.x = penguin.x;
shieldCountdownTxt.y = penguin.y - penguin.height - 10;
game.addChild(shieldCountdownTxt);
// Shield icon to show next to countdown
var shieldIcon = LK.getAsset('Shield', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.7,
scaleY: 0.7,
x: shieldCountdownTxt.x + 60,
y: shieldCountdownTxt.y
});
shieldIcon.visible = false;
game.addChild(shieldIcon);
// Health bar setup
var maxHealth = 3;
var penguinHealth = maxHealth;
// Health bar background (red)
var healthBarBg = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 30,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xff0000 // red
});
LK.gui.top.addChild(healthBarBg);
// Health bar foreground (dynamic hearts)
var healthHearts = [];
for (var i = 0; i < maxHealth; i++) {
var heart = LK.getAsset('coin', {
anchorX: 0,
anchorY: 0,
x: 130 + i * 90,
y: 40,
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.top.addChild(heart);
healthHearts.push(heart);
}
// Helper to update health bar
function updateHealthBar() {
for (var i = 0; i < maxHealth; i++) {
healthHearts[i].alpha = i < penguinHealth ? 1 : 0.2;
}
}
updateHealthBar();
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: spawn car
function spawnCar() {
// Randomly pick car type: 'log', 'rock', or 'pit'
// Adjusted carTypes to decrease CarOnlyLeft and increase CarOnlyLeft2 frequency
var carTypes = ['CarOnlyLeft', 'CarOnlyLeft2', 'CarOnlyLeft2', 'CarOnlyRight2', 'CarOnlyRight2', 'CarOnlyRight'];
var carType = carTypes[Math.floor(Math.random() * carTypes.length)];
// Calculate Y spawn range to use all road assets (between top and bottom ground)
var groundHeight = 300;
var minY = groundHeight; // 300
var maxY = 2732 - groundHeight; // 2432
// Random Y position between minY and maxY, covering all roads
var carY = minY + Math.random() * (maxY - minY);
if (carType === 'CarOnlyLeft') {
var car = new CarOnlyLeft();
car.x = game.carStartRight;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyLeft2') {
var car = new CarOnlyLeft2();
car.x = game.carStartRight;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyRight') {
var car = new CarOnlyRight();
car.x = game.carStartLeft;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyRight2') {
var car = new CarOnlyRight2();
car.x = game.carStartLeft;
car.y = carY;
game.addChild(car);
cars.push(car);
}
}
// Touch/Swipe handling for penguin movement (up/down/left/right)
var touchStartX = 0,
touchStartY = 0,
touchStartTime = 0;
var swipeThreshold = 80; // px
var swipeTime = 400; // ms
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
touchStartTime = Date.now();
};
game.up = function (x, y, obj) {
var dx = x - touchStartX;
var dy = y - touchStartY;
var dt = Date.now() - touchStartTime;
if (dt > swipeTime) return; // Too slow
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > swipeThreshold) {
// Move right
if (penguin.x < 2048 - 200) penguin.x += 220;
} else if (dx < -swipeThreshold) {
// Move left
if (penguin.x > 200) {
penguin.x -= 320;
if (penguin.x < 200) penguin.x = 200;
}
}
} else {
// Vertical swipe
if (dy < -swipeThreshold) {
// Move up
if (penguin.y > road.y - road.height + 200) penguin.y -= 250; // Adjust to ensure penguin stays on the road and matches new lane distance
} else if (dy > swipeThreshold) {
// Move down
if (penguin.y < game.penguinStartY) penguin.y += 250;
}
}
};
// Main update loop
var collectableHealths = [];
game.update = function () {
if (game.isGameOver) return;
game.ticks++;
// Spawn cars at intervals
// Car spawn interval decreases as time passes (minimum 12 ticks, max 36 at start)
var minInterval = 12;
var maxInterval = 36;
var interval = Math.max(minInterval, maxInterval - Math.floor(game.ticks / 300));
if (game.ticks % interval === 0) {
spawnCar();
}
// Only spawn a new CollectableHealth or Shield if there are none currently on screen or being collected
if (collectableHealths.length === 0) {
if (typeof game.nextHealthCanSpawn === "undefined") game.nextHealthCanSpawn = true;
// First, try to spawn Shield (much lower chance)
if (game.nextHealthCanSpawn && Math.random() < 0.0007) {
var shield = new CollectableShield();
// Pick a random lane
var laneIndex = Math.floor(Math.random() * game.laneY.length);
// Penguin possible X positions
var penguinPossibleX = [];
for (var px = 200; px <= 2048 - 200; px += 320) {
penguinPossibleX.push(px);
}
var xIndex = Math.floor(Math.random() * penguinPossibleX.length);
shield.x = penguinPossibleX[xIndex];
shield.y = game.laneY[laneIndex];
game.addChild(shield);
collectableHealths.push(shield);
game.nextHealthCanSpawn = false;
} else if (game.nextHealthCanSpawn && Math.random() < 0.0015) {
var health = new CollectableHealth();
// Pick a random lane
var laneIndex = Math.floor(Math.random() * game.laneY.length);
var penguinPossibleX = [];
for (var px = 200; px <= 2048 - 200; px += 320) {
penguinPossibleX.push(px);
}
var xIndex = Math.floor(Math.random() * penguinPossibleX.length);
health.x = penguinPossibleX[xIndex];
health.y = game.laneY[laneIndex];
game.addChild(health);
collectableHealths.push(health);
game.nextHealthCanSpawn = false;
}
} else {
game.nextHealthCanSpawn = false;
}
if (collectableHealths.length === 0) {
game.nextHealthCanSpawn = true;
}
// Update cars
for (var i = cars.length - 1; i >= 0; i--) {
var car = cars[i];
// Update all cars regardless of Y position (so they use all road assets)
car.update();
// Remove if off screen
if (car.speed > 0 && car.x > 2048 + 300 || car.speed < 0 && car.x < -300) {
car.destroy();
cars.splice(i, 1);
continue;
}
// Collision with penguin
var pRect = penguin.getCollisionRect();
var cRect = car.getCollisionRect();
if (rectsIntersect(pRect, cRect)) {
if (shieldActive) {
// Shield blocks damage, just destroy car and continue
car.destroy();
cars.splice(i, 1);
continue;
}
LK.getSound('hitSound').play();
LK.getSound('chickenSound').play();
LK.effects.flashScreen(0xff0000, 800);
// Remove car after hit
car.destroy();
cars.splice(i, 1);
// Reduce health
penguinHealth--;
updateHealthBar();
if (penguinHealth <= 0) {
// Game over: play sound, then show game over popup and show score
game.isGameOver = true;
LK.getSound('GameOver').play();
LK.setTimeout(function () {
LK.showGameOver({
score: game.score
});
}, 700); // Wait 700ms for sound to play before showing game over
return;
}
// Brief invulnerability: move penguin back to start Y (optional, or just flash)
// penguin.y = game.penguinStartY;
continue;
}
}
// Update and check CollectableHealths
for (var i = collectableHealths.length - 1; i >= 0; i--) {
var health = collectableHealths[i];
health.update();
// Remove if off screen or timer expired
if (health.y > 2732 || !health.collected && typeof health.timer !== "undefined" && health.timer <= 0) {
health.destroy();
collectableHealths.splice(i, 1);
continue;
}
// Check collision with penguin
if (!health.collected && rectsIntersect(penguin.getCollisionRect(), health.getCollisionRect())) {
health.collected = true;
health.destroy();
collectableHealths.splice(i, 1);
// If it's a shield, activate shield
if (health instanceof CollectableShield) {
shieldActive = true;
shieldTimer = shieldDuration;
} else {
// Only increase health if not at max
if (penguinHealth < maxHealth) {
penguinHealth++;
updateHealthBar();
}
}
continue;
}
}
// Check if penguin touches the top ground asset (give point and respawn at start)
var penguinRect = penguin.getCollisionRect();
var topGroundRect = new Rectangle(topGround.x - topGround.width / 2, topGround.y, topGround.width, topGround.height);
if (rectsIntersect(penguinRect, topGroundRect)) {
// Give 1 point
game.score++;
scoreTxt.setText(game.score.toString());
LK.getSound('PointGained').play();
// Respawn penguin at start position
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
// Prevent double point if penguin is still colliding next frame
// (move penguin just below top ground)
// (No need to snap, since respawn is at start)
}
// Update shield timer
if (shieldActive) {
shieldTimer--;
// Show countdown above penguin
shieldCountdownTxt.visible = true;
shieldCountdownTxt.x = penguin.x;
shieldCountdownTxt.y = penguin.y - penguin.height - 10;
var secondsLeft = Math.ceil(shieldTimer / 60);
shieldCountdownTxt.setText(secondsLeft.toString());
// Show and position shield icon next to countdown
shieldIcon.visible = true;
shieldIcon.x = shieldCountdownTxt.x + shieldCountdownTxt.width / 2 + 40;
shieldIcon.y = shieldCountdownTxt.y - 10;
if (shieldTimer <= 0) {
shieldActive = false;
shieldCountdownTxt.visible = false;
shieldIcon.visible = false;
}
} else {
shieldCountdownTxt.visible = false;
shieldIcon.visible = false;
}
// No win for reaching the bottom, only top is the goal
};
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Reset on game restart
game.on('reset', function () {
// Play relaxMusic again on reset
LK.playMusic('RelaxMusic');
// Remove all cars
for (var i = 0; i < cars.length; i++) cars[i].destroy();
cars = [];
// Remove all collectable healths
if (typeof collectableHealths !== "undefined") {
for (var i = 0; i < collectableHealths.length; i++) collectableHealths[i].destroy();
collectableHealths = [];
}
game.ticks = 0;
game.score = 0;
game.isGameOver = false;
scoreTxt.setText('0');
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
// Reset health
penguinHealth = maxHealth;
updateHealthBar();
// Reset shield
shieldActive = false;
shieldTimer = 0;
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// CarOnlyLeft class
var CarOnlyLeft = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyLeft', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = -16 - Math.random() * 8 - carSpeedBoost; // Speed from right to left
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyLeft) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x + otherCar.width + 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the left
if (self.x < -300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyLeft2 class
var CarOnlyLeft2 = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyLeft2', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = -16 - Math.random() * 8 - carSpeedBoost; // Speed from right to left
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyLeft2) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x + otherCar.width + 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the left
if (self.x < -300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyRight class
var CarOnlyRight = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyRight', {
anchorX: 0.5,
anchorY: 1
});
// Car speed increases with score: base speed + 0.5 per point, capped at +20
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = 16 + Math.random() * 8 + carSpeedBoost; // Speed from left to right
self.update = function () {
self.x += self.speed;
// Check for collision with other cars moving in the same direction
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyRight) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
// Adjust position to prevent overlap
self.x = otherCar.x - self.width - 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the right
if (self.x > 2048 + 300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// CarOnlyRight2 class
var CarOnlyRight2 = Container.expand(function () {
var self = Container.call(this);
self.attachAsset('CarOnlyRight2', {
anchorX: 0.5,
anchorY: 1
});
var carSpeedBoost = Math.min(game && game.score ? game.score * 0.5 : 0, 20);
self.speed = 16 + Math.random() * 8 + carSpeedBoost; // Speed from left to right
self.update = function () {
self.x += self.speed;
for (var i = 0; i < cars.length; i++) {
var otherCar = cars[i];
if (otherCar !== self && otherCar instanceof CarOnlyRight2) {
var selfRect = self.getCollisionRect();
var otherRect = otherCar.getCollisionRect();
if (rectsIntersect(selfRect, otherRect)) {
self.x = otherCar.x - self.width - 10;
}
}
}
// Destroy car if it touches or overlaps the top or bottom ground assets
var topGroundY = 0;
var bottomGroundY = 2732 - 300;
if (self.y - 65 <= topGroundY + 10 || self.y + 65 >= bottomGroundY + 300 - 10) {
self.destroy();
return;
}
// Check if the car is off-screen to the right
if (self.x > 2048 + 300) {
self.destroy();
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.lane = 1;
self.collected = false;
self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 35, self.y - 80, 70, 70);
};
self.update = function () {
self.y += game.speed;
};
return self;
});
// CollectableHealth class
var CollectableHealth = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.timer = 600; // 10 seconds at 60 FPS
self.attachAsset('CollectableHealth', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 50, self.y - 100, 100, 100);
};
self.update = function () {
self.y += game.speed;
if (!self.collected) {
self.timer--;
}
};
return self;
});
// Shield collectable class (spawns like CollectableHealth, but rarer)
var CollectableShield = Container.expand(function () {
var self = Container.call(this);
self.collected = false;
self.timer = 600; // 10 seconds at 60 FPS
self.attachAsset('Shield', {
anchorX: 0.5,
anchorY: 1
});
self.getCollisionRect = function () {
return new Rectangle(self.x - 50, self.y - 100, 100, 100);
};
self.update = function () {
self.y += game.speed;
if (!self.collected) {
self.timer--;
}
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.type = 'CarOnlyLeft'; // CarOnlyLeft
self.lane = 1;
self.passed = false;
self.setType = function (type) {
self.type = type;
self.removeChildren();
if (type === 'CarOnlyLeft') {
self.attachAsset('CarOnlyLeft', {
anchorX: 0.5,
anchorY: 1
});
self.width = 200;
self.height = 130;
}
};
self.getCollisionRect = function () {
return new Rectangle(self.x - 100, self.y - 65, 200, 130);
};
self.update = function () {
self.y += game.speed;
};
return self;
});
// Runner class (Penguin for crossing game)
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.width = runnerSprite.width;
self.height = runnerSprite.height;
// For collision box
self.getCollisionRect = function () {
var w = self.width * 0.7;
var h = self.height * 0.9;
return new Rectangle(self.x - w / 2, self.y - h, w, h);
};
self.update = function () {
// No-op for penguin (no auto movement)
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Play relaxMusic as soon as the game starts
LK.playMusic('RelaxMusic');
// New road asset
// Sound effects
// Jungle ground
// Power-up (not yet used, but for future)
// Coin
// Lane obstacles
// Character (runner)
// Penguin crossing game setup
// Define lanes for cars (3 lanes, horizontal) - make lanes closer together
game.laneY = [2732 - 400,
// bottom lane (closest to player)
2732 - 650,
// middle lane
2732 - 900 // top lane (furthest from player)
];
// X positions for cars (off-screen left/right)
game.carStartLeft = -200;
game.carStartRight = 2048 + 200;
// Penguin start position (bottom center, a bit further down)
game.penguinStartX = 2048 / 2;
game.penguinStartY = 2732 - 30; // moved even lower
// Game state
game.ticks = 0;
game.score = 0;
game.isGameOver = false;
// Shield state
var shieldActive = false;
var shieldTimer = 0;
var shieldDuration = 600; // 10 seconds at 60 FPS
// Arrays for cars
var cars = [];
// Add ground to the top of the screen
var topGround = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
game.addChild(topGround);
// Add ground to the bottom of the screen
var bottomGround = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 2732
});
game.addChild(bottomGround);
// Add multiple road assets to cover the area strictly between the top and bottom ground, with reduced vertical gaps
var roadHeight = 300; // Height of each road asset
var groundHeight = 300;
var roadStartY = groundHeight; // just below top ground
var roadEndY = 2732 - groundHeight; // just above bottom ground
var numberOfRoads = Math.ceil((roadEndY - roadStartY) / roadHeight);
for (var i = 0; i < numberOfRoads; i++) {
var roadY = roadEndY - i * roadHeight + 10; // Add slight overlap to remove visible gaps
// Clamp roadY so that the bottom of the topmost road does not go above roadStartY
if (roadY < roadStartY + roadHeight) roadY = roadStartY + roadHeight;
// Clamp roadY so that the top of the bottommost road does not go below roadEndY
if (roadY > roadEndY) roadY = roadEndY;
// Only add road if its bottom is below top ground and its top is above bottom ground
if (roadY <= roadEndY && roadY - roadHeight >= roadStartY) {
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: roadY
});
game.addChild(road);
}
}
// Add penguin (player)
var penguin = new Runner();
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
penguin.lane = 1; // start in center
game.addChild(penguin);
// Shield countdown text above penguin
var shieldCountdownTxt = new Text2('', {
size: 60,
fill: 0x00EAFF,
font: "Arial Black"
});
shieldCountdownTxt.anchor.set(0.5, 1);
shieldCountdownTxt.x = penguin.x;
shieldCountdownTxt.y = penguin.y - penguin.height - 10;
game.addChild(shieldCountdownTxt);
// Shield icon to show next to countdown
var shieldIcon = LK.getAsset('Shield', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.7,
scaleY: 0.7,
x: shieldCountdownTxt.x + 60,
y: shieldCountdownTxt.y
});
shieldIcon.visible = false;
game.addChild(shieldIcon);
// Health bar setup
var maxHealth = 3;
var penguinHealth = maxHealth;
// Health bar background (red)
var healthBarBg = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 30,
scaleX: 0.1,
scaleY: 0.1,
tint: 0xff0000 // red
});
LK.gui.top.addChild(healthBarBg);
// Health bar foreground (dynamic hearts)
var healthHearts = [];
for (var i = 0; i < maxHealth; i++) {
var heart = LK.getAsset('coin', {
anchorX: 0,
anchorY: 0,
x: 130 + i * 90,
y: 40,
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.top.addChild(heart);
healthHearts.push(heart);
}
// Helper to update health bar
function updateHealthBar() {
for (var i = 0; i < maxHealth; i++) {
healthHearts[i].alpha = i < penguinHealth ? 1 : 0.2;
}
}
updateHealthBar();
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: spawn car
function spawnCar() {
// Randomly pick car type: 'log', 'rock', or 'pit'
// Adjusted carTypes to decrease CarOnlyLeft and increase CarOnlyLeft2 frequency
var carTypes = ['CarOnlyLeft', 'CarOnlyLeft2', 'CarOnlyLeft2', 'CarOnlyRight2', 'CarOnlyRight2', 'CarOnlyRight'];
var carType = carTypes[Math.floor(Math.random() * carTypes.length)];
// Calculate Y spawn range to use all road assets (between top and bottom ground)
var groundHeight = 300;
var minY = groundHeight; // 300
var maxY = 2732 - groundHeight; // 2432
// Random Y position between minY and maxY, covering all roads
var carY = minY + Math.random() * (maxY - minY);
if (carType === 'CarOnlyLeft') {
var car = new CarOnlyLeft();
car.x = game.carStartRight;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyLeft2') {
var car = new CarOnlyLeft2();
car.x = game.carStartRight;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyRight') {
var car = new CarOnlyRight();
car.x = game.carStartLeft;
car.y = carY;
game.addChild(car);
cars.push(car);
} else if (carType === 'CarOnlyRight2') {
var car = new CarOnlyRight2();
car.x = game.carStartLeft;
car.y = carY;
game.addChild(car);
cars.push(car);
}
}
// Touch/Swipe handling for penguin movement (up/down/left/right)
var touchStartX = 0,
touchStartY = 0,
touchStartTime = 0;
var swipeThreshold = 80; // px
var swipeTime = 400; // ms
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
touchStartTime = Date.now();
};
game.up = function (x, y, obj) {
var dx = x - touchStartX;
var dy = y - touchStartY;
var dt = Date.now() - touchStartTime;
if (dt > swipeTime) return; // Too slow
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > swipeThreshold) {
// Move right
if (penguin.x < 2048 - 200) penguin.x += 220;
} else if (dx < -swipeThreshold) {
// Move left
if (penguin.x > 200) {
penguin.x -= 320;
if (penguin.x < 200) penguin.x = 200;
}
}
} else {
// Vertical swipe
if (dy < -swipeThreshold) {
// Move up
if (penguin.y > road.y - road.height + 200) penguin.y -= 250; // Adjust to ensure penguin stays on the road and matches new lane distance
} else if (dy > swipeThreshold) {
// Move down
if (penguin.y < game.penguinStartY) penguin.y += 250;
}
}
};
// Main update loop
var collectableHealths = [];
game.update = function () {
if (game.isGameOver) return;
game.ticks++;
// Spawn cars at intervals
// Car spawn interval decreases as time passes (minimum 12 ticks, max 36 at start)
var minInterval = 12;
var maxInterval = 36;
var interval = Math.max(minInterval, maxInterval - Math.floor(game.ticks / 300));
if (game.ticks % interval === 0) {
spawnCar();
}
// Only spawn a new CollectableHealth or Shield if there are none currently on screen or being collected
if (collectableHealths.length === 0) {
if (typeof game.nextHealthCanSpawn === "undefined") game.nextHealthCanSpawn = true;
// First, try to spawn Shield (much lower chance)
if (game.nextHealthCanSpawn && Math.random() < 0.0007) {
var shield = new CollectableShield();
// Pick a random lane
var laneIndex = Math.floor(Math.random() * game.laneY.length);
// Penguin possible X positions
var penguinPossibleX = [];
for (var px = 200; px <= 2048 - 200; px += 320) {
penguinPossibleX.push(px);
}
var xIndex = Math.floor(Math.random() * penguinPossibleX.length);
shield.x = penguinPossibleX[xIndex];
shield.y = game.laneY[laneIndex];
game.addChild(shield);
collectableHealths.push(shield);
game.nextHealthCanSpawn = false;
} else if (game.nextHealthCanSpawn && Math.random() < 0.0015) {
var health = new CollectableHealth();
// Pick a random lane
var laneIndex = Math.floor(Math.random() * game.laneY.length);
var penguinPossibleX = [];
for (var px = 200; px <= 2048 - 200; px += 320) {
penguinPossibleX.push(px);
}
var xIndex = Math.floor(Math.random() * penguinPossibleX.length);
health.x = penguinPossibleX[xIndex];
health.y = game.laneY[laneIndex];
game.addChild(health);
collectableHealths.push(health);
game.nextHealthCanSpawn = false;
}
} else {
game.nextHealthCanSpawn = false;
}
if (collectableHealths.length === 0) {
game.nextHealthCanSpawn = true;
}
// Update cars
for (var i = cars.length - 1; i >= 0; i--) {
var car = cars[i];
// Update all cars regardless of Y position (so they use all road assets)
car.update();
// Remove if off screen
if (car.speed > 0 && car.x > 2048 + 300 || car.speed < 0 && car.x < -300) {
car.destroy();
cars.splice(i, 1);
continue;
}
// Collision with penguin
var pRect = penguin.getCollisionRect();
var cRect = car.getCollisionRect();
if (rectsIntersect(pRect, cRect)) {
if (shieldActive) {
// Shield blocks damage, just destroy car and continue
car.destroy();
cars.splice(i, 1);
continue;
}
LK.getSound('hitSound').play();
LK.getSound('chickenSound').play();
LK.effects.flashScreen(0xff0000, 800);
// Remove car after hit
car.destroy();
cars.splice(i, 1);
// Reduce health
penguinHealth--;
updateHealthBar();
if (penguinHealth <= 0) {
// Game over: play sound, then show game over popup and show score
game.isGameOver = true;
LK.getSound('GameOver').play();
LK.setTimeout(function () {
LK.showGameOver({
score: game.score
});
}, 700); // Wait 700ms for sound to play before showing game over
return;
}
// Brief invulnerability: move penguin back to start Y (optional, or just flash)
// penguin.y = game.penguinStartY;
continue;
}
}
// Update and check CollectableHealths
for (var i = collectableHealths.length - 1; i >= 0; i--) {
var health = collectableHealths[i];
health.update();
// Remove if off screen or timer expired
if (health.y > 2732 || !health.collected && typeof health.timer !== "undefined" && health.timer <= 0) {
health.destroy();
collectableHealths.splice(i, 1);
continue;
}
// Check collision with penguin
if (!health.collected && rectsIntersect(penguin.getCollisionRect(), health.getCollisionRect())) {
health.collected = true;
health.destroy();
collectableHealths.splice(i, 1);
// If it's a shield, activate shield
if (health instanceof CollectableShield) {
shieldActive = true;
shieldTimer = shieldDuration;
} else {
// Only increase health if not at max
if (penguinHealth < maxHealth) {
penguinHealth++;
updateHealthBar();
}
}
continue;
}
}
// Check if penguin touches the top ground asset (give point and respawn at start)
var penguinRect = penguin.getCollisionRect();
var topGroundRect = new Rectangle(topGround.x - topGround.width / 2, topGround.y, topGround.width, topGround.height);
if (rectsIntersect(penguinRect, topGroundRect)) {
// Give 1 point
game.score++;
scoreTxt.setText(game.score.toString());
LK.getSound('PointGained').play();
// Respawn penguin at start position
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
// Prevent double point if penguin is still colliding next frame
// (move penguin just below top ground)
// (No need to snap, since respawn is at start)
}
// Update shield timer
if (shieldActive) {
shieldTimer--;
// Show countdown above penguin
shieldCountdownTxt.visible = true;
shieldCountdownTxt.x = penguin.x;
shieldCountdownTxt.y = penguin.y - penguin.height - 10;
var secondsLeft = Math.ceil(shieldTimer / 60);
shieldCountdownTxt.setText(secondsLeft.toString());
// Show and position shield icon next to countdown
shieldIcon.visible = true;
shieldIcon.x = shieldCountdownTxt.x + shieldCountdownTxt.width / 2 + 40;
shieldIcon.y = shieldCountdownTxt.y - 10;
if (shieldTimer <= 0) {
shieldActive = false;
shieldCountdownTxt.visible = false;
shieldIcon.visible = false;
}
} else {
shieldCountdownTxt.visible = false;
shieldIcon.visible = false;
}
// No win for reaching the bottom, only top is the goal
};
// Rectangle intersection helper
function rectsIntersect(r1, r2) {
return !(r2.x > r1.x + r1.width || r2.x + r2.width < r1.x || r2.y > r1.y + r1.height || r2.y + r2.height < r1.y);
}
// Reset on game restart
game.on('reset', function () {
// Play relaxMusic again on reset
LK.playMusic('RelaxMusic');
// Remove all cars
for (var i = 0; i < cars.length; i++) cars[i].destroy();
cars = [];
// Remove all collectable healths
if (typeof collectableHealths !== "undefined") {
for (var i = 0; i < collectableHealths.length; i++) collectableHealths[i].destroy();
collectableHealths = [];
}
game.ticks = 0;
game.score = 0;
game.isGameOver = false;
scoreTxt.setText('0');
penguin.x = game.penguinStartX;
penguin.y = game.penguinStartY;
// Reset health
penguinHealth = maxHealth;
updateHealthBar();
// Reset shield
shieldActive = false;
shieldTimer = 0;
});
make a cobblestone sidewalk butt be sure that the image is competible with 2048x300. In-Game asset. 2d. High contrast. No shadows
make the runner as crazy chicken. In-Game asset. 2d. High contrast. No shadows
make a fancy car. In-Game asset. 2d. High contrast. No shadows
pixel hearth. In-Game asset. 2d. High contrast. No shadows
Car image but its head should be on right side not left. Make a fancy car. In-Game asset. 2d. High contrast. No shadows
Make a freeway with image ratio of 2048x2732 but freeway should be seem like top view and should go from left to right. In-Game asset. 2d. High contrast. No shadows
A pixel shield image. In-Game asset. 2d. High contrast. No shadows
Car image but its head should be on right side not left. Make a fancy car colored green.. In-Game asset. 2d. High contrast. No shadows