User prompt
Add a power-up that the player can collect while running. When the player touches it, the character becomes temporarily invincible for a few seconds. During this time, the character can go through obstacles without getting hurt. The power-up should be easy to spot and appear sometimes during the run. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Sonic Lane Runner
Initial prompt
An endless runner game with infinite levels where the player controls a Roblox-style Sonic character. The character runs forward on 3 lanes (left, center, right), dodging obstacles like spikes and cube-shaped enemies, while collecting golden rings. The camera should be in third-person view, positioned behind the character (not top-down), just like in Subway Surfers. The game is fast-paced and gets harder over time. The player can swipe left or right to change lanes, swipe up to jump, and swipe down to slide. The world is colorful and 3D, with cheerful music and simple controls for kids. Inspired by Sonic and Roblox.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Obstacle = Container.expand(function (type) { var self = Container.call(this); self.type = type; var assetName = type === 'spike' ? 'spike' : type === 'cube' ? 'cubeEnemy' : 'barrier'; var obstacleGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.update = function () { self.y += self.speed; }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.collected = false; self.update = function () { self.y += self.speed; // Pulsing effect to make it stand out powerupGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.2; powerupGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.2; powerupGraphics.rotation += 0.05; }; return self; }); var Ring = Container.expand(function () { var self = Container.call(this); var ringGraphics = self.attachAsset('ring', { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.collected = false; self.update = function () { self.y += self.speed; ringGraphics.rotation += 0.1; }; return self; }); var SonicCharacter = Container.expand(function () { var self = Container.call(this); var characterGraphics = self.attachAsset('sonicCharacter', { anchorX: 0.5, anchorY: 0.5 }); self.currentLane = 1; // 0 = left, 1 = center, 2 = right self.isJumping = false; self.isSliding = false; self.jumpStartY = 0; self.baseY = 0; self.isInvincible = false; self.invincibilityTimer = 0; self.activateInvincibility = function () { self.isInvincible = true; self.invincibilityTimer = LK.ticks + 300; // 5 seconds at 60fps // Visual effect - golden tint tween(characterGraphics, { tint: 0xFFD700 }, { duration: 200 }); // Pulsing effect during invincibility self.invincibilityPulse(); }; self.invincibilityPulse = function () { if (self.isInvincible) { tween(characterGraphics, { alpha: 0.5 }, { duration: 250, easing: tween.easeInOut, onFinish: function onFinish() { if (self.isInvincible) { tween(characterGraphics, { alpha: 1 }, { duration: 250, easing: tween.easeInOut, onFinish: function onFinish() { self.invincibilityPulse(); } }); } } }); } }; self.deactivateInvincibility = function () { self.isInvincible = false; // Remove visual effects tween.stop(characterGraphics, { alpha: true }); tween(characterGraphics, { tint: 0xFFFFFF, alpha: 1 }, { duration: 200 }); }; self.update = function () { // Check if invincibility should end if (self.isInvincible && LK.ticks >= self.invincibilityTimer) { self.deactivateInvincibility(); } }; self.moveTo = function (lane) { if (lane < 0 || lane > 2) return; self.currentLane = lane; var targetX = lanePositions[lane]; tween(self, { x: targetX }, { duration: 200, easing: tween.easeOut }); }; self.jump = function () { if (self.isJumping || self.isSliding) return; self.isJumping = true; self.jumpStartY = self.y; LK.getSound('jump').play(); tween(self, { y: self.jumpStartY - 150 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { y: self.jumpStartY }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { self.isJumping = false; } }); } }); }; self.slide = function () { if (self.isJumping || self.isSliding) return; self.isSliding = true; var originalScale = characterGraphics.scaleY; characterGraphics.scaleY = 0.5; LK.setTimeout(function () { characterGraphics.scaleY = originalScale; self.isSliding = false; }, 500); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ // Game variables var gameSpeed = 8; var speedIncrement = 0.02; var lanePositions = [2048 * 0.25, 2048 * 0.5, 2048 * 0.75]; var obstacles = []; var rings = []; var powerups = []; var lastObstacleSpawn = 0; var lastRingSpawn = 0; var lastPowerupSpawn = 0; var ringCount = 0; var gameDistance = 0; // Create lanes visual for (var i = 0; i < 3; i++) { var lane = LK.getAsset('lane', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3, x: lanePositions[i], y: 2732 * 0.8 }); game.addChild(lane); } // Create character var sonic = game.addChild(new SonicCharacter()); sonic.x = lanePositions[1]; sonic.y = 2732 * 0.7; sonic.baseY = sonic.y; // Create UI var scoreText = new Text2('Rings: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var distanceText = new Text2('Distance: 0m', { size: 60, fill: 0xFFFFFF }); distanceText.anchor.set(0, 0); distanceText.x = 50; distanceText.y = 50; LK.gui.topLeft.addChild(distanceText); // Touch controls var touchStartX = 0; var touchStartY = 0; var minSwipeDistance = 50; game.down = function (x, y, obj) { touchStartX = x; touchStartY = y; }; game.up = function (x, y, obj) { var deltaX = x - touchStartX; var deltaY = y - touchStartY; if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (Math.abs(deltaX) > minSwipeDistance) { if (deltaX > 0) { // Swipe right sonic.moveTo(sonic.currentLane + 1); } else { // Swipe left sonic.moveTo(sonic.currentLane - 1); } } } else { // Vertical swipe if (Math.abs(deltaY) > minSwipeDistance) { if (deltaY < 0) { // Swipe up - jump sonic.jump(); } else { // Swipe down - slide sonic.slide(); } } } }; function spawnObstacle() { var obstacleTypes = ['spike', 'cube', 'barrier']; var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)]; var lane = Math.floor(Math.random() * 3); var obstacle = new Obstacle(type); obstacle.x = lanePositions[lane]; obstacle.y = -100; obstacle.speed = gameSpeed; obstacles.push(obstacle); game.addChild(obstacle); } function spawnRing() { var lane = Math.floor(Math.random() * 3); var ring = new Ring(); ring.x = lanePositions[lane]; ring.y = -100; ring.speed = gameSpeed; rings.push(ring); game.addChild(ring); } function spawnPowerup() { var lane = Math.floor(Math.random() * 3); var powerup = new PowerUp(); powerup.x = lanePositions[lane]; powerup.y = -100; powerup.speed = gameSpeed; powerups.push(powerup); game.addChild(powerup); } function checkCollisions() { // Check ring collection for (var i = rings.length - 1; i >= 0; i--) { var ring = rings[i]; if (!ring.collected && ring.intersects(sonic)) { ring.collected = true; ringCount++; LK.setScore(ringCount); scoreText.setText('Rings: ' + ringCount); LK.getSound('collect').play(); // Visual effect tween(ring, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 300, onFinish: function onFinish() { ring.destroy(); } }); rings.splice(i, 1); } } // Check power-up collection for (var i = powerups.length - 1; i >= 0; i--) { var powerup = powerups[i]; if (!powerup.collected && powerup.intersects(sonic)) { powerup.collected = true; sonic.activateInvincibility(); LK.getSound('powerup').play(); // Visual effect tween(powerup, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 400, onFinish: function onFinish() { powerup.destroy(); } }); powerups.splice(i, 1); } } // Check obstacle collisions for (var i = 0; i < obstacles.length; i++) { var obstacle = obstacles[i]; if (obstacle.intersects(sonic)) { // Check if player can avoid obstacle var canAvoid = false; if (obstacle.type === 'spike' && sonic.isJumping) { canAvoid = true; } else if (obstacle.type === 'barrier' && sonic.isSliding) { canAvoid = true; } else if (obstacle.type === 'cube' && (sonic.isJumping || sonic.isSliding)) { canAvoid = true; } if (!canAvoid && !sonic.isInvincible) { // Game over LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } } } } function updateGameObjects() { // Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obstacle = obstacles[i]; obstacle.speed = gameSpeed; if (obstacle.y > 2732 + 100) { obstacle.destroy(); obstacles.splice(i, 1); } } // Update rings for (var i = rings.length - 1; i >= 0; i--) { var ring = rings[i]; ring.speed = gameSpeed; if (ring.y > 2732 + 100) { ring.destroy(); rings.splice(i, 1); } } // Update power-ups for (var i = powerups.length - 1; i >= 0; i--) { var powerup = powerups[i]; powerup.speed = gameSpeed; if (powerup.y > 2732 + 100) { powerup.destroy(); powerups.splice(i, 1); } } // Increase game speed gradually gameSpeed += speedIncrement; // Update distance gameDistance += gameSpeed * 0.1; distanceText.setText('Distance: ' + Math.floor(gameDistance) + 'm'); } // Start background music LK.playMusic('bgmusic'); game.update = function () { // Spawn obstacles if (LK.ticks - lastObstacleSpawn > 120 - Math.min(gameSpeed * 2, 60)) { spawnObstacle(); lastObstacleSpawn = LK.ticks; } // Spawn rings if (LK.ticks - lastRingSpawn > 80) { if (Math.random() < 0.7) { spawnRing(); } lastRingSpawn = LK.ticks; } // Spawn power-ups (less frequent than rings) if (LK.ticks - lastPowerupSpawn > 600) { // Every 10 seconds if (Math.random() < 0.3) { // 30% chance spawnPowerup(); } lastPowerupSpawn = LK.ticks; } checkCollisions(); updateGameObjects(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Obstacle = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
var assetName = type === 'spike' ? 'spike' : type === 'cube' ? 'cubeEnemy' : 'barrier';
var obstacleGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.collected = false;
self.update = function () {
self.y += self.speed;
// Pulsing effect to make it stand out
powerupGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.2;
powerupGraphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.2;
powerupGraphics.rotation += 0.05;
};
return self;
});
var Ring = Container.expand(function () {
var self = Container.call(this);
var ringGraphics = self.attachAsset('ring', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.collected = false;
self.update = function () {
self.y += self.speed;
ringGraphics.rotation += 0.1;
};
return self;
});
var SonicCharacter = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('sonicCharacter', {
anchorX: 0.5,
anchorY: 0.5
});
self.currentLane = 1; // 0 = left, 1 = center, 2 = right
self.isJumping = false;
self.isSliding = false;
self.jumpStartY = 0;
self.baseY = 0;
self.isInvincible = false;
self.invincibilityTimer = 0;
self.activateInvincibility = function () {
self.isInvincible = true;
self.invincibilityTimer = LK.ticks + 300; // 5 seconds at 60fps
// Visual effect - golden tint
tween(characterGraphics, {
tint: 0xFFD700
}, {
duration: 200
});
// Pulsing effect during invincibility
self.invincibilityPulse();
};
self.invincibilityPulse = function () {
if (self.isInvincible) {
tween(characterGraphics, {
alpha: 0.5
}, {
duration: 250,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.isInvincible) {
tween(characterGraphics, {
alpha: 1
}, {
duration: 250,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.invincibilityPulse();
}
});
}
}
});
}
};
self.deactivateInvincibility = function () {
self.isInvincible = false;
// Remove visual effects
tween.stop(characterGraphics, {
alpha: true
});
tween(characterGraphics, {
tint: 0xFFFFFF,
alpha: 1
}, {
duration: 200
});
};
self.update = function () {
// Check if invincibility should end
if (self.isInvincible && LK.ticks >= self.invincibilityTimer) {
self.deactivateInvincibility();
}
};
self.moveTo = function (lane) {
if (lane < 0 || lane > 2) return;
self.currentLane = lane;
var targetX = lanePositions[lane];
tween(self, {
x: targetX
}, {
duration: 200,
easing: tween.easeOut
});
};
self.jump = function () {
if (self.isJumping || self.isSliding) return;
self.isJumping = true;
self.jumpStartY = self.y;
LK.getSound('jump').play();
tween(self, {
y: self.jumpStartY - 150
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.jumpStartY
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
};
self.slide = function () {
if (self.isJumping || self.isSliding) return;
self.isSliding = true;
var originalScale = characterGraphics.scaleY;
characterGraphics.scaleY = 0.5;
LK.setTimeout(function () {
characterGraphics.scaleY = originalScale;
self.isSliding = false;
}, 500);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var gameSpeed = 8;
var speedIncrement = 0.02;
var lanePositions = [2048 * 0.25, 2048 * 0.5, 2048 * 0.75];
var obstacles = [];
var rings = [];
var powerups = [];
var lastObstacleSpawn = 0;
var lastRingSpawn = 0;
var lastPowerupSpawn = 0;
var ringCount = 0;
var gameDistance = 0;
// Create lanes visual
for (var i = 0; i < 3; i++) {
var lane = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
x: lanePositions[i],
y: 2732 * 0.8
});
game.addChild(lane);
}
// Create character
var sonic = game.addChild(new SonicCharacter());
sonic.x = lanePositions[1];
sonic.y = 2732 * 0.7;
sonic.baseY = sonic.y;
// Create UI
var scoreText = new Text2('Rings: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var distanceText = new Text2('Distance: 0m', {
size: 60,
fill: 0xFFFFFF
});
distanceText.anchor.set(0, 0);
distanceText.x = 50;
distanceText.y = 50;
LK.gui.topLeft.addChild(distanceText);
// Touch controls
var touchStartX = 0;
var touchStartY = 0;
var minSwipeDistance = 50;
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
var deltaX = x - touchStartX;
var deltaY = y - touchStartY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (Math.abs(deltaX) > minSwipeDistance) {
if (deltaX > 0) {
// Swipe right
sonic.moveTo(sonic.currentLane + 1);
} else {
// Swipe left
sonic.moveTo(sonic.currentLane - 1);
}
}
} else {
// Vertical swipe
if (Math.abs(deltaY) > minSwipeDistance) {
if (deltaY < 0) {
// Swipe up - jump
sonic.jump();
} else {
// Swipe down - slide
sonic.slide();
}
}
}
};
function spawnObstacle() {
var obstacleTypes = ['spike', 'cube', 'barrier'];
var type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
var lane = Math.floor(Math.random() * 3);
var obstacle = new Obstacle(type);
obstacle.x = lanePositions[lane];
obstacle.y = -100;
obstacle.speed = gameSpeed;
obstacles.push(obstacle);
game.addChild(obstacle);
}
function spawnRing() {
var lane = Math.floor(Math.random() * 3);
var ring = new Ring();
ring.x = lanePositions[lane];
ring.y = -100;
ring.speed = gameSpeed;
rings.push(ring);
game.addChild(ring);
}
function spawnPowerup() {
var lane = Math.floor(Math.random() * 3);
var powerup = new PowerUp();
powerup.x = lanePositions[lane];
powerup.y = -100;
powerup.speed = gameSpeed;
powerups.push(powerup);
game.addChild(powerup);
}
function checkCollisions() {
// Check ring collection
for (var i = rings.length - 1; i >= 0; i--) {
var ring = rings[i];
if (!ring.collected && ring.intersects(sonic)) {
ring.collected = true;
ringCount++;
LK.setScore(ringCount);
scoreText.setText('Rings: ' + ringCount);
LK.getSound('collect').play();
// Visual effect
tween(ring, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
onFinish: function onFinish() {
ring.destroy();
}
});
rings.splice(i, 1);
}
}
// Check power-up collection
for (var i = powerups.length - 1; i >= 0; i--) {
var powerup = powerups[i];
if (!powerup.collected && powerup.intersects(sonic)) {
powerup.collected = true;
sonic.activateInvincibility();
LK.getSound('powerup').play();
// Visual effect
tween(powerup, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 400,
onFinish: function onFinish() {
powerup.destroy();
}
});
powerups.splice(i, 1);
}
}
// Check obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (obstacle.intersects(sonic)) {
// Check if player can avoid obstacle
var canAvoid = false;
if (obstacle.type === 'spike' && sonic.isJumping) {
canAvoid = true;
} else if (obstacle.type === 'barrier' && sonic.isSliding) {
canAvoid = true;
} else if (obstacle.type === 'cube' && (sonic.isJumping || sonic.isSliding)) {
canAvoid = true;
}
if (!canAvoid && !sonic.isInvincible) {
// Game over
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
return;
}
}
}
}
function updateGameObjects() {
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = gameSpeed;
if (obstacle.y > 2732 + 100) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update rings
for (var i = rings.length - 1; i >= 0; i--) {
var ring = rings[i];
ring.speed = gameSpeed;
if (ring.y > 2732 + 100) {
ring.destroy();
rings.splice(i, 1);
}
}
// Update power-ups
for (var i = powerups.length - 1; i >= 0; i--) {
var powerup = powerups[i];
powerup.speed = gameSpeed;
if (powerup.y > 2732 + 100) {
powerup.destroy();
powerups.splice(i, 1);
}
}
// Increase game speed gradually
gameSpeed += speedIncrement;
// Update distance
gameDistance += gameSpeed * 0.1;
distanceText.setText('Distance: ' + Math.floor(gameDistance) + 'm');
}
// Start background music
LK.playMusic('bgmusic');
game.update = function () {
// Spawn obstacles
if (LK.ticks - lastObstacleSpawn > 120 - Math.min(gameSpeed * 2, 60)) {
spawnObstacle();
lastObstacleSpawn = LK.ticks;
}
// Spawn rings
if (LK.ticks - lastRingSpawn > 80) {
if (Math.random() < 0.7) {
spawnRing();
}
lastRingSpawn = LK.ticks;
}
// Spawn power-ups (less frequent than rings)
if (LK.ticks - lastPowerupSpawn > 600) {
// Every 10 seconds
if (Math.random() < 0.3) {
// 30% chance
spawnPowerup();
}
lastPowerupSpawn = LK.ticks;
}
checkCollisions();
updateGameObjects();
};
Make Sonic run but with the camera above. In-Game asset. High contrast. No shadows
make sonic's enemy doctor eggman. In-Game asset. High contrast. No shadows
beam to sonic's ring. In-Game asset. High contrast. No shadows
barrel. In-Game asset. High contrast. No shadows
hot dog. In-Game asset. High contrast. No shadows
make a cactus. In-Game asset. High contrast. No shadows
make sand. In-Game asset. High contrast. No shadows