/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Coin var Coin = Container.expand(function () { var self = Container.call(this); self.lane = 1; self.collected = false; self.speed = 0; self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.y += self.speed; }; return self; }); // Obstacle base class var Obstacle = Container.expand(function () { var self = Container.call(this); self.type = 'barrier'; // default self.lane = 1; self.passed = false; self.speed = 0; self.update = function () { self.y += self.speed; }; return self; }); // Train var Train = Obstacle.expand(function () { var self = Obstacle.call(this); self.type = 'train'; self.attachAsset('train', { anchorX: 0.5, anchorY: 1 }); return self; }); // Sign (slide under) var Sign = Obstacle.expand(function () { var self = Obstacle.call(this); self.type = 'sign'; self.attachAsset('sign', { anchorX: 0.5, anchorY: 1 }); return self; }); // Barrier var Barrier = Obstacle.expand(function () { var self = Obstacle.call(this); self.type = 'barrier'; self.attachAsset('barrier', { anchorX: 0.5, anchorY: 1 }); return self; }); // Runner class var Runner = Container.expand(function () { var self = Container.call(this); var runnerSprite = self.attachAsset('runner', { anchorX: 0.5, anchorY: 1 }); self.lane = 1; // 0: left, 1: center, 2: right self.isJumping = false; self.isSliding = false; self.jumpY = 0; self.slideTimer = 0; // Jump action self.jump = function () { if (self.isJumping || self.isSliding) return; self.isJumping = true; // Animate jump: up then down tween(self, { jumpY: -320 }, { duration: 260, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { jumpY: 0 }, { duration: 320, easing: tween.cubicIn, onFinish: function onFinish() { self.isJumping = false; } }); } }); }; // Slide action self.slide = function () { if (self.isSliding || self.isJumping) return; self.isSliding = true; self.slideTimer = 24; // ~0.4s at 60fps tween(runnerSprite, { height: 120 }, { duration: 80, easing: tween.linear }); }; // Lane change self.moveLane = function (dir) { if (self.isJumping || self.isSliding) return; var newLane = self.lane + dir; if (newLane < 0 || newLane > 2) return; self.lane = newLane; var targetX = lanesX[self.lane]; tween(self, { x: targetX }, { duration: 120, easing: tween.cubicOut }); }; // Update per frame self.update = function () { // Slide timer if (self.isSliding) { self.slideTimer--; if (self.slideTimer <= 0) { self.isSliding = false; tween(runnerSprite, { height: 240 }, { duration: 80, easing: tween.linear }); } } // Apply jumpY offset self.y = runnerBaseY + self.jumpY; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222831 }); /**** * Game Code ****/ // Coin // Obstacle: Sign (slide under) // Obstacle: Train // Obstacle: Barrier // Lane ground // Character: The runner // Lane positions (3 lanes) var lanesX = [2048 / 2 - 320, // left 2048 / 2, // center 2048 / 2 + 320 // right ]; var runnerBaseY = 2732 - 420; // Y position for runner standing on ground // Add lane backgrounds for (var i = 0; i < 3; i++) { var lane = LK.getAsset('lane', { anchorX: 0.5, anchorY: 1, x: lanesX[i], y: 2732 }); game.addChild(lane); } // Create runner var runner = new Runner(); runner.x = lanesX[1]; runner.y = runnerBaseY; game.addChild(runner); // Score and coins var score = 0; var coins = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var coinTxt = new Text2('0', { size: 80, fill: 0xFFE066 }); coinTxt.anchor.set(1, 0); LK.gui.topRight.addChild(coinTxt); // Game state var obstacles = []; var coinsArr = []; var gameSpeed = 18; // pixels per frame var spawnTimer = 0; var coinSpawnTimer = 0; var ticks = 0; var lastTouch = { x: 0, y: 0 }; var swipeThreshold = 80; // px var swipeTime = 18; // frames // Touch/Swipe controls var touchStartX = 0, touchStartY = 0, touchStartTick = 0; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; touchStartTick = LK.ticks; lastTouch.x = x; lastTouch.y = y; }; game.up = function (x, y, obj) { var dx = x - touchStartX; var dy = y - touchStartY; var dt = LK.ticks - touchStartTick; if (dt > swipeTime) return; // too slow if (Math.abs(dx) > Math.abs(dy)) { // Horizontal swipe if (dx > swipeThreshold) { runner.moveLane(1); // right } else if (dx < -swipeThreshold) { runner.moveLane(-1); // left } } else { // Vertical swipe if (dy < -swipeThreshold) { runner.jump(); } else if (dy > swipeThreshold) { runner.slide(); } } }; game.move = function (x, y, obj) { lastTouch.x = x; lastTouch.y = y; }; // Main update loop game.update = function () { ticks++; // Increase speed over time if (ticks % 180 === 0 && gameSpeed < 38) { gameSpeed += 1.2; } // Spawn obstacles spawnTimer--; if (spawnTimer <= 0) { spawnTimer = 54 + Math.floor(Math.random() * 36); // 0.9-1.5s var typeRand = Math.random(); var lane = Math.floor(Math.random() * 3); var obs; if (typeRand < 0.18) { obs = new Train(); } else if (typeRand < 0.48) { obs = new Barrier(); } else { obs = new Sign(); } obs.lane = lane; obs.x = lanesX[lane]; obs.y = -40; obs.speed = gameSpeed; obstacles.push(obs); game.addChild(obs); } // Spawn coins coinSpawnTimer--; if (coinSpawnTimer <= 0) { coinSpawnTimer = 36 + Math.floor(Math.random() * 36); // 0.6-1.2s var lane = Math.floor(Math.random() * 3); var coin = new Coin(); coin.lane = lane; coin.x = lanesX[lane]; coin.y = -60; coin.speed = gameSpeed; coinsArr.push(coin); game.addChild(coin); } // Update runner runner.update(); // Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off screen if (obs.y > 2732 + 200) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision detection if (!obs.passed && Math.abs(obs.y - runner.y) < 120 && obs.lane === runner.lane) { if (obs.type === 'barrier') { if (!runner.isJumping) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } else if (obs.type === 'sign') { if (!runner.isSliding) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } else if (obs.type === 'train') { // Train: always game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } obs.passed = true; score += 10; scoreTxt.setText(score); } else if (!obs.passed && obs.y > runner.y + 60) { obs.passed = true; score += 2; scoreTxt.setText(score); } } // Update coins for (var i = coinsArr.length - 1; i >= 0; i--) { var coin = coinsArr[i]; coin.update(); // Remove if off screen if (coin.y > 2732 + 100) { coin.destroy(); coinsArr.splice(i, 1); continue; } // Collect coin if (!coin.collected && Math.abs(coin.y - runner.y + 60) < 100 && coin.lane === runner.lane && !runner.isSliding) { coin.collected = true; coins++; coinTxt.setText(coins); coin.destroy(); coinsArr.splice(i, 1); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Coin
var Coin = Container.expand(function () {
var self = Container.call(this);
self.lane = 1;
self.collected = false;
self.speed = 0;
self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.y += self.speed;
};
return self;
});
// Obstacle base class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
self.type = 'barrier'; // default
self.lane = 1;
self.passed = false;
self.speed = 0;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Train
var Train = Obstacle.expand(function () {
var self = Obstacle.call(this);
self.type = 'train';
self.attachAsset('train', {
anchorX: 0.5,
anchorY: 1
});
return self;
});
// Sign (slide under)
var Sign = Obstacle.expand(function () {
var self = Obstacle.call(this);
self.type = 'sign';
self.attachAsset('sign', {
anchorX: 0.5,
anchorY: 1
});
return self;
});
// Barrier
var Barrier = Obstacle.expand(function () {
var self = Obstacle.call(this);
self.type = 'barrier';
self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 1
});
return self;
});
// Runner class
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // 0: left, 1: center, 2: right
self.isJumping = false;
self.isSliding = false;
self.jumpY = 0;
self.slideTimer = 0;
// Jump action
self.jump = function () {
if (self.isJumping || self.isSliding) return;
self.isJumping = true;
// Animate jump: up then down
tween(self, {
jumpY: -320
}, {
duration: 260,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
jumpY: 0
}, {
duration: 320,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
};
// Slide action
self.slide = function () {
if (self.isSliding || self.isJumping) return;
self.isSliding = true;
self.slideTimer = 24; // ~0.4s at 60fps
tween(runnerSprite, {
height: 120
}, {
duration: 80,
easing: tween.linear
});
};
// Lane change
self.moveLane = function (dir) {
if (self.isJumping || self.isSliding) return;
var newLane = self.lane + dir;
if (newLane < 0 || newLane > 2) return;
self.lane = newLane;
var targetX = lanesX[self.lane];
tween(self, {
x: targetX
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Update per frame
self.update = function () {
// Slide timer
if (self.isSliding) {
self.slideTimer--;
if (self.slideTimer <= 0) {
self.isSliding = false;
tween(runnerSprite, {
height: 240
}, {
duration: 80,
easing: tween.linear
});
}
}
// Apply jumpY offset
self.y = runnerBaseY + self.jumpY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222831
});
/****
* Game Code
****/
// Coin
// Obstacle: Sign (slide under)
// Obstacle: Train
// Obstacle: Barrier
// Lane ground
// Character: The runner
// Lane positions (3 lanes)
var lanesX = [2048 / 2 - 320,
// left
2048 / 2,
// center
2048 / 2 + 320 // right
];
var runnerBaseY = 2732 - 420; // Y position for runner standing on ground
// Add lane backgrounds
for (var i = 0; i < 3; i++) {
var lane = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 1,
x: lanesX[i],
y: 2732
});
game.addChild(lane);
}
// Create runner
var runner = new Runner();
runner.x = lanesX[1];
runner.y = runnerBaseY;
game.addChild(runner);
// Score and coins
var score = 0;
var coins = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: 0xFFE066
});
coinTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(coinTxt);
// Game state
var obstacles = [];
var coinsArr = [];
var gameSpeed = 18; // pixels per frame
var spawnTimer = 0;
var coinSpawnTimer = 0;
var ticks = 0;
var lastTouch = {
x: 0,
y: 0
};
var swipeThreshold = 80; // px
var swipeTime = 18; // frames
// Touch/Swipe controls
var touchStartX = 0,
touchStartY = 0,
touchStartTick = 0;
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
touchStartTick = LK.ticks;
lastTouch.x = x;
lastTouch.y = y;
};
game.up = function (x, y, obj) {
var dx = x - touchStartX;
var dy = y - touchStartY;
var dt = LK.ticks - touchStartTick;
if (dt > swipeTime) return; // too slow
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > swipeThreshold) {
runner.moveLane(1); // right
} else if (dx < -swipeThreshold) {
runner.moveLane(-1); // left
}
} else {
// Vertical swipe
if (dy < -swipeThreshold) {
runner.jump();
} else if (dy > swipeThreshold) {
runner.slide();
}
}
};
game.move = function (x, y, obj) {
lastTouch.x = x;
lastTouch.y = y;
};
// Main update loop
game.update = function () {
ticks++;
// Increase speed over time
if (ticks % 180 === 0 && gameSpeed < 38) {
gameSpeed += 1.2;
}
// Spawn obstacles
spawnTimer--;
if (spawnTimer <= 0) {
spawnTimer = 54 + Math.floor(Math.random() * 36); // 0.9-1.5s
var typeRand = Math.random();
var lane = Math.floor(Math.random() * 3);
var obs;
if (typeRand < 0.18) {
obs = new Train();
} else if (typeRand < 0.48) {
obs = new Barrier();
} else {
obs = new Sign();
}
obs.lane = lane;
obs.x = lanesX[lane];
obs.y = -40;
obs.speed = gameSpeed;
obstacles.push(obs);
game.addChild(obs);
}
// Spawn coins
coinSpawnTimer--;
if (coinSpawnTimer <= 0) {
coinSpawnTimer = 36 + Math.floor(Math.random() * 36); // 0.6-1.2s
var lane = Math.floor(Math.random() * 3);
var coin = new Coin();
coin.lane = lane;
coin.x = lanesX[lane];
coin.y = -60;
coin.speed = gameSpeed;
coinsArr.push(coin);
game.addChild(coin);
}
// Update runner
runner.update();
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.y > 2732 + 200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection
if (!obs.passed && Math.abs(obs.y - runner.y) < 120 && obs.lane === runner.lane) {
if (obs.type === 'barrier') {
if (!runner.isJumping) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else if (obs.type === 'sign') {
if (!runner.isSliding) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
} else if (obs.type === 'train') {
// Train: always game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
obs.passed = true;
score += 10;
scoreTxt.setText(score);
} else if (!obs.passed && obs.y > runner.y + 60) {
obs.passed = true;
score += 2;
scoreTxt.setText(score);
}
}
// Update coins
for (var i = coinsArr.length - 1; i >= 0; i--) {
var coin = coinsArr[i];
coin.update();
// Remove if off screen
if (coin.y > 2732 + 100) {
coin.destroy();
coinsArr.splice(i, 1);
continue;
}
// Collect coin
if (!coin.collected && Math.abs(coin.y - runner.y + 60) < 100 && coin.lane === runner.lane && !runner.isSliding) {
coin.collected = true;
coins++;
coinTxt.setText(coins);
coin.destroy();
coinsArr.splice(i, 1);
}
}
};