/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Apple (collectible)
var Apple = Container.expand(function () {
var self = Container.call(this);
var appleSprite = self.attachAsset('apple', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.collected = false;
self.update = function () {
// Movement handled by game
};
return self;
});
// Fence (obstacle)
var Fence = Container.expand(function () {
var self = Container.call(this);
var fenceSprite = self.attachAsset('fence', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
// Horse (player) class
var Horse = Container.expand(function () {
var self = Container.call(this);
var horseSprite = self.attachAsset('horse', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // 0: left, 1: center, 2: right
self.isJumping = false;
self.jumpY = 0; // Offset for jump animation
self.jumpTween = null;
self.groundY = 0; // Set after adding to game
self.jumpHeight = 320; // How high the horse jumps
self.jumpDuration = 420; // ms
// Start jump if not already jumping
self.jump = function () {
if (self.isJumping) return;
self.isJumping = true;
if (self.jumpTween) tween.stop(self, {
jumpY: true
});
tween(self, {
jumpY: -self.jumpHeight
}, {
duration: self.jumpDuration,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
jumpY: 0
}, {
duration: self.jumpDuration,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
};
// Move to lane (0,1,2)
self.moveToLane = function (lane) {
if (lane < 0) lane = 0;
if (lane > 2) lane = 2;
self.lane = lane;
// Animate x to new lane
var targetX = laneX(lane);
tween(self, {
x: targetX
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Update position for jump
self.update = function () {
self.y = self.groundY + self.jumpY;
};
return self;
});
// Puddle (obstacle)
var Puddle = Container.expand(function () {
var self = Container.call(this);
var puddleSprite = self.attachAsset('puddle', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
// Rock (obstacle)
var Rock = Container.expand(function () {
var self = Container.call(this);
var rockSprite = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xA7D36C // Soft green field
});
/****
* Game Code
****/
// Lane marker (for visual lanes) - 8x2732, light gray box
// Puddle (obstacle) - 140x60, blue ellipse
// Fence (obstacle) - 160x40, tan box
// Rock (obstacle) - 120x80, gray ellipse
// Apple (collectible) - 100x100, red ellipse
// Horse (player) - 220x180, brown box
// Lane logic
var laneCount = 3;
var laneWidth = 480; // (2048 - 2*104) / 3 = ~613, but use 480 for margin
var laneMargin = 104; // left/right margin
function laneX(lane) {
return laneMargin + lane * laneWidth + laneWidth / 2;
}
// Ground Y (where horse stands)
var groundY = 2200;
// Game speed (pixels per tick)
var baseSpeed = 16;
var speed = baseSpeed;
var speedIncrease = 0.0007; // per tick
var maxSpeed = 38;
// Arrays for obstacles and apples
var obstacles = [];
var apples = [];
// Score
var score = 0;
// Distance (for speed up)
var distance = 0;
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lane markers (visual only)
for (var i = 1; i < laneCount; i++) {
var marker = LK.getAsset('laneMarker', {
anchorX: 0.5,
anchorY: 0,
x: laneMargin + i * laneWidth,
y: 0,
scaleY: 1
});
game.addChild(marker);
}
// Create horse
var horse = new Horse();
horse.x = laneX(1);
horse.y = groundY;
horse.groundY = groundY;
game.addChild(horse);
// Touch/drag controls
var dragStartX = null;
var dragStartY = null;
var dragStartLane = null;
var dragStartTime = null;
var dragActive = false;
function getLaneFromX(x) {
for (var i = 0; i < laneCount; i++) {
var lx = laneX(i);
if (x < lx + laneWidth / 2) return i;
}
return laneCount - 1;
}
// Touch down: record start
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragStartLane = horse.lane;
dragStartTime = LK.ticks;
dragActive = true;
};
// Touch move: if horizontal drag, change lane
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
// Lane change
if (dx > 0 && horse.lane < laneCount - 1) {
horse.moveToLane(horse.lane + 1);
dragStartX = x;
dragStartLane = horse.lane;
} else if (dx < 0 && horse.lane > 0) {
horse.moveToLane(horse.lane - 1);
dragStartX = x;
dragStartLane = horse.lane;
}
}
};
// Touch up: if short tap or upward swipe, jump
game.up = function (x, y, obj) {
if (!dragActive) return;
var dt = LK.ticks - dragStartTime;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (dt < 30 && Math.abs(dx) < 60 && Math.abs(dy) < 60) {
// Tap: jump
horse.jump();
} else if (dy < -100 && Math.abs(dy) > Math.abs(dx)) {
// Upward swipe: jump
horse.jump();
}
dragActive = false;
};
// Obstacle/Apple spawn logic
var lastObstacleY = 0;
var lastAppleY = 0;
var minObstacleGap = 520;
var minAppleGap = 340;
var obstacleTypes = ['rock', 'fence', 'puddle'];
// Helper: spawn obstacle at y, lane
function spawnObstacle(type, lane, y) {
var obs;
if (type === 'rock') obs = new Rock();else if (type === 'fence') obs = new Fence();else obs = new Puddle();
obs.lane = lane;
obs.x = laneX(lane);
obs.y = y;
game.addChild(obs);
obstacles.push(obs);
}
// Helper: spawn apple at y, lane
function spawnApple(lane, y) {
var apple = new Apple();
apple.lane = lane;
apple.x = laneX(lane);
apple.y = y;
game.addChild(apple);
apples.push(apple);
}
// Main game update
game.update = function () {
// Speed up over time
if (speed < maxSpeed) speed += speedIncrease;
distance += speed;
// Move obstacles/apples down
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y += speed;
// Remove if off screen
if (obs.y > 2800) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision: only if in same lane and not already hit
if (!obs.hit && obs.lane === horse.lane) {
// Fence: must be jumped over
if (obs instanceof Fence) {
if (Math.abs(obs.y - horse.y) < 120 && horse.jumpY > -40) {
// Not high enough: hit
obs.hit = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else {
// Rock/Puddle: must be jumped or avoided
if (Math.abs(obs.y - horse.y) < 100 && horse.jumpY > -40) {
obs.hit = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
}
}
for (var i = apples.length - 1; i >= 0; i--) {
var apple = apples[i];
apple.y += speed;
// Remove if off screen
if (apple.y > 2800) {
apple.destroy();
apples.splice(i, 1);
continue;
}
// Collect: same lane, close, not already collected, and not jumping over
if (!apple.collected && apple.lane === horse.lane && Math.abs(apple.y - horse.y) < 120 && horse.jumpY > -120) {
apple.collected = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Animate apple
tween(apple, {
y: apple.y - 120,
alpha: 0
}, {
duration: 320,
easing: tween.cubicIn,
onFinish: function onFinish() {
apple.destroy();
}
});
apples.splice(i, 1);
}
}
// Spawn obstacles
if (distance - lastObstacleY > minObstacleGap + Math.random() * 320) {
var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
var lane = Math.floor(Math.random() * laneCount);
spawnObstacle(type, lane, -180);
lastObstacleY = distance;
}
// Spawn apples
if (distance - lastAppleY > minAppleGap + Math.random() * 220) {
var lane = Math.floor(Math.random() * laneCount);
spawnApple(lane, -120);
lastAppleY = distance;
}
};
// Reset score on game start
LK.setScore(0);
scoreTxt.setText('0'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Apple (collectible)
var Apple = Container.expand(function () {
var self = Container.call(this);
var appleSprite = self.attachAsset('apple', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.collected = false;
self.update = function () {
// Movement handled by game
};
return self;
});
// Fence (obstacle)
var Fence = Container.expand(function () {
var self = Container.call(this);
var fenceSprite = self.attachAsset('fence', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
// Horse (player) class
var Horse = Container.expand(function () {
var self = Container.call(this);
var horseSprite = self.attachAsset('horse', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // 0: left, 1: center, 2: right
self.isJumping = false;
self.jumpY = 0; // Offset for jump animation
self.jumpTween = null;
self.groundY = 0; // Set after adding to game
self.jumpHeight = 320; // How high the horse jumps
self.jumpDuration = 420; // ms
// Start jump if not already jumping
self.jump = function () {
if (self.isJumping) return;
self.isJumping = true;
if (self.jumpTween) tween.stop(self, {
jumpY: true
});
tween(self, {
jumpY: -self.jumpHeight
}, {
duration: self.jumpDuration,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
jumpY: 0
}, {
duration: self.jumpDuration,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
};
// Move to lane (0,1,2)
self.moveToLane = function (lane) {
if (lane < 0) lane = 0;
if (lane > 2) lane = 2;
self.lane = lane;
// Animate x to new lane
var targetX = laneX(lane);
tween(self, {
x: targetX
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Update position for jump
self.update = function () {
self.y = self.groundY + self.jumpY;
};
return self;
});
// Puddle (obstacle)
var Puddle = Container.expand(function () {
var self = Container.call(this);
var puddleSprite = self.attachAsset('puddle', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
// Rock (obstacle)
var Rock = Container.expand(function () {
var self = Container.call(this);
var rockSprite = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1;
self.hit = false;
self.update = function () {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xA7D36C // Soft green field
});
/****
* Game Code
****/
// Lane marker (for visual lanes) - 8x2732, light gray box
// Puddle (obstacle) - 140x60, blue ellipse
// Fence (obstacle) - 160x40, tan box
// Rock (obstacle) - 120x80, gray ellipse
// Apple (collectible) - 100x100, red ellipse
// Horse (player) - 220x180, brown box
// Lane logic
var laneCount = 3;
var laneWidth = 480; // (2048 - 2*104) / 3 = ~613, but use 480 for margin
var laneMargin = 104; // left/right margin
function laneX(lane) {
return laneMargin + lane * laneWidth + laneWidth / 2;
}
// Ground Y (where horse stands)
var groundY = 2200;
// Game speed (pixels per tick)
var baseSpeed = 16;
var speed = baseSpeed;
var speedIncrease = 0.0007; // per tick
var maxSpeed = 38;
// Arrays for obstacles and apples
var obstacles = [];
var apples = [];
// Score
var score = 0;
// Distance (for speed up)
var distance = 0;
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lane markers (visual only)
for (var i = 1; i < laneCount; i++) {
var marker = LK.getAsset('laneMarker', {
anchorX: 0.5,
anchorY: 0,
x: laneMargin + i * laneWidth,
y: 0,
scaleY: 1
});
game.addChild(marker);
}
// Create horse
var horse = new Horse();
horse.x = laneX(1);
horse.y = groundY;
horse.groundY = groundY;
game.addChild(horse);
// Touch/drag controls
var dragStartX = null;
var dragStartY = null;
var dragStartLane = null;
var dragStartTime = null;
var dragActive = false;
function getLaneFromX(x) {
for (var i = 0; i < laneCount; i++) {
var lx = laneX(i);
if (x < lx + laneWidth / 2) return i;
}
return laneCount - 1;
}
// Touch down: record start
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragStartLane = horse.lane;
dragStartTime = LK.ticks;
dragActive = true;
};
// Touch move: if horizontal drag, change lane
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
// Lane change
if (dx > 0 && horse.lane < laneCount - 1) {
horse.moveToLane(horse.lane + 1);
dragStartX = x;
dragStartLane = horse.lane;
} else if (dx < 0 && horse.lane > 0) {
horse.moveToLane(horse.lane - 1);
dragStartX = x;
dragStartLane = horse.lane;
}
}
};
// Touch up: if short tap or upward swipe, jump
game.up = function (x, y, obj) {
if (!dragActive) return;
var dt = LK.ticks - dragStartTime;
var dx = x - dragStartX;
var dy = y - dragStartY;
if (dt < 30 && Math.abs(dx) < 60 && Math.abs(dy) < 60) {
// Tap: jump
horse.jump();
} else if (dy < -100 && Math.abs(dy) > Math.abs(dx)) {
// Upward swipe: jump
horse.jump();
}
dragActive = false;
};
// Obstacle/Apple spawn logic
var lastObstacleY = 0;
var lastAppleY = 0;
var minObstacleGap = 520;
var minAppleGap = 340;
var obstacleTypes = ['rock', 'fence', 'puddle'];
// Helper: spawn obstacle at y, lane
function spawnObstacle(type, lane, y) {
var obs;
if (type === 'rock') obs = new Rock();else if (type === 'fence') obs = new Fence();else obs = new Puddle();
obs.lane = lane;
obs.x = laneX(lane);
obs.y = y;
game.addChild(obs);
obstacles.push(obs);
}
// Helper: spawn apple at y, lane
function spawnApple(lane, y) {
var apple = new Apple();
apple.lane = lane;
apple.x = laneX(lane);
apple.y = y;
game.addChild(apple);
apples.push(apple);
}
// Main game update
game.update = function () {
// Speed up over time
if (speed < maxSpeed) speed += speedIncrease;
distance += speed;
// Move obstacles/apples down
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y += speed;
// Remove if off screen
if (obs.y > 2800) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision: only if in same lane and not already hit
if (!obs.hit && obs.lane === horse.lane) {
// Fence: must be jumped over
if (obs instanceof Fence) {
if (Math.abs(obs.y - horse.y) < 120 && horse.jumpY > -40) {
// Not high enough: hit
obs.hit = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else {
// Rock/Puddle: must be jumped or avoided
if (Math.abs(obs.y - horse.y) < 100 && horse.jumpY > -40) {
obs.hit = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
}
}
for (var i = apples.length - 1; i >= 0; i--) {
var apple = apples[i];
apple.y += speed;
// Remove if off screen
if (apple.y > 2800) {
apple.destroy();
apples.splice(i, 1);
continue;
}
// Collect: same lane, close, not already collected, and not jumping over
if (!apple.collected && apple.lane === horse.lane && Math.abs(apple.y - horse.y) < 120 && horse.jumpY > -120) {
apple.collected = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Animate apple
tween(apple, {
y: apple.y - 120,
alpha: 0
}, {
duration: 320,
easing: tween.cubicIn,
onFinish: function onFinish() {
apple.destroy();
}
});
apples.splice(i, 1);
}
}
// Spawn obstacles
if (distance - lastObstacleY > minObstacleGap + Math.random() * 320) {
var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
var lane = Math.floor(Math.random() * laneCount);
spawnObstacle(type, lane, -180);
lastObstacleY = distance;
}
// Spawn apples
if (distance - lastAppleY > minAppleGap + Math.random() * 220) {
var lane = Math.floor(Math.random() * laneCount);
spawnApple(lane, -120);
lastAppleY = distance;
}
};
// Reset score on game start
LK.setScore(0);
scoreTxt.setText('0');
horse with tongue sticking out with saddle . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
horse dung with green whiff cloud trailing out. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
APPLE . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
unlucky horse shoe. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat