/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bestDistance: 0, coins: 0, upgrades: { power: 1, bounce: 1 } }); /**** * Classes ****/ // Bat class: the weapon to hit the geek var Bat = Container.expand(function () { var self = Container.call(this); var batSprite = self.attachAsset('bat', { anchorX: 0.1, anchorY: 0.5 }); self.swinging = false; self.angle = -Math.PI / 4; // resting angle self.power = 0; self.maxPower = 1; self.swingTween = null; self.setAngle = function (a) { self.rotation = a; self.angle = a; }; self.swing = function (onHit) { if (self.swinging) return; self.swinging = true; // Animate swing tween(self, { rotation: Math.PI / 2 }, { duration: 180, easing: tween.cubicOut, onFinish: function onFinish() { self.swinging = false; self.setAngle(-Math.PI / 4); if (onHit) onHit(); } }); }; return self; }); // Geek class: the character to launch var Geek = Container.expand(function () { var self = Container.call(this); var geekSprite = self.attachAsset('geek', { anchorX: 0.5, anchorY: 0.5 }); self.radius = geekSprite.width / 2; self.vx = 0; // velocity x self.vy = 0; // velocity y self.rotationSpeed = 0; self.isFlying = false; self.isStopped = false; self.distance = 0; self.bounceCount = 0; // For collision detection self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; // Called every tick self.update = function () { if (!self.isFlying || self.isStopped) return; // Physics self.x += self.vx; self.y += self.vy; self.vy += 1.2; // gravity // Air friction self.vx *= 0.995; self.vy *= 0.995; // Rotation self.rotation += self.rotationSpeed; // Distance tracking self.distance += self.vx; // Ground collision if (self.y + self.radius >= groundY) { self.y = groundY - self.radius; if (Math.abs(self.vy) > 8) { // Bounce self.vy = -Math.abs(self.vy) * (0.45 + 0.05 * upgrades.bounce); self.vx *= 0.92; self.rotationSpeed *= 0.7; self.bounceCount++; LK.getSound('bounce').play(); // Small upward nudge to avoid sticking self.y -= 2; } else { // Stop self.vy = 0; self.vx = 0; self.isStopped = true; self.isFlying = false; onGeekStopped(); } } }; return self; }); // Obstacle (rock) var Rock = Container.expand(function () { var self = Container.call(this); var rockSprite = self.attachAsset('rock', { anchorX: 0.5, anchorY: 0.5 }); self.radius = rockSprite.width / 2; self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; return self; }); // Power-up (spring) var Spring = Container.expand(function () { var self = Container.call(this); var springSprite = self.attachAsset('spring', { anchorX: 0.5, anchorY: 0.5 }); self.radius = springSprite.width / 2; self.getBounds = function () { return { x: self.x - self.radius, y: self.y - self.radius, width: self.radius * 2, height: self.radius * 2 }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Music // Sound effects // Power-up (spring) // Obstacle (rock) // Ground // Bat (the weapon) // Geek (the character to launch) // --- Global variables --- var groundY = 2732 - 80; // ground top var geek, bat; var rocks = []; var springs = []; var upgrades = storage.upgrades || { power: 1, bounce: 1 }; var coins = storage.coins || 0; var bestDistance = storage.bestDistance || 0; var runDistance = 0; var runCoins = 0; var isAiming = false; var aimPower = 0; var aimAngle = -Math.PI / 4; var aimMaxPower = 1.5 + 0.2 * upgrades.power; var aimMinAngle = -Math.PI / 3; var aimMaxAngle = -Math.PI / 8; var aimDir = 1; var aimTimer = 0; var gameState = 'aim'; // 'aim', 'flying', 'stopped' // --- GUI --- var scoreTxt = new Text2('0 m', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var coinTxt = new Text2('0', { size: 70, fill: 0xFFD700 }); coinTxt.anchor.set(1, 0); LK.gui.topRight.addChild(coinTxt); var bestTxt = new Text2('Best: 0 m', { size: 60, fill: "#fff" }); bestTxt.anchor.set(0, 0); LK.gui.topLeft.addChild(bestTxt); var upgradeTxt = new Text2('', { size: 60, fill: "#fff" }); upgradeTxt.anchor.set(0.5, 1); LK.gui.bottom.addChild(upgradeTxt); // --- Ground --- var ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: groundY }); game.addChild(ground); // --- Functions --- function resetRun() { // Remove old objects if (geek) geek.destroy(); if (bat) bat.destroy(); for (var i = 0; i < rocks.length; i++) rocks[i].destroy(); for (var i = 0; i < springs.length; i++) springs[i].destroy(); rocks = []; springs = []; runDistance = 0; runCoins = 0; aimPower = 0; aimAngle = -Math.PI / 4; aimDir = 1; aimTimer = 0; gameState = 'aim'; // Geek geek = new Geek(); geek.x = 350; geek.y = groundY - 120; geek.rotation = 0; game.addChild(geek); // Bat bat = new Bat(); bat.x = geek.x - 80; bat.y = groundY - 120; bat.setAngle(-Math.PI / 4); game.addChild(bat); // Obstacles for (var i = 0; i < 3; i++) { var rock = new Rock(); rock.x = 900 + 700 * i + Math.random() * 200; rock.y = groundY - 30; rocks.push(rock); game.addChild(rock); } // Power-ups for (var i = 0; i < 2; i++) { var spring = new Spring(); spring.x = 1200 + 1000 * i + Math.random() * 300; spring.y = groundY - 20; springs.push(spring); game.addChild(spring); } // GUI scoreTxt.setText('0 m'); coinTxt.setText('' + coins); bestTxt.setText('Best: ' + bestDistance + ' m'); upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); } function onGeekStopped() { // End of run runDistance = Math.floor(geek.distance / 10); if (runDistance > bestDistance) { bestDistance = runDistance; storage.bestDistance = bestDistance; } coins += Math.floor(runDistance / 10) + runCoins; storage.coins = coins; // Show game over LK.effects.flashScreen(0x000000, 600); LK.showGameOver(); } function launchGeek(power, angle) { // Bat swing animation bat.swing(function () { // Launch var p = 38 * power * (1 + 0.15 * upgrades.power); geek.vx = p * Math.cos(angle); geek.vy = p * Math.sin(angle) - 10; geek.rotationSpeed = 0.2 + 0.1 * power; geek.isFlying = true; gameState = 'flying'; LK.getSound('hit').play(); // Remove bat after swing LK.setTimeout(function () { bat.destroy(); }, 300); }); } // --- Input handling --- game.down = function (x, y, obj) { if (gameState === 'aim') { isAiming = true; aimTimer = 0; } else if (gameState === 'stopped') { // Check if bottom area tapped for upgrades if (y > 2732 - 300) { // Alternate upgrades: tap left half for power, right half for bounce if (x < 2048 / 2) { // Power upgrade var cost = 10 + upgrades.power * 10; if (coins >= cost) { upgrades.power++; coins -= cost; storage.upgrades = upgrades; storage.coins = coins; LK.effects.flashObject(upgradeTxt, 0x00ff00, 400); } else { LK.effects.flashObject(upgradeTxt, 0xff0000, 400); } } else { // Bounce upgrade var cost = 10 + upgrades.bounce * 10; if (coins >= cost) { upgrades.bounce++; coins -= cost; storage.upgrades = upgrades; storage.coins = coins; LK.effects.flashObject(upgradeTxt, 0x00ff00, 400); } else { LK.effects.flashObject(upgradeTxt, 0xff0000, 400); } } upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); coinTxt.setText('' + coins); } } }; game.up = function (x, y, obj) { if (gameState === 'aim' && isAiming) { isAiming = false; // Launch! launchGeek(aimPower, aimAngle); } }; function checkCollision(a, b) { var ab = a.getBounds(); var bb = b.getBounds(); return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y; } // --- Main update loop --- game.update = function () { // Aiming phase if (gameState === 'aim' && isAiming) { // Power oscillates up and down aimTimer++; aimPower = 0.5 + 0.5 * Math.abs(Math.sin(aimTimer * 0.05)); // Angle oscillates between min and max aimAngle += aimDir * 0.012; if (aimAngle > aimMaxAngle) aimDir = -1; if (aimAngle < aimMinAngle) aimDir = 1; bat.setAngle(aimAngle); // Bat color flashes with power var color = 0x964b00 + Math.floor(aimPower * 0x002200); bat.children[0].tint = color; } // Update geek if (geek) geek.update(); // Camera follow: move all objects left as geek moves if (geek && geek.isFlying) { var camX = Math.max(0, geek.x - 350); // Move all except GUI and ground for (var i = 0; i < rocks.length; i++) rocks[i].x -= camX - (rocks[i].camX || 0); for (var i = 0; i < springs.length; i++) springs[i].x -= camX - (springs[i].camX || 0); if (bat && !bat.destroyed) bat.x -= camX - (bat.camX || 0); geek.x -= camX - (geek.camX || 0); // Store camX for next frame for (var i = 0; i < rocks.length; i++) rocks[i].camX = camX; for (var i = 0; i < springs.length; i++) springs[i].camX = camX; if (bat && !bat.destroyed) bat.camX = camX; geek.camX = camX; } // Collisions: obstacles if (geek && geek.isFlying && !geek.isStopped) { for (var i = 0; i < rocks.length; i++) { var rock = rocks[i]; if (!rock.hit && checkCollision(geek, rock)) { // Hit rock: lose speed, bounce up geek.vx *= 0.6; geek.vy = -Math.abs(geek.vy) * 0.7 - 8; geek.rotationSpeed *= 0.7; rock.hit = true; LK.effects.flashObject(rock, 0xff0000, 400); LK.getSound('bounce').play(); } } // Collisions: power-ups for (var i = 0; i < springs.length; i++) { var spring = springs[i]; if (!spring.used && checkCollision(geek, spring)) { // Hit spring: big bounce, gain coins geek.vy = -32 - 8 * upgrades.bounce; geek.vx *= 1.12; geek.rotationSpeed += 0.2; spring.used = true; runCoins += 5; LK.effects.flashObject(spring, 0xffd700, 500); LK.getSound('powerup').play(); } } } // Update GUI if (geek) { var dist = Math.floor(geek.distance / 10); scoreTxt.setText(dist + ' m'); } coinTxt.setText('' + coins); // If geek stopped, allow upgrades and restart if (geek && geek.isStopped && gameState !== 'stopped') { gameState = 'stopped'; upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')'); } }; // --- Game over handler --- LK.on('gameover', function () { // Reset run for next play resetRun(); }); // --- Music --- LK.playMusic('bgmusic', { fade: { start: 0, end: 1, duration: 1000 } }); // --- Start game --- resetRun();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
bestDistance: 0,
coins: 0,
upgrades: {
power: 1,
bounce: 1
}
});
/****
* Classes
****/
// Bat class: the weapon to hit the geek
var Bat = Container.expand(function () {
var self = Container.call(this);
var batSprite = self.attachAsset('bat', {
anchorX: 0.1,
anchorY: 0.5
});
self.swinging = false;
self.angle = -Math.PI / 4; // resting angle
self.power = 0;
self.maxPower = 1;
self.swingTween = null;
self.setAngle = function (a) {
self.rotation = a;
self.angle = a;
};
self.swing = function (onHit) {
if (self.swinging) return;
self.swinging = true;
// Animate swing
tween(self, {
rotation: Math.PI / 2
}, {
duration: 180,
easing: tween.cubicOut,
onFinish: function onFinish() {
self.swinging = false;
self.setAngle(-Math.PI / 4);
if (onHit) onHit();
}
});
};
return self;
});
// Geek class: the character to launch
var Geek = Container.expand(function () {
var self = Container.call(this);
var geekSprite = self.attachAsset('geek', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = geekSprite.width / 2;
self.vx = 0; // velocity x
self.vy = 0; // velocity y
self.rotationSpeed = 0;
self.isFlying = false;
self.isStopped = false;
self.distance = 0;
self.bounceCount = 0;
// For collision detection
self.getBounds = function () {
return {
x: self.x - self.radius,
y: self.y - self.radius,
width: self.radius * 2,
height: self.radius * 2
};
};
// Called every tick
self.update = function () {
if (!self.isFlying || self.isStopped) return;
// Physics
self.x += self.vx;
self.y += self.vy;
self.vy += 1.2; // gravity
// Air friction
self.vx *= 0.995;
self.vy *= 0.995;
// Rotation
self.rotation += self.rotationSpeed;
// Distance tracking
self.distance += self.vx;
// Ground collision
if (self.y + self.radius >= groundY) {
self.y = groundY - self.radius;
if (Math.abs(self.vy) > 8) {
// Bounce
self.vy = -Math.abs(self.vy) * (0.45 + 0.05 * upgrades.bounce);
self.vx *= 0.92;
self.rotationSpeed *= 0.7;
self.bounceCount++;
LK.getSound('bounce').play();
// Small upward nudge to avoid sticking
self.y -= 2;
} else {
// Stop
self.vy = 0;
self.vx = 0;
self.isStopped = true;
self.isFlying = false;
onGeekStopped();
}
}
};
return self;
});
// Obstacle (rock)
var Rock = Container.expand(function () {
var self = Container.call(this);
var rockSprite = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = rockSprite.width / 2;
self.getBounds = function () {
return {
x: self.x - self.radius,
y: self.y - self.radius,
width: self.radius * 2,
height: self.radius * 2
};
};
return self;
});
// Power-up (spring)
var Spring = Container.expand(function () {
var self = Container.call(this);
var springSprite = self.attachAsset('spring', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = springSprite.width / 2;
self.getBounds = function () {
return {
x: self.x - self.radius,
y: self.y - self.radius,
width: self.radius * 2,
height: self.radius * 2
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Music
// Sound effects
// Power-up (spring)
// Obstacle (rock)
// Ground
// Bat (the weapon)
// Geek (the character to launch)
// --- Global variables ---
var groundY = 2732 - 80; // ground top
var geek, bat;
var rocks = [];
var springs = [];
var upgrades = storage.upgrades || {
power: 1,
bounce: 1
};
var coins = storage.coins || 0;
var bestDistance = storage.bestDistance || 0;
var runDistance = 0;
var runCoins = 0;
var isAiming = false;
var aimPower = 0;
var aimAngle = -Math.PI / 4;
var aimMaxPower = 1.5 + 0.2 * upgrades.power;
var aimMinAngle = -Math.PI / 3;
var aimMaxAngle = -Math.PI / 8;
var aimDir = 1;
var aimTimer = 0;
var gameState = 'aim'; // 'aim', 'flying', 'stopped'
// --- GUI ---
var scoreTxt = new Text2('0 m', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 70,
fill: 0xFFD700
});
coinTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(coinTxt);
var bestTxt = new Text2('Best: 0 m', {
size: 60,
fill: "#fff"
});
bestTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(bestTxt);
var upgradeTxt = new Text2('', {
size: 60,
fill: "#fff"
});
upgradeTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(upgradeTxt);
// --- Ground ---
var ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY
});
game.addChild(ground);
// --- Functions ---
function resetRun() {
// Remove old objects
if (geek) geek.destroy();
if (bat) bat.destroy();
for (var i = 0; i < rocks.length; i++) rocks[i].destroy();
for (var i = 0; i < springs.length; i++) springs[i].destroy();
rocks = [];
springs = [];
runDistance = 0;
runCoins = 0;
aimPower = 0;
aimAngle = -Math.PI / 4;
aimDir = 1;
aimTimer = 0;
gameState = 'aim';
// Geek
geek = new Geek();
geek.x = 350;
geek.y = groundY - 120;
geek.rotation = 0;
game.addChild(geek);
// Bat
bat = new Bat();
bat.x = geek.x - 80;
bat.y = groundY - 120;
bat.setAngle(-Math.PI / 4);
game.addChild(bat);
// Obstacles
for (var i = 0; i < 3; i++) {
var rock = new Rock();
rock.x = 900 + 700 * i + Math.random() * 200;
rock.y = groundY - 30;
rocks.push(rock);
game.addChild(rock);
}
// Power-ups
for (var i = 0; i < 2; i++) {
var spring = new Spring();
spring.x = 1200 + 1000 * i + Math.random() * 300;
spring.y = groundY - 20;
springs.push(spring);
game.addChild(spring);
}
// GUI
scoreTxt.setText('0 m');
coinTxt.setText('' + coins);
bestTxt.setText('Best: ' + bestDistance + ' m');
upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
}
function onGeekStopped() {
// End of run
runDistance = Math.floor(geek.distance / 10);
if (runDistance > bestDistance) {
bestDistance = runDistance;
storage.bestDistance = bestDistance;
}
coins += Math.floor(runDistance / 10) + runCoins;
storage.coins = coins;
// Show game over
LK.effects.flashScreen(0x000000, 600);
LK.showGameOver();
}
function launchGeek(power, angle) {
// Bat swing animation
bat.swing(function () {
// Launch
var p = 38 * power * (1 + 0.15 * upgrades.power);
geek.vx = p * Math.cos(angle);
geek.vy = p * Math.sin(angle) - 10;
geek.rotationSpeed = 0.2 + 0.1 * power;
geek.isFlying = true;
gameState = 'flying';
LK.getSound('hit').play();
// Remove bat after swing
LK.setTimeout(function () {
bat.destroy();
}, 300);
});
}
// --- Input handling ---
game.down = function (x, y, obj) {
if (gameState === 'aim') {
isAiming = true;
aimTimer = 0;
} else if (gameState === 'stopped') {
// Check if bottom area tapped for upgrades
if (y > 2732 - 300) {
// Alternate upgrades: tap left half for power, right half for bounce
if (x < 2048 / 2) {
// Power upgrade
var cost = 10 + upgrades.power * 10;
if (coins >= cost) {
upgrades.power++;
coins -= cost;
storage.upgrades = upgrades;
storage.coins = coins;
LK.effects.flashObject(upgradeTxt, 0x00ff00, 400);
} else {
LK.effects.flashObject(upgradeTxt, 0xff0000, 400);
}
} else {
// Bounce upgrade
var cost = 10 + upgrades.bounce * 10;
if (coins >= cost) {
upgrades.bounce++;
coins -= cost;
storage.upgrades = upgrades;
storage.coins = coins;
LK.effects.flashObject(upgradeTxt, 0x00ff00, 400);
} else {
LK.effects.flashObject(upgradeTxt, 0xff0000, 400);
}
}
upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
coinTxt.setText('' + coins);
}
}
};
game.up = function (x, y, obj) {
if (gameState === 'aim' && isAiming) {
isAiming = false;
// Launch!
launchGeek(aimPower, aimAngle);
}
};
function checkCollision(a, b) {
var ab = a.getBounds();
var bb = b.getBounds();
return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y;
}
// --- Main update loop ---
game.update = function () {
// Aiming phase
if (gameState === 'aim' && isAiming) {
// Power oscillates up and down
aimTimer++;
aimPower = 0.5 + 0.5 * Math.abs(Math.sin(aimTimer * 0.05));
// Angle oscillates between min and max
aimAngle += aimDir * 0.012;
if (aimAngle > aimMaxAngle) aimDir = -1;
if (aimAngle < aimMinAngle) aimDir = 1;
bat.setAngle(aimAngle);
// Bat color flashes with power
var color = 0x964b00 + Math.floor(aimPower * 0x002200);
bat.children[0].tint = color;
}
// Update geek
if (geek) geek.update();
// Camera follow: move all objects left as geek moves
if (geek && geek.isFlying) {
var camX = Math.max(0, geek.x - 350);
// Move all except GUI and ground
for (var i = 0; i < rocks.length; i++) rocks[i].x -= camX - (rocks[i].camX || 0);
for (var i = 0; i < springs.length; i++) springs[i].x -= camX - (springs[i].camX || 0);
if (bat && !bat.destroyed) bat.x -= camX - (bat.camX || 0);
geek.x -= camX - (geek.camX || 0);
// Store camX for next frame
for (var i = 0; i < rocks.length; i++) rocks[i].camX = camX;
for (var i = 0; i < springs.length; i++) springs[i].camX = camX;
if (bat && !bat.destroyed) bat.camX = camX;
geek.camX = camX;
}
// Collisions: obstacles
if (geek && geek.isFlying && !geek.isStopped) {
for (var i = 0; i < rocks.length; i++) {
var rock = rocks[i];
if (!rock.hit && checkCollision(geek, rock)) {
// Hit rock: lose speed, bounce up
geek.vx *= 0.6;
geek.vy = -Math.abs(geek.vy) * 0.7 - 8;
geek.rotationSpeed *= 0.7;
rock.hit = true;
LK.effects.flashObject(rock, 0xff0000, 400);
LK.getSound('bounce').play();
}
}
// Collisions: power-ups
for (var i = 0; i < springs.length; i++) {
var spring = springs[i];
if (!spring.used && checkCollision(geek, spring)) {
// Hit spring: big bounce, gain coins
geek.vy = -32 - 8 * upgrades.bounce;
geek.vx *= 1.12;
geek.rotationSpeed += 0.2;
spring.used = true;
runCoins += 5;
LK.effects.flashObject(spring, 0xffd700, 500);
LK.getSound('powerup').play();
}
}
}
// Update GUI
if (geek) {
var dist = Math.floor(geek.distance / 10);
scoreTxt.setText(dist + ' m');
}
coinTxt.setText('' + coins);
// If geek stopped, allow upgrades and restart
if (geek && geek.isStopped && gameState !== 'stopped') {
gameState = 'stopped';
upgradeTxt.setText('Tap bottom to upgrade: Power(' + upgrades.power + '), Bounce(' + upgrades.bounce + ')');
}
};
// --- Game over handler ---
LK.on('gameover', function () {
// Reset run for next play
resetRun();
});
// --- Music ---
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// --- Start game ---
resetRun();