/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
currentLevel: 1,
highScores: {}
});
/****
* Classes
****/
var BouncePad = Container.expand(function () {
var self = Container.call(this);
// BouncePad properties
self.width = 150;
self.height = 20;
self.bounceStrength = 15;
self.pulseTimer = 0;
// Attach bouncePad asset
var bouncePadGraphics = self.attachAsset('bouncePad', {
anchorX: 0.5,
anchorY: 0.5,
width: self.width,
height: self.height
});
self.update = function () {
// Subtle pulsing effect
self.pulseTimer += 0.03;
var pulseFactor = 1 + Math.sin(self.pulseTimer) * 0.05;
bouncePadGraphics.scaleY = pulseFactor;
};
self.bounce = function () {
// Visual bounce effect
tween(bouncePadGraphics, {
scaleY: 0.7
}, {
duration: 100,
onFinish: function onFinish() {
tween(bouncePadGraphics, {
scaleY: 1
}, {
duration: 300
});
}
});
};
return self;
});
var Bubble = Container.expand(function () {
var self = Container.call(this);
// Bubble properties
self.radius = 50;
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.3;
self.friction = 0.98;
self.bounceFactor = 0.8;
self.isGrounded = false;
self.hasSpeedBoost = false;
self.speedBoostTimer = 0;
// Attach bubble asset
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
width: self.radius * 2,
height: self.radius * 2
});
self.update = function () {
// Apply gravity
self.velocityY += self.gravity;
// Apply friction
self.velocityX *= self.friction;
// Apply speed boost if active
if (self.hasSpeedBoost) {
self.speedBoostTimer--;
if (self.speedBoostTimer <= 0) {
self.hasSpeedBoost = false;
tween(bubbleGraphics, {
tint: 0x3498db
}, {
duration: 500
});
}
}
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Check world boundaries
if (self.x < self.radius) {
self.x = self.radius;
self.velocityX = -self.velocityX * self.bounceFactor;
LK.getSound('bounce').play();
} else if (self.x > 2048 - self.radius) {
self.x = 2048 - self.radius;
self.velocityX = -self.velocityX * self.bounceFactor;
LK.getSound('bounce').play();
}
if (self.y < self.radius) {
self.y = self.radius;
self.velocityY = -self.velocityY * self.bounceFactor;
LK.getSound('bounce').play();
} else if (self.y > 2732 - self.radius) {
self.y = 2732 - self.radius;
self.velocityY = -self.velocityY * self.bounceFactor;
self.isGrounded = true;
LK.getSound('bounce').play();
} else {
self.isGrounded = false;
}
};
self.applyForce = function (forceX, forceY) {
self.velocityX += forceX;
self.velocityY += forceY;
};
self.bounce = function (magnitude) {
self.velocityY = -magnitude;
LK.getSound('bounce').play();
};
self.activateSpeedBoost = function () {
self.hasSpeedBoost = true;
self.speedBoostTimer = 180; // 3 seconds at 60fps
self.friction = 0.99;
tween(bubbleGraphics, {
tint: 0xe67e22
}, {
duration: 500
});
LK.getSound('powerup').play();
};
self.deactivateSpeedBoost = function () {
self.hasSpeedBoost = false;
self.friction = 0.98;
tween(bubbleGraphics, {
tint: 0x3498db
}, {
duration: 500
});
};
return self;
});
var Level = Container.expand(function (levelData) {
var self = Container.call(this);
// Level properties
self.stars = [];
self.obstacles = [];
self.bouncePads = [];
self.speedBoosts = [];
self.completed = false;
self.starCount = 0;
self.totalStars = 0;
// Initialize level from level data
self.init = function (levelData) {
// Create obstacles
if (levelData.obstacles) {
levelData.obstacles.forEach(function (obstacleData) {
var obstacle = new Obstacle();
obstacle.x = obstacleData.x;
obstacle.y = obstacleData.y;
if (obstacleData.rotation) {
obstacle.rotation = obstacleData.rotation * (Math.PI / 180);
}
if (obstacleData.width && obstacleData.height) {
obstacle.width = obstacleData.width;
obstacle.height = obstacleData.height;
obstacle.children[0].width = obstacleData.width;
obstacle.children[0].height = obstacleData.height;
}
self.obstacles.push(obstacle);
self.addChild(obstacle);
});
}
// Create bounce pads
if (levelData.bouncePads) {
levelData.bouncePads.forEach(function (padData) {
var bouncePad = new BouncePad();
bouncePad.x = padData.x;
bouncePad.y = padData.y;
if (padData.rotation) {
bouncePad.rotation = padData.rotation * (Math.PI / 180);
}
if (padData.strength) {
bouncePad.bounceStrength = padData.strength;
}
self.bouncePads.push(bouncePad);
self.addChild(bouncePad);
});
}
// Create stars
if (levelData.stars) {
levelData.stars.forEach(function (starData) {
var star = new Star();
star.x = starData.x;
star.y = starData.y;
self.stars.push(star);
self.addChild(star);
self.totalStars++;
});
}
// Create speed boosts
if (levelData.speedBoosts) {
levelData.speedBoosts.forEach(function (boostData) {
var speedBoost = new SpeedBoost();
speedBoost.x = boostData.x;
speedBoost.y = boostData.y;
self.speedBoosts.push(speedBoost);
self.addChild(speedBoost);
});
}
// Create portal
if (levelData.portal) {
self.portal = new Portal();
self.portal.x = levelData.portal.x;
self.portal.y = levelData.portal.y;
self.addChild(self.portal);
}
};
if (levelData) {
self.init(levelData);
}
self.update = function () {
// Update all elements
self.portal.update();
self.stars.forEach(function (star) {
if (!star.collected) {
star.update();
}
});
self.bouncePads.forEach(function (pad) {
pad.update();
});
self.speedBoosts.forEach(function (boost) {
if (!boost.collected) {
boost.update();
}
});
};
self.checkCollisions = function (bubble) {
// Check collisions with obstacles
for (var i = 0; i < self.obstacles.length; i++) {
var obstacle = self.obstacles[i];
// Calculate rotated rectangle collision
var angle = obstacle.rotation;
var rectCenterX = obstacle.x;
var rectCenterY = obstacle.y;
var rectWidth = obstacle.width;
var rectHeight = obstacle.height;
// Translate bubble position relative to rectangle
var bubbleX = bubble.x - rectCenterX;
var bubbleY = bubble.y - rectCenterY;
// Rotate bubble position to align with rectangle
var rotatedX = bubbleX * Math.cos(-angle) - bubbleY * Math.sin(-angle);
var rotatedY = bubbleX * Math.sin(-angle) + bubbleY * Math.cos(-angle);
// Find closest point on rectangle to bubble
var closestX = Math.max(-rectWidth / 2, Math.min(rectWidth / 2, rotatedX));
var closestY = Math.max(-rectHeight / 2, Math.min(rectHeight / 2, rotatedY));
// Calculate distance from closest point to bubble center
var distanceX = rotatedX - closestX;
var distanceY = rotatedY - closestY;
var distanceSquared = distanceX * distanceX + distanceY * distanceY;
// Check collision
if (distanceSquared < bubble.radius * bubble.radius) {
// Collision detected
// Find normal vector
var normalX = distanceX;
var normalY = distanceY;
var normalLength = Math.sqrt(normalX * normalX + normalY * normalY);
if (normalLength > 0) {
normalX /= normalLength;
normalY /= normalLength;
// Rotate normal back to world space
var worldNormalX = normalX * Math.cos(angle) - normalY * Math.sin(angle);
var worldNormalY = normalX * Math.sin(angle) + normalY * Math.cos(angle);
// Reflect velocity
var dotProduct = bubble.velocityX * worldNormalX + bubble.velocityY * worldNormalY;
bubble.velocityX = bubble.velocityX - 2 * dotProduct * worldNormalX;
bubble.velocityY = bubble.velocityY - 2 * dotProduct * worldNormalY;
// Scale by bounce factor
bubble.velocityX *= bubble.bounceFactor;
bubble.velocityY *= bubble.bounceFactor;
// Adjust position to prevent sinking
var pushDistance = bubble.radius - Math.sqrt(distanceSquared);
bubble.x += worldNormalX * pushDistance;
bubble.y += worldNormalY * pushDistance;
LK.getSound('bounce').play();
}
}
}
// Check collisions with bounce pads
for (var i = 0; i < self.bouncePads.length; i++) {
var pad = self.bouncePads[i];
// Similar collision detection to obstacles
var angle = pad.rotation;
var padCenterX = pad.x;
var padCenterY = pad.y;
var padWidth = pad.width;
var padHeight = pad.height;
var bubbleX = bubble.x - padCenterX;
var bubbleY = bubble.y - padCenterY;
var rotatedX = bubbleX * Math.cos(-angle) - bubbleY * Math.sin(-angle);
var rotatedY = bubbleX * Math.sin(-angle) + bubbleY * Math.cos(-angle);
var closestX = Math.max(-padWidth / 2, Math.min(padWidth / 2, rotatedX));
var closestY = Math.max(-padHeight / 2, Math.min(padHeight / 2, rotatedY));
var distanceX = rotatedX - closestX;
var distanceY = rotatedY - closestY;
var distanceSquared = distanceX * distanceX + distanceY * distanceY;
if (distanceSquared < bubble.radius * bubble.radius) {
// Collision with bounce pad
// Get bounce direction based on pad rotation
var bounceX = Math.sin(angle) * pad.bounceStrength;
var bounceY = -Math.cos(angle) * pad.bounceStrength;
bubble.velocityX = bubble.velocityX * 0.5 + bounceX;
bubble.velocityY = bounceY;
pad.bounce();
LK.getSound('bounce').play();
}
}
// Check collisions with stars
for (var i = 0; i < self.stars.length; i++) {
var star = self.stars[i];
if (!star.collected) {
var dx = bubble.x - star.x;
var dy = bubble.y - star.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < bubble.radius + star.radius) {
if (star.collect()) {
self.starCount++;
LK.setScore(LK.getScore() + 100);
}
}
}
}
// Check collisions with speed boosts
for (var i = 0; i < self.speedBoosts.length; i++) {
var boost = self.speedBoosts[i];
if (!boost.collected) {
var dx = bubble.x - boost.x;
var dy = bubble.y - boost.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < bubble.radius + boost.radius) {
if (boost.collect()) {
bubble.activateSpeedBoost();
}
}
}
}
// Check collision with portal
var dx = bubble.x - self.portal.x;
var dy = bubble.y - self.portal.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < bubble.radius + self.portal.radius && !self.completed && self.starCount === self.totalStars) {
self.completed = true;
self.portal.activate();
// Calculate stars earned (based on collection percentage)
var starsEarned = Math.ceil(self.starCount / self.totalStars * 3);
// Save level data
var currentLevel = storage.currentLevel || 1;
if (!storage.highScores) {
storage.highScores = {};
}
if (!storage.highScores[currentLevel] || starsEarned > storage.highScores[currentLevel]) {
storage.highScores[currentLevel] = starsEarned;
}
// Move to next level
storage.currentLevel = currentLevel + 1;
// Show win screen
LK.setTimeout(function () {
LK.showYouWin();
}, 1000);
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Obstacle properties
self.width = 200;
self.height = 50;
// Attach obstacle asset
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
width: self.width,
height: self.height
});
return self;
});
var Portal = Container.expand(function () {
var self = Container.call(this);
// Portal properties
self.radius = 75;
self.rotationSpeed = 0.02;
self.pulseTimer = 0;
// Attach portal asset
var portalGraphics = self.attachAsset('portal', {
anchorX: 0.5,
anchorY: 0.5,
width: self.radius * 2,
height: self.radius * 2
});
self.update = function () {
// Rotate the portal
portalGraphics.rotation += self.rotationSpeed;
// Pulsing effect
self.pulseTimer += 0.05;
var pulseFactor = 1 + Math.sin(self.pulseTimer) * 0.1;
portalGraphics.scaleX = pulseFactor;
portalGraphics.scaleY = pulseFactor;
};
self.activate = function () {
LK.getSound('portal').play();
// Visual activation effect
tween(portalGraphics, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
tween(portalGraphics, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 0
});
}
});
};
return self;
});
var SpeedBoost = Container.expand(function () {
var self = Container.call(this);
// SpeedBoost properties
self.radius = 35;
self.collected = false;
self.rotationSpeed = 0.03;
// Attach speedBoost asset
var speedBoostGraphics = self.attachAsset('speedBoost', {
anchorX: 0.5,
anchorY: 0.5,
width: self.radius * 2,
height: self.radius * 2
});
self.update = function () {
// Rotate the speed boost
speedBoostGraphics.rotation += self.rotationSpeed;
};
self.collect = function () {
if (!self.collected) {
self.collected = true;
// Visual collection effect
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
self.visible = false;
}
});
return true;
}
return false;
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
// Star properties
self.radius = 30;
self.collected = false;
self.rotationSpeed = 0.01;
// Attach star asset
var starGraphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5,
width: self.radius * 2,
height: self.radius * 2
});
self.update = function () {
// Rotate the star
starGraphics.rotation += self.rotationSpeed;
};
self.collect = function () {
if (!self.collected) {
self.collected = true;
// Visual collection effect
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
self.visible = false;
}
});
LK.getSound('collect').play();
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue background
});
/****
* Game Code
****/
// Game state variables
var bubble;
var currentLevel;
var levelData = {};
var tiltSensitivity = 0.3;
var lastTouchX = 0;
var lastTouchY = 0;
var dragging = false;
// Level designs
var levels = [
// Level 1 - Simple introduction
{
stars: [{
x: 500,
y: 800
}, {
x: 1024,
y: 600
}, {
x: 1500,
y: 800
}],
obstacles: [{
x: 1024,
y: 1500,
width: 800,
height: 50
}],
bouncePads: [{
x: 500,
y: 2000,
strength: 15
}, {
x: 1500,
y: 2000,
strength: 15
}],
portal: {
x: 1024,
y: 2400
}
},
// Level 2 - More obstacles
{
stars: [{
x: 500,
y: 800
}, {
x: 1024,
y: 1200
}, {
x: 1500,
y: 800
}, {
x: 1024,
y: 400
}],
obstacles: [{
x: 1024,
y: 1000,
width: 1000,
height: 50
}, {
x: 500,
y: 1500,
width: 400,
height: 50,
rotation: 45
}, {
x: 1500,
y: 1500,
width: 400,
height: 50,
rotation: -45
}],
bouncePads: [{
x: 1024,
y: 2000,
strength: 20
}],
speedBoosts: [{
x: 1024,
y: 600
}],
portal: {
x: 1024,
y: 2400
}
},
// Level 3 - Complex layout
{
stars: [{
x: 400,
y: 400
}, {
x: 1600,
y: 400
}, {
x: 400,
y: 1600
}, {
x: 1600,
y: 1600
}, {
x: 1024,
y: 1024
}],
obstacles: [{
x: 800,
y: 800,
width: 50,
height: 800,
rotation: 0
}, {
x: 1200,
y: 800,
width: 50,
height: 800,
rotation: 0
}, {
x: 800,
y: 800,
width: 800,
height: 50,
rotation: 0
}, {
x: 800,
y: 1200,
width: 800,
height: 50,
rotation: 0
}],
bouncePads: [{
x: 400,
y: 2000,
strength: 15
}, {
x: 1600,
y: 2000,
strength: 15
}, {
x: 800,
y: 1800,
strength: 12,
rotation: -45
}, {
x: 1200,
y: 1800,
strength: 12,
rotation: 45
}],
speedBoosts: [{
x: 400,
y: 1000
}, {
x: 1600,
y: 1000
}],
portal: {
x: 1024,
y: 2400
}
}];
// UI Elements
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topRight.addChild(levelText);
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var starsText = new Text2('Stars: 0/0', {
size: 60,
fill: 0xFFFFFF
});
starsText.anchor.set(1, 0);
LK.gui.topLeft.addChild(starsText);
// Game initialization
function initGame() {
// Reset score
LK.setScore(0);
// Get current level
var currentLevelNum = Math.min(storage.currentLevel || 1, levels.length);
levelText.setText('Level: ' + currentLevelNum);
// Load level data
levelData = levels[currentLevelNum - 1];
// Create level
currentLevel = new Level(levelData);
game.addChild(currentLevel);
// Update stars text
starsText.setText('Stars: 0/' + currentLevel.totalStars);
// Create bubble
bubble = new Bubble();
bubble.x = 1024;
bubble.y = 200;
game.addChild(bubble);
// Start music
LK.playMusic('bgmusic', {
loop: true,
fade: {
start: 0,
end: 0.4,
duration: 1000
}
});
}
// Handle touch/mouse events
function handleDown(x, y, obj) {
lastTouchX = x;
lastTouchY = y;
dragging = true;
}
function handleMove(x, y, obj) {
if (dragging) {
// Calculate delta movement
var deltaX = x - lastTouchX;
var deltaY = y - lastTouchY;
// Apply force to bubble based on delta
bubble.applyForce(deltaX * tiltSensitivity, deltaY * tiltSensitivity * 0.5);
// Update last position
lastTouchX = x;
lastTouchY = y;
}
}
function handleUp(x, y, obj) {
dragging = false;
}
// Assign event handlers
game.down = handleDown;
game.move = handleMove;
game.up = handleUp;
// Main game update loop
game.update = function () {
if (bubble && currentLevel) {
// Update bubble physics
bubble.update();
// Update level elements
currentLevel.update();
// Check collisions
currentLevel.checkCollisions(bubble);
// Update UI
scoreText.setText('Score: ' + LK.getScore());
starsText.setText('Stars: ' + currentLevel.starCount + '/' + currentLevel.totalStars);
// Check for falling off screen
if (bubble.y > 2732 + 200) {
LK.showGameOver();
}
}
};
// Initialize the game
initGame(); ===================================================================
--- original.js
+++ change.js
@@ -347,9 +347,9 @@
// Check collision with portal
var dx = bubble.x - self.portal.x;
var dy = bubble.y - self.portal.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- if (distance < bubble.radius + self.portal.radius && !self.completed) {
+ if (distance < bubble.radius + self.portal.radius && !self.completed && self.starCount === self.totalStars) {
self.completed = true;
self.portal.activate();
// Calculate stars earned (based on collection percentage)
var starsEarned = Math.ceil(self.starCount / self.totalStars * 3);