User prompt
great, but now some ball lost collision and dont collide with each other
User prompt
nice, it works great now! amazing job! but curently I need to hold the actuall ball for it to move, when instead I should be able to press anywhere for that to happen
User prompt
we need to simplify things. remove the preview that shows the next ball and thenextfruitbg and instead of showing the next ball as a static icon in a fixed place, place that at the top of the screen, as the next dropping fruit. it remains in the same Y axis, but it now becomes part of the gameplay rather than of UI. while the player keeps the finger on the screen, the active ball follows it, but only moving on the X axis, and when the figner is released, the ball drops and then the next one pawns in it's place, so there's always an active ball that is about to be dropped
User prompt
upon release, the balls dont drop, they simply diappear
User prompt
we need to simplify things. remove the preview that shows the next ball and thenextfruitbg and instead of showing the next ball as a static icon in a fixed place, place that at the top of the screen, as the next dropping fruit. it remains in the same Y axis, but it now becomes part of the gameplay rather than of UI. while the player keeps the finger on the screen, the active ball follows it, but only moving on the X axis, and when the figner is released, the ball drops and then the next one pawns in it's place, so there's always an active ball that is about to be dropped
User prompt
make the balls drop even faster
User prompt
balls can still rotate exponentially. ground applies friction, but collision with other balls doesnt, making balls on top spin uncontrolably. fix this please
User prompt
the rotation seems to be too much and looks unrealistic. ensure ground adds friction which decreases the rotation
User prompt
balls should also rotate when colliding with other balls. the rotation should follow physics rules, based on the point of impact ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
remove the numerical value from the balls
User prompt
balls numbers are wrng, smallest fruit should be 1, next one 2 instead of 3, next one 3 instead of 6 and so on, keep it simple, chronological
User prompt
the smallest starting fruit size should be 100 and each next evolution is 100 pixels higher, so first is 100, next 200, next 300 and so on. change not the values in the code, but also the actual assets sizes
User prompt
the smallest starting fruit size should be 150 and each next evolution is 150 pixels higher, so first is 150, next 300, next 450 and so on
User prompt
the game seems to only have 5 fruits, I need another 5, so we have 10 total
User prompt
there seems to b a lag before identical balls merge, the merge should hapen instantly, as soon as the balls touch
User prompt
only balls of different evolutions should colide, identical ones need to merge
User prompt
balls currently overlap and intersect each other, that should not be possible, they need to collide with each other, and never overlap
User prompt
upon merging, balls should evolve to the next level, right now they dont, they just disappear. that's ok, but also create a new higher level ball upon merging
User prompt
increase the balls drop speed
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var fruitGraphics = self.attachAsset(self.type.id, {' Line Number: 39
User prompt
the game goes to game over randomly after releasing balls, fix this bug
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var fruitGraphics = self.attachAsset(self.type.id, {' Line Number: 39
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var fruitGraphics = self.attachAsset(self.type.id, {' Line Number: 36
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'setText')' in or related to this line: 'scoreText.setText("SCORE: " + LK.getScore());' Line Number: 303
Code edit (1 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Fruit = Container.expand(function (type) { var self = Container.call(this); // FruitTypes is being used before it's defined, so we need to handle this case self.type = type; self.vx = 0; self.vy = 0; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.96; // Increased from 0.97 for more friction self.groundAngularFriction = 0.85; // Additional friction when touching ground self.gravity = 1.8; // Doubled gravity to make fruits drop faster self.friction = 0.98; self.elasticity = 0.7; self.merging = false; self.isStatic = false; self.maxAngularVelocity = 0.15; // Add maximum angular velocity cap // Only attempt to attach the asset if self.type exists and has necessary properties if (self.type && self.type.id && self.type.points && self.type.size) { var fruitGraphics = self.attachAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }); // No point value shown on fruit } else { // This will be initialized properly when the game is fully loaded console.log("Warning: Fruit type not available yet or missing required properties"); } self.update = function () { if (self.isStatic || self.merging) { return; } }; self.merge = function (otherFruit) { if (self.merging || !self.type.next) { return; } self.merging = true; otherFruit.merging = true; // Calculate midpoint between fruits for new fruit position var midX = (self.x + otherFruit.x) / 2; var midY = (self.y + otherFruit.y) / 2; // Create merge animation tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut }); tween(otherFruit, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Create new merged fruit var nextType = FruitTypes[self.type.next.toUpperCase()]; var newFruit = new Fruit(nextType); newFruit.x = midX; newFruit.y = midY; newFruit.scaleX = 0.5; newFruit.scaleY = 0.5; game.addChild(newFruit); fruits.push(newFruit); // Add merge points LK.setScore(LK.getScore() + nextType.points); updateScoreDisplay(); // Animate new fruit growing tween(newFruit, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); // Remove old fruits var indexSelf = fruits.indexOf(self); if (indexSelf !== -1) { fruits.splice(indexSelf, 1); } self.destroy(); var indexOther = fruits.indexOf(otherFruit); if (indexOther !== -1) { fruits.splice(indexOther, 1); } otherFruit.destroy(); // Play merge sound LK.getSound('merge').play(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf6e58d }); /**** * Game Code ****/ // Game variables var FruitTypes = { CHERRY: { id: 'cherry', size: 100, points: 1, next: 'grape' }, GRAPE: { id: 'grape', size: 200, points: 2, next: 'apple' }, APPLE: { id: 'apple', size: 300, points: 3, next: 'orange' }, ORANGE: { id: 'orange', size: 400, points: 4, next: 'watermelon' }, WATERMELON: { id: 'watermelon', size: 500, points: 5, next: 'pineapple' }, PINEAPPLE: { id: 'pineapple', size: 600, points: 6, next: 'melon' }, MELON: { id: 'melon', size: 700, points: 7, next: 'peach' }, PEACH: { id: 'peach', size: 800, points: 8, next: 'coconut' }, COCONUT: { id: 'coconut', size: 900, points: 9, next: 'durian' }, DURIAN: { id: 'durian', size: 1000, points: 10, next: null } }; var gameWidth = 2048; var gameHeight = 2732; var fruits = []; var nextFruitType = null; var activeFruit = null; // The fruit currently controlled by the player var wallLeft, wallRight, gameFloor; var dropPointY = 200; // Y coordinate where new fruits appear var gameOver = false; var scoreText; var isDragging = false; // Flag to check if the player is currently dragging // Setup game boundaries function setupBoundaries() { // Left wall wallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallLeft.x = 0; wallLeft.y = gameHeight / 2; // Right wall wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; // Floor gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; } // Create new next fruit function createNextFruit() { // Determine which fruit to spawn (for now just start with smaller fruits) var fruitProbability = Math.random(); var fruitType; if (fruitProbability < 0.6) { fruitType = FruitTypes.CHERRY; } else if (fruitProbability < 0.85) { fruitType = FruitTypes.GRAPE; } else { fruitType = FruitTypes.APPLE; } nextFruitType = fruitType; // Update display // No explicit preview display needed, the next fruit will be the one the player controls // Create the active fruit activeFruit = new Fruit(nextFruitType); // Position at the top center initially activeFruit.x = gameWidth / 2; activeFruit.y = dropPointY; // Make it static while the player controls it activeFruit.isStatic = true; game.addChild(activeFruit); } // Drop fruit at specified position function dropFruit() { if (gameOver || !activeFruit) { return; } // Make the active fruit dynamic so it drops activeFruit.isStatic = false; // Add small random x velocity to make it interesting activeFruit.vx = (Math.random() - 0.5) * 3; // Add the fruit to the main fruits array fruits.push(activeFruit); // Play drop sound LK.getSound('drop').play(); // Clear active fruit activeFruit = null; // Prepare the next fruit after a short delay LK.setTimeout(function () { createNextFruit(); }, 500); // Delay before creating the next fruit } // Update score display function updateScoreDisplay() { scoreText.setText("SCORE: " + LK.getScore()); } // Setup UI function setupUI() { // Score display scoreText = new Text2("SCORE: 0", { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 30; } // Check for fruit collisions // Check for fruit collisions function checkFruitCollisions() { for (var i = 0; i < fruits.length; i++) { // Skip collision for the active fruit if (fruits[i] === activeFruit) { continue; } for (var j = i + 1; j < fruits.length; j++) { var fruit1 = fruits[i]; var fruit2 = fruits[j]; // Skip collision for the active fruit if (fruits[j] === activeFruit) { continue; } // Skip if either fruit is already merging if (fruit1.merging || fruit2.merging) { continue; } // Calculate distance between centers var dx = fruit2.x - fruit1.x; var dy = fruit2.y - fruit1.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if they are overlapping var combinedRadius = (fruit1.type.size + fruit2.type.size) / 2; if (distance < combinedRadius) { // Resolve collision (simple separation and velocity adjustment) var overlap = combinedRadius - distance; var normalizeX = dx / distance; var normalizeY = dy / distance; var moveX = overlap / 2 * normalizeX; var moveY = overlap / 2 * normalizeY; fruit1.x -= moveX; fruit1.y -= moveY; fruit2.x += moveX; fruit2.y += moveY; // Calculate relative velocity var rvX = fruit2.vx - fruit1.vx; var rvY = fruit2.vy - fruit1.vy; var contactVelocity = rvX * normalizeX + rvY * normalizeY; // Only resolve if velocities are separating if (contactVelocity < 0) { var impulse = -(1 + fruit1.elasticity) * contactVelocity; var totalMass = fruit1.type.size + fruit2.type.size; // Using size as a proxy for mass var impulse1 = impulse * (fruit2.type.size / totalMass); var impulse2 = impulse * (fruit1.type.size / totalMass); fruit1.vx -= impulse1 * normalizeX; fruit1.vy -= impulse1 * normalizeY; fruit2.vx += impulse2 * normalizeX; fruit2.vy += impulse2 * normalizeY; // Apply friction between colliding fruits var tangentX = -normalizeY; var tangentY = normalizeX; var tangentVelocity = rvX * tangentX + rvY * tangentY; var frictionImpulse = -tangentVelocity * 0.1; // Simple friction factor fruit1.vx -= frictionImpulse * tangentX; fruit1.vy -= frictionImpulse * tangentY; fruit2.vx += frictionImpulse * tangentX; fruit2.vy += frictionImpulse * tangentY; // Apply angular velocity change based on collision fruit1.angularVelocity += impulse1 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; fruit2.angularVelocity -= impulse2 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; // Cap angular velocity fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity); fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity); if (Math.abs(contactVelocity) > 1) { LK.getSound('bounce').play(); } } // Only proceed with merge if fruits are the same type if (fruit1.type === fruit2.type) { // Merge fruits if they are close enough and of the same type fruit1.merge(fruit2); break; } } } } } // Check if game is over (too many fruits on screen or stacked too high) function checkGameOver() { if (gameOver) { return; } if (fruits.length > 30) { gameOver = true; LK.showGameOver(); return; } // Check if any fruits are too high for (var i = 0; i < fruits.length; i++) { // Don't check game over for the active fruit if (fruits[i] === activeFruit) { continue; } // Only check for game over if fruit is high AND has had time to settle if (fruits[i].y < 300 && !fruits[i].merging) { var isFruitMoving = Math.abs(fruits[i].vx) > 0.5 || Math.abs(fruits[i].vy) > 0.5; // Add a countdown to ensure the fruit has truly stopped moving if (!isFruitMoving) { // Initialize the stable timer if not already set if (fruits[i].stableTimer === undefined) { fruits[i].stableTimer = 60; // Give 1 second (60 frames) to confirm stability } else { fruits[i].stableTimer--; if (fruits[i].stableTimer <= 0) { gameOver = true; LK.showGameOver(); return; } } } else { // Reset timer if the fruit starts moving again fruits[i].stableTimer = undefined; } } } } // Initialize game function initGame() { LK.setScore(0); gameOver = false; fruits = []; // Start background music LK.playMusic('bgmusic'); // Setup game elements setupBoundaries(); setupUI(); updateScoreDisplay(); createNextFruit(); } // Event handlers game.down = function (x, y) { // We don't need to check specific boundaries to start dragging. // As long as there's an active fruit, we can start dragging. if (activeFruit) { isDragging = true; // Update active fruit position immediately game.move(x, y); } }; // Mouse or touch move on game object game.move = function (x, y) { if (isDragging && activeFruit) { // Only move the active fruit on the X axis var minX = wallLeft.x + wallLeft.width / 2 + activeFruit.type.size / 2; var maxX = wallRight.x - wallRight.width / 2 - activeFruit.type.size / 2; activeFruit.x = Math.max(minX, Math.min(maxX, x)); } }; // Mouse or touch up on game object game.up = function () { if (isDragging && activeFruit) { dropFruit(); } isDragging = false; }; // Game update loop game.update = function () { // Apply physics and check collisions for each fruit for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (fruit.isStatic || fruit.merging) { continue; } // Store last position for boundary checks if (fruit.lastY === undefined) { fruit.lastY = fruit.y; } if (fruit.lastX === undefined) { fruit.lastX = fruit.x; } // Apply gravity fruit.vy += fruit.gravity; // Apply velocity fruit.x += fruit.vx; fruit.y += fruit.vy; // Apply rotation fruit.rotation += fruit.angularVelocity; // Apply friction fruit.vx *= fruit.friction; fruit.vy *= fruit.friction; // Apply angular friction fruit.angularVelocity *= fruit.angularFriction; // Clamp angular velocity fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity); // Wall collision if (fruit.x < wallLeft.x + wallLeft.width / 2 + fruit.type.size / 2) { fruit.x = wallLeft.x + wallLeft.width / 2 + fruit.type.size / 2; fruit.vx = -fruit.vx * fruit.elasticity; fruit.angularVelocity += fruit.vy * 0.005; if (Math.abs(fruit.vx) > 1) { LK.getSound('bounce').play(); } } else if (fruit.x > wallRight.x - wallRight.width / 2 - fruit.type.size / 2) { fruit.x = wallRight.x - wallRight.width / 2 - fruit.type.size / 2; fruit.vx = -fruit.vx * fruit.elasticity; fruit.angularVelocity -= fruit.vy * 0.005; if (Math.abs(fruit.vx) > 1) { LK.getSound('bounce').play(); } } // Floor collision if (fruit.y > gameFloor.y - gameFloor.height / 2 - fruit.type.size / 2) { fruit.y = gameFloor.y - gameFloor.height / 2 - fruit.type.size / 2; fruit.vy = -fruit.vy * fruit.elasticity; if (Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity = fruit.vx * 0.01; } fruit.angularVelocity *= fruit.groundAngularFriction; if (Math.abs(fruit.vy) < 2) { fruit.vy = 0; } if (Math.abs(fruit.angularVelocity) < 0.03) { fruit.angularVelocity = 0; } if (Math.abs(fruit.vy) > 1) { LK.getSound('bounce').play(); } } // Update last positions fruit.lastX = fruit.x; fruit.lastY = fruit.y; } // Check for fruit collisions checkFruitCollisions(); // Check game over conditions checkGameOver(); }; // Initialize the game initGame();
===================================================================
--- original.js
+++ change.js
@@ -270,8 +270,9 @@
LK.gui.top.addChild(scoreText);
scoreText.y = 30;
}
// Check for fruit collisions
+// Check for fruit collisions
function checkFruitCollisions() {
for (var i = 0; i < fruits.length; i++) {
// Skip collision for the active fruit
if (fruits[i] === activeFruit) {
@@ -293,13 +294,55 @@
var dy = fruit2.y - fruit1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if they are overlapping
var combinedRadius = (fruit1.type.size + fruit2.type.size) / 2;
- // Use a smaller threshold for merging to prevent overlap issues
- if (distance < combinedRadius * 0.9) {
+ if (distance < combinedRadius) {
+ // Resolve collision (simple separation and velocity adjustment)
+ var overlap = combinedRadius - distance;
+ var normalizeX = dx / distance;
+ var normalizeY = dy / distance;
+ var moveX = overlap / 2 * normalizeX;
+ var moveY = overlap / 2 * normalizeY;
+ fruit1.x -= moveX;
+ fruit1.y -= moveY;
+ fruit2.x += moveX;
+ fruit2.y += moveY;
+ // Calculate relative velocity
+ var rvX = fruit2.vx - fruit1.vx;
+ var rvY = fruit2.vy - fruit1.vy;
+ var contactVelocity = rvX * normalizeX + rvY * normalizeY;
+ // Only resolve if velocities are separating
+ if (contactVelocity < 0) {
+ var impulse = -(1 + fruit1.elasticity) * contactVelocity;
+ var totalMass = fruit1.type.size + fruit2.type.size; // Using size as a proxy for mass
+ var impulse1 = impulse * (fruit2.type.size / totalMass);
+ var impulse2 = impulse * (fruit1.type.size / totalMass);
+ fruit1.vx -= impulse1 * normalizeX;
+ fruit1.vy -= impulse1 * normalizeY;
+ fruit2.vx += impulse2 * normalizeX;
+ fruit2.vy += impulse2 * normalizeY;
+ // Apply friction between colliding fruits
+ var tangentX = -normalizeY;
+ var tangentY = normalizeX;
+ var tangentVelocity = rvX * tangentX + rvY * tangentY;
+ var frictionImpulse = -tangentVelocity * 0.1; // Simple friction factor
+ fruit1.vx -= frictionImpulse * tangentX;
+ fruit1.vy -= frictionImpulse * tangentY;
+ fruit2.vx += frictionImpulse * tangentX;
+ fruit2.vy += frictionImpulse * tangentY;
+ // Apply angular velocity change based on collision
+ fruit1.angularVelocity += impulse1 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005;
+ fruit2.angularVelocity -= impulse2 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005;
+ // Cap angular velocity
+ fruit1.angularVelocity = Math.min(Math.max(fruit1.angularVelocity, -fruit1.maxAngularVelocity), fruit1.maxAngularVelocity);
+ fruit2.angularVelocity = Math.min(Math.max(fruit2.angularVelocity, -fruit2.maxAngularVelocity), fruit2.maxAngularVelocity);
+ if (Math.abs(contactVelocity) > 1) {
+ LK.getSound('bounce').play();
+ }
+ }
// Only proceed with merge if fruits are the same type
if (fruit1.type === fruit2.type) {
- // Merge fruits immediately upon contact, regardless of velocity
+ // Merge fruits if they are close enough and of the same type
fruit1.merge(fruit2);
break;
}
}