/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animal class (player)
var Animal = Container.expand(function () {
var self = Container.call(this);
// Default to chicken
self.type = 'chicken';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Set a more accurate hitbox for the animal (chicken, cat, dog)
function setAnimalHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Shrink hitbox for more fair collisions
if (type === 'chicken') {
// chicken: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
} else if (type === 'cat' || type === 'dog') {
// cat/dog: 62% width, 62% height
self.hitArea = new Rectangle(-w * 0.31, -h * 0.31, w * 0.62, h * 0.62);
} else {
// fallback: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
}
}
setAnimalHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setAnimalHitArea(type);
};
// Animate hop
self.hop = function (targetX, targetY, _onFinish) {
tween(self, {
x: targetX,
y: targetY,
scaleY: 1.2
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 1
}, {
duration: 60,
easing: tween.cubicIn,
onFinish: _onFinish
});
}
});
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Animate spin
self.update = function () {
self.sprite.rotation += 0.15;
};
return self;
});
// Vehicle class
var Vehicle = Container.expand(function () {
var self = Container.call(this);
// type: 'car1', 'car2', 'truck'
self.type = 'car1';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Headlights for night effect
self.headlights = null;
function addHeadlights() {
// Remove old headlights if any
if (self.headlights) {
self.headlights.destroy();
self.headlights = null;
}
// Only add headlights for cars and truck
if (self.type === 'car1' || self.type === 'car2' || self.type === 'truck') {
// Attach headlight asset, anchor at left for right-to-left, right for left-to-right
self.headlights = self.attachAsset('car_headlight', {
anchorX: self.dir === 1 ? 0 : 1,
anchorY: 0.5
});
// Remove hitbox from headlights so they do not participate in collision
self.headlights.hitArea = null;
// Place headlights further inside the car for a more realistic look
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
// Scale headlights to match car width
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Start hidden, will be shown at night
self.headlights.alpha = 0;
}
}
// Set a much smaller hitbox for the vehicle (extremely forgiving)
function setVehicleHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Make hitbox ultra tiny (almost no damage at all)
if (type === 'car1') {
// car1: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'car2') {
// car2: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'truck') {
// truck: 2.5% width, 2% height
self.hitArea = new Rectangle(-w * 0.0125, -h * 0.01, w * 0.025, h * 0.02);
} else {
// fallback: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
}
}
setVehicleHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setVehicleHitArea(type);
// Set sprite orientation to always face movement direction, regardless of asset's default orientation
if (type === 'car2') {
// Car2 asset default faces right, so:
if (self.dir === 1) {
// Left-to-right: no flip needed
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
// Right-to-left: flip horizontally
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
// Other vehicles: keep existing logic
if (self.dir === 1) {
// Always face right for left-to-right movement
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
} else {
// Always face left for right-to-left movement
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
}
}
// Add or update headlights
addHeadlights();
};
// Lane index (0-19)
self.lane = 0;
// Direction: 1 = left to right, -1 = right to left
self.dir = 1;
// Speed in px per frame
self.speed = 6;
self.update = function () {
if (typeof self.lastX === "undefined") self.lastX = self.x;
self.x += self.speed * self.dir;
// Ensure sprite faces the correct direction, always facing movement direction
if (self.type === 'car2') {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
}
self.lastX = self.x;
// Update headlights orientation, scale, and visibility for night
if (self.headlights) {
// Update position and scale in case car was resized or direction changed
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Show headlights only at night (dayNight.t >= 0.5), with semi-transparency
self.headlights.alpha = typeof dayNight !== "undefined" && dayNight.t >= 0.5 ? 0.45 : 0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Day sky blue
});
/****
* Game Code
****/
// Music
// Sounds
// Coin
// Vehicles
// Lane types
// Animal characters
// --- Game constants ---
var NUM_LANES = 20;
var LANE_HEIGHT = 120;
var LANE_TYPES = []; // 'grass' or 'road'
var LANE_Y = []; // y positions of each lane (0 = bottom)
var LANE_CONFIG = []; // {type, y, speed, dir, spacing, vehicleTypes}
var LANE_TOP = 2732 - NUM_LANES * LANE_HEIGHT; // Top y of first lane
// --- Day/Night cycle ---
var dayNight = {
t: 0,
// 0 = day, 1 = night
speed: 0.00025,
// How fast day/night cycles (per tick)
colorDay: 0x87ceeb,
colorNight: 0x23234a
};
// --- Game state ---
var player = null;
var vehicles = [];
var coins = [];
var score = 0;
var coinsCollected = 0;
var isMoving = false;
var dragStart = null;
var dragTarget = null;
var lastLane = 0;
var gameOver = false;
// --- Level state ---
var currentLevel = 1;
var maxLevel = 5;
var flag = null;
// --- UI ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: 0xFFD700
});
coinTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(coinTxt);
// --- Lane setup ---
function setupLanes() {
LANE_TYPES.length = 0;
LANE_Y.length = 0;
LANE_CONFIG.length = 0;
for (var i = 0; i < NUM_LANES; i++) {
// Alternate: grass every 3rd lane, rest are road
var type = i % 3 === 0 ? 'grass' : 'road';
LANE_TYPES.push(type);
var y = 2732 - (i + 0.5) * LANE_HEIGHT;
LANE_Y.push(y);
// Lane config
if (type === 'road') {
// Assign direction: even lanes go left-to-right, odd lanes go right-to-left (never mixed)
var dir = i % 2 === 0 ? 1 : -1;
var speed = 4 + i % 4 * 1.5; // 4, 5.5, 7, 8.5
var spacing = 350 + i % 3 * 80; // 350, 430, 510
var vehicleTypes = i % 4 === 0 ? ['truck'] : ['car1', 'car2'];
LANE_CONFIG.push({
type: type,
y: y,
speed: speed,
dir: dir,
spacing: spacing,
vehicleTypes: vehicleTypes
});
} else {
LANE_CONFIG.push({
type: type,
y: y
});
}
}
}
// --- Draw lanes ---
function drawLanes() {
for (var i = 0; i < NUM_LANES; i++) {
var laneType = LANE_TYPES[i];
var y = LANE_Y[i];
var lane = LK.getAsset(laneType, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: y
});
game.addChild(lane);
// Add direction arrows for road lanes
// (Removed small car images used as arrows)
}
}
// --- Spawn player ---
function spawnPlayer() {
player = new Animal();
player.setType('chicken');
// Start at bottom grass lane, center
player.x = 2048 / 2;
player.y = LANE_Y[0];
lastLane = 0;
game.addChild(player);
}
// --- Spawn flag at the end of the level ---
function spawnFlag() {
if (flag) {
flag.destroy();
flag = null;
}
// Place flag at the topmost grass lane
var topLaneIdx = NUM_LANES - 1;
var flagY = LANE_Y[topLaneIdx];
flag = LK.getAsset('flag', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: flagY
});
game.addChild(flag);
}
// --- Spawn vehicles ---
function spawnVehicle(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'road') return;
// Check for minimum distance to last vehicle in this lane
var lastVehicle = null;
var lastDist = 99999;
for (var i = 0; i < vehicles.length; i++) {
if (vehicles[i].lane === laneIdx) {
var dist = Math.abs(vehicles[i].x - 2048 / 2);
if (dist < lastDist) {
lastDist = dist;
lastVehicle = vehicles[i];
}
}
}
// Only spawn if no vehicle or far enough from last vehicle in this lane
if (!lastVehicle || lastDist > config.spacing) {
var v = new Vehicle();
// Random vehicle type
var vtype = config.vehicleTypes[Math.floor(Math.random() * config.vehicleTypes.length)];
v.setType(vtype);
v.lane = laneIdx;
v.dir = config.dir;
v.speed = config.speed + Math.random() * 1.5;
// Y position
v.y = config.y;
// X position: offscreen left or right
if (v.dir === 1) {
v.x = -v.sprite.width / 2 - 20;
// Car2: always face right for left-to-right movement
if (v.type === 'car2') {
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face right for left-to-right movement
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
} else {
v.x = 2048 + v.sprite.width / 2 + 20;
// Car2: always face left for right-to-left movement
if (v.type === 'car2') {
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face left for right-to-left movement
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
}
// Update headlights after direction and type are set
if (typeof v.addHeadlights === "function") v.addHeadlights && v.addHeadlights();
vehicles.push(v);
game.addChild(v);
}
}
// --- Spawn coin ---
function spawnCoin(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'grass') return;
// Only one coin per grass lane at a time
for (var i = 0; i < coins.length; i++) {
if (Math.abs(coins[i].y - config.y) < 10) return;
}
var c = new Coin();
c.x = 200 + Math.random() * (2048 - 400);
c.y = config.y;
coins.push(c);
game.addChild(c);
}
// --- Remove vehicle ---
function removeVehicle(idx) {
vehicles[idx].destroy();
vehicles.splice(idx, 1);
}
// --- Remove coin ---
function removeCoin(idx) {
coins[idx].destroy();
coins.splice(idx, 1);
}
// --- Update UI ---
function updateUI() {
scoreTxt.setText(score);
coinTxt.setText(coinsCollected);
}
// --- Sun and Moon sprites ---
var sunSprite = null;
var moonSprite = null;
var skySpriteLayer = new Container();
game.addChild(skySpriteLayer);
// --- Day/Night cycle update ---
function updateDayNight() {
dayNight.t += dayNight.speed;
if (dayNight.t > 1) dayNight.t = 0;
// Interpolate color
var t = dayNight.t;
var c1 = dayNight.colorDay,
c2 = dayNight.colorNight;
var r = (c1 >> 16) * (1 - t) + (c2 >> 16) * t & 0xff;
var g = (c1 >> 8 & 0xff) * (1 - t) + (c2 >> 8 & 0xff) * t & 0xff;
var b = (c1 & 0xff) * (1 - t) + (c2 & 0xff) * t & 0xff;
var color = r << 16 | g << 8 | b;
game.setBackgroundColor(color);
// Sun and Moon movement
// Sun: visible during t in [0,0.5), Moon: visible during t in [0.5,1)
// Both move in an arc from left to right
var skyY = 350;
var skyRadius = 800;
var centerX = 2048 / 2;
var centerY = skyY + 200;
// Sun
if (!sunSprite) {
sunSprite = LK.getAsset('sun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(sunSprite);
}
// Moon
if (!moonSprite) {
moonSprite = LK.getAsset('ay', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(moonSprite);
}
// Sun position: t in [0,0.5) → left to right, high arc
var sunT = t < 0.5 ? t / 0.5 : 0;
var sunAngle = Math.PI * (1 - sunT); // from left (π) to right (0)
sunSprite.x = centerX + skyRadius * Math.cos(sunAngle);
sunSprite.y = centerY - skyRadius * Math.sin(sunAngle);
sunSprite.alpha = t < 0.5 ? 1 : 0;
// Moon position: t in [0.5,1) → left to right, high arc
var moonT = t >= 0.5 ? (t - 0.5) / 0.5 : 0;
var moonAngle = Math.PI * (1 - moonT); // from left (π) to right (0)
moonSprite.x = centerX + skyRadius * Math.cos(moonAngle);
moonSprite.y = centerY - skyRadius * Math.sin(moonAngle);
moonSprite.alpha = t >= 0.5 ? 1 : 0;
}
// --- Collision detection ---
function checkCollisions() {
// Vehicles
for (var i = 0; i < vehicles.length; i++) {
if (player.intersects(vehicles[i])) {
// Crash!
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('crash').play();
gameOver = true;
LK.showGameOver();
return;
}
}
// Coins
for (var i = coins.length - 1; i >= 0; i--) {
if (player.intersects(coins[i])) {
coinsCollected++;
updateUI();
LK.getSound('coin').play();
removeCoin(i);
}
}
}
// --- Move player (grid) ---
function tryMove(dx, dy) {
if (isMoving || gameOver) return;
var newLane = lastLane + dy;
if (newLane < 0 || newLane >= NUM_LANES) return;
var newX = player.x + dx * 220;
if (newX < 80 || newX > 2048 - 80) return;
var newY = LANE_Y[newLane];
isMoving = true;
player.hop(newX, newY, function () {
isMoving = false;
lastLane = newLane;
// Score: advance only if moving up
if (dy > 0) {
score++;
updateUI();
if (score >= NUM_LANES - 1) {
// Win!
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
});
LK.getSound('hop').play();
}
// --- Touch controls (swipe) ---
var touchStart = null;
game.down = function (x, y, obj) {
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
};
// --- Main update loop ---
game.update = function () {
if (gameOver) return;
// Day/Night
updateDayNight();
// Vehicles
for (var i = vehicles.length - 1; i >= 0; i--) {
vehicles[i].update();
// Remove if offscreen
if (vehicles[i].dir === 1 && vehicles[i].x > 2048 + vehicles[i].sprite.width / 2 + 40 || vehicles[i].dir === -1 && vehicles[i].x < -vehicles[i].sprite.width / 2 - 40) {
removeVehicle(i);
}
}
// Coins
for (var i = 0; i < coins.length; i++) {
coins[i].update();
}
// Spawn vehicles
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'road') continue;
// Try to spawn a vehicle with a small probability
if (Math.random() < 0.025) {
// ~1 every 40 frames
spawnVehicle(i);
}
}
// Spawn coins
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'grass') continue;
// 1% chance per frame
if (Math.random() < 0.01) {
spawnCoin(i);
}
}
// Remove coins if offscreen (shouldn't happen, but safety)
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].y < LANE_TOP - 100 || coins[i].y > 2732 + 100) {
removeCoin(i);
}
}
// Collisions
checkCollisions();
// Check for flag collision and level progression
if (flag && player.intersects(flag)) {
// Advance to next level if possible
if (currentLevel < maxLevel) {
currentLevel++;
// Increase difficulty: more/faster vehicles, less grass, etc.
NUM_LANES = 20 + currentLevel * 2;
LANE_HEIGHT = Math.max(80, 120 - currentLevel * 5);
// Remove all game objects
for (var i = vehicles.length - 1; i >= 0; i--) removeVehicle(i);
for (var i = coins.length - 1; i >= 0; i--) removeCoin(i);
if (player) {
player.destroy();
player = null;
}
if (flag) {
flag.destroy();
flag = null;
}
// Re-setup lanes and redraw
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
gameOver = false;
} else {
// Last level: show win
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
};
// --- Start Menu Overlay ---
var startMenuOverlay = new Container();
var startMenuBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 4,
scaleY: 4
});
startMenuOverlay.addChild(startMenuBg);
// Play button (image)
var playBtn = LK.getAsset('start', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 250,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(playBtn);
// Options button (image)
var optionsBtn = LK.getAsset('options', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 450,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(optionsBtn);
// Exit button (image)
var exitBtn = LK.getAsset('exit', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 600,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(exitBtn);
var startMenuActive = true;
game.addChild(startMenuOverlay);
// Helper for button hit detection (works for both Text2 and image buttons)
function isInsideBtn(btn, x, y) {
// Use anchorX/anchorY for images, anchor for Text2
var anchorX = typeof btn.anchorX === "number" ? btn.anchorX : btn.anchor && btn.anchor.x ? btn.anchor.x : 0.5;
var anchorY = typeof btn.anchorY === "number" ? btn.anchorY : btn.anchor && btn.anchor.y ? btn.anchor.y : 0.5;
var bx = btn.x - btn.width * anchorX;
var by = btn.y - btn.height * anchorY;
return x >= bx && x <= bx + btn.width && y >= by && y <= by + btn.height;
}
// Block input/gameplay until play is pressed
function startGame() {
if (!startMenuActive) return;
startMenuActive = false;
startMenuOverlay.visible = false;
// Now start the game
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
}
// --- Options Overlay ---
var optionsOverlay = new Container();
optionsOverlay.visible = false;
// Background for options
var optionsBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 3.5,
scaleY: 3.5
});
optionsOverlay.addChild(optionsBg);
// Title
var optionsTitle = new Text2("OPTIONS", {
size: 140,
fill: "#fff"
});
optionsTitle.anchor.set(0.5, 0.5);
optionsTitle.x = 2048 / 2;
optionsTitle.y = 2732 / 2 - 350;
optionsOverlay.addChild(optionsTitle);
// --- Sound Volume Controls ---
var soundLabel = new Text2("Sound Volume", {
size: 90,
fill: "#fff"
});
soundLabel.anchor.set(0.5, 0.5);
soundLabel.x = 2048 / 2;
soundLabel.y = 2732 / 2 - 150;
optionsOverlay.addChild(soundLabel);
var soundDownBtn = new Text2("-", {
size: 120,
fill: "#fff"
});
soundDownBtn.anchor.set(0.5, 0.5);
soundDownBtn.x = 2048 / 2 - 200;
soundDownBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundDownBtn);
var soundUpBtn = new Text2("+", {
size: 120,
fill: "#fff"
});
soundUpBtn.anchor.set(0.5, 0.5);
soundUpBtn.x = 2048 / 2 + 200;
soundUpBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundUpBtn);
var soundVolTxt = new Text2("100%", {
size: 90,
fill: "#fff"
});
soundVolTxt.anchor.set(0.5, 0.5);
soundVolTxt.x = 2048 / 2;
soundVolTxt.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundVolTxt);
// --- Music Controls ---
var musicLabel = new Text2("Music", {
size: 90,
fill: "#fff"
});
musicLabel.anchor.set(0.5, 0.5);
musicLabel.x = 2048 / 2;
musicLabel.y = 2732 / 2 + 80;
optionsOverlay.addChild(musicLabel);
var musicToggleBtn = new Text2("Mute", {
size: 100,
fill: "#fff"
});
musicToggleBtn.anchor.set(0.5, 0.5);
musicToggleBtn.x = 2048 / 2;
musicToggleBtn.y = 2732 / 2 + 180;
optionsOverlay.addChild(musicToggleBtn);
// --- Close Button ---
var optionsCloseBtn = new Text2("CLOSE", {
size: 100,
fill: "#fff"
});
optionsCloseBtn.anchor.set(0.5, 0.5);
optionsCloseBtn.x = 2048 / 2;
optionsCloseBtn.y = 2732 / 2 + 350;
optionsOverlay.addChild(optionsCloseBtn);
game.addChild(optionsOverlay);
// --- Sound/Music State ---
var soundVolume = 1.0; // 0.0 - 1.0
var musicMuted = false;
// Helper to update UI and apply sound/music settings
function updateOptionsUI() {
soundVolTxt.setText(Math.round(soundVolume * 100) + "%");
// Set all sound volumes
LK.getSound('coin').volume = soundVolume;
LK.getSound('crash').volume = soundVolume;
LK.getSound('hop').volume = soundVolume;
// Set music volume or mute
if (musicMuted) {
LK.setMusicVolume && LK.setMusicVolume(0);
musicToggleBtn.setText("Unmute");
} else {
LK.setMusicVolume && LK.setMusicVolume(soundVolume);
musicToggleBtn.setText("Mute");
}
}
// --- Options Button Handlers ---
// Show options overlay
function showOptions() {
optionsOverlay.visible = true;
startMenuOverlay.visible = false;
updateOptionsUI();
}
// Hide options overlay
function closeOptions() {
optionsOverlay.visible = false;
if (startMenuActive) startMenuOverlay.visible = true;
}
// Touch handler for options overlay
optionsOverlay.down = function (x, y, obj) {
if (isInsideBtn(soundDownBtn, x, y)) {
soundVolume = Math.max(0, soundVolume - 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(soundUpBtn, x, y)) {
soundVolume = Math.min(1, soundVolume + 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(musicToggleBtn, x, y)) {
musicMuted = !musicMuted;
updateOptionsUI();
return;
}
if (isInsideBtn(optionsCloseBtn, x, y)) {
closeOptions();
return;
}
};
// Forward touch events to options overlay if visible
var origGameDown = game.down;
game.down = function (x, y, obj) {
if (optionsOverlay.visible) {
optionsOverlay.down(x, y, obj);
return;
}
origGameDown(x, y, obj);
};
function exitGame() {
LK.effects.flashScreen(0x222222, 400);
}
// Touch handler for menu
game.down = function (x, y, obj) {
if (startMenuActive) {
if (isInsideBtn(playBtn, x, y)) {
startGame();
return;
} else if (isInsideBtn(optionsBtn, x, y)) {
showOptions();
return;
} else if (isInsideBtn(exitBtn, x, y)) {
exitGame();
return;
}
// Ignore touches outside buttons while menu is active
return;
}
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (startMenuActive) return;
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animal class (player)
var Animal = Container.expand(function () {
var self = Container.call(this);
// Default to chicken
self.type = 'chicken';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Set a more accurate hitbox for the animal (chicken, cat, dog)
function setAnimalHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Shrink hitbox for more fair collisions
if (type === 'chicken') {
// chicken: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
} else if (type === 'cat' || type === 'dog') {
// cat/dog: 62% width, 62% height
self.hitArea = new Rectangle(-w * 0.31, -h * 0.31, w * 0.62, h * 0.62);
} else {
// fallback: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
}
}
setAnimalHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setAnimalHitArea(type);
};
// Animate hop
self.hop = function (targetX, targetY, _onFinish) {
tween(self, {
x: targetX,
y: targetY,
scaleY: 1.2
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 1
}, {
duration: 60,
easing: tween.cubicIn,
onFinish: _onFinish
});
}
});
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Animate spin
self.update = function () {
self.sprite.rotation += 0.15;
};
return self;
});
// Vehicle class
var Vehicle = Container.expand(function () {
var self = Container.call(this);
// type: 'car1', 'car2', 'truck'
self.type = 'car1';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Headlights for night effect
self.headlights = null;
function addHeadlights() {
// Remove old headlights if any
if (self.headlights) {
self.headlights.destroy();
self.headlights = null;
}
// Only add headlights for cars and truck
if (self.type === 'car1' || self.type === 'car2' || self.type === 'truck') {
// Attach headlight asset, anchor at left for right-to-left, right for left-to-right
self.headlights = self.attachAsset('car_headlight', {
anchorX: self.dir === 1 ? 0 : 1,
anchorY: 0.5
});
// Remove hitbox from headlights so they do not participate in collision
self.headlights.hitArea = null;
// Place headlights further inside the car for a more realistic look
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
// Scale headlights to match car width
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Start hidden, will be shown at night
self.headlights.alpha = 0;
}
}
// Set a much smaller hitbox for the vehicle (extremely forgiving)
function setVehicleHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Make hitbox ultra tiny (almost no damage at all)
if (type === 'car1') {
// car1: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'car2') {
// car2: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'truck') {
// truck: 2.5% width, 2% height
self.hitArea = new Rectangle(-w * 0.0125, -h * 0.01, w * 0.025, h * 0.02);
} else {
// fallback: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
}
}
setVehicleHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setVehicleHitArea(type);
// Set sprite orientation to always face movement direction, regardless of asset's default orientation
if (type === 'car2') {
// Car2 asset default faces right, so:
if (self.dir === 1) {
// Left-to-right: no flip needed
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
// Right-to-left: flip horizontally
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
// Other vehicles: keep existing logic
if (self.dir === 1) {
// Always face right for left-to-right movement
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
} else {
// Always face left for right-to-left movement
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
}
}
// Add or update headlights
addHeadlights();
};
// Lane index (0-19)
self.lane = 0;
// Direction: 1 = left to right, -1 = right to left
self.dir = 1;
// Speed in px per frame
self.speed = 6;
self.update = function () {
if (typeof self.lastX === "undefined") self.lastX = self.x;
self.x += self.speed * self.dir;
// Ensure sprite faces the correct direction, always facing movement direction
if (self.type === 'car2') {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
}
self.lastX = self.x;
// Update headlights orientation, scale, and visibility for night
if (self.headlights) {
// Update position and scale in case car was resized or direction changed
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Show headlights only at night (dayNight.t >= 0.5), with semi-transparency
self.headlights.alpha = typeof dayNight !== "undefined" && dayNight.t >= 0.5 ? 0.45 : 0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Day sky blue
});
/****
* Game Code
****/
// Music
// Sounds
// Coin
// Vehicles
// Lane types
// Animal characters
// --- Game constants ---
var NUM_LANES = 20;
var LANE_HEIGHT = 120;
var LANE_TYPES = []; // 'grass' or 'road'
var LANE_Y = []; // y positions of each lane (0 = bottom)
var LANE_CONFIG = []; // {type, y, speed, dir, spacing, vehicleTypes}
var LANE_TOP = 2732 - NUM_LANES * LANE_HEIGHT; // Top y of first lane
// --- Day/Night cycle ---
var dayNight = {
t: 0,
// 0 = day, 1 = night
speed: 0.00025,
// How fast day/night cycles (per tick)
colorDay: 0x87ceeb,
colorNight: 0x23234a
};
// --- Game state ---
var player = null;
var vehicles = [];
var coins = [];
var score = 0;
var coinsCollected = 0;
var isMoving = false;
var dragStart = null;
var dragTarget = null;
var lastLane = 0;
var gameOver = false;
// --- Level state ---
var currentLevel = 1;
var maxLevel = 5;
var flag = null;
// --- UI ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: 0xFFD700
});
coinTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(coinTxt);
// --- Lane setup ---
function setupLanes() {
LANE_TYPES.length = 0;
LANE_Y.length = 0;
LANE_CONFIG.length = 0;
for (var i = 0; i < NUM_LANES; i++) {
// Alternate: grass every 3rd lane, rest are road
var type = i % 3 === 0 ? 'grass' : 'road';
LANE_TYPES.push(type);
var y = 2732 - (i + 0.5) * LANE_HEIGHT;
LANE_Y.push(y);
// Lane config
if (type === 'road') {
// Assign direction: even lanes go left-to-right, odd lanes go right-to-left (never mixed)
var dir = i % 2 === 0 ? 1 : -1;
var speed = 4 + i % 4 * 1.5; // 4, 5.5, 7, 8.5
var spacing = 350 + i % 3 * 80; // 350, 430, 510
var vehicleTypes = i % 4 === 0 ? ['truck'] : ['car1', 'car2'];
LANE_CONFIG.push({
type: type,
y: y,
speed: speed,
dir: dir,
spacing: spacing,
vehicleTypes: vehicleTypes
});
} else {
LANE_CONFIG.push({
type: type,
y: y
});
}
}
}
// --- Draw lanes ---
function drawLanes() {
for (var i = 0; i < NUM_LANES; i++) {
var laneType = LANE_TYPES[i];
var y = LANE_Y[i];
var lane = LK.getAsset(laneType, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: y
});
game.addChild(lane);
// Add direction arrows for road lanes
// (Removed small car images used as arrows)
}
}
// --- Spawn player ---
function spawnPlayer() {
player = new Animal();
player.setType('chicken');
// Start at bottom grass lane, center
player.x = 2048 / 2;
player.y = LANE_Y[0];
lastLane = 0;
game.addChild(player);
}
// --- Spawn flag at the end of the level ---
function spawnFlag() {
if (flag) {
flag.destroy();
flag = null;
}
// Place flag at the topmost grass lane
var topLaneIdx = NUM_LANES - 1;
var flagY = LANE_Y[topLaneIdx];
flag = LK.getAsset('flag', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: flagY
});
game.addChild(flag);
}
// --- Spawn vehicles ---
function spawnVehicle(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'road') return;
// Check for minimum distance to last vehicle in this lane
var lastVehicle = null;
var lastDist = 99999;
for (var i = 0; i < vehicles.length; i++) {
if (vehicles[i].lane === laneIdx) {
var dist = Math.abs(vehicles[i].x - 2048 / 2);
if (dist < lastDist) {
lastDist = dist;
lastVehicle = vehicles[i];
}
}
}
// Only spawn if no vehicle or far enough from last vehicle in this lane
if (!lastVehicle || lastDist > config.spacing) {
var v = new Vehicle();
// Random vehicle type
var vtype = config.vehicleTypes[Math.floor(Math.random() * config.vehicleTypes.length)];
v.setType(vtype);
v.lane = laneIdx;
v.dir = config.dir;
v.speed = config.speed + Math.random() * 1.5;
// Y position
v.y = config.y;
// X position: offscreen left or right
if (v.dir === 1) {
v.x = -v.sprite.width / 2 - 20;
// Car2: always face right for left-to-right movement
if (v.type === 'car2') {
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face right for left-to-right movement
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
} else {
v.x = 2048 + v.sprite.width / 2 + 20;
// Car2: always face left for right-to-left movement
if (v.type === 'car2') {
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face left for right-to-left movement
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
}
// Update headlights after direction and type are set
if (typeof v.addHeadlights === "function") v.addHeadlights && v.addHeadlights();
vehicles.push(v);
game.addChild(v);
}
}
// --- Spawn coin ---
function spawnCoin(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'grass') return;
// Only one coin per grass lane at a time
for (var i = 0; i < coins.length; i++) {
if (Math.abs(coins[i].y - config.y) < 10) return;
}
var c = new Coin();
c.x = 200 + Math.random() * (2048 - 400);
c.y = config.y;
coins.push(c);
game.addChild(c);
}
// --- Remove vehicle ---
function removeVehicle(idx) {
vehicles[idx].destroy();
vehicles.splice(idx, 1);
}
// --- Remove coin ---
function removeCoin(idx) {
coins[idx].destroy();
coins.splice(idx, 1);
}
// --- Update UI ---
function updateUI() {
scoreTxt.setText(score);
coinTxt.setText(coinsCollected);
}
// --- Sun and Moon sprites ---
var sunSprite = null;
var moonSprite = null;
var skySpriteLayer = new Container();
game.addChild(skySpriteLayer);
// --- Day/Night cycle update ---
function updateDayNight() {
dayNight.t += dayNight.speed;
if (dayNight.t > 1) dayNight.t = 0;
// Interpolate color
var t = dayNight.t;
var c1 = dayNight.colorDay,
c2 = dayNight.colorNight;
var r = (c1 >> 16) * (1 - t) + (c2 >> 16) * t & 0xff;
var g = (c1 >> 8 & 0xff) * (1 - t) + (c2 >> 8 & 0xff) * t & 0xff;
var b = (c1 & 0xff) * (1 - t) + (c2 & 0xff) * t & 0xff;
var color = r << 16 | g << 8 | b;
game.setBackgroundColor(color);
// Sun and Moon movement
// Sun: visible during t in [0,0.5), Moon: visible during t in [0.5,1)
// Both move in an arc from left to right
var skyY = 350;
var skyRadius = 800;
var centerX = 2048 / 2;
var centerY = skyY + 200;
// Sun
if (!sunSprite) {
sunSprite = LK.getAsset('sun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(sunSprite);
}
// Moon
if (!moonSprite) {
moonSprite = LK.getAsset('ay', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(moonSprite);
}
// Sun position: t in [0,0.5) → left to right, high arc
var sunT = t < 0.5 ? t / 0.5 : 0;
var sunAngle = Math.PI * (1 - sunT); // from left (π) to right (0)
sunSprite.x = centerX + skyRadius * Math.cos(sunAngle);
sunSprite.y = centerY - skyRadius * Math.sin(sunAngle);
sunSprite.alpha = t < 0.5 ? 1 : 0;
// Moon position: t in [0.5,1) → left to right, high arc
var moonT = t >= 0.5 ? (t - 0.5) / 0.5 : 0;
var moonAngle = Math.PI * (1 - moonT); // from left (π) to right (0)
moonSprite.x = centerX + skyRadius * Math.cos(moonAngle);
moonSprite.y = centerY - skyRadius * Math.sin(moonAngle);
moonSprite.alpha = t >= 0.5 ? 1 : 0;
}
// --- Collision detection ---
function checkCollisions() {
// Vehicles
for (var i = 0; i < vehicles.length; i++) {
if (player.intersects(vehicles[i])) {
// Crash!
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('crash').play();
gameOver = true;
LK.showGameOver();
return;
}
}
// Coins
for (var i = coins.length - 1; i >= 0; i--) {
if (player.intersects(coins[i])) {
coinsCollected++;
updateUI();
LK.getSound('coin').play();
removeCoin(i);
}
}
}
// --- Move player (grid) ---
function tryMove(dx, dy) {
if (isMoving || gameOver) return;
var newLane = lastLane + dy;
if (newLane < 0 || newLane >= NUM_LANES) return;
var newX = player.x + dx * 220;
if (newX < 80 || newX > 2048 - 80) return;
var newY = LANE_Y[newLane];
isMoving = true;
player.hop(newX, newY, function () {
isMoving = false;
lastLane = newLane;
// Score: advance only if moving up
if (dy > 0) {
score++;
updateUI();
if (score >= NUM_LANES - 1) {
// Win!
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
});
LK.getSound('hop').play();
}
// --- Touch controls (swipe) ---
var touchStart = null;
game.down = function (x, y, obj) {
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
};
// --- Main update loop ---
game.update = function () {
if (gameOver) return;
// Day/Night
updateDayNight();
// Vehicles
for (var i = vehicles.length - 1; i >= 0; i--) {
vehicles[i].update();
// Remove if offscreen
if (vehicles[i].dir === 1 && vehicles[i].x > 2048 + vehicles[i].sprite.width / 2 + 40 || vehicles[i].dir === -1 && vehicles[i].x < -vehicles[i].sprite.width / 2 - 40) {
removeVehicle(i);
}
}
// Coins
for (var i = 0; i < coins.length; i++) {
coins[i].update();
}
// Spawn vehicles
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'road') continue;
// Try to spawn a vehicle with a small probability
if (Math.random() < 0.025) {
// ~1 every 40 frames
spawnVehicle(i);
}
}
// Spawn coins
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'grass') continue;
// 1% chance per frame
if (Math.random() < 0.01) {
spawnCoin(i);
}
}
// Remove coins if offscreen (shouldn't happen, but safety)
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].y < LANE_TOP - 100 || coins[i].y > 2732 + 100) {
removeCoin(i);
}
}
// Collisions
checkCollisions();
// Check for flag collision and level progression
if (flag && player.intersects(flag)) {
// Advance to next level if possible
if (currentLevel < maxLevel) {
currentLevel++;
// Increase difficulty: more/faster vehicles, less grass, etc.
NUM_LANES = 20 + currentLevel * 2;
LANE_HEIGHT = Math.max(80, 120 - currentLevel * 5);
// Remove all game objects
for (var i = vehicles.length - 1; i >= 0; i--) removeVehicle(i);
for (var i = coins.length - 1; i >= 0; i--) removeCoin(i);
if (player) {
player.destroy();
player = null;
}
if (flag) {
flag.destroy();
flag = null;
}
// Re-setup lanes and redraw
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
gameOver = false;
} else {
// Last level: show win
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
};
// --- Start Menu Overlay ---
var startMenuOverlay = new Container();
var startMenuBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 4,
scaleY: 4
});
startMenuOverlay.addChild(startMenuBg);
// Play button (image)
var playBtn = LK.getAsset('start', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 250,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(playBtn);
// Options button (image)
var optionsBtn = LK.getAsset('options', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 450,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(optionsBtn);
// Exit button (image)
var exitBtn = LK.getAsset('exit', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 600,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(exitBtn);
var startMenuActive = true;
game.addChild(startMenuOverlay);
// Helper for button hit detection (works for both Text2 and image buttons)
function isInsideBtn(btn, x, y) {
// Use anchorX/anchorY for images, anchor for Text2
var anchorX = typeof btn.anchorX === "number" ? btn.anchorX : btn.anchor && btn.anchor.x ? btn.anchor.x : 0.5;
var anchorY = typeof btn.anchorY === "number" ? btn.anchorY : btn.anchor && btn.anchor.y ? btn.anchor.y : 0.5;
var bx = btn.x - btn.width * anchorX;
var by = btn.y - btn.height * anchorY;
return x >= bx && x <= bx + btn.width && y >= by && y <= by + btn.height;
}
// Block input/gameplay until play is pressed
function startGame() {
if (!startMenuActive) return;
startMenuActive = false;
startMenuOverlay.visible = false;
// Now start the game
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
}
// --- Options Overlay ---
var optionsOverlay = new Container();
optionsOverlay.visible = false;
// Background for options
var optionsBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 3.5,
scaleY: 3.5
});
optionsOverlay.addChild(optionsBg);
// Title
var optionsTitle = new Text2("OPTIONS", {
size: 140,
fill: "#fff"
});
optionsTitle.anchor.set(0.5, 0.5);
optionsTitle.x = 2048 / 2;
optionsTitle.y = 2732 / 2 - 350;
optionsOverlay.addChild(optionsTitle);
// --- Sound Volume Controls ---
var soundLabel = new Text2("Sound Volume", {
size: 90,
fill: "#fff"
});
soundLabel.anchor.set(0.5, 0.5);
soundLabel.x = 2048 / 2;
soundLabel.y = 2732 / 2 - 150;
optionsOverlay.addChild(soundLabel);
var soundDownBtn = new Text2("-", {
size: 120,
fill: "#fff"
});
soundDownBtn.anchor.set(0.5, 0.5);
soundDownBtn.x = 2048 / 2 - 200;
soundDownBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundDownBtn);
var soundUpBtn = new Text2("+", {
size: 120,
fill: "#fff"
});
soundUpBtn.anchor.set(0.5, 0.5);
soundUpBtn.x = 2048 / 2 + 200;
soundUpBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundUpBtn);
var soundVolTxt = new Text2("100%", {
size: 90,
fill: "#fff"
});
soundVolTxt.anchor.set(0.5, 0.5);
soundVolTxt.x = 2048 / 2;
soundVolTxt.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundVolTxt);
// --- Music Controls ---
var musicLabel = new Text2("Music", {
size: 90,
fill: "#fff"
});
musicLabel.anchor.set(0.5, 0.5);
musicLabel.x = 2048 / 2;
musicLabel.y = 2732 / 2 + 80;
optionsOverlay.addChild(musicLabel);
var musicToggleBtn = new Text2("Mute", {
size: 100,
fill: "#fff"
});
musicToggleBtn.anchor.set(0.5, 0.5);
musicToggleBtn.x = 2048 / 2;
musicToggleBtn.y = 2732 / 2 + 180;
optionsOverlay.addChild(musicToggleBtn);
// --- Close Button ---
var optionsCloseBtn = new Text2("CLOSE", {
size: 100,
fill: "#fff"
});
optionsCloseBtn.anchor.set(0.5, 0.5);
optionsCloseBtn.x = 2048 / 2;
optionsCloseBtn.y = 2732 / 2 + 350;
optionsOverlay.addChild(optionsCloseBtn);
game.addChild(optionsOverlay);
// --- Sound/Music State ---
var soundVolume = 1.0; // 0.0 - 1.0
var musicMuted = false;
// Helper to update UI and apply sound/music settings
function updateOptionsUI() {
soundVolTxt.setText(Math.round(soundVolume * 100) + "%");
// Set all sound volumes
LK.getSound('coin').volume = soundVolume;
LK.getSound('crash').volume = soundVolume;
LK.getSound('hop').volume = soundVolume;
// Set music volume or mute
if (musicMuted) {
LK.setMusicVolume && LK.setMusicVolume(0);
musicToggleBtn.setText("Unmute");
} else {
LK.setMusicVolume && LK.setMusicVolume(soundVolume);
musicToggleBtn.setText("Mute");
}
}
// --- Options Button Handlers ---
// Show options overlay
function showOptions() {
optionsOverlay.visible = true;
startMenuOverlay.visible = false;
updateOptionsUI();
}
// Hide options overlay
function closeOptions() {
optionsOverlay.visible = false;
if (startMenuActive) startMenuOverlay.visible = true;
}
// Touch handler for options overlay
optionsOverlay.down = function (x, y, obj) {
if (isInsideBtn(soundDownBtn, x, y)) {
soundVolume = Math.max(0, soundVolume - 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(soundUpBtn, x, y)) {
soundVolume = Math.min(1, soundVolume + 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(musicToggleBtn, x, y)) {
musicMuted = !musicMuted;
updateOptionsUI();
return;
}
if (isInsideBtn(optionsCloseBtn, x, y)) {
closeOptions();
return;
}
};
// Forward touch events to options overlay if visible
var origGameDown = game.down;
game.down = function (x, y, obj) {
if (optionsOverlay.visible) {
optionsOverlay.down(x, y, obj);
return;
}
origGameDown(x, y, obj);
};
function exitGame() {
LK.effects.flashScreen(0x222222, 400);
}
// Touch handler for menu
game.down = function (x, y, obj) {
if (startMenuActive) {
if (isInsideBtn(playBtn, x, y)) {
startGame();
return;
} else if (isInsideBtn(optionsBtn, x, y)) {
showOptions();
return;
} else if (isInsideBtn(exitBtn, x, y)) {
exitGame();
return;
}
// Ignore touches outside buttons while menu is active
return;
}
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (startMenuActive) return;
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
};
chicken. In-Game asset. 2d. High contrast. No shadows
road çif şeritli düz yol. In-Game asset. 2d. High contrast. No shadows
car. In-Game asset. 2d. High contrast. No shadows
kamyon. In-Game asset. 2d. High contrast. No shadows
truck. In-Game asset. 2d. High contrast. No shadows
grass çim zemim kısa çimli zemin. In-Game asset. 2d. High contrast. No shadows
flag. In-Game asset. 2d. High contrast. No shadows
sun. In-Game asset. 2d. High contrast. No shadows
moon. In-Game asset. 2d. High contrast. No shadows
far ışığı yansıması. In-Game asset. 2d. High contrast. No shadows