/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.gravity = 0.5; self.isFlying = false; self.startX = 0; self.startY = 0; self.reset = function () { self.x = 1024; self.y = 2500; self.velocityX = 0; self.velocityY = 0; self.isFlying = false; self.startX = self.x; self.startY = self.y; self.lastY = self.y; }; self.flick = function (targetX, targetY, power) { var deltaX = targetX - self.x; var deltaY = targetY - self.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); self.velocityX = deltaX / distance * power * 0.1; self.velocityY = deltaY / distance * power * 0.1; // Apply wind effect self.velocityX += windStrength * 0.01; self.isFlying = true; LK.getSound('kick').play(); }; self.update = function () { if (self.isFlying) { self.lastY = self.y; self.x += self.velocityX; self.y += self.velocityY; // Calculate rotation based on velocity for spinning effect var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY); ballGraphics.rotation += speed * 0.02; // Apply air resistance self.velocityX *= 0.998; self.velocityY *= 0.998; // Ground collision if (self.y >= 2500) { self.y = 2500; self.velocityY = -self.velocityY * 0.6; self.velocityX *= 0.8; if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 1) { self.isFlying = false; } } // Side boundaries if (self.x <= 30 || self.x >= 2018) { self.velocityX = -self.velocityX * 0.7; self.x = Math.max(30, Math.min(2018, self.x)); } // Top boundary if (self.y <= 30) { self.y = 30; self.velocityY = Math.abs(self.velocityY); } } }; return self; }); var Goal = Container.expand(function () { var self = Container.call(this); var crossbar = self.attachAsset('crossbar', { anchorX: 0.5, anchorY: 0.5 }); self.goalWidth = 200; self.goalHeight = 200; self.setup = function (x, y) { self.x = x; self.y = y; crossbar.x = 0; crossbar.y = -self.goalHeight; }; self.checkGoal = function (ballX, ballY, ballLastY) { var leftPostX = self.x - self.goalWidth / 2; var rightPostX = self.x + self.goalWidth / 2; var crossbarY = self.y - self.goalHeight; // Check if ball is within goal area horizontally var inGoalAreaHorizontally = ballX > leftPostX && ballX < rightPostX; // Check if ball is currently above crossbar (inside goal vertically) var aboveCrossbar = ballY < crossbarY; // Check if ball entered from below the crossbar (was below crossbar and now above it) var enteredFromBelow = ballLastY >= crossbarY && ballY < crossbarY; return inGoalAreaHorizontally && aboveCrossbar && enteredFromBelow; }; return self; }); var Goalkeeper = Container.expand(function () { var self = Container.call(this); var keeperGraphics = self.attachAsset('goalkeeper', { anchorX: 0.5, anchorY: 1.0 }); self.moveSpeed = 2; self.direction = 1; self.goalX = 0; self.goalWidth = 200; self.setup = function (goalX, goalWidth) { self.goalX = goalX; self.goalWidth = goalWidth; self.x = goalX; self.y = 800; // Position at ground level in front of goal }; self.update = function () { // Move horizontally within goal area self.x += self.moveSpeed * self.direction; // Bounce between goal posts var leftBound = self.goalX - self.goalWidth / 2; var rightBound = self.goalX + self.goalWidth / 2; if (self.x <= leftBound || self.x >= rightBound) { self.direction *= -1; // Use tween for smooth direction change tween(self, { scaleX: self.direction }, { duration: 200, easing: tween.easeOut }); } }; return self; }); var WindIndicator = Container.expand(function () { var self = Container.call(this); var arrow = self.attachAsset('windArrow', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { arrow.rotation = windDirection; arrow.scaleX = Math.abs(windStrength) * 0.5 + 0.5; arrow.alpha = Math.abs(windStrength) * 0.3 + 0.7; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x4a90e2 }); /**** * Game Code ****/ var ball = new Ball(); var goal = new Goal(); var goalkeeper = new Goalkeeper(); var windIndicator = new WindIndicator(); // Game state variables var gameState = 'aiming'; // 'aiming', 'flying', 'resetting' var shots = 0; var maxShots = 10; var windStrength = 0; var windDirection = 0; var difficulty = 1; var goalMoveSpeed = 0; var goalDirection = 1; // Hold-to-power variables var isHolding = false; var holdStartTime = 0; var holdTargetX = 0; var holdTargetY = 0; var maxHoldTime = 2000; // 2 seconds for maximum power // Double tap variables var lastTapTime = 0; var doubleTapDelay = 300; // 300ms window for double tap // Initialize ground var ground = game.attachAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 2632 }); // Initialize game objects ball.reset(); goal.setup(1024, 800); goalkeeper.setup(1024, 200); windIndicator.x = 200; windIndicator.y = 200; game.addChild(ball); game.addChild(goal); game.addChild(goalkeeper); game.addChild(windIndicator); // Goals display var goalsScoredCount = 0; var scoreTxt = new Text2('Goals: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Shots remaining display var shotsTxt = new Text2('Shots: 10', { size: 60, fill: 0xFFFFFF }); shotsTxt.anchor.set(1, 0); shotsTxt.x = -50; shotsTxt.y = 100; LK.gui.topRight.addChild(shotsTxt); // Wind display var windTxt = new Text2('Wind: None', { size: 50, fill: 0xFFFFFF }); windTxt.anchor.set(0, 0); windTxt.x = 50; windTxt.y = 100; LK.gui.topLeft.addChild(windTxt); // Power display var powerTxt = new Text2('Power: 0%', { size: 60, fill: 0xFFFF00 }); powerTxt.anchor.set(0.5, 0); powerTxt.x = 0; powerTxt.y = 150; LK.gui.top.addChild(powerTxt); function updateWind() { windDirection = (Math.random() - 0.5) * Math.PI; windStrength = (Math.random() - 0.5) * 2; var windText = 'Wind: '; if (Math.abs(windStrength) < 0.3) { windText += 'Light'; } else if (Math.abs(windStrength) < 0.7) { windText += 'Moderate'; } else { windText += 'Strong'; } if (windStrength > 0) { windText += ' β'; } else { windText += ' β'; } windTxt.setText(windText); } function resetRound() { ball.reset(); gameState = 'aiming'; shots++; // Update shots display shotsTxt.setText('Shots: ' + (maxShots - shots)); // Check game over if (shots >= maxShots) { LK.showGameOver(); return; } // Increase difficulty difficulty = Math.floor(shots / 3) + 1; goalMoveSpeed = Math.min(difficulty * 0.5, 3); // Update wind updateWind(); // Move goal to new position var newGoalX = 500 + Math.random() * 1048; var newGoalY = 300 + Math.random() * 500; goal.setup(newGoalX, newGoalY); goalkeeper.setup(newGoalX, goal.goalWidth); } function calculatePower(startX, startY, endX, endY) { var deltaX = endX - startX; var deltaY = endY - startY; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); } // Initialize first round updateWind(); game.down = function (x, y, obj) { var currentTime = Date.now(); // Check for double tap if (currentTime - lastTapTime < doubleTapDelay) { // Double tap detected - skip to next shot if (gameState === 'flying' || gameState === 'aiming') { resetRound(); return; } } lastTapTime = currentTime; if (gameState === 'aiming' && !ball.isFlying) { // Start holding for power calculation isHolding = true; holdStartTime = Date.now(); holdTargetX = x; holdTargetY = y; } }; game.up = function (x, y, obj) { if (gameState === 'aiming' && !ball.isFlying && isHolding) { // Calculate hold time and convert to power var holdTime = Date.now() - holdStartTime; var power = Math.min(holdTime / maxHoldTime * 400, 400); // Scale to max 400 power power = Math.max(power, 50); // Ensure minimum power // Shoot toward the target point ball.flick(holdTargetX, holdTargetY, power); gameState = 'flying'; isHolding = false; } }; game.update = function () { // Update wind indicator windIndicator.update(); // Update goalkeeper movement goalkeeper.update(); // Update power display during hold if (isHolding) { var holdTime = Date.now() - holdStartTime; var powerPercent = Math.min(holdTime / maxHoldTime * 100, 100); powerTxt.setText('Power: ' + Math.floor(powerPercent) + '%'); powerTxt.visible = true; } else { powerTxt.visible = false; } // Move goal based on difficulty if (difficulty > 2) { goal.x += goalMoveSpeed * goalDirection; if (goal.x <= 300 || goal.x >= 1748) { goalDirection *= -1; } } // Check for collision with goalkeeper if (ball.isFlying && ball.intersects(goalkeeper)) { // Calculate reflection angle based on where ball hits goalkeeper var relativeHitX = ball.x - goalkeeper.x; var reflectionAngle = relativeHitX * 0.02; // Increased reflection sensitivity // Reflect velocity with energy loss - ensure ball goes back towards player ball.velocityX = -Math.abs(ball.velocityX) * 0.9 + reflectionAngle; // Force negative X (back towards player) ball.velocityY = -Math.abs(ball.velocityY) * 0.8; // Always bounce upward with more energy // Add goalkeeper movement influence ball.velocityX += goalkeeper.direction * goalkeeper.moveSpeed * 0.5; // Add some spin effect tween(ball, { rotation: ball.rotation + Math.PI * 2 }, { duration: 500, easing: tween.easeOut }); // Move ball away from goalkeeper to prevent multiple collisions ball.x = goalkeeper.x - 60; // Position ball in front of goalkeeper ball.y -= 10; // Lift ball slightly } // Check for goal if (ball.isFlying && goal.checkGoal(ball.x, ball.y, ball.lastY)) { goalsScoredCount++; scoreTxt.setText('Goals: ' + goalsScoredCount); LK.getSound('goal').play(); LK.effects.flashScreen(0x00ff00, 500); resetRound(); } // Check if ball has stopped and missed if (gameState === 'flying' && !ball.isFlying) { resetRound(); } // Victory condition if (goalsScoredCount >= 5) { LK.showYouWin(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.gravity = 0.5;
self.isFlying = false;
self.startX = 0;
self.startY = 0;
self.reset = function () {
self.x = 1024;
self.y = 2500;
self.velocityX = 0;
self.velocityY = 0;
self.isFlying = false;
self.startX = self.x;
self.startY = self.y;
self.lastY = self.y;
};
self.flick = function (targetX, targetY, power) {
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
self.velocityX = deltaX / distance * power * 0.1;
self.velocityY = deltaY / distance * power * 0.1;
// Apply wind effect
self.velocityX += windStrength * 0.01;
self.isFlying = true;
LK.getSound('kick').play();
};
self.update = function () {
if (self.isFlying) {
self.lastY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate rotation based on velocity for spinning effect
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
ballGraphics.rotation += speed * 0.02;
// Apply air resistance
self.velocityX *= 0.998;
self.velocityY *= 0.998;
// Ground collision
if (self.y >= 2500) {
self.y = 2500;
self.velocityY = -self.velocityY * 0.6;
self.velocityX *= 0.8;
if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 1) {
self.isFlying = false;
}
}
// Side boundaries
if (self.x <= 30 || self.x >= 2018) {
self.velocityX = -self.velocityX * 0.7;
self.x = Math.max(30, Math.min(2018, self.x));
}
// Top boundary
if (self.y <= 30) {
self.y = 30;
self.velocityY = Math.abs(self.velocityY);
}
}
};
return self;
});
var Goal = Container.expand(function () {
var self = Container.call(this);
var crossbar = self.attachAsset('crossbar', {
anchorX: 0.5,
anchorY: 0.5
});
self.goalWidth = 200;
self.goalHeight = 200;
self.setup = function (x, y) {
self.x = x;
self.y = y;
crossbar.x = 0;
crossbar.y = -self.goalHeight;
};
self.checkGoal = function (ballX, ballY, ballLastY) {
var leftPostX = self.x - self.goalWidth / 2;
var rightPostX = self.x + self.goalWidth / 2;
var crossbarY = self.y - self.goalHeight;
// Check if ball is within goal area horizontally
var inGoalAreaHorizontally = ballX > leftPostX && ballX < rightPostX;
// Check if ball is currently above crossbar (inside goal vertically)
var aboveCrossbar = ballY < crossbarY;
// Check if ball entered from below the crossbar (was below crossbar and now above it)
var enteredFromBelow = ballLastY >= crossbarY && ballY < crossbarY;
return inGoalAreaHorizontally && aboveCrossbar && enteredFromBelow;
};
return self;
});
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
self.moveSpeed = 2;
self.direction = 1;
self.goalX = 0;
self.goalWidth = 200;
self.setup = function (goalX, goalWidth) {
self.goalX = goalX;
self.goalWidth = goalWidth;
self.x = goalX;
self.y = 800; // Position at ground level in front of goal
};
self.update = function () {
// Move horizontally within goal area
self.x += self.moveSpeed * self.direction;
// Bounce between goal posts
var leftBound = self.goalX - self.goalWidth / 2;
var rightBound = self.goalX + self.goalWidth / 2;
if (self.x <= leftBound || self.x >= rightBound) {
self.direction *= -1;
// Use tween for smooth direction change
tween(self, {
scaleX: self.direction
}, {
duration: 200,
easing: tween.easeOut
});
}
};
return self;
});
var WindIndicator = Container.expand(function () {
var self = Container.call(this);
var arrow = self.attachAsset('windArrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
arrow.rotation = windDirection;
arrow.scaleX = Math.abs(windStrength) * 0.5 + 0.5;
arrow.alpha = Math.abs(windStrength) * 0.3 + 0.7;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a90e2
});
/****
* Game Code
****/
var ball = new Ball();
var goal = new Goal();
var goalkeeper = new Goalkeeper();
var windIndicator = new WindIndicator();
// Game state variables
var gameState = 'aiming'; // 'aiming', 'flying', 'resetting'
var shots = 0;
var maxShots = 10;
var windStrength = 0;
var windDirection = 0;
var difficulty = 1;
var goalMoveSpeed = 0;
var goalDirection = 1;
// Hold-to-power variables
var isHolding = false;
var holdStartTime = 0;
var holdTargetX = 0;
var holdTargetY = 0;
var maxHoldTime = 2000; // 2 seconds for maximum power
// Double tap variables
var lastTapTime = 0;
var doubleTapDelay = 300; // 300ms window for double tap
// Initialize ground
var ground = game.attachAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2632
});
// Initialize game objects
ball.reset();
goal.setup(1024, 800);
goalkeeper.setup(1024, 200);
windIndicator.x = 200;
windIndicator.y = 200;
game.addChild(ball);
game.addChild(goal);
game.addChild(goalkeeper);
game.addChild(windIndicator);
// Goals display
var goalsScoredCount = 0;
var scoreTxt = new Text2('Goals: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Shots remaining display
var shotsTxt = new Text2('Shots: 10', {
size: 60,
fill: 0xFFFFFF
});
shotsTxt.anchor.set(1, 0);
shotsTxt.x = -50;
shotsTxt.y = 100;
LK.gui.topRight.addChild(shotsTxt);
// Wind display
var windTxt = new Text2('Wind: None', {
size: 50,
fill: 0xFFFFFF
});
windTxt.anchor.set(0, 0);
windTxt.x = 50;
windTxt.y = 100;
LK.gui.topLeft.addChild(windTxt);
// Power display
var powerTxt = new Text2('Power: 0%', {
size: 60,
fill: 0xFFFF00
});
powerTxt.anchor.set(0.5, 0);
powerTxt.x = 0;
powerTxt.y = 150;
LK.gui.top.addChild(powerTxt);
function updateWind() {
windDirection = (Math.random() - 0.5) * Math.PI;
windStrength = (Math.random() - 0.5) * 2;
var windText = 'Wind: ';
if (Math.abs(windStrength) < 0.3) {
windText += 'Light';
} else if (Math.abs(windStrength) < 0.7) {
windText += 'Moderate';
} else {
windText += 'Strong';
}
if (windStrength > 0) {
windText += ' β';
} else {
windText += ' β';
}
windTxt.setText(windText);
}
function resetRound() {
ball.reset();
gameState = 'aiming';
shots++;
// Update shots display
shotsTxt.setText('Shots: ' + (maxShots - shots));
// Check game over
if (shots >= maxShots) {
LK.showGameOver();
return;
}
// Increase difficulty
difficulty = Math.floor(shots / 3) + 1;
goalMoveSpeed = Math.min(difficulty * 0.5, 3);
// Update wind
updateWind();
// Move goal to new position
var newGoalX = 500 + Math.random() * 1048;
var newGoalY = 300 + Math.random() * 500;
goal.setup(newGoalX, newGoalY);
goalkeeper.setup(newGoalX, goal.goalWidth);
}
function calculatePower(startX, startY, endX, endY) {
var deltaX = endX - startX;
var deltaY = endY - startY;
return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}
// Initialize first round
updateWind();
game.down = function (x, y, obj) {
var currentTime = Date.now();
// Check for double tap
if (currentTime - lastTapTime < doubleTapDelay) {
// Double tap detected - skip to next shot
if (gameState === 'flying' || gameState === 'aiming') {
resetRound();
return;
}
}
lastTapTime = currentTime;
if (gameState === 'aiming' && !ball.isFlying) {
// Start holding for power calculation
isHolding = true;
holdStartTime = Date.now();
holdTargetX = x;
holdTargetY = y;
}
};
game.up = function (x, y, obj) {
if (gameState === 'aiming' && !ball.isFlying && isHolding) {
// Calculate hold time and convert to power
var holdTime = Date.now() - holdStartTime;
var power = Math.min(holdTime / maxHoldTime * 400, 400); // Scale to max 400 power
power = Math.max(power, 50); // Ensure minimum power
// Shoot toward the target point
ball.flick(holdTargetX, holdTargetY, power);
gameState = 'flying';
isHolding = false;
}
};
game.update = function () {
// Update wind indicator
windIndicator.update();
// Update goalkeeper movement
goalkeeper.update();
// Update power display during hold
if (isHolding) {
var holdTime = Date.now() - holdStartTime;
var powerPercent = Math.min(holdTime / maxHoldTime * 100, 100);
powerTxt.setText('Power: ' + Math.floor(powerPercent) + '%');
powerTxt.visible = true;
} else {
powerTxt.visible = false;
}
// Move goal based on difficulty
if (difficulty > 2) {
goal.x += goalMoveSpeed * goalDirection;
if (goal.x <= 300 || goal.x >= 1748) {
goalDirection *= -1;
}
}
// Check for collision with goalkeeper
if (ball.isFlying && ball.intersects(goalkeeper)) {
// Calculate reflection angle based on where ball hits goalkeeper
var relativeHitX = ball.x - goalkeeper.x;
var reflectionAngle = relativeHitX * 0.02; // Increased reflection sensitivity
// Reflect velocity with energy loss - ensure ball goes back towards player
ball.velocityX = -Math.abs(ball.velocityX) * 0.9 + reflectionAngle; // Force negative X (back towards player)
ball.velocityY = -Math.abs(ball.velocityY) * 0.8; // Always bounce upward with more energy
// Add goalkeeper movement influence
ball.velocityX += goalkeeper.direction * goalkeeper.moveSpeed * 0.5;
// Add some spin effect
tween(ball, {
rotation: ball.rotation + Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut
});
// Move ball away from goalkeeper to prevent multiple collisions
ball.x = goalkeeper.x - 60; // Position ball in front of goalkeeper
ball.y -= 10; // Lift ball slightly
}
// Check for goal
if (ball.isFlying && goal.checkGoal(ball.x, ball.y, ball.lastY)) {
goalsScoredCount++;
scoreTxt.setText('Goals: ' + goalsScoredCount);
LK.getSound('goal').play();
LK.effects.flashScreen(0x00ff00, 500);
resetRound();
}
// Check if ball has stopped and missed
if (gameState === 'flying' && !ball.isFlying) {
resetRound();
}
// Victory condition
if (goalsScoredCount >= 5) {
LK.showYouWin();
}
};