/****
* 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.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
@@ -1,6 +1,773 @@
-/****
+/****
+* 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.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: 0x000000
-});
\ No newline at end of file
+ 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();
\ No newline at end of file