/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); // Ball properties self.size = 1; // 1-9 self.radius = 60; // default, will be set in init self.vx = 0; self.vy = 0; self.isMerging = false; // Prevent double merge // Attach asset based on size self.setSize = function (size) { self.size = size; // Remove previous asset if any if (self.ballAsset) { self.removeChild(self.ballAsset); } var assetId = 'ball' + size; self.ballAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.ballAsset.width / 2; }; // Set initial size self.setSize(1); // For merge animation self.flash = function () { tween(self.ballAsset, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self.ballAsset, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); }; // For showing value self.showValue = function () { if (self.valueText) { self.removeChild(self.valueText); } self.valueText = new Text2('' + self.size, { size: Math.max(60, self.radius), fill: 0xFFFFFF }); self.valueText.anchor.set(0.5, 0.5); self.addChild(self.valueText); }; self.showValue(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222831 }); /**** * Game Code ****/ // Game constants // Ball shapes for different sizes (size1, size2, size3, size4, size5, size6, size7, size8, size9) // Sound for merging var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BALL_MIN_SIZE = 1; var BALL_MAX_SIZE = 12; // Now only up to 3 new big balls (ball10, ball11, ball12) var BALL_LIMIT = 16; // Max balls allowed on screen before game over // var INIT_BALLS = 9; // No longer needed, no initial balls var GRAVITY = 1.2; var BOUNCE = 0.7; var FRICTION = 0.995; var WALL_BOUNCE = 0.8; var SPAWN_Y = 180; // y position for new balls // Ball array var balls = []; // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Next ball preview var nextBallSize = BALL_MIN_SIZE + Math.floor(Math.random() * 4); // Only 1-4 var nextBallPreview = LK.getAsset('ball' + nextBallSize, { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: SPAWN_Y - 100 }); game.addChild(nextBallPreview); // Helper: get random ball size (only 1-4 for spawn) function getRandomBallSize() { // Weighted random: 1: 45%, 2: 30%, 3: 15%, 4: 10% var r = Math.random(); if (r < 0.45) return 1; if (r < 0.75) return 2; if (r < 0.90) return 3; return 4; } // Helper: clamp function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Helper: distance squared function dist2(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; return dx * dx + dy * dy; } // Helper: check overlap function isOverlapping(ballA, ballB) { var rSum = ballA.radius + ballB.radius; return dist2(ballA, ballB) < rSum * rSum - 1; } // Helper: resolve overlap function resolveOverlap(ballA, ballB) { var dx = ballB.x - ballA.x; var dy = ballB.y - ballA.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) { // Random nudge dx = 1; dy = 0; dist = 1; } var overlap = ballA.radius + ballB.radius - dist; if (overlap > 0) { var pushX = dx / dist * (overlap / 2); var pushY = dy / dist * (overlap / 2); ballA.x -= pushX; ballA.y -= pushY; ballB.x += pushX; ballB.y += pushY; } } // Helper: merge balls function mergeBalls(ballA, ballB) { // Only merge if both are not merging and same size if (ballA.isMerging || ballB.isMerging) return false; if (ballA.size !== ballB.size) return false; ballA.isMerging = true; ballB.isMerging = true; // New ball at average position var newBall = new Ball(); var newSize = clamp(ballA.size + 1, BALL_MIN_SIZE, BALL_MAX_SIZE); newBall.setSize(newSize); newBall.x = (ballA.x + ballB.x) / 2; newBall.y = (ballA.y + ballB.y) / 2; newBall.vx = (ballA.vx + ballB.vx) / 2; newBall.vy = (ballA.vy + ballB.vy) / 2; newBall.showValue(); newBall.flash(); // Remove old balls ballA.destroy(); ballB.destroy(); var idxA = balls.indexOf(ballA); var idxB = balls.indexOf(ballB); if (idxA !== -1) balls.splice(idxA, 1); if (idxB !== -1) balls.splice(balls.indexOf(ballB), 1); // index may have shifted // Add new ball balls.push(newBall); game.addChild(newBall); // Play merge sound LK.getSound('merge').play(); // Add score score += Math.pow(2, newSize); scoreTxt.setText(score); // Animate newBall.flash(); // Feature 9: Shake nearby balls after merge var SHAKE_RADIUS = newBall.radius * 3; for (var i = 0; i < balls.length; i++) { var b = balls[i]; if (b !== newBall && !b.isMerging) { var dx = b.x - newBall.x; var dy = b.y - newBall.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < SHAKE_RADIUS) { // Give a small random velocity burst var angle = Math.atan2(dy, dx); var force = 10 + Math.random() * 10; b.vx += Math.cos(angle) * force * (0.7 + 0.6 * Math.random()); b.vy += Math.sin(angle) * force * (0.7 + 0.6 * Math.random()); } } } return newBall; } // Spawn a ball at (x, y) with given size function spawnBall(x, y, size) { var ball = new Ball(); ball.setSize(size); ball.x = clamp(x, ball.radius, GAME_WIDTH - ball.radius); ball.y = y; ball.vx = (Math.random() - 0.5) * 16; ball.vy = 0; ball.showValue(); balls.push(ball); game.addChild(ball); return ball; } // No initial balls function needed // Drag and drop for new balls var isDragging = false; var dragX = GAME_WIDTH / 2; var dragBall = null; // Show drag preview var dragPreview = null; function showDragPreview(x) { if (!dragPreview) { dragPreview = LK.getAsset('ball' + nextBallSize, { anchorX: 0.5, anchorY: 0.5, x: x, y: SPAWN_Y }); game.addChild(dragPreview); } dragPreview.x = clamp(x, dragPreview.width / 2, GAME_WIDTH - dragPreview.width / 2); dragPreview.y = SPAWN_Y; } function hideDragPreview() { if (dragPreview) { dragPreview.destroy(); dragPreview = null; } } // Handle touch/mouse down game.down = function (x, y, obj) { // Only allow drop in top 1/3 of screen if (y > GAME_HEIGHT / 2) return; isDragging = true; dragX = clamp(x, 100, GAME_WIDTH - 100); showDragPreview(dragX); }; // Handle move game.move = function (x, y, obj) { if (isDragging) { dragX = clamp(x, 100, GAME_WIDTH - 100); showDragPreview(dragX); } }; // Handle up (drop ball) game.up = function (x, y, obj) { if (isDragging) { isDragging = false; hideDragPreview(); // Drop ball at dragX spawnBall(dragX, SPAWN_Y, nextBallSize); // Next ball nextBallSize = getRandomBallSize(); // Update preview if (nextBallPreview) { nextBallPreview.destroy(); } nextBallPreview = LK.getAsset('ball' + nextBallSize, { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: SPAWN_Y - 100 }); game.addChild(nextBallPreview); } }; // Main update loop game.update = function () { // Physics for each ball for (var i = 0; i < balls.length; i++) { var ball = balls[i]; // Gravity ball.vy += GRAVITY; // Move ball.x += ball.vx; ball.y += ball.vy; // Wall collision if (ball.x - ball.radius < 0) { ball.x = ball.radius; ball.vx = -ball.vx * WALL_BOUNCE; } if (ball.x + ball.radius > GAME_WIDTH) { ball.x = GAME_WIDTH - ball.radius; ball.vx = -ball.vx * WALL_BOUNCE; } // Floor if (ball.y + ball.radius > GAME_HEIGHT) { ball.y = GAME_HEIGHT - ball.radius; ball.vy = -ball.vy * BOUNCE; ball.vx *= FRICTION; // Stop tiny bounces if (Math.abs(ball.vy) < 2) ball.vy = 0; } // Ceiling if (ball.y - ball.radius < 0) { ball.y = ball.radius; ball.vy = -ball.vy * BOUNCE; } // Friction ball.vx *= FRICTION; ball.vy *= FRICTION; } // Ball-ball collision and merging for (var i = 0; i < balls.length; i++) { for (var j = i + 1; j < balls.length; j++) { var a = balls[i], b = balls[j]; if (isOverlapping(a, b)) { // If same size, merge if (a.size === b.size && !a.isMerging && !b.isMerging) { mergeBalls(a, b); break; // balls array changed, restart } else { // Elastic collision resolveOverlap(a, b); // Simple velocity swap var dx = b.x - a.x; var dy = b.y - a.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var nx = dx / dist, ny = dy / dist; var p = 2 * (a.vx * nx + a.vy * ny - b.vx * nx - b.vy * ny) / 2; a.vx = a.vx - p * nx; a.vy = a.vy - p * ny; b.vx = b.vx + p * nx; b.vy = b.vy + p * ny; } } } } } // Remove balls marked for destroy (already handled in mergeBalls) // Check for game over if (balls.length > BALL_LIMIT) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); } // Optionally: win condition (e.g. create a ball of size 12) for (var i = 0; i < balls.length; i++) { if (balls[i].size === 12) { LK.effects.flashScreen(0xffff00, 1000); LK.showYouWin(); break; } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
// Ball properties
self.size = 1; // 1-9
self.radius = 60; // default, will be set in init
self.vx = 0;
self.vy = 0;
self.isMerging = false; // Prevent double merge
// Attach asset based on size
self.setSize = function (size) {
self.size = size;
// Remove previous asset if any
if (self.ballAsset) {
self.removeChild(self.ballAsset);
}
var assetId = 'ball' + size;
self.ballAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.ballAsset.width / 2;
};
// Set initial size
self.setSize(1);
// For merge animation
self.flash = function () {
tween(self.ballAsset, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.ballAsset, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
// For showing value
self.showValue = function () {
if (self.valueText) {
self.removeChild(self.valueText);
}
self.valueText = new Text2('' + self.size, {
size: Math.max(60, self.radius),
fill: 0xFFFFFF
});
self.valueText.anchor.set(0.5, 0.5);
self.addChild(self.valueText);
};
self.showValue();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222831
});
/****
* Game Code
****/
// Game constants
// Ball shapes for different sizes (size1, size2, size3, size4, size5, size6, size7, size8, size9)
// Sound for merging
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BALL_MIN_SIZE = 1;
var BALL_MAX_SIZE = 12; // Now only up to 3 new big balls (ball10, ball11, ball12)
var BALL_LIMIT = 16; // Max balls allowed on screen before game over
// var INIT_BALLS = 9; // No longer needed, no initial balls
var GRAVITY = 1.2;
var BOUNCE = 0.7;
var FRICTION = 0.995;
var WALL_BOUNCE = 0.8;
var SPAWN_Y = 180; // y position for new balls
// Ball array
var balls = [];
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Next ball preview
var nextBallSize = BALL_MIN_SIZE + Math.floor(Math.random() * 4); // Only 1-4
var nextBallPreview = LK.getAsset('ball' + nextBallSize, {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: SPAWN_Y - 100
});
game.addChild(nextBallPreview);
// Helper: get random ball size (only 1-4 for spawn)
function getRandomBallSize() {
// Weighted random: 1: 45%, 2: 30%, 3: 15%, 4: 10%
var r = Math.random();
if (r < 0.45) return 1;
if (r < 0.75) return 2;
if (r < 0.90) return 3;
return 4;
}
// Helper: clamp
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Helper: distance squared
function dist2(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return dx * dx + dy * dy;
}
// Helper: check overlap
function isOverlapping(ballA, ballB) {
var rSum = ballA.radius + ballB.radius;
return dist2(ballA, ballB) < rSum * rSum - 1;
}
// Helper: resolve overlap
function resolveOverlap(ballA, ballB) {
var dx = ballB.x - ballA.x;
var dy = ballB.y - ballA.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) {
// Random nudge
dx = 1;
dy = 0;
dist = 1;
}
var overlap = ballA.radius + ballB.radius - dist;
if (overlap > 0) {
var pushX = dx / dist * (overlap / 2);
var pushY = dy / dist * (overlap / 2);
ballA.x -= pushX;
ballA.y -= pushY;
ballB.x += pushX;
ballB.y += pushY;
}
}
// Helper: merge balls
function mergeBalls(ballA, ballB) {
// Only merge if both are not merging and same size
if (ballA.isMerging || ballB.isMerging) return false;
if (ballA.size !== ballB.size) return false;
ballA.isMerging = true;
ballB.isMerging = true;
// New ball at average position
var newBall = new Ball();
var newSize = clamp(ballA.size + 1, BALL_MIN_SIZE, BALL_MAX_SIZE);
newBall.setSize(newSize);
newBall.x = (ballA.x + ballB.x) / 2;
newBall.y = (ballA.y + ballB.y) / 2;
newBall.vx = (ballA.vx + ballB.vx) / 2;
newBall.vy = (ballA.vy + ballB.vy) / 2;
newBall.showValue();
newBall.flash();
// Remove old balls
ballA.destroy();
ballB.destroy();
var idxA = balls.indexOf(ballA);
var idxB = balls.indexOf(ballB);
if (idxA !== -1) balls.splice(idxA, 1);
if (idxB !== -1) balls.splice(balls.indexOf(ballB), 1); // index may have shifted
// Add new ball
balls.push(newBall);
game.addChild(newBall);
// Play merge sound
LK.getSound('merge').play();
// Add score
score += Math.pow(2, newSize);
scoreTxt.setText(score);
// Animate
newBall.flash();
// Feature 9: Shake nearby balls after merge
var SHAKE_RADIUS = newBall.radius * 3;
for (var i = 0; i < balls.length; i++) {
var b = balls[i];
if (b !== newBall && !b.isMerging) {
var dx = b.x - newBall.x;
var dy = b.y - newBall.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < SHAKE_RADIUS) {
// Give a small random velocity burst
var angle = Math.atan2(dy, dx);
var force = 10 + Math.random() * 10;
b.vx += Math.cos(angle) * force * (0.7 + 0.6 * Math.random());
b.vy += Math.sin(angle) * force * (0.7 + 0.6 * Math.random());
}
}
}
return newBall;
}
// Spawn a ball at (x, y) with given size
function spawnBall(x, y, size) {
var ball = new Ball();
ball.setSize(size);
ball.x = clamp(x, ball.radius, GAME_WIDTH - ball.radius);
ball.y = y;
ball.vx = (Math.random() - 0.5) * 16;
ball.vy = 0;
ball.showValue();
balls.push(ball);
game.addChild(ball);
return ball;
}
// No initial balls function needed
// Drag and drop for new balls
var isDragging = false;
var dragX = GAME_WIDTH / 2;
var dragBall = null;
// Show drag preview
var dragPreview = null;
function showDragPreview(x) {
if (!dragPreview) {
dragPreview = LK.getAsset('ball' + nextBallSize, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: SPAWN_Y
});
game.addChild(dragPreview);
}
dragPreview.x = clamp(x, dragPreview.width / 2, GAME_WIDTH - dragPreview.width / 2);
dragPreview.y = SPAWN_Y;
}
function hideDragPreview() {
if (dragPreview) {
dragPreview.destroy();
dragPreview = null;
}
}
// Handle touch/mouse down
game.down = function (x, y, obj) {
// Only allow drop in top 1/3 of screen
if (y > GAME_HEIGHT / 2) return;
isDragging = true;
dragX = clamp(x, 100, GAME_WIDTH - 100);
showDragPreview(dragX);
};
// Handle move
game.move = function (x, y, obj) {
if (isDragging) {
dragX = clamp(x, 100, GAME_WIDTH - 100);
showDragPreview(dragX);
}
};
// Handle up (drop ball)
game.up = function (x, y, obj) {
if (isDragging) {
isDragging = false;
hideDragPreview();
// Drop ball at dragX
spawnBall(dragX, SPAWN_Y, nextBallSize);
// Next ball
nextBallSize = getRandomBallSize();
// Update preview
if (nextBallPreview) {
nextBallPreview.destroy();
}
nextBallPreview = LK.getAsset('ball' + nextBallSize, {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: SPAWN_Y - 100
});
game.addChild(nextBallPreview);
}
};
// Main update loop
game.update = function () {
// Physics for each ball
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
// Gravity
ball.vy += GRAVITY;
// Move
ball.x += ball.vx;
ball.y += ball.vy;
// Wall collision
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
ball.vx = -ball.vx * WALL_BOUNCE;
}
if (ball.x + ball.radius > GAME_WIDTH) {
ball.x = GAME_WIDTH - ball.radius;
ball.vx = -ball.vx * WALL_BOUNCE;
}
// Floor
if (ball.y + ball.radius > GAME_HEIGHT) {
ball.y = GAME_HEIGHT - ball.radius;
ball.vy = -ball.vy * BOUNCE;
ball.vx *= FRICTION;
// Stop tiny bounces
if (Math.abs(ball.vy) < 2) ball.vy = 0;
}
// Ceiling
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
ball.vy = -ball.vy * BOUNCE;
}
// Friction
ball.vx *= FRICTION;
ball.vy *= FRICTION;
}
// Ball-ball collision and merging
for (var i = 0; i < balls.length; i++) {
for (var j = i + 1; j < balls.length; j++) {
var a = balls[i],
b = balls[j];
if (isOverlapping(a, b)) {
// If same size, merge
if (a.size === b.size && !a.isMerging && !b.isMerging) {
mergeBalls(a, b);
break; // balls array changed, restart
} else {
// Elastic collision
resolveOverlap(a, b);
// Simple velocity swap
var dx = b.x - a.x;
var dy = b.y - a.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var nx = dx / dist,
ny = dy / dist;
var p = 2 * (a.vx * nx + a.vy * ny - b.vx * nx - b.vy * ny) / 2;
a.vx = a.vx - p * nx;
a.vy = a.vy - p * ny;
b.vx = b.vx + p * nx;
b.vy = b.vy + p * ny;
}
}
}
}
}
// Remove balls marked for destroy (already handled in mergeBalls)
// Check for game over
if (balls.length > BALL_LIMIT) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
// Optionally: win condition (e.g. create a ball of size 12)
for (var i = 0; i < balls.length; i++) {
if (balls[i].size === 12) {
LK.effects.flashScreen(0xffff00, 1000);
LK.showYouWin();
break;
}
}
};
Pinpon ball. In-Game asset. 2d. High contrast. No shadows
tennis ball. In-Game asset. 2d. High contrast. No shadows
american football ball. In-Game asset. 2d. High contrast. No shadows
volleyball ball. In-Game asset. 2d. High contrast. No shadows
football ball. In-Game asset. 2d. High contrast. No shadows
basketball ball. In-Game asset. 2d. High contrast. No shadows
dodge ball ball. In-Game asset. 2d. High contrast. No shadows
medicine ball. In-Game asset. 2d. High contrast. No shadows
yoga ball. In-Game asset. 2d. High contrast. No shadows
beach ball. In-Game asset. 2d. High contrast. No shadows
zorbing ball. In-Game asset. 2d. High contrast. No shadows
flying air balloon. In-Game asset. 2d. High contrast. No shadows