Code edit (1 edits merged)
Please save this source code
User prompt
now upon reset, the icons are visible but they are too small, not the same size as the regular icons
User prompt
Fix charged ball icon transparency reset timing to happen at the same moment balls are released, your last change didnt work
User prompt
the 8 50% alpha icons need to have that transparency set at the same moment the 8 balls are released. right now, they only reset after I do an additional fruit release, which leaves a window where there's no icons visible on the screen
User prompt
right now, as soon as the 8th ball has charged, the balls are also released. instead, wait for one more turn, so first make the 8th ball 100% alpha and only on the next ball drop, release all 8 balls. as soon as this happens, and all 8 balls are released, the static icons turn back to 50% transparency so the cycle can reset
Code edit (1 edits merged)
Please save this source code
User prompt
move the 8 icons 200 pixels lower
User prompt
after dropping the 8 balls on top, it takes another tap to show the icons again. they should show up immediatelly as 50% alpha after the 8 have been released. right now they drop but the icons are shown back at 50% alpha after the next ball is dropped. this basically breaks the UI for a split second that should never happen
User prompt
after dropping the 8 balls on top, it takes another tap to show the icons again. they should show up immediatelly as 50% alpha after the 8 have been released
User prompt
when fruits hit the ground, they should not just bounce perfectly vertical, but also imprint a slight angle so they can bounce a bit to the left or right
User prompt
good first try with the 8 balls mechanic, but first of all, the grid should be on a single row,not 2, so have all 8 balls oon a single row, that are equally spread between the 2 edges of the screen. and the balls UI should be the same size as the actual balls that are being dropped. and upon restarting the cycle, ensure the cycle resets completely and the icons size is not affected
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
/****
* 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;
// Calculate elasticity based on fruit level
// The biggest fruit (DURIAN) has elasticity of 0.7
// Smaller fruits are more bouncy with elasticity closer to 1.0
var fruitLevels = {
'CHERRY': 1,
'GRAPE': 2,
'APPLE': 3,
'ORANGE': 4,
'WATERMELON': 5,
'PINEAPPLE': 6,
'MELON': 7,
'PEACH': 8,
'COCONUT': 9,
'DURIAN': 10
};
var currentLevel = self.type ? fruitLevels[self.type.id.toUpperCase()] || 10 : 10;
// Scale elasticity from 0.9 (most bouncy) for level 1 to 0.7 (least bouncy) for level 10
self.elasticity = 0.9 - (currentLevel - 1) * (0.2 / 9);
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
self.width = fruitGraphics.width;
self.height = fruitGraphics.height;
// 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 walls to apply wall friction
var fruitRadius = self.width / 2;
var isContactingLeftWall = self.x <= wallLeft.x + wallLeft.width / 2 + fruitRadius + 2;
var isContactingRightWall = self.x >= wallRight.x - wallRight.width / 2 - fruitRadius - 2;
if (isContactingLeftWall || isContactingRightWall) {
// Apply progressive wall friction based on how long the fruit has been in contact
if (!self.wallContactFrames) {
self.wallContactFrames = 1;
} else {
self.wallContactFrames++;
}
// Increase wall friction the longer the fruit stays in contact
var progressiveFriction = Math.min(0.85, 0.65 + self.wallContactFrames * 0.01);
self.angularVelocity *= progressiveFriction;
} else {
// Reset wall contact frames when not touching walls
self.wallContactFrames = 0;
}
// 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);
var combinedRadius = (this.type.size + otherFruit.type.size) / 2;
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();
// Play merge sound
LK.getSound('merge').play();
// 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
var chargedBalls = []; // Array to hold charged ball icons
var chargedBallContainer = null; // Container for charged ball icons
var chargeCounter = 0; // Counter to track dropped balls for charging
// 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, but 100px lower than before
activeFruit.x = gameWidth / 2;
activeFruit.y = dropPointY + 100;
// 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 angle variation - random angle between -10 and +10 degrees
var angle = (Math.random() * 20 - 10) * (Math.PI / 180); // Convert to radians
var force = 3.5; // Force magnitude
// Apply velocity based on angle
activeFruit.vx = Math.sin(angle) * force;
activeFruit.vy = Math.abs(Math.cos(angle) * force); // Make sure initial Y velocity is downward
// Add the fruit to the main fruits array
fruits.push(activeFruit);
// Play drop sound
LK.getSound('drop').play();
// Increment charge counter
chargeCounter++;
// Update charged ball display
updateChargedBallDisplay();
// Check if we've reached 8 charged balls and the 9th drop
if (chargeCounter > 8) {
// Release all charged balls on the 9th drop
releaseChargedBalls();
}
// Clear active fruit
activeFruit = null;
// Create the next fruit immediately
createNextFruit();
}
// 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;
// Create charged ball grid
setupChargedBallDisplay();
}
// Create the charged ball grid
function setupChargedBallDisplay() {
// Create container for charged balls
chargedBallContainer = new Container();
game.addChild(chargedBallContainer);
// Position the container at the top of the screen, 200 pixels lower
chargedBallContainer.y = 600;
// Calculate total width needed for all balls
var ballType = FruitTypes.GRAPE; // Level 2 ball type
var ballSize = ballType.size; // Get actual size of grape
var spacing = (gameWidth - 200) / 7; // Evenly distribute balls
// Horizontal position to start the row
var startX = 100; // Starting position from left edge
// Create 8 balls in a single row
for (var i = 0; i < 8; i++) {
var ball = new Container();
// Use grape as charged balls (level 2) with actual size
var ballGraphics = ball.attachAsset('grape', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
// Position in single row with equal spacing
ball.x = startX + i * spacing;
// Set to semi-transparent initially
ball.alpha = 0.5;
chargedBallContainer.addChild(ball);
chargedBalls.push(ball);
}
// Center the container horizontally
chargedBallContainer.x = 0;
}
// Function to update charged ball display
function updateChargedBallDisplay() {
// Update the visibility of balls based on chargeCounter
for (var i = 0; i < chargedBalls.length; i++) {
if (i < chargeCounter) {
// Balls that are charged are fully visible
if (chargedBalls[i].alpha !== 1) {
tween(chargedBalls[i], {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Ensure correct size is maintained
chargedBalls[i].scaleX = 1;
chargedBalls[i].scaleY = 1;
} else {
// Balls that are not yet charged are semi-transparent
if (chargedBalls[i].alpha !== 0.5) {
chargedBalls[i].alpha = 0.5;
}
// Ensure correct size is maintained
chargedBalls[i].scaleX = 1;
chargedBalls[i].scaleY = 1;
}
}
}
// Function to release all charged balls
function releaseChargedBalls() {
// No need to set transparency here as we'll set it through the animation
// We'll reset the transparency through the animation
// Create and drop 8 level 2 (grape) balls
for (var i = 0; i < 8; i++) {
var grape = new Fruit(FruitTypes.GRAPE);
// Distribute the balls across the width of the game
var offset = i * (gameWidth - 300) / 7 + 150;
grape.x = offset;
grape.y = dropPointY + 100;
// Make it dynamic so it drops
grape.isStatic = false;
// Add random horizontal velocity and downward vertical velocity
var angle = (Math.random() * 20 - 10) * (Math.PI / 180);
var force = 3.5;
grape.vx = Math.sin(angle) * force;
grape.vy = Math.abs(Math.cos(angle) * force);
// Add to game and fruits array
game.addChild(grape);
fruits.push(grape);
// Add animation to make it look like the charged balls are dropping
// Also reset transparency to 50% at the same time
tween(chargedBalls[i], {
alpha: 0.5,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn
});
}
// Play drop sound
LK.getSound('drop').play();
// Reset charge counter
chargeCounter = 0;
// Ensure proper reset of all charged ball properties
LK.setTimeout(function () {
for (var i = 0; i < chargedBalls.length; i++) {
// Ensure scaling is reset to original size
chargedBalls[i].scaleX = 1;
chargedBalls[i].scaleY = 1;
}
}, 300);
}
// 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
var fruit1Radius = fruit1.width / 2; // Use actual width of the asset
var fruit2Radius = fruit2.width / 2; // Use actual width of the asset
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) {
// Use the higher elasticity for the collision (smaller fruits bounce more)
var collisionElasticity = Math.max(fruit1.elasticity, fruit2.elasticity);
var impulse = -(1 + collisionElasticity) * 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);
// Apply impact scaling for smaller fruits against bigger ones
// Smaller fruits should bounce away more from larger fruits
var sizeDifference = Math.abs(fruit1.type.size - fruit2.type.size) / Math.max(fruit1.type.size, fruit2.type.size);
if (fruit1.type.size < fruit2.type.size) {
impulse1 *= 1 + sizeDifference * 0.5; // Smaller fruit gets extra bounce
} else if (fruit2.type.size < fruit1.type.size) {
impulse2 *= 1 + sizeDifference * 0.5; // Smaller fruit gets extra bounce
}
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) {
// Bounce sound removed
}
}
// 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
var fruitRadius = fruits[i].height / 2;
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 = [];
chargeCounter = 0;
chargedBalls = [];
// 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; // Use actual width of the asset
if (fruit.x < wallLeft.x + wallLeft.width / 2 + fruitRadius) {
fruit.x = wallLeft.x + wallLeft.width / 2 + fruitRadius;
fruit.vx = -fruit.vx * fruit.elasticity;
// Smaller fruits get more angular velocity from impacts
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
fruit.angularVelocity += fruit.vy * angularImpactMultiplier * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact
// Apply wall friction - stronger when in wall contact and proportional to fruit size
var wallFriction = 0.65 + (fruit.elasticity - 0.7) * 0.5; // More elastic (smaller) fruits get less wall friction
fruit.angularVelocity *= wallFriction;
fruit.angularVelocity *= fruit.groundAngularFriction;
if (Math.abs(fruit.vx) > 1) {
// Bounce sound removed
}
} else if (fruit.x > wallRight.x - wallRight.width / 2 - fruitRadius) {
fruit.x = wallRight.x - wallRight.width / 2 - fruitRadius;
fruit.vx = -fruit.vx * fruit.elasticity;
// Smaller fruits get more angular velocity from impacts
var angularImpactMultiplier = 0.005 * (1 + (0.9 - fruit.elasticity) * 5);
fruit.angularVelocity -= fruit.vy * angularImpactMultiplier * (fruit.vx / Math.abs(fruit.vx || 1)); // Apply angular velocity based on vertical velocity and direction of impact
// Apply wall friction - stronger when in wall contact and proportional to fruit size
var wallFriction = 0.65 + (fruit.elasticity - 0.7) * 0.5; // More elastic (smaller) fruits get less wall friction
fruit.angularVelocity *= wallFriction;
fruit.angularVelocity *= fruit.groundAngularFriction;
if (Math.abs(fruit.vx) > 1) {
// Bounce sound removed
}
}
// Floor collision - use actual fruit height for accurate collision
var fruitRadius = fruit.height / 2; // Use actual height of the asset
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) {
// Smaller fruits get more angular velocity from impacts
var angularImpactMultiplier = 0.01 * (1 + (0.9 - fruit.elasticity) * 5);
fruit.angularVelocity += fruit.vx * angularImpactMultiplier * (fruit.vy / Math.abs(fruit.vy || 1)); // Apply angular velocity based on horizontal velocity and direction of impact
}
// Smaller fruits should spin longer after impact
var angularDamping = fruit.elasticity > 0.85 ? 0.85 : fruit.groundAngularFriction;
fruit.angularVelocity *= angularDamping;
// Smaller fruits take more time to come to rest
var restThreshold = 1 + (fruit.elasticity - 0.7) * 10;
if (Math.abs(fruit.vy) < restThreshold) {
fruit.vy = 0;
}
// Angular rest threshold should also scale with elasticity
var angularRestThreshold = 0.03 * (1 - (fruit.elasticity - 0.7) * 2);
if (Math.abs(fruit.angularVelocity) < angularRestThreshold) {
fruit.angularVelocity = 0;
}
if (Math.abs(fruit.vy) > 1) {
// Bounce sound removed
}
}
// 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
@@ -372,9 +372,11 @@
var ball = new Container();
// Use grape as charged balls (level 2) with actual size
var ballGraphics = ball.attachAsset('grape', {
anchorX: 0.5,
- anchorY: 0.5
+ anchorY: 0.5,
+ scaleX: 1,
+ scaleY: 1
});
// Position in single row with equal spacing
ball.x = startX + i * spacing;
// Set to semi-transparent initially