/**** * 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);
}
}
};