/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
self.minX = 0;
self.maxX = 0;
self.isDiving = false;
self.dive = function (direction) {
if (!self.isDiving) {
self.isDiving = true;
var targetX = direction > 0 ? self.maxX : self.minX;
tween(self, {
x: targetX,
scaleX: direction > 0 ? 1.2 : -1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1
}, {
duration: 200
});
self.isDiving = false;
}
});
}
};
return self;
});
var SoccerBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('soccerBall', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.x += self.speedX;
self.y += self.speedY;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4CAF50
});
/****
* Game Code
****/
// Game variables
var goalkeeper;
var soccerBalls = [];
var goalsAllowed = 0;
var maxGoals = 5;
var consecutiveSaves = 0;
var ballSpawnCounter = 0;
var ballSpawnRate = 120; // frames between balls
var gameSpeed = 1;
var isDragging = false;
// Create goal structure
var goalBackground = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0.3
}));
goalBackground.x = 2048 / 2;
goalBackground.y = 2732 - 200;
// Goal posts
var leftPost = game.addChild(LK.getAsset('goalPost', {
anchorX: 0.5,
anchorY: 1.0
}));
leftPost.x = 2048 / 2 - 400;
leftPost.y = 2732 - 200;
var rightPost = game.addChild(LK.getAsset('goalPost', {
anchorX: 0.5,
anchorY: 1.0
}));
rightPost.x = 2048 / 2 + 400;
rightPost.y = 2732 - 200;
// Goal line
var goalLine = game.addChild(LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5
}));
goalLine.x = 2048 / 2;
goalLine.y = 2732 - 200;
// Create goalkeeper
goalkeeper = game.addChild(new Goalkeeper());
goalkeeper.x = 2048 / 2;
goalkeeper.y = 2732 - 200;
goalkeeper.minX = 2048 / 2 - 350;
goalkeeper.maxX = 2048 / 2 + 350;
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var goalsTxt = new Text2('Goals: 0/5', {
size: 60,
fill: 0xFFFFFF
});
goalsTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(goalsTxt);
var streakTxt = new Text2('Saves: 0', {
size: 50,
fill: 0xFFFF00
});
streakTxt.anchor.set(0, 0);
LK.gui.left.addChild(streakTxt);
// Touch/drag controls
game.down = function (x, y, obj) {
isDragging = true;
var clampedX = Math.max(goalkeeper.minX, Math.min(goalkeeper.maxX, x));
goalkeeper.x = clampedX;
};
game.move = function (x, y, obj) {
if (isDragging) {
var clampedX = Math.max(goalkeeper.minX, Math.min(goalkeeper.maxX, x));
goalkeeper.x = clampedX;
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Spawn soccer ball
function spawnSoccerBall() {
var ball = new SoccerBall();
// Random spawn position along top of screen
ball.x = Math.random() * 1848 + 100;
ball.y = 100;
// Calculate trajectory toward goal
var targetX = 2048 / 2 + (Math.random() - 0.5) * 600;
var targetY = 2732 - 250;
var distance = Math.sqrt(Math.pow(targetX - ball.x, 2) + Math.pow(targetY - ball.y, 2));
var speed = 8 + Math.random() * 4 * gameSpeed;
ball.speedX = (targetX - ball.x) / distance * speed;
ball.speedY = (targetY - ball.y) / distance * speed;
ball.lastY = ball.y;
ball.lastActive = true;
soccerBalls.push(ball);
game.addChild(ball);
}
// Main game update loop
game.update = function () {
// Update ball spawn counter
ballSpawnCounter++;
if (ballSpawnCounter >= ballSpawnRate) {
spawnSoccerBall();
ballSpawnCounter = 0;
// Increase difficulty over time
if (ballSpawnRate > 60) {
ballSpawnRate = Math.max(60, ballSpawnRate - 1);
}
gameSpeed += 0.01;
}
// Update soccer balls
for (var i = soccerBalls.length - 1; i >= 0; i--) {
var ball = soccerBalls[i];
if (!ball.isActive) continue;
// Check if ball went off screen (missed goal)
if (ball.lastY < 2732 - 180 && ball.y >= 2732 - 180 && ball.isActive) {
// Check if ball is within goal area
if (ball.x > 2048 / 2 - 400 && ball.x < 2048 / 2 + 400) {
// Check if goalkeeper saved it
if (ball.intersects(goalkeeper)) {
// SAVE!
ball.isActive = false;
LK.setScore(LK.getScore() + 10);
consecutiveSaves++;
// Bonus points for consecutive saves
if (consecutiveSaves > 1) {
LK.setScore(LK.getScore() + consecutiveSaves * 5);
}
// Diving save bonus
if (goalkeeper.isDiving) {
LK.setScore(LK.getScore() + 20);
LK.effects.flashObject(goalkeeper, 0xffff00, 500);
}
LK.getSound('save').play();
LK.effects.flashObject(ball, 0x00ff00, 300);
// Trigger goalkeeper dive animation
var direction = ball.x > goalkeeper.x ? 1 : -1;
goalkeeper.dive(direction);
} else {
// GOAL!
goalsAllowed++;
consecutiveSaves = 0;
LK.getSound('goal').play();
LK.effects.flashScreen(0xff0000, 500);
if (goalsAllowed >= maxGoals) {
LK.getSound('whistle').play();
LK.showGameOver();
return;
}
}
// Remove ball
ball.destroy();
soccerBalls.splice(i, 1);
continue;
}
}
// Remove balls that go completely off screen
if (ball.y > 2732 + 100 || ball.x < -100 || ball.x > 2148) {
ball.destroy();
soccerBalls.splice(i, 1);
continue;
}
// Update last position
ball.lastY = ball.y;
}
// Update UI
scoreTxt.setText('Score: ' + LK.getScore());
goalsTxt.setText('Goals: ' + goalsAllowed + '/' + maxGoals);
streakTxt.setText('Saves: ' + consecutiveSaves);
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Goalkeeper = Container.expand(function () {
var self = Container.call(this);
var keeperGraphics = self.attachAsset('goalkeeper', {
anchorX: 0.5,
anchorY: 1.0
});
self.minX = 0;
self.maxX = 0;
self.isDiving = false;
self.dive = function (direction) {
if (!self.isDiving) {
self.isDiving = true;
var targetX = direction > 0 ? self.maxX : self.minX;
tween(self, {
x: targetX,
scaleX: direction > 0 ? 1.2 : -1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1
}, {
duration: 200
});
self.isDiving = false;
}
});
}
};
return self;
});
var SoccerBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('soccerBall', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.isActive = true;
self.update = function () {
if (self.isActive) {
self.x += self.speedX;
self.y += self.speedY;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4CAF50
});
/****
* Game Code
****/
// Game variables
var goalkeeper;
var soccerBalls = [];
var goalsAllowed = 0;
var maxGoals = 5;
var consecutiveSaves = 0;
var ballSpawnCounter = 0;
var ballSpawnRate = 120; // frames between balls
var gameSpeed = 1;
var isDragging = false;
// Create goal structure
var goalBackground = game.addChild(LK.getAsset('goal', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0.3
}));
goalBackground.x = 2048 / 2;
goalBackground.y = 2732 - 200;
// Goal posts
var leftPost = game.addChild(LK.getAsset('goalPost', {
anchorX: 0.5,
anchorY: 1.0
}));
leftPost.x = 2048 / 2 - 400;
leftPost.y = 2732 - 200;
var rightPost = game.addChild(LK.getAsset('goalPost', {
anchorX: 0.5,
anchorY: 1.0
}));
rightPost.x = 2048 / 2 + 400;
rightPost.y = 2732 - 200;
// Goal line
var goalLine = game.addChild(LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5
}));
goalLine.x = 2048 / 2;
goalLine.y = 2732 - 200;
// Create goalkeeper
goalkeeper = game.addChild(new Goalkeeper());
goalkeeper.x = 2048 / 2;
goalkeeper.y = 2732 - 200;
goalkeeper.minX = 2048 / 2 - 350;
goalkeeper.maxX = 2048 / 2 + 350;
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var goalsTxt = new Text2('Goals: 0/5', {
size: 60,
fill: 0xFFFFFF
});
goalsTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(goalsTxt);
var streakTxt = new Text2('Saves: 0', {
size: 50,
fill: 0xFFFF00
});
streakTxt.anchor.set(0, 0);
LK.gui.left.addChild(streakTxt);
// Touch/drag controls
game.down = function (x, y, obj) {
isDragging = true;
var clampedX = Math.max(goalkeeper.minX, Math.min(goalkeeper.maxX, x));
goalkeeper.x = clampedX;
};
game.move = function (x, y, obj) {
if (isDragging) {
var clampedX = Math.max(goalkeeper.minX, Math.min(goalkeeper.maxX, x));
goalkeeper.x = clampedX;
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Spawn soccer ball
function spawnSoccerBall() {
var ball = new SoccerBall();
// Random spawn position along top of screen
ball.x = Math.random() * 1848 + 100;
ball.y = 100;
// Calculate trajectory toward goal
var targetX = 2048 / 2 + (Math.random() - 0.5) * 600;
var targetY = 2732 - 250;
var distance = Math.sqrt(Math.pow(targetX - ball.x, 2) + Math.pow(targetY - ball.y, 2));
var speed = 8 + Math.random() * 4 * gameSpeed;
ball.speedX = (targetX - ball.x) / distance * speed;
ball.speedY = (targetY - ball.y) / distance * speed;
ball.lastY = ball.y;
ball.lastActive = true;
soccerBalls.push(ball);
game.addChild(ball);
}
// Main game update loop
game.update = function () {
// Update ball spawn counter
ballSpawnCounter++;
if (ballSpawnCounter >= ballSpawnRate) {
spawnSoccerBall();
ballSpawnCounter = 0;
// Increase difficulty over time
if (ballSpawnRate > 60) {
ballSpawnRate = Math.max(60, ballSpawnRate - 1);
}
gameSpeed += 0.01;
}
// Update soccer balls
for (var i = soccerBalls.length - 1; i >= 0; i--) {
var ball = soccerBalls[i];
if (!ball.isActive) continue;
// Check if ball went off screen (missed goal)
if (ball.lastY < 2732 - 180 && ball.y >= 2732 - 180 && ball.isActive) {
// Check if ball is within goal area
if (ball.x > 2048 / 2 - 400 && ball.x < 2048 / 2 + 400) {
// Check if goalkeeper saved it
if (ball.intersects(goalkeeper)) {
// SAVE!
ball.isActive = false;
LK.setScore(LK.getScore() + 10);
consecutiveSaves++;
// Bonus points for consecutive saves
if (consecutiveSaves > 1) {
LK.setScore(LK.getScore() + consecutiveSaves * 5);
}
// Diving save bonus
if (goalkeeper.isDiving) {
LK.setScore(LK.getScore() + 20);
LK.effects.flashObject(goalkeeper, 0xffff00, 500);
}
LK.getSound('save').play();
LK.effects.flashObject(ball, 0x00ff00, 300);
// Trigger goalkeeper dive animation
var direction = ball.x > goalkeeper.x ? 1 : -1;
goalkeeper.dive(direction);
} else {
// GOAL!
goalsAllowed++;
consecutiveSaves = 0;
LK.getSound('goal').play();
LK.effects.flashScreen(0xff0000, 500);
if (goalsAllowed >= maxGoals) {
LK.getSound('whistle').play();
LK.showGameOver();
return;
}
}
// Remove ball
ball.destroy();
soccerBalls.splice(i, 1);
continue;
}
}
// Remove balls that go completely off screen
if (ball.y > 2732 + 100 || ball.x < -100 || ball.x > 2148) {
ball.destroy();
soccerBalls.splice(i, 1);
continue;
}
// Update last position
ball.lastY = ball.y;
}
// Update UI
scoreTxt.setText('Score: ' + LK.getScore());
goalsTxt.setText('Goals: ' + goalsAllowed + '/' + maxGoals);
streakTxt.setText('Saves: ' + consecutiveSaves);
};