/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Blueberry = Container.expand(function (characterType) {
var self = Container.call(this);
self.characterType = characterType || 'classic';
var assetName = 'blueberry_' + self.characterType;
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSize = 80;
self.growthFactor = 1.0;
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.95;
self.bounceForce = 1.2;
self.lastBounced = false;
self.bounceCooldown = 0;
// Character-specific properties
switch (self.characterType) {
case 'red':
self.friction = 0.92; // Less friction, more slippery
self.bounceForce = 1.4; // Bounces harder
break;
case 'green':
self.friction = 0.98; // More friction, better control
break;
case 'purple':
self.bounceForce = 1.0; // Less bouncy, more stable
break;
case 'gold':
self.friction = 0.90; // Very slippery
self.bounceForce = 1.6; // Very bouncy
break;
default:
// classic
break;
}
self.grow = function () {
self.growthFactor += 0.03;
graphics.scaleX = self.growthFactor;
graphics.scaleY = self.growthFactor;
};
self.getBounds = function () {
var radius = self.baseSize * self.growthFactor / 2;
return {
left: self.x - radius,
right: self.x + radius,
top: self.y - radius,
bottom: self.y + radius,
radius: radius
};
};
self.update = function () {
// Apply gravity only when not being dragged
if (dragNode !== self) {
self.velocityY += gravity;
}
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Continuous rolling based on horizontal movement
if (Math.abs(self.velocityX) > 0.05) {
var rollDirection = self.velocityX > 0 ? 1 : -1;
graphics.rotation += rollDirection * Math.abs(self.velocityX) * 0.02;
}
var bounds = self.getBounds();
var bounced = false;
if (bounds.left <= 0) {
self.x = bounds.radius;
self.velocityX = Math.abs(self.velocityX) * self.bounceForce;
bounced = true;
// Add rolling animation when bouncing off left wall
if (Math.abs(self.velocityX) > 0.1) {
var rollSpeed = Math.abs(self.velocityX) * 0.1;
tween(graphics, {
rotation: graphics.rotation + Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
if (bounds.right >= 2048) {
self.x = 2048 - bounds.radius;
self.velocityX = -Math.abs(self.velocityX) * self.bounceForce;
bounced = true;
// Add rolling animation when bouncing off right wall
if (Math.abs(self.velocityX) > 0.1) {
var rollSpeed = Math.abs(self.velocityX) * 0.2;
tween(graphics, {
rotation: graphics.rotation - Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
if (bounds.top <= 0) {
self.y = bounds.radius;
self.velocityY = Math.abs(self.velocityY) * self.bounceForce;
bounced = true;
}
// Ground collision instead of bottom screen edge
if (bounds.bottom >= groundLevel) {
self.y = groundLevel - bounds.radius;
self.velocityY = -Math.abs(self.velocityY) * self.bounceForce;
bounced = true;
// Create dust particles when hitting ground with significant velocity
if (Math.abs(self.velocityY) > 3 || Math.abs(self.velocityX) > 3) {
for (var d = 0; d < 5; d++) {
createDustParticle(self.x, groundLevel);
}
}
// Stop trailing when landing
if (isTrailing && Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 2) {
isTrailing = false;
}
// Add rolling animation when hitting ground
if (Math.abs(self.velocityX) > 0.1) {
var rollDirection = self.velocityX > 0 ? 1 : -1;
var rollSpeed = Math.abs(self.velocityX) * 0.1;
tween(graphics, {
rotation: graphics.rotation + rollDirection * Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
// Create trail particles when moving fast
if (isTrailing && (Math.abs(self.velocityX) > 1 || Math.abs(self.velocityY) > 1)) {
if (LK.ticks % 3 === 0) {
// Create particle every 3 frames
createTrailParticle(self.x, self.y);
}
}
// Update bounce cooldown
if (self.bounceCooldown > 0) {
self.bounceCooldown--;
}
// Only play bounce sound when transitioning from not bounced to bounced
// and cooldown has expired and there's significant velocity
if (bounced && !self.lastBounced && self.bounceCooldown === 0 && (Math.abs(self.velocityX) > 2 || Math.abs(self.velocityY) > 2)) {
LK.getSound('bounce').play();
self.bounceCooldown = 1; // 1 frames cooldown (~0.33 seconds at 60fps)
}
self.lastBounced = bounced;
};
return self;
});
var GoldenBerry = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('goldenBerry', {
anchorX: 0.5,
anchorY: 0.5
});
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.05;
self.startY = 0;
self.update = function () {
self.y = self.startY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 10;
};
return self;
});
var RedThorn = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('redThorn', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.rotation = Math.PI / 1;
self.pulseOffset = Math.random() * Math.PI * 2;
self.pulseSpeed = 0.08;
self.update = function () {
var pulse = 1 + Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.1;
graphics.scaleX = pulse;
graphics.scaleY = pulse;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x9edbf3
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'characterSelect', 'playing'
var selectedCharacter = 'classic';
var menuContainer = game.addChild(new Container());
var characterSelectContainer = game.addChild(new Container());
characterSelectContainer.visible = false;
// Main menu setup
var titleText = new Text2('Berry Bounce Adventure!', {
size: 120,
fill: 0x4169E1
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
menuContainer.addChild(titleText);
var instructionText = new Text2('Help the hungry berry\neat grapes!\n\nDrag and throw him around\nto collect food and avoid\nthe red thorns!', {
size: 80,
fill: 0x333333
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 1000;
menuContainer.addChild(instructionText);
var startText = new Text2('TAP TO START', {
size: 100,
fill: 0xFF6B35
});
startText.anchor.set(0.5, 0.5);
startText.x = 1024;
startText.y = 1600;
menuContainer.addChild(startText);
// Blueberry setup (initially hidden)
var blueberry = game.addChild(new Blueberry(selectedCharacter));
blueberry.x = 1024;
blueberry.y = 1366;
blueberry.visible = false;
var goldenBerries = [];
var redThorns = [];
var dragNode = null;
var lastMouseX = 0;
var lastMouseY = 0;
var thornSpawnTimer = 0;
var berrySpawnTimer = 0;
var gravity = 0.8;
var groundLevel = 2632; // 100px from bottom of screen
var trailParticles = [];
var isTrailing = false;
function createTrailParticle(x, y) {
var particle = LK.getAsset('trailParticle', {
width: 20,
height: 20,
color: 0x87CEEB,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
particle.x = x;
particle.y = y;
particle.life = 30; // frames to live
particle.maxLife = 30;
trailParticles.push(particle);
game.addChild(particle);
}
var dustParticles = [];
function createDustParticle(x, y) {
var particle = LK.getAsset('dustParticle', {
width: 8,
height: 8,
color: 0xD2B48C,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2,
alpha: 0.6
});
particle.x = x + (Math.random() - 0.5) * 60;
particle.y = y;
particle.velocityX = (Math.random() - 0.5) * 4;
particle.velocityY = -(Math.random() * 3 + 1);
particle.life = 40;
particle.maxLife = 40;
dustParticles.push(particle);
game.addChild(particle);
}
// Menu blueberry for decoration
var menuBlueberry = menuContainer.addChild(new Blueberry());
menuBlueberry.x = 1024;
menuBlueberry.y = 1300;
// Character selection setup
var characterSelectTitle = new Text2('SELECT CHARACTER', {
size: 100,
fill: 0x4169E1
});
characterSelectTitle.anchor.set(0.5, 0.5);
characterSelectTitle.x = 1024;
characterSelectTitle.y = 400;
characterSelectContainer.addChild(characterSelectTitle);
var characterTypes = ['classic', 'red', 'green', 'purple', 'gold'];
var characterNames = ['Classic', 'Speedy', 'Steady', 'Stable', 'Bouncy'];
var characterDescriptions = ['Balanced stats', 'Fast & bouncy', 'Great control', 'Very stable', 'Super bouncy'];
var characterPreviewContainers = [];
// Set initial character name
for (var c = 0; c < characterTypes.length; c++) {
var charContainer = characterSelectContainer.addChild(new Container());
var charPreview = charContainer.addChild(new Blueberry(characterTypes[c]));
charPreview.x = 200 + c * 350;
charPreview.y = 800;
charPreview.characterIndex = c;
var charName = new Text2(characterNames[c], {
size: 60,
fill: 0x333333
});
charName.anchor.set(0.5, 0.5);
charName.x = charPreview.x;
charName.y = charPreview.y + 120;
charContainer.addChild(charName);
var charDesc = new Text2(characterDescriptions[c], {
size: 40,
fill: 0x666666
});
charDesc.anchor.set(0.5, 0.5);
charDesc.x = charPreview.x;
charDesc.y = charPreview.y + 180;
charContainer.addChild(charDesc);
characterPreviewContainers.push(charContainer);
// Add selection indicator
var selectionRing = LK.getAsset('selectionRing', {
width: 120,
height: 120,
color: 0xFFD700,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
});
selectionRing.x = charPreview.x;
selectionRing.y = charPreview.y;
selectionRing.visible = c === 0; // Show for first character initially
charContainer.addChild(selectionRing);
charContainer.selectionRing = selectionRing;
// Animate character previews
tween(charPreview, {
y: charPreview.y - 20
}, {
duration: 1500 + c * 200,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
}
var selectText = new Text2('TAP CHARACTER TO SELECT', {
size: 80,
fill: 0xFF6B35
});
selectText.anchor.set(0.5, 0.5);
selectText.x = 1024;
selectText.y = 1200;
characterSelectContainer.addChild(selectText);
var playText = new Text2('TAP TO PLAY', {
size: 100,
fill: 0x32CD32
});
playText.anchor.set(0.5, 0.5);
playText.x = 1024;
playText.y = 1500;
characterSelectContainer.addChild(playText);
// Animate play text pulsing
tween(playText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Animate menu blueberry
tween(menuBlueberry, {
y: 1250
}, {
duration: 2000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Animate start text pulsing
tween(startText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.visible = false; // Initially hidden
// Character name display
var characterNameTxt = new Text2('Classic Barry', {
size: 80,
fill: 0xFFD700
});
characterNameTxt.anchor.set(0.5, 0);
characterNameTxt.x = 0;
characterNameTxt.y = 150;
LK.gui.top.addChild(characterNameTxt);
characterNameTxt.visible = false; // Initially hidden
function spawnGoldenBerry() {
var berry = new GoldenBerry();
var margin = 50;
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
var tooClose = true;
var attempts = 0;
while (tooClose && attempts < 20) {
tooClose = false;
var berryBounds = {
x: berry.x,
y: berry.y,
radius: 20
};
var blueberryBounds = blueberry.getBounds();
var dist = Math.sqrt(Math.pow(berry.x - blueberry.x, 2) + Math.pow(berry.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 100) {
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
tooClose = true;
}
// Check distance from all red thorns
for (var k = 0; k < redThorns.length; k++) {
var thornDist = Math.sqrt(Math.pow(berry.x - redThorns[k].x, 2) + Math.pow(berry.y - redThorns[k].y, 2));
if (thornDist < 120) {
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
tooClose = true;
break;
}
// Update trail particles
for (var t = trailParticles.length - 1; t >= 0; t--) {
var particle = trailParticles[t];
particle.life--;
// Fade out particle
particle.alpha = particle.life / particle.maxLife * 0.2;
particle.scaleX = particle.life / particle.maxLife * 0.1;
particle.scaleY = particle.life / particle.maxLife * 0.1;
// Remove dead particles
if (particle.life <= 0) {
particle.destroy();
trailParticles.splice(t, 1);
}
}
}
;
attempts++;
}
goldenBerries.push(berry);
game.addChild(berry);
}
function spawnRedThorn() {
var thorn = new RedThorn();
var margin = 50;
thorn.x = margin + Math.random() * (2048 - margin * 2);
thorn.y = margin + Math.random() * (groundLevel - margin * 2);
var tooClose = true;
var attempts = 0;
while (tooClose && attempts < 15) {
tooClose = false;
var blueberryBounds = blueberry.getBounds();
var dist = Math.sqrt(Math.pow(thorn.x - blueberry.x, 2) + Math.pow(thorn.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 200) {
thorn.x = margin + Math.random() * (2048 - margin * 2);
thorn.y = margin + Math.random() * (groundLevel - margin * 2);
tooClose = true;
}
attempts++;
}
redThorns.push(thorn);
game.addChild(thorn);
}
function handleMove(x, y, obj) {
if (dragNode) {
var deltaX = x - lastMouseX;
var deltaY = y - lastMouseY;
// Instead of teleporting, smoothly move towards target position
var targetX = x;
var targetY = y;
var currentX = dragNode.x;
var currentY = dragNode.y;
// Smooth interpolation towards target position
var lerpFactor = 0.15;
dragNode.x = currentX + (targetX - currentX) * lerpFactor;
dragNode.y = currentY + (targetY - currentY) * lerpFactor;
dragNode.velocityX = deltaX * 0.3;
dragNode.velocityY = deltaY * 0.3;
}
lastMouseX = x;
lastMouseY = y;
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Transition to character select
gameState = 'characterSelect';
menuContainer.visible = false;
// Destroy menu blueberry to prevent invisible bouncing
menuBlueberry.destroy();
characterSelectContainer.visible = true;
return;
} else if (gameState === 'characterSelect') {
// Check if tapped on a character
for (var c = 0; c < characterPreviewContainers.length; c++) {
var charContainer = characterPreviewContainers[c];
var charPreview = charContainer.children[0];
var dist = Math.sqrt(Math.pow(x - charPreview.x, 2) + Math.pow(y - charPreview.y, 2));
if (dist < 80) {
// Selected a character
selectedCharacter = characterTypes[c];
// Update selection rings
for (var r = 0; r < characterPreviewContainers.length; r++) {
characterPreviewContainers[r].selectionRing.visible = r === c;
}
// Update character name display
var selectedCharacterName = characterNames[c] + ' Barry';
characterNameTxt.setText(selectedCharacterName);
return;
}
}
// Check if tapped play button area
if (y > 1400 && y < 1600) {
startGame();
return;
}
}
dragNode = blueberry;
lastMouseX = x;
lastMouseY = y;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (dragNode) {
// Calculate throw velocity based on drag distance and speed
var throwMultiplier = 0.5;
var maxThrowSpeed = 15;
// Use the current velocity from dragging as throw velocity
var throwVelX = Math.max(-maxThrowSpeed, Math.min(maxThrowSpeed, dragNode.velocityX * throwMultiplier));
var throwVelY = Math.max(-maxThrowSpeed, Math.min(maxThrowSpeed, dragNode.velocityY * throwMultiplier));
// Apply the throw velocity to the blueberry
dragNode.velocityX = throwVelX;
dragNode.velocityY = throwVelY;
// Start trailing effect if there's significant velocity
if (Math.abs(throwVelX) > 2 || Math.abs(throwVelY) > 2) {
isTrailing = true;
}
// Add throwing effect - scale bounce animation
var blueberryGraphics = dragNode.children[0]; // Get the graphics child
var currentGrowthFactor = dragNode.growthFactor; // Store growth factor before clearing dragNode
if (blueberryGraphics) {
// Stop any existing scale tweens
tween.stop(blueberryGraphics, {
scaleX: true,
scaleY: true
});
// Create throwing effect with scale bounce
tween(blueberryGraphics, {
scaleX: currentGrowthFactor * 1.3,
scaleY: currentGrowthFactor * 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(blueberryGraphics, {
scaleX: currentGrowthFactor,
scaleY: currentGrowthFactor
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
}
dragNode = null;
};
// Create ground visual (initially hidden)
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
}));
ground.x = 0;
ground.y = groundLevel;
ground.visible = false;
// Game elements start hidden
function startGame() {
gameState = 'playing';
menuContainer.visible = false;
characterSelectContainer.visible = false;
// Destroy all character selection blueberries to prevent invisible bouncing
for (var c = 0; c < characterPreviewContainers.length; c++) {
var charPreview = characterPreviewContainers[c].children[0];
if (charPreview && charPreview.destroy) {
charPreview.destroy();
}
}
// Recreate blueberry with selected character
blueberry.destroy();
blueberry = game.addChild(new Blueberry(selectedCharacter));
blueberry.x = 1024;
blueberry.y = 1366;
blueberry.visible = true;
ground.visible = true;
scoreTxt.visible = true;
characterNameTxt.visible = true;
// Spawn initial berries
spawnGoldenBerry();
spawnGoldenBerry();
spawnGoldenBerry();
}
game.update = function () {
// Only update game logic when playing
if (gameState !== 'playing') {
return;
}
berrySpawnTimer++;
thornSpawnTimer++;
if (berrySpawnTimer > 150 && goldenBerries.length < 5) {
spawnGoldenBerry();
berrySpawnTimer = 0;
}
var thornSpawnRate = Math.max(360 - Math.floor(LK.getScore() * 3), 120);
if (thornSpawnTimer > thornSpawnRate) {
spawnRedThorn();
thornSpawnTimer = 0;
}
var blueberryBounds = blueberry.getBounds();
for (var i = goldenBerries.length - 1; i >= 0; i--) {
var berry = goldenBerries[i];
var dist = Math.sqrt(Math.pow(berry.x - blueberry.x, 2) + Math.pow(berry.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 20) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
blueberry.grow();
LK.getSound('collect').play();
LK.effects.flashObject(berry, 0xFFFFFF, 200);
berry.destroy();
goldenBerries.splice(i, 1);
if (LK.getScore() >= 100) {
LK.showYouWin();
}
}
}
for (var j = 0; j < redThorns.length; j++) {
var thorn = redThorns[j];
var dist = Math.sqrt(Math.pow(thorn.x - blueberry.x, 2) + Math.pow(thorn.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 20) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
}
// Update dust particles
for (var d = dustParticles.length - 1; d >= 0; d--) {
var dustParticle = dustParticles[d];
dustParticle.x += dustParticle.velocityX;
dustParticle.y += dustParticle.velocityY;
dustParticle.velocityX *= 0.98;
dustParticle.velocityY += 0.1; // gravity
dustParticle.life--;
// Fade out dust particle
dustParticle.alpha = dustParticle.life / dustParticle.maxLife * 0.6;
dustParticle.scaleX = dustParticle.life / dustParticle.maxLife * 0.2;
dustParticle.scaleY = dustParticle.life / dustParticle.maxLife * 0.2;
// Remove dead dust particles
if (dustParticle.life <= 0) {
dustParticle.destroy();
dustParticles.splice(d, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Blueberry = Container.expand(function (characterType) {
var self = Container.call(this);
self.characterType = characterType || 'classic';
var assetName = 'blueberry_' + self.characterType;
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.baseSize = 80;
self.growthFactor = 1.0;
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.95;
self.bounceForce = 1.2;
self.lastBounced = false;
self.bounceCooldown = 0;
// Character-specific properties
switch (self.characterType) {
case 'red':
self.friction = 0.92; // Less friction, more slippery
self.bounceForce = 1.4; // Bounces harder
break;
case 'green':
self.friction = 0.98; // More friction, better control
break;
case 'purple':
self.bounceForce = 1.0; // Less bouncy, more stable
break;
case 'gold':
self.friction = 0.90; // Very slippery
self.bounceForce = 1.6; // Very bouncy
break;
default:
// classic
break;
}
self.grow = function () {
self.growthFactor += 0.03;
graphics.scaleX = self.growthFactor;
graphics.scaleY = self.growthFactor;
};
self.getBounds = function () {
var radius = self.baseSize * self.growthFactor / 2;
return {
left: self.x - radius,
right: self.x + radius,
top: self.y - radius,
bottom: self.y + radius,
radius: radius
};
};
self.update = function () {
// Apply gravity only when not being dragged
if (dragNode !== self) {
self.velocityY += gravity;
}
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Continuous rolling based on horizontal movement
if (Math.abs(self.velocityX) > 0.05) {
var rollDirection = self.velocityX > 0 ? 1 : -1;
graphics.rotation += rollDirection * Math.abs(self.velocityX) * 0.02;
}
var bounds = self.getBounds();
var bounced = false;
if (bounds.left <= 0) {
self.x = bounds.radius;
self.velocityX = Math.abs(self.velocityX) * self.bounceForce;
bounced = true;
// Add rolling animation when bouncing off left wall
if (Math.abs(self.velocityX) > 0.1) {
var rollSpeed = Math.abs(self.velocityX) * 0.1;
tween(graphics, {
rotation: graphics.rotation + Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
if (bounds.right >= 2048) {
self.x = 2048 - bounds.radius;
self.velocityX = -Math.abs(self.velocityX) * self.bounceForce;
bounced = true;
// Add rolling animation when bouncing off right wall
if (Math.abs(self.velocityX) > 0.1) {
var rollSpeed = Math.abs(self.velocityX) * 0.2;
tween(graphics, {
rotation: graphics.rotation - Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
if (bounds.top <= 0) {
self.y = bounds.radius;
self.velocityY = Math.abs(self.velocityY) * self.bounceForce;
bounced = true;
}
// Ground collision instead of bottom screen edge
if (bounds.bottom >= groundLevel) {
self.y = groundLevel - bounds.radius;
self.velocityY = -Math.abs(self.velocityY) * self.bounceForce;
bounced = true;
// Create dust particles when hitting ground with significant velocity
if (Math.abs(self.velocityY) > 3 || Math.abs(self.velocityX) > 3) {
for (var d = 0; d < 5; d++) {
createDustParticle(self.x, groundLevel);
}
}
// Stop trailing when landing
if (isTrailing && Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 2) {
isTrailing = false;
}
// Add rolling animation when hitting ground
if (Math.abs(self.velocityX) > 0.1) {
var rollDirection = self.velocityX > 0 ? 1 : -1;
var rollSpeed = Math.abs(self.velocityX) * 0.1;
tween(graphics, {
rotation: graphics.rotation + rollDirection * Math.PI * 2
}, {
duration: 1000 / rollSpeed,
easing: tween.linear
});
}
}
// Create trail particles when moving fast
if (isTrailing && (Math.abs(self.velocityX) > 1 || Math.abs(self.velocityY) > 1)) {
if (LK.ticks % 3 === 0) {
// Create particle every 3 frames
createTrailParticle(self.x, self.y);
}
}
// Update bounce cooldown
if (self.bounceCooldown > 0) {
self.bounceCooldown--;
}
// Only play bounce sound when transitioning from not bounced to bounced
// and cooldown has expired and there's significant velocity
if (bounced && !self.lastBounced && self.bounceCooldown === 0 && (Math.abs(self.velocityX) > 2 || Math.abs(self.velocityY) > 2)) {
LK.getSound('bounce').play();
self.bounceCooldown = 1; // 1 frames cooldown (~0.33 seconds at 60fps)
}
self.lastBounced = bounced;
};
return self;
});
var GoldenBerry = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('goldenBerry', {
anchorX: 0.5,
anchorY: 0.5
});
self.floatOffset = Math.random() * Math.PI * 2;
self.floatSpeed = 0.05;
self.startY = 0;
self.update = function () {
self.y = self.startY + Math.sin(LK.ticks * self.floatSpeed + self.floatOffset) * 10;
};
return self;
});
var RedThorn = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('redThorn', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.rotation = Math.PI / 1;
self.pulseOffset = Math.random() * Math.PI * 2;
self.pulseSpeed = 0.08;
self.update = function () {
var pulse = 1 + Math.sin(LK.ticks * self.pulseSpeed + self.pulseOffset) * 0.1;
graphics.scaleX = pulse;
graphics.scaleY = pulse;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x9edbf3
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'characterSelect', 'playing'
var selectedCharacter = 'classic';
var menuContainer = game.addChild(new Container());
var characterSelectContainer = game.addChild(new Container());
characterSelectContainer.visible = false;
// Main menu setup
var titleText = new Text2('Berry Bounce Adventure!', {
size: 120,
fill: 0x4169E1
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
menuContainer.addChild(titleText);
var instructionText = new Text2('Help the hungry berry\neat grapes!\n\nDrag and throw him around\nto collect food and avoid\nthe red thorns!', {
size: 80,
fill: 0x333333
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 1000;
menuContainer.addChild(instructionText);
var startText = new Text2('TAP TO START', {
size: 100,
fill: 0xFF6B35
});
startText.anchor.set(0.5, 0.5);
startText.x = 1024;
startText.y = 1600;
menuContainer.addChild(startText);
// Blueberry setup (initially hidden)
var blueberry = game.addChild(new Blueberry(selectedCharacter));
blueberry.x = 1024;
blueberry.y = 1366;
blueberry.visible = false;
var goldenBerries = [];
var redThorns = [];
var dragNode = null;
var lastMouseX = 0;
var lastMouseY = 0;
var thornSpawnTimer = 0;
var berrySpawnTimer = 0;
var gravity = 0.8;
var groundLevel = 2632; // 100px from bottom of screen
var trailParticles = [];
var isTrailing = false;
function createTrailParticle(x, y) {
var particle = LK.getAsset('trailParticle', {
width: 20,
height: 20,
color: 0x87CEEB,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.8
});
particle.x = x;
particle.y = y;
particle.life = 30; // frames to live
particle.maxLife = 30;
trailParticles.push(particle);
game.addChild(particle);
}
var dustParticles = [];
function createDustParticle(x, y) {
var particle = LK.getAsset('dustParticle', {
width: 8,
height: 8,
color: 0xD2B48C,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2,
alpha: 0.6
});
particle.x = x + (Math.random() - 0.5) * 60;
particle.y = y;
particle.velocityX = (Math.random() - 0.5) * 4;
particle.velocityY = -(Math.random() * 3 + 1);
particle.life = 40;
particle.maxLife = 40;
dustParticles.push(particle);
game.addChild(particle);
}
// Menu blueberry for decoration
var menuBlueberry = menuContainer.addChild(new Blueberry());
menuBlueberry.x = 1024;
menuBlueberry.y = 1300;
// Character selection setup
var characterSelectTitle = new Text2('SELECT CHARACTER', {
size: 100,
fill: 0x4169E1
});
characterSelectTitle.anchor.set(0.5, 0.5);
characterSelectTitle.x = 1024;
characterSelectTitle.y = 400;
characterSelectContainer.addChild(characterSelectTitle);
var characterTypes = ['classic', 'red', 'green', 'purple', 'gold'];
var characterNames = ['Classic', 'Speedy', 'Steady', 'Stable', 'Bouncy'];
var characterDescriptions = ['Balanced stats', 'Fast & bouncy', 'Great control', 'Very stable', 'Super bouncy'];
var characterPreviewContainers = [];
// Set initial character name
for (var c = 0; c < characterTypes.length; c++) {
var charContainer = characterSelectContainer.addChild(new Container());
var charPreview = charContainer.addChild(new Blueberry(characterTypes[c]));
charPreview.x = 200 + c * 350;
charPreview.y = 800;
charPreview.characterIndex = c;
var charName = new Text2(characterNames[c], {
size: 60,
fill: 0x333333
});
charName.anchor.set(0.5, 0.5);
charName.x = charPreview.x;
charName.y = charPreview.y + 120;
charContainer.addChild(charName);
var charDesc = new Text2(characterDescriptions[c], {
size: 40,
fill: 0x666666
});
charDesc.anchor.set(0.5, 0.5);
charDesc.x = charPreview.x;
charDesc.y = charPreview.y + 180;
charContainer.addChild(charDesc);
characterPreviewContainers.push(charContainer);
// Add selection indicator
var selectionRing = LK.getAsset('selectionRing', {
width: 120,
height: 120,
color: 0xFFD700,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
});
selectionRing.x = charPreview.x;
selectionRing.y = charPreview.y;
selectionRing.visible = c === 0; // Show for first character initially
charContainer.addChild(selectionRing);
charContainer.selectionRing = selectionRing;
// Animate character previews
tween(charPreview, {
y: charPreview.y - 20
}, {
duration: 1500 + c * 200,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
}
var selectText = new Text2('TAP CHARACTER TO SELECT', {
size: 80,
fill: 0xFF6B35
});
selectText.anchor.set(0.5, 0.5);
selectText.x = 1024;
selectText.y = 1200;
characterSelectContainer.addChild(selectText);
var playText = new Text2('TAP TO PLAY', {
size: 100,
fill: 0x32CD32
});
playText.anchor.set(0.5, 0.5);
playText.x = 1024;
playText.y = 1500;
characterSelectContainer.addChild(playText);
// Animate play text pulsing
tween(playText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Animate menu blueberry
tween(menuBlueberry, {
y: 1250
}, {
duration: 2000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Animate start text pulsing
tween(startText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.visible = false; // Initially hidden
// Character name display
var characterNameTxt = new Text2('Classic Barry', {
size: 80,
fill: 0xFFD700
});
characterNameTxt.anchor.set(0.5, 0);
characterNameTxt.x = 0;
characterNameTxt.y = 150;
LK.gui.top.addChild(characterNameTxt);
characterNameTxt.visible = false; // Initially hidden
function spawnGoldenBerry() {
var berry = new GoldenBerry();
var margin = 50;
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
var tooClose = true;
var attempts = 0;
while (tooClose && attempts < 20) {
tooClose = false;
var berryBounds = {
x: berry.x,
y: berry.y,
radius: 20
};
var blueberryBounds = blueberry.getBounds();
var dist = Math.sqrt(Math.pow(berry.x - blueberry.x, 2) + Math.pow(berry.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 100) {
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
tooClose = true;
}
// Check distance from all red thorns
for (var k = 0; k < redThorns.length; k++) {
var thornDist = Math.sqrt(Math.pow(berry.x - redThorns[k].x, 2) + Math.pow(berry.y - redThorns[k].y, 2));
if (thornDist < 120) {
berry.x = margin + Math.random() * (2048 - margin * 2);
berry.y = margin + Math.random() * (groundLevel - margin * 2);
berry.startY = berry.y;
tooClose = true;
break;
}
// Update trail particles
for (var t = trailParticles.length - 1; t >= 0; t--) {
var particle = trailParticles[t];
particle.life--;
// Fade out particle
particle.alpha = particle.life / particle.maxLife * 0.2;
particle.scaleX = particle.life / particle.maxLife * 0.1;
particle.scaleY = particle.life / particle.maxLife * 0.1;
// Remove dead particles
if (particle.life <= 0) {
particle.destroy();
trailParticles.splice(t, 1);
}
}
}
;
attempts++;
}
goldenBerries.push(berry);
game.addChild(berry);
}
function spawnRedThorn() {
var thorn = new RedThorn();
var margin = 50;
thorn.x = margin + Math.random() * (2048 - margin * 2);
thorn.y = margin + Math.random() * (groundLevel - margin * 2);
var tooClose = true;
var attempts = 0;
while (tooClose && attempts < 15) {
tooClose = false;
var blueberryBounds = blueberry.getBounds();
var dist = Math.sqrt(Math.pow(thorn.x - blueberry.x, 2) + Math.pow(thorn.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 200) {
thorn.x = margin + Math.random() * (2048 - margin * 2);
thorn.y = margin + Math.random() * (groundLevel - margin * 2);
tooClose = true;
}
attempts++;
}
redThorns.push(thorn);
game.addChild(thorn);
}
function handleMove(x, y, obj) {
if (dragNode) {
var deltaX = x - lastMouseX;
var deltaY = y - lastMouseY;
// Instead of teleporting, smoothly move towards target position
var targetX = x;
var targetY = y;
var currentX = dragNode.x;
var currentY = dragNode.y;
// Smooth interpolation towards target position
var lerpFactor = 0.15;
dragNode.x = currentX + (targetX - currentX) * lerpFactor;
dragNode.y = currentY + (targetY - currentY) * lerpFactor;
dragNode.velocityX = deltaX * 0.3;
dragNode.velocityY = deltaY * 0.3;
}
lastMouseX = x;
lastMouseY = y;
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (gameState === 'menu') {
// Transition to character select
gameState = 'characterSelect';
menuContainer.visible = false;
// Destroy menu blueberry to prevent invisible bouncing
menuBlueberry.destroy();
characterSelectContainer.visible = true;
return;
} else if (gameState === 'characterSelect') {
// Check if tapped on a character
for (var c = 0; c < characterPreviewContainers.length; c++) {
var charContainer = characterPreviewContainers[c];
var charPreview = charContainer.children[0];
var dist = Math.sqrt(Math.pow(x - charPreview.x, 2) + Math.pow(y - charPreview.y, 2));
if (dist < 80) {
// Selected a character
selectedCharacter = characterTypes[c];
// Update selection rings
for (var r = 0; r < characterPreviewContainers.length; r++) {
characterPreviewContainers[r].selectionRing.visible = r === c;
}
// Update character name display
var selectedCharacterName = characterNames[c] + ' Barry';
characterNameTxt.setText(selectedCharacterName);
return;
}
}
// Check if tapped play button area
if (y > 1400 && y < 1600) {
startGame();
return;
}
}
dragNode = blueberry;
lastMouseX = x;
lastMouseY = y;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (dragNode) {
// Calculate throw velocity based on drag distance and speed
var throwMultiplier = 0.5;
var maxThrowSpeed = 15;
// Use the current velocity from dragging as throw velocity
var throwVelX = Math.max(-maxThrowSpeed, Math.min(maxThrowSpeed, dragNode.velocityX * throwMultiplier));
var throwVelY = Math.max(-maxThrowSpeed, Math.min(maxThrowSpeed, dragNode.velocityY * throwMultiplier));
// Apply the throw velocity to the blueberry
dragNode.velocityX = throwVelX;
dragNode.velocityY = throwVelY;
// Start trailing effect if there's significant velocity
if (Math.abs(throwVelX) > 2 || Math.abs(throwVelY) > 2) {
isTrailing = true;
}
// Add throwing effect - scale bounce animation
var blueberryGraphics = dragNode.children[0]; // Get the graphics child
var currentGrowthFactor = dragNode.growthFactor; // Store growth factor before clearing dragNode
if (blueberryGraphics) {
// Stop any existing scale tweens
tween.stop(blueberryGraphics, {
scaleX: true,
scaleY: true
});
// Create throwing effect with scale bounce
tween(blueberryGraphics, {
scaleX: currentGrowthFactor * 1.3,
scaleY: currentGrowthFactor * 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(blueberryGraphics, {
scaleX: currentGrowthFactor,
scaleY: currentGrowthFactor
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
}
dragNode = null;
};
// Create ground visual (initially hidden)
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0
}));
ground.x = 0;
ground.y = groundLevel;
ground.visible = false;
// Game elements start hidden
function startGame() {
gameState = 'playing';
menuContainer.visible = false;
characterSelectContainer.visible = false;
// Destroy all character selection blueberries to prevent invisible bouncing
for (var c = 0; c < characterPreviewContainers.length; c++) {
var charPreview = characterPreviewContainers[c].children[0];
if (charPreview && charPreview.destroy) {
charPreview.destroy();
}
}
// Recreate blueberry with selected character
blueberry.destroy();
blueberry = game.addChild(new Blueberry(selectedCharacter));
blueberry.x = 1024;
blueberry.y = 1366;
blueberry.visible = true;
ground.visible = true;
scoreTxt.visible = true;
characterNameTxt.visible = true;
// Spawn initial berries
spawnGoldenBerry();
spawnGoldenBerry();
spawnGoldenBerry();
}
game.update = function () {
// Only update game logic when playing
if (gameState !== 'playing') {
return;
}
berrySpawnTimer++;
thornSpawnTimer++;
if (berrySpawnTimer > 150 && goldenBerries.length < 5) {
spawnGoldenBerry();
berrySpawnTimer = 0;
}
var thornSpawnRate = Math.max(360 - Math.floor(LK.getScore() * 3), 120);
if (thornSpawnTimer > thornSpawnRate) {
spawnRedThorn();
thornSpawnTimer = 0;
}
var blueberryBounds = blueberry.getBounds();
for (var i = goldenBerries.length - 1; i >= 0; i--) {
var berry = goldenBerries[i];
var dist = Math.sqrt(Math.pow(berry.x - blueberry.x, 2) + Math.pow(berry.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 20) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
blueberry.grow();
LK.getSound('collect').play();
LK.effects.flashObject(berry, 0xFFFFFF, 200);
berry.destroy();
goldenBerries.splice(i, 1);
if (LK.getScore() >= 100) {
LK.showYouWin();
}
}
}
for (var j = 0; j < redThorns.length; j++) {
var thorn = redThorns[j];
var dist = Math.sqrt(Math.pow(thorn.x - blueberry.x, 2) + Math.pow(thorn.y - blueberry.y, 2));
if (dist < blueberryBounds.radius + 20) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
}
// Update dust particles
for (var d = dustParticles.length - 1; d >= 0; d--) {
var dustParticle = dustParticles[d];
dustParticle.x += dustParticle.velocityX;
dustParticle.y += dustParticle.velocityY;
dustParticle.velocityX *= 0.98;
dustParticle.velocityY += 0.1; // gravity
dustParticle.life--;
// Fade out dust particle
dustParticle.alpha = dustParticle.life / dustParticle.maxLife * 0.6;
dustParticle.scaleX = dustParticle.life / dustParticle.maxLife * 0.2;
dustParticle.scaleY = dustParticle.life / dustParticle.maxLife * 0.2;
// Remove dead dust particles
if (dustParticle.life <= 0) {
dustParticle.destroy();
dustParticles.splice(d, 1);
}
}
};