/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.8;
self.bounceReduction = 0.75;
self.lastY = 0;
self.update = function () {
self.lastY = self.y;
// Apply gravity
self.velocityY += self.gravity;
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off sides
if (self.x <= 40 || self.x >= 2008) {
self.velocityX *= -0.7;
self.x = self.x <= 40 ? 40 : 2008;
}
};
self.bounceOffBoot = function (bootY, bootSpeed) {
var baseForce = Math.abs(self.velocityY) * 0.8 + 8;
var speedBonus = bootSpeed * 0.3;
var impactForce = baseForce + speedBonus;
self.velocityY = -impactForce;
self.velocityX += (Math.random() - 0.5) * 4 + bootSpeed * 0.1;
self.y = bootY - 100;
// Clamp horizontal velocity with higher limits for speed
var maxVel = 12 + speedBonus * 0.2;
self.velocityX = Math.max(-maxVel, Math.min(maxVel, self.velocityX));
};
return self;
});
var Boot = Container.expand(function () {
var self = Container.call(this);
var bootGraphics = self.attachAsset('boot', {
anchorX: 0.5,
anchorY: 0.5
});
self.lastX = 0;
self.lastY = 0;
self.speed = 0;
self.angleFromBall = 0;
self.lastAngleFromBall = 0;
self.angleSum = 0;
self.lastBounceTime = 0;
self.update = function () {
// Calculate speed
var deltaX = self.x - self.lastX;
var deltaY = self.y - self.lastY;
self.speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
var ball = game.addChild(new Ball());
var boot = game.addChild(new Boot());
// Game state variables
var bounceCount = 0;
var atwCount = 0;
var bestScore = storage.bestScore || 0;
var gameStarted = false;
var dragNode = null;
var maxHeight = 0;
var ballStartY = 400;
// Achievement tracking
var achievementsThisGame = {
eyesUp: false,
orbitOfEarth: false,
hopscotchBunny: false
};
var achievementTexts = [];
// Initialize positions
ball.x = 1024;
ball.y = 400;
ball.velocityY = 2;
boot.x = 1024;
boot.y = 2200;
// UI Elements
var bounceText = new Text2('Bounces: 0', {
size: 80,
fill: 0xFFFFFF
});
bounceText.anchor.set(0.5, 0);
LK.gui.top.addChild(bounceText);
var atwText = new Text2('ATW: 0', {
size: 60,
fill: 0x00FF00
});
atwText.anchor.set(0, 0);
atwText.y = 100;
LK.gui.topLeft.addChild(atwText);
var instructionText = new Text2('Drag boot to keep ball up!', {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
var bestText = new Text2('Best: ' + bestScore, {
size: 50,
fill: 0xFFFF88
});
bestText.anchor.set(1, 0);
LK.gui.topRight.addChild(bestText);
var heightText = new Text2('Height: 0m', {
size: 60,
fill: 0xFFFFFF
});
heightText.anchor.set(1, 0);
heightText.x = -10;
heightText.y = 100;
LK.gui.topRight.addChild(heightText);
// Achievement texts
var eyesUpText = new Text2('Eyes Up!', {
size: 45,
fill: 0xFFD700
});
eyesUpText.anchor.set(1, 0);
eyesUpText.x = -10;
eyesUpText.y = 170;
eyesUpText.alpha = 0;
LK.gui.topRight.addChild(eyesUpText);
achievementTexts.push(eyesUpText);
var orbitText = new Text2('Orbit of Earth!', {
size: 45,
fill: 0x00FFFF
});
orbitText.anchor.set(1, 0);
orbitText.x = -10;
orbitText.y = 220;
orbitText.alpha = 0;
LK.gui.topRight.addChild(orbitText);
achievementTexts.push(orbitText);
var bunnyText = new Text2('Hopscotch Bunny!', {
size: 45,
fill: 0xFF69B4
});
bunnyText.anchor.set(1, 0);
bunnyText.x = -10;
bunnyText.y = 270;
bunnyText.alpha = 0;
LK.gui.topRight.addChild(bunnyText);
achievementTexts.push(bunnyText);
// Achievement counter
var achievementCountText = new Text2('Achievements: 0/3', {
size: 50,
fill: 0xFFFFFF
});
achievementCountText.anchor.set(0.5, 1);
achievementCountText.y = -20;
LK.gui.bottom.addChild(achievementCountText);
// Arrow indicator for ball position above screen
var arrowIndicator = new Text2('▼', {
size: 80,
fill: 0xFF4444
});
arrowIndicator.anchor.set(0.5, 0);
arrowIndicator.alpha = 0;
LK.gui.top.addChild(arrowIndicator);
// Achievement functions
function updateAchievementCounter() {
var count = 0;
if (achievementsThisGame.eyesUp) count++;
if (achievementsThisGame.orbitOfEarth) count++;
if (achievementsThisGame.hopscotchBunny) count++;
achievementCountText.setText('Achievements: ' + count + '/3');
}
function checkAndShowAchievement(type, text) {
if (!achievementsThisGame[type]) {
achievementsThisGame[type] = true;
text.alpha = 1;
updateAchievementCounter();
LK.effects.flashScreen(0xFFD700, 300);
}
}
// Game interaction
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
instructionText.alpha = 0;
maxHeight = 0;
heightText.setText('Height: 0m');
// Reset bounce count to 0 for new game
bounceCount = 0;
bounceText.setText('Bounces: 0');
// Reset achievements for new game
achievementsThisGame.eyesUp = false;
achievementsThisGame.orbitOfEarth = false;
achievementsThisGame.hopscotchBunny = false;
for (var i = 0; i < achievementTexts.length; i++) {
achievementTexts[i].alpha = 0;
}
updateAchievementCounter();
}
dragNode = boot;
boot.x = x;
boot.y = y;
};
game.move = function (x, y, obj) {
if (dragNode && gameStarted) {
boot.x = x;
boot.y = y;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game loop
game.update = function () {
if (!gameStarted) {
return;
}
// Calculate angle from boot to ball for ATW detection
var deltaX = ball.x - boot.x;
var deltaY = ball.y - boot.y;
var currentAngle = Math.atan2(deltaY, deltaX);
// Normalize angle difference
var angleDiff = currentAngle - boot.lastAngleFromBall;
if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
// Track cumulative angle change
boot.angleSum += angleDiff;
boot.lastAngleFromBall = currentAngle;
// Check collision between ball and boot
if (ball.intersects(boot)) {
if (ball.velocityY > 0) {
// Only bounce if ball is falling
var currentTime = LK.ticks;
// Check for ATW (3/4 rotation around ball between bounces)
if (Math.abs(boot.angleSum) >= 1.5 * Math.PI && currentTime - boot.lastBounceTime > 30) {
atwCount++;
bounceCount += 5; // ATW gives 5 bounces
atwText.setText('ATW: ' + atwCount);
// Visual feedback for ATW
tween(boot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.effects.flashScreen(0x00FF00, 500);
} else {
bounceCount++;
}
ball.bounceOffBoot(boot.y, boot.speed);
bounceText.setText('Bounces: ' + bounceCount);
LK.getSound('bounce').play();
// Update score
LK.setScore(bounceCount);
// Reset ATW tracking
boot.angleSum = 0;
boot.lastBounceTime = currentTime;
}
}
// Track current height (convert screen coordinates to meters)
// Screen height is 2732px = 100 meters, so 1 meter = 27.32px
// Height 0 = ground (bottom), Height 100 = top of screen
var currentHeightInMeters = Math.max(0, (2732 - ball.y) / 27.32);
heightText.setText('Height: ' + Math.round(currentHeightInMeters) + 'm');
if (currentHeightInMeters > maxHeight) {
maxHeight = currentHeightInMeters;
}
// Check for "Eyes Up" achievement (300+ meters)
if (currentHeightInMeters > 300) {
checkAndShowAchievement('eyesUp', eyesUpText);
}
// Check for "Orbit of Earth" achievement (5+ ATW)
if (atwCount >= 5) {
checkAndShowAchievement('orbitOfEarth', orbitText);
}
// Check for "Hopscotch Bunny" achievement (10+ bounces)
if (bounceCount >= 10) {
checkAndShowAchievement('hopscotchBunny', bunnyText);
}
// Handle arrow indicator when ball is above screen (y < 0)
if (ball.y < 0) {
// Show arrow and position it based on ball's X coordinate
arrowIndicator.alpha = 1;
// Convert ball's game X coordinate to GUI coordinate system
// Use proper coordinate conversion from game to GUI
var ballGlobalPos = game.toGlobal({
x: ball.x,
y: ball.y
});
var guiLocalPos = LK.gui.top.toLocal(ballGlobalPos);
arrowIndicator.x = guiLocalPos.x;
// Scale arrow size based on how high above screen (grows as ball goes higher)
var heightAboveScreen = Math.abs(ball.y);
var scaleProgress = Math.min(heightAboveScreen / 500, 1); // Scale over 500px above screen
var arrowScale = 1 + scaleProgress * 0.8;
arrowIndicator.scale.set(arrowScale, arrowScale);
// Change color as ball goes higher
var redIntensity = Math.floor(255 * scaleProgress);
var greenBlue = Math.floor(68 * (1 - scaleProgress));
arrowIndicator.tint = redIntensity << 16 | greenBlue << 8 | greenBlue;
} else {
arrowIndicator.alpha = 0;
}
// Check if ball hits bottom (game over)
if (ball.y > 2732) {
// Update best score
if (bounceCount > bestScore) {
bestScore = bounceCount;
storage.bestScore = bestScore;
bestText.setText('Best: ' + bestScore);
}
LK.showGameOver();
}
}; /****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.8;
self.bounceReduction = 0.75;
self.lastY = 0;
self.update = function () {
self.lastY = self.y;
// Apply gravity
self.velocityY += self.gravity;
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off sides
if (self.x <= 40 || self.x >= 2008) {
self.velocityX *= -0.7;
self.x = self.x <= 40 ? 40 : 2008;
}
};
self.bounceOffBoot = function (bootY, bootSpeed) {
var baseForce = Math.abs(self.velocityY) * 0.8 + 8;
var speedBonus = bootSpeed * 0.3;
var impactForce = baseForce + speedBonus;
self.velocityY = -impactForce;
self.velocityX += (Math.random() - 0.5) * 4 + bootSpeed * 0.1;
self.y = bootY - 100;
// Clamp horizontal velocity with higher limits for speed
var maxVel = 12 + speedBonus * 0.2;
self.velocityX = Math.max(-maxVel, Math.min(maxVel, self.velocityX));
};
return self;
});
var Boot = Container.expand(function () {
var self = Container.call(this);
var bootGraphics = self.attachAsset('boot', {
anchorX: 0.5,
anchorY: 0.5
});
self.lastX = 0;
self.lastY = 0;
self.speed = 0;
self.angleFromBall = 0;
self.lastAngleFromBall = 0;
self.angleSum = 0;
self.lastBounceTime = 0;
self.update = function () {
// Calculate speed
var deltaX = self.x - self.lastX;
var deltaY = self.y - self.lastY;
self.speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
var ball = game.addChild(new Ball());
var boot = game.addChild(new Boot());
// Game state variables
var bounceCount = 0;
var atwCount = 0;
var bestScore = storage.bestScore || 0;
var gameStarted = false;
var dragNode = null;
var maxHeight = 0;
var ballStartY = 400;
// Achievement tracking
var achievementsThisGame = {
eyesUp: false,
orbitOfEarth: false,
hopscotchBunny: false
};
var achievementTexts = [];
// Initialize positions
ball.x = 1024;
ball.y = 400;
ball.velocityY = 2;
boot.x = 1024;
boot.y = 2200;
// UI Elements
var bounceText = new Text2('Bounces: 0', {
size: 80,
fill: 0xFFFFFF
});
bounceText.anchor.set(0.5, 0);
LK.gui.top.addChild(bounceText);
var atwText = new Text2('ATW: 0', {
size: 60,
fill: 0x00FF00
});
atwText.anchor.set(0, 0);
atwText.y = 100;
LK.gui.topLeft.addChild(atwText);
var instructionText = new Text2('Drag boot to keep ball up!', {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
var bestText = new Text2('Best: ' + bestScore, {
size: 50,
fill: 0xFFFF88
});
bestText.anchor.set(1, 0);
LK.gui.topRight.addChild(bestText);
var heightText = new Text2('Height: 0m', {
size: 60,
fill: 0xFFFFFF
});
heightText.anchor.set(1, 0);
heightText.x = -10;
heightText.y = 100;
LK.gui.topRight.addChild(heightText);
// Achievement texts
var eyesUpText = new Text2('Eyes Up!', {
size: 45,
fill: 0xFFD700
});
eyesUpText.anchor.set(1, 0);
eyesUpText.x = -10;
eyesUpText.y = 170;
eyesUpText.alpha = 0;
LK.gui.topRight.addChild(eyesUpText);
achievementTexts.push(eyesUpText);
var orbitText = new Text2('Orbit of Earth!', {
size: 45,
fill: 0x00FFFF
});
orbitText.anchor.set(1, 0);
orbitText.x = -10;
orbitText.y = 220;
orbitText.alpha = 0;
LK.gui.topRight.addChild(orbitText);
achievementTexts.push(orbitText);
var bunnyText = new Text2('Hopscotch Bunny!', {
size: 45,
fill: 0xFF69B4
});
bunnyText.anchor.set(1, 0);
bunnyText.x = -10;
bunnyText.y = 270;
bunnyText.alpha = 0;
LK.gui.topRight.addChild(bunnyText);
achievementTexts.push(bunnyText);
// Achievement counter
var achievementCountText = new Text2('Achievements: 0/3', {
size: 50,
fill: 0xFFFFFF
});
achievementCountText.anchor.set(0.5, 1);
achievementCountText.y = -20;
LK.gui.bottom.addChild(achievementCountText);
// Arrow indicator for ball position above screen
var arrowIndicator = new Text2('▼', {
size: 80,
fill: 0xFF4444
});
arrowIndicator.anchor.set(0.5, 0);
arrowIndicator.alpha = 0;
LK.gui.top.addChild(arrowIndicator);
// Achievement functions
function updateAchievementCounter() {
var count = 0;
if (achievementsThisGame.eyesUp) count++;
if (achievementsThisGame.orbitOfEarth) count++;
if (achievementsThisGame.hopscotchBunny) count++;
achievementCountText.setText('Achievements: ' + count + '/3');
}
function checkAndShowAchievement(type, text) {
if (!achievementsThisGame[type]) {
achievementsThisGame[type] = true;
text.alpha = 1;
updateAchievementCounter();
LK.effects.flashScreen(0xFFD700, 300);
}
}
// Game interaction
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
instructionText.alpha = 0;
maxHeight = 0;
heightText.setText('Height: 0m');
// Reset bounce count to 0 for new game
bounceCount = 0;
bounceText.setText('Bounces: 0');
// Reset achievements for new game
achievementsThisGame.eyesUp = false;
achievementsThisGame.orbitOfEarth = false;
achievementsThisGame.hopscotchBunny = false;
for (var i = 0; i < achievementTexts.length; i++) {
achievementTexts[i].alpha = 0;
}
updateAchievementCounter();
}
dragNode = boot;
boot.x = x;
boot.y = y;
};
game.move = function (x, y, obj) {
if (dragNode && gameStarted) {
boot.x = x;
boot.y = y;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game loop
game.update = function () {
if (!gameStarted) {
return;
}
// Calculate angle from boot to ball for ATW detection
var deltaX = ball.x - boot.x;
var deltaY = ball.y - boot.y;
var currentAngle = Math.atan2(deltaY, deltaX);
// Normalize angle difference
var angleDiff = currentAngle - boot.lastAngleFromBall;
if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
// Track cumulative angle change
boot.angleSum += angleDiff;
boot.lastAngleFromBall = currentAngle;
// Check collision between ball and boot
if (ball.intersects(boot)) {
if (ball.velocityY > 0) {
// Only bounce if ball is falling
var currentTime = LK.ticks;
// Check for ATW (3/4 rotation around ball between bounces)
if (Math.abs(boot.angleSum) >= 1.5 * Math.PI && currentTime - boot.lastBounceTime > 30) {
atwCount++;
bounceCount += 5; // ATW gives 5 bounces
atwText.setText('ATW: ' + atwCount);
// Visual feedback for ATW
tween(boot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(boot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
LK.effects.flashScreen(0x00FF00, 500);
} else {
bounceCount++;
}
ball.bounceOffBoot(boot.y, boot.speed);
bounceText.setText('Bounces: ' + bounceCount);
LK.getSound('bounce').play();
// Update score
LK.setScore(bounceCount);
// Reset ATW tracking
boot.angleSum = 0;
boot.lastBounceTime = currentTime;
}
}
// Track current height (convert screen coordinates to meters)
// Screen height is 2732px = 100 meters, so 1 meter = 27.32px
// Height 0 = ground (bottom), Height 100 = top of screen
var currentHeightInMeters = Math.max(0, (2732 - ball.y) / 27.32);
heightText.setText('Height: ' + Math.round(currentHeightInMeters) + 'm');
if (currentHeightInMeters > maxHeight) {
maxHeight = currentHeightInMeters;
}
// Check for "Eyes Up" achievement (300+ meters)
if (currentHeightInMeters > 300) {
checkAndShowAchievement('eyesUp', eyesUpText);
}
// Check for "Orbit of Earth" achievement (5+ ATW)
if (atwCount >= 5) {
checkAndShowAchievement('orbitOfEarth', orbitText);
}
// Check for "Hopscotch Bunny" achievement (10+ bounces)
if (bounceCount >= 10) {
checkAndShowAchievement('hopscotchBunny', bunnyText);
}
// Handle arrow indicator when ball is above screen (y < 0)
if (ball.y < 0) {
// Show arrow and position it based on ball's X coordinate
arrowIndicator.alpha = 1;
// Convert ball's game X coordinate to GUI coordinate system
// Use proper coordinate conversion from game to GUI
var ballGlobalPos = game.toGlobal({
x: ball.x,
y: ball.y
});
var guiLocalPos = LK.gui.top.toLocal(ballGlobalPos);
arrowIndicator.x = guiLocalPos.x;
// Scale arrow size based on how high above screen (grows as ball goes higher)
var heightAboveScreen = Math.abs(ball.y);
var scaleProgress = Math.min(heightAboveScreen / 500, 1); // Scale over 500px above screen
var arrowScale = 1 + scaleProgress * 0.8;
arrowIndicator.scale.set(arrowScale, arrowScale);
// Change color as ball goes higher
var redIntensity = Math.floor(255 * scaleProgress);
var greenBlue = Math.floor(68 * (1 - scaleProgress));
arrowIndicator.tint = redIntensity << 16 | greenBlue << 8 | greenBlue;
} else {
arrowIndicator.alpha = 0;
}
// Check if ball hits bottom (game over)
if (ball.y > 2732) {
// Update best score
if (bounceCount > bestScore) {
bestScore = bounceCount;
storage.bestScore = bestScore;
bestText.setText('Best: ' + bestScore);
}
LK.showGameOver();
}
};