/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Drawn Line class (user drawn) var DrawnLine = Container.expand(function () { var self = Container.call(this); // Line endpoints in game coordinates self.x1 = 0; self.y1 = 0; self.x2 = 0; self.y2 = 0; // For rendering, we use a box asset and stretch/rotate it var lineSprite = self.attachAsset('drawnLine', { anchorX: 0, anchorY: 0.5 }); // Set endpoints and update visual self.setEndpoints = function (x1, y1, x2, y2) { self.x1 = x1; self.y1 = y1; self.x2 = x2; self.y2 = y2; // Position at (x1, y1) self.x = x1; self.y = y1; // Calculate length and angle var dx = x2 - x1; var dy = y2 - y1; var len = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); lineSprite.width = len; lineSprite.height = 16; self.rotation = angle; }; // Collision detection with droplet (circle-line segment) self.collidesWithDroplet = function (droplet) { // Closest point on line segment to droplet center var x1 = self.x1, y1 = self.y1, x2 = self.x2, y2 = self.y2; var px = droplet.x, py = droplet.y; var dx = x2 - x1, dy = y2 - y1; var lengthSq = dx * dx + dy * dy; var t = ((px - x1) * dx + (py - y1) * dy) / (lengthSq || 1); t = Math.max(0, Math.min(1, t)); var closestX = x1 + t * dx; var closestY = y1 + t * dy; var distSq = (px - closestX) * (px - closestX) + (py - closestY) * (py - closestY); return distSq <= (droplet.radius + 8) * (droplet.radius + 8); }; // Get normal vector of the line (unit vector) self.getNormal = function () { var dx = self.x2 - self.x1; var dy = self.y2 - self.y1; var len = Math.sqrt(dx * dx + dy * dy) || 1; // Perpendicular (normal) vector return { x: -dy / len, y: dx / len }; }; return self; }); // Water Droplet class var Droplet = Container.expand(function () { var self = Container.call(this); var dropletSprite = self.attachAsset('droplet', { anchorX: 0.5, anchorY: 0.5 }); // Physics properties self.vx = 0; self.vy = 0; self.radius = dropletSprite.width / 2; self.caught = false; // If already scored // Update method called every tick self.update = function () { // Gravity self.vy += 0.4 * dropletGravityMultiplier; self.x += self.vx; self.y += self.vy; // Collide with drawn lines for (var i = 0; i < drawnLines.length; i++) { var line = drawnLines[i]; if (line.collidesWithDroplet(self)) { // Reflect velocity based on line angle var normal = line.getNormal(); // Project velocity onto normal var dot = self.vx * normal.x + self.vy * normal.y; self.vx = self.vx - 2 * dot * normal.x; self.vy = self.vy - 2 * dot * normal.y; // Dampen velocity a bit self.vx *= 0.7; self.vy *= 0.7; // Move droplet slightly away from line to prevent sticking self.x += normal.x * 4; self.y += normal.y * 4; } } // Collide with obstacles for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (self.intersects(obs)) { // Simple bounce: reverse vy, dampen self.vy = -Math.abs(self.vy) * 0.6; // Nudge out of obstacle if (self.y < obs.y) { self.y = obs.y - obs.height / 2 - self.radius - 2; } else { self.y = obs.y + obs.height / 2 + self.radius + 2; } } } // Collide with bucket if (!self.caught && self.intersects(bucket)) { // Check if inside bucket opening (not just touching sides) var bucketTop = bucket.y - bucket.height / 2; if (self.y + self.radius > bucketTop) { self.caught = true; // Play waterfall sound LK.getSound('waterfall').play(); LK.setScore(LK.getScore() + 1); scoreText.setText(LK.getScore()); // Animate droplet into bucket tween(self, { alpha: 0, y: bucket.y + bucket.height / 2 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { self.destroy(); removeDroplet(self); } }); return; } } // Out of bounds (left/right/bottom) if (self.y - self.radius > 2732 || self.x + self.radius < 0 || self.x - self.radius > 2048) { if (!self.caught) { lostDroplets++; updateLostText(); if (lostDroplets >= maxLostDroplets) { LK.showGameOver(); } } self.destroy(); removeDroplet(self); } }; return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); var obsSprite = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222244 }); /**** * Game Code ****/ // Game variables // Droplet: blue ellipse // Bucket: gray box // Obstacle: dark box // Line: blue box (for drawn lines) var droplets = []; var drawnLines = []; var obstacles = []; var dropletSpawnTimer = 0; var dropletSpawnInterval = 60; // ticks var dropletGravityMultiplier = 1; var lostDroplets = 0; var maxLostDroplets = 10; var currentLevel = 1; var isDrawing = false; var drawStartX = 0, drawStartY = 0; var drawLinePreview = null; var bucket = null; // Score display var scoreText = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Lost droplets display var lostText = new Text2('Lost: 0/10', { size: 60, fill: 0xFF6666 }); lostText.anchor.set(1, 0); LK.gui.topRight.addChild(lostText); function updateLostText() { lostText.setText('Lost: ' + lostDroplets + '/' + maxLostDroplets); } // Bucket setup bucket = new Container(); var bucketSprite = bucket.attachAsset('bucket', { anchorX: 0.5, anchorY: 0.5 }); bucket.width = bucketSprite.width; bucket.height = bucketSprite.height; bucket.x = 2048 / 2; bucket.y = 2732 - 180; game.addChild(bucket); // Obstacles setup (for level 1, one obstacle; more in higher levels) function setupObstacles(level) { // Remove old for (var i = 0; i < obstacles.length; i++) { obstacles[i].destroy(); } obstacles = []; if (level === 1) { var obs = new Obstacle(); obs.x = 2048 / 2; obs.y = 1200; game.addChild(obs); obstacles.push(obs); } else if (level === 2) { var obs1 = new Obstacle(); obs1.x = 2048 / 2 - 300; obs1.y = 1100; game.addChild(obs1); obstacles.push(obs1); var obs2 = new Obstacle(); obs2.x = 2048 / 2 + 300; obs2.y = 1500; game.addChild(obs2); obstacles.push(obs2); } else if (level >= 3) { for (var i = 0; i < 3; i++) { var obs = new Obstacle(); obs.x = 600 + i * 400; obs.y = 900 + i * 400; game.addChild(obs); obstacles.push(obs); } } } setupObstacles(currentLevel); // Remove droplet from array function removeDroplet(droplet) { for (var i = droplets.length - 1; i >= 0; i--) { if (droplets[i] === droplet) { droplets.splice(i, 1); break; } } } // Draw line preview (while dragging) function showDrawPreview(x1, y1, x2, y2) { if (!drawLinePreview) { drawLinePreview = new DrawnLine(); game.addChild(drawLinePreview); } drawLinePreview.setEndpoints(x1, y1, x2, y2); drawLinePreview.alpha = 0.5; } function hideDrawPreview() { if (drawLinePreview) { drawLinePreview.destroy(); drawLinePreview = null; } } // Drawing lines: only allow a max number of lines at once var maxDrawnLines = 4; function addDrawnLine(x1, y1, x2, y2) { if (drawnLines.length >= maxDrawnLines) { // Remove oldest var old = drawnLines.shift(); old.destroy(); } var line = new DrawnLine(); line.setEndpoints(x1, y1, x2, y2); game.addChild(line); drawnLines.push(line); } // Touch/mouse events for drawing lines game.down = function (x, y, obj) { // Only allow drawing in lower 90% of screen (avoid top menu) if (y < 120) return; isDrawing = true; drawStartX = x; drawStartY = y; showDrawPreview(drawStartX, drawStartY, x, y); }; game.move = function (x, y, obj) { if (isDrawing) { showDrawPreview(drawStartX, drawStartY, x, y); } }; game.up = function (x, y, obj) { if (isDrawing) { // Only draw if line is long enough var dx = x - drawStartX, dy = y - drawStartY; var len = Math.sqrt(dx * dx + dy * dy); if (len > 80) { addDrawnLine(drawStartX, drawStartY, x, y); } hideDrawPreview(); isDrawing = false; } }; // Droplet spawning function spawnDroplet() { var droplet = new Droplet(); // Random x, avoid spawning at extreme edges droplet.x = 180 + Math.random() * (2048 - 360); droplet.y = -40; // Small random initial vx droplet.vx = (Math.random() - 0.5) * 2; droplet.vy = 2 + Math.random() * 2 + dropletGravityMultiplier; droplets.push(droplet); game.addChild(droplet); } // Level progression function nextLevel() { currentLevel++; if (currentLevel > 5) currentLevel = 5; dropletGravityMultiplier = 1 + 0.2 * (currentLevel - 1); dropletSpawnInterval = Math.max(30, 60 - 8 * (currentLevel - 1)); setupObstacles(currentLevel); } // Main game update game.update = function () { // Spawn droplets dropletSpawnTimer++; if (dropletSpawnTimer >= dropletSpawnInterval) { dropletSpawnTimer = 0; spawnDroplet(); } // Update all droplets for (var i = 0; i < droplets.length; i++) { if (droplets[i].update) droplets[i].update(); } // Level up every 10 points var score = LK.getScore(); if (score > 0 && score % 10 === 0 && currentLevel < 5) { nextLevel(); } }; // Reset game state on new game LK.on('gameStart', function () { // Remove all droplets for (var i = 0; i < droplets.length; i++) { droplets[i].destroy(); } droplets = []; // Remove all lines for (var i = 0; i < drawnLines.length; i++) { drawnLines[i].destroy(); } drawnLines = []; // Remove obstacles for (var i = 0; i < obstacles.length; i++) { obstacles[i].destroy(); } obstacles = []; // Reset variables dropletSpawnTimer = 0; dropletSpawnInterval = 60; dropletGravityMultiplier = 1; lostDroplets = 0; maxLostDroplets = 10; currentLevel = 1; updateLostText(); scoreText.setText('0'); setupObstacles(currentLevel); });
===================================================================
--- original.js
+++ change.js
@@ -124,8 +124,10 @@
// Check if inside bucket opening (not just touching sides)
var bucketTop = bucket.y - bucket.height / 2;
if (self.y + self.radius > bucketTop) {
self.caught = true;
+ // Play waterfall sound
+ LK.getSound('waterfall').play();
LK.setScore(LK.getScore() + 1);
scoreText.setText(LK.getScore());
// Animate droplet into bucket
tween(self, {
@@ -176,13 +178,13 @@
/****
* Game Code
****/
-// Line: blue box (for drawn lines)
-// Obstacle: dark box
-// Bucket: gray box
-// Droplet: blue ellipse
// Game variables
+// Droplet: blue ellipse
+// Bucket: gray box
+// Obstacle: dark box
+// Line: blue box (for drawn lines)
var droplets = [];
var drawnLines = [];
var obstacles = [];
var dropletSpawnTimer = 0;