User prompt
let's create a special mechanic that charges up 8 level 2 balls. these 8 balls are aranged on a grid at the top of the screen, and each ball is represented as a 50% transparent icon, which means it's inactive. after the player drops a ball, the first ball becomes full visible so 100% alpha. the second ball drop, the next icon becomes fully visible and so on until the 8th icon, meaning all 8 icons are now 100% visible. On the 9th drop, these balls become active, and they are all 8 of them dropp at the same time as the 9th active ball is eing dropped too. Then the cycle resets, the icons are inactive again, so show them as 50% transparent, and then on the 10th ball drop the cycle repeats and the icons start charing up again ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when dropping a ball, dont always drop it straight down, add a slight variation so each drop can drop it slightly to the left or the right from the point of dropping. and also move the starting Y position for the active balls 100 pixels lower. like add a slight angle of 10 degrees to the left and 10 degrees to the right, so the ball shoots with a sort of an arch from the spawn point
User prompt
when dropping a ball, dont always drop it straight down, add a slight variation so each drop can drop it slightly to the left or the right from the point of dropping. and also move the starting Y position for the active balls 100 pixels lower
User prompt
after dropping a ball, instantly show the next in line instead of waiting a short delay
User prompt
when balls get in contact with walls, they keep rotating the balls, ensure walls also add friction to the balls rotation so they dont kep rotating indefinitely
User prompt
the smaller the ball's level is, the more it should bounce. like, larger more evolved balls should be less impacted by the force of smaller balls and bounce less. keeps the current value for the level 10 ball, but as the balls value level decerases towards 1, it should be more bouncy
User prompt
remove the bounce sound effect
User prompt
create a new sound named Merge and play it every time 2 balls merge
User prompt
the latest collision detection improvement made the hitbox area for certain areas too large, leaving like an aura around them, making it worse, not they push balls away outside their actual size
User prompt
improve collision detection hitbox between balls
User prompt
make the fruits hitbox area be the same as the actual asset size
User prompt
cchange the size of all fruits, so level 1 starts from 150 pixels, level two is 200 pixels and so on, so increase each level by 50 pixels each
User prompt
the balls keep spinning indefinitely, they need to have friction so they halt after a while, especially while touching other resting balls
User prompt
rename the asset name too. like here the name still doesnt have a prefix. "LK.init.shape('coconut', {width:900, height:900, color:0x8c7e75, shape:'ellipse'})" add a prefix to the actuall assets name too
User prompt
please rename all fruits so their format is 1.Cherry and 2.Apple and so on. start each fruit with it's level number so I can easily see each fruit level numerically
User prompt
apply this scoring formula when merging balls. so when two level one balls merge, they award a point. this is the points for each level. Ball Level Merging Score 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 10 89
User prompt
Please fix the bug: 'fruit is not defined' in or related to this line: 'if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) {' Line Number: 59
User prompt
the balls rotation is still bugged, especially for balls on top of other balls that don't touch the ground, they seem to keep spinning, never coming to a rest
User prompt
it seems the spinning of the ball is not correlated to the force of impact. they do rotate, but the rotation doesn't look natural. they either rotate too much from a small itneraction, or sometimes stopp too abruptly
User prompt
balls reverted back to spinning uncontrolably, as if there's no friction. balls should have friction when interacting with the ground or other balls, so they dont keep spinning forever
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
/**** * 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.92; // Increased friction for faster angular velocity reduction self.groundAngularFriction = 0.75; // Stronger ground friction to stop spinning faster 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 }); // Set width and height directly from the actual asset for accurate hitbox // Store both the actual size and the visual hitbox size (slightly smaller) self.width = fruitGraphics.width; self.height = fruitGraphics.height; self.hitboxWidth = self.width * 0.85; // Visual hitbox is 85% of the actual size self.hitboxHeight = self.height * 0.85; // Visual hitbox is 85% of the actual size // 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; } // Add damping when velocity is low if (Math.abs(self.vx) < 0.5 && Math.abs(self.vy) < 0.5) { self.angularVelocity *= 0.9; // Apply damping when fruit is almost at rest } // Check for contact with other fruits to apply additional friction var isContactingOtherFruit = false; for (var i = 0; i < fruits.length; i++) { var otherFruit = fruits[i]; if (otherFruit !== this && !otherFruit.merging && !otherFruit.isStatic) { var dx = otherFruit.x - this.x; var dy = otherFruit.y - this.y; var distance = Math.sqrt(dx * dx + dy * dy); // Calculate effective radius in the direction of collision var angle = Math.atan2(dy, dx); var thisRadiusX = this.width / 2 * 0.85; // Scale down hitbox slightly to match visual size var thisRadiusY = this.height / 2 * 0.85; var otherRadiusX = otherFruit.width / 2 * 0.85; // Scale down hitbox slightly to match visual size var otherRadiusY = otherFruit.height / 2 * 0.85; // More accurate elliptical radius calculation var thisRadius = thisRadiusX * thisRadiusY / Math.sqrt(Math.pow(thisRadiusY * Math.cos(angle), 2) + Math.pow(thisRadiusX * Math.sin(angle), 2)); var otherRadius = otherRadiusX * otherRadiusY / Math.sqrt(Math.pow(otherRadiusY * Math.cos(angle), 2) + Math.pow(otherRadiusX * Math.sin(angle), 2)); var combinedRadius = thisRadius + otherRadius; if (distance < combinedRadius + 2) { // Small buffer for contact detection isContactingOtherFruit = true; break; } } } // Apply stronger friction when in contact with other fruits if (isContactingOtherFruit) { self.angularVelocity *= 0.8; // Stronger friction when touching other fruits } // Apply extreme damping when almost stopped rotating if (Math.abs(self.angularVelocity) < 0.01) { self.angularVelocity = 0; } }; 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 based on the new fruit's level 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: 150, points: 1, next: 'grape' }, GRAPE: { id: 'grape', size: 200, points: 2, next: 'apple' }, APPLE: { id: 'apple', size: 250, points: 3, next: 'orange' }, ORANGE: { id: 'orange', size: 300, points: 5, next: 'watermelon' }, WATERMELON: { id: 'watermelon', size: 350, points: 8, next: 'pineapple' }, PINEAPPLE: { id: 'pineapple', size: 400, points: 13, next: 'melon' }, MELON: { id: 'melon', size: 450, points: 21, next: 'peach' }, PEACH: { id: 'peach', size: 500, points: 34, next: 'coconut' }, COCONUT: { id: 'coconut', size: 550, points: 55, next: 'durian' }, DURIAN: { id: 'durian', size: 600, points: 89, 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 - use actual asset dimensions for more accurate hitboxes // Calculate effective radius based on the fruit orientation - using a more accurate elliptical model var fruit1RadiusX = fruit1.width / 2 * 0.85; // Scale down hitbox slightly to match visual size var fruit1RadiusY = fruit1.height / 2 * 0.85; var fruit2RadiusX = fruit2.width / 2 * 0.85; // Scale down hitbox slightly to match visual size var fruit2RadiusY = fruit2.height / 2 * 0.85; // Calculate effective radius in the direction of collision var angle = Math.atan2(dy, dx); // More accurate elliptical radius calculation var fruit1Radius = fruit1RadiusX * fruit1RadiusY / Math.sqrt(Math.pow(fruit1RadiusY * Math.cos(angle), 2) + Math.pow(fruit1RadiusX * Math.sin(angle), 2)); var fruit2Radius = fruit2RadiusX * fruit2RadiusY / Math.sqrt(Math.pow(fruit2RadiusY * Math.cos(angle), 2) + Math.pow(fruit2RadiusX * Math.sin(angle), 2)); var combinedRadius = fruit1Radius + fruit2Radius; 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.2; // Increased 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 with proportional damping var fruit1AngularImpulse = impulse1 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; var fruit2AngularImpulse = impulse2 * (tangentX * normalizeY - tangentY * normalizeX) * 0.0005; fruit1.angularVelocity += fruit1AngularImpulse; fruit2.angularVelocity -= fruit2AngularImpulse; // Apply additional angular damping during collisions fruit1.angularVelocity *= 0.9; fruit2.angularVelocity *= 0.9; // 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 // Use the fruit's height to determine proper position check, scaled to match visual size var fruitRadius = fruits[i].height / 2 * 0.85; if (fruits[i].y < 300 + fruitRadius && !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 - use actual fruit width var fruitRadius = activeFruit.width / 2; var minX = wallLeft.x + wallLeft.width / 2 + fruitRadius; var maxX = wallRight.x - wallRight.width / 2 - fruitRadius; 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; // Force fruits to stop rotating when they're barely moving if (Math.abs(fruit.vx) < 0.1 && Math.abs(fruit.vy) < 0.1 && Math.abs(fruit.angularVelocity) < 0.03) { fruit.angularVelocity = 0; } // Apply stronger angular friction when moving slowly if (Math.abs(fruit.vx) < 0.8 && Math.abs(fruit.vy) < 0.8) { fruit.angularVelocity *= 0.9; } // Clamp angular velocity fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity); // Wall collision - use actual fruit width for accurate collision var fruitRadius = fruit.width / 2 * 0.85; // Use actual width of the asset, scaled down to match visual if (fruit.x < wallLeft.x + wallLeft.width / 2 + fruitRadius) { fruit.x = wallLeft.x + wallLeft.width / 2 + fruitRadius; fruit.vx = -fruit.vx * fruit.elasticity; fruit.angularVelocity += fruit.vy * 0.005 * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact fruit.angularVelocity *= fruit.groundAngularFriction; if (Math.abs(fruit.vx) > 1) { LK.getSound('bounce').play(); } } else if (fruit.x > wallRight.x - wallRight.width / 2 - fruitRadius) { fruit.x = wallRight.x - wallRight.width / 2 - fruitRadius; fruit.vx = -fruit.vx * fruit.elasticity; fruit.angularVelocity -= fruit.vy * 0.005 * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact fruit.angularVelocity *= fruit.groundAngularFriction; if (Math.abs(fruit.vx) > 1) { LK.getSound('bounce').play(); } } // Floor collision - use actual fruit height for accurate collision var fruitRadius = fruit.height / 2 * 0.85; // Use actual height of the asset, scaled down to match visual if (fruit.y > gameFloor.y - gameFloor.height / 2 - fruitRadius) { fruit.y = gameFloor.y - gameFloor.height / 2 - fruitRadius; fruit.vy = -fruit.vy * fruit.elasticity; if (Math.abs(fruit.vx) > 0.5) { fruit.angularVelocity += fruit.vx * 0.01 * (fruit.vy / Math.abs(fruit.vy || 1)); // Apply angular velocity based on horizontal velocity and direction of impact } 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
@@ -29,10 +29,13 @@
anchorX: 0.5,
anchorY: 0.5
});
// Set width and height directly from the actual asset for accurate hitbox
+ // Store both the actual size and the visual hitbox size (slightly smaller)
self.width = fruitGraphics.width;
self.height = fruitGraphics.height;
+ self.hitboxWidth = self.width * 0.85; // Visual hitbox is 85% of the actual size
+ self.hitboxHeight = self.height * 0.85; // Visual hitbox is 85% of the actual size
// 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");
@@ -54,14 +57,15 @@
var dy = otherFruit.y - this.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate effective radius in the direction of collision
var angle = Math.atan2(dy, dx);
- var thisRadiusX = this.width / 2;
- var thisRadiusY = this.height / 2;
- var otherRadiusX = otherFruit.width / 2;
- var otherRadiusY = otherFruit.height / 2;
- var thisRadius = Math.abs(Math.cos(angle) * thisRadiusX) + Math.abs(Math.sin(angle) * thisRadiusY);
- var otherRadius = Math.abs(Math.cos(angle) * otherRadiusX) + Math.abs(Math.sin(angle) * otherRadiusY);
+ var thisRadiusX = this.width / 2 * 0.85; // Scale down hitbox slightly to match visual size
+ var thisRadiusY = this.height / 2 * 0.85;
+ var otherRadiusX = otherFruit.width / 2 * 0.85; // Scale down hitbox slightly to match visual size
+ var otherRadiusY = otherFruit.height / 2 * 0.85;
+ // More accurate elliptical radius calculation
+ var thisRadius = thisRadiusX * thisRadiusY / Math.sqrt(Math.pow(thisRadiusY * Math.cos(angle), 2) + Math.pow(thisRadiusX * Math.sin(angle), 2));
+ var otherRadius = otherRadiusX * otherRadiusY / Math.sqrt(Math.pow(otherRadiusY * Math.cos(angle), 2) + Math.pow(otherRadiusX * Math.sin(angle), 2));
var combinedRadius = thisRadius + otherRadius;
if (distance < combinedRadius + 2) {
// Small buffer for contact detection
isContactingOtherFruit = true;
@@ -332,17 +336,18 @@
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 - use actual asset dimensions for more accurate hitboxes
- // Calculate effective radius based on the fruit orientation
- var fruit1RadiusX = fruit1.width / 2;
- var fruit1RadiusY = fruit1.height / 2;
- var fruit2RadiusX = fruit2.width / 2;
- var fruit2RadiusY = fruit2.height / 2;
+ // Calculate effective radius based on the fruit orientation - using a more accurate elliptical model
+ var fruit1RadiusX = fruit1.width / 2 * 0.85; // Scale down hitbox slightly to match visual size
+ var fruit1RadiusY = fruit1.height / 2 * 0.85;
+ var fruit2RadiusX = fruit2.width / 2 * 0.85; // Scale down hitbox slightly to match visual size
+ var fruit2RadiusY = fruit2.height / 2 * 0.85;
// Calculate effective radius in the direction of collision
var angle = Math.atan2(dy, dx);
- var fruit1Radius = Math.abs(Math.cos(angle) * fruit1RadiusX) + Math.abs(Math.sin(angle) * fruit1RadiusY);
- var fruit2Radius = Math.abs(Math.cos(angle) * fruit2RadiusX) + Math.abs(Math.sin(angle) * fruit2RadiusY);
+ // More accurate elliptical radius calculation
+ var fruit1Radius = fruit1RadiusX * fruit1RadiusY / Math.sqrt(Math.pow(fruit1RadiusY * Math.cos(angle), 2) + Math.pow(fruit1RadiusX * Math.sin(angle), 2));
+ var fruit2Radius = fruit2RadiusX * fruit2RadiusY / Math.sqrt(Math.pow(fruit2RadiusY * Math.cos(angle), 2) + Math.pow(fruit2RadiusX * Math.sin(angle), 2));
var combinedRadius = fruit1Radius + fruit2Radius;
if (distance < combinedRadius) {
// Resolve collision (simple separation and velocity adjustment)
var overlap = combinedRadius - distance;
@@ -418,10 +423,10 @@
if (fruits[i] === activeFruit) {
continue;
}
// Only check for game over if fruit is high AND has had time to settle
- // Use the fruit's height to determine proper position check
- var fruitRadius = fruits[i].height / 2;
+ // Use the fruit's height to determine proper position check, scaled to match visual size
+ var fruitRadius = fruits[i].height / 2 * 0.85;
if (fruits[i].y < 300 + fruitRadius && !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) {
@@ -520,9 +525,9 @@
}
// Clamp angular velocity
fruit.angularVelocity = Math.min(Math.max(fruit.angularVelocity, -fruit.maxAngularVelocity), fruit.maxAngularVelocity);
// Wall collision - use actual fruit width for accurate collision
- var fruitRadius = fruit.width / 2; // Use actual width of the asset
+ var fruitRadius = fruit.width / 2 * 0.85; // Use actual width of the asset, scaled down to match visual
if (fruit.x < wallLeft.x + wallLeft.width / 2 + fruitRadius) {
fruit.x = wallLeft.x + wallLeft.width / 2 + fruitRadius;
fruit.vx = -fruit.vx * fruit.elasticity;
fruit.angularVelocity += fruit.vy * 0.005 * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact
@@ -539,9 +544,9 @@
LK.getSound('bounce').play();
}
}
// Floor collision - use actual fruit height for accurate collision
- var fruitRadius = fruit.height / 2; // Use actual height of the asset
+ var fruitRadius = fruit.height / 2 * 0.85; // Use actual height of the asset, scaled down to match visual
if (fruit.y > gameFloor.y - gameFloor.height / 2 - fruitRadius) {
fruit.y = gameFloor.y - gameFloor.height / 2 - fruitRadius;
fruit.vy = -fruit.vy * fruit.elasticity;
if (Math.abs(fruit.vx) > 0.5) {