/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Collectible class: moves downward, destroys itself off screen var Collectible = Container.expand(function () { var self = Container.call(this); var colAsset = self.attachAsset('collectible', { anchorX: 0.5, anchorY: 0.5 }); self.radius = colAsset.width / 2; self.speed = 12; // Will be set by game self.update = function () { self.y += self.speed; }; return self; }); // Fireball projectile: moves upward, destroys itself off screen or on hit var Fireball = Container.expand(function () { var self = Container.call(this); var asset = self.attachAsset('skin_gold', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60 }); self.radius = asset.width / 2; self.speed = -36; // Fast upward self.update = function () { self.y += self.speed; }; return self; }); // FireballPowerup: when collected, enables fireball shooting for a duration var FireballPowerup = Container.expand(function () { var self = Container.call(this); var asset = self.attachAsset('fireball_powerup', { anchorX: 0.5, anchorY: 0.5, width: 100, height: 100 }); self.radius = asset.width / 2; self.speed = 12; self.update = function () { self.y += self.speed; }; return self; }); // Obstacle class: moves downward, destroys itself off screen var Obstacle = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; self.speed = 12; // Will be set by game self.update = function () { self.y += self.speed; }; return self; }); // Player class: follows face position, grows when mouth is open var Player = Container.expand(function () { var self = Container.call(this); var playerAsset = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.radius = playerAsset.width / 2; // For collision, always use self.x, self.y, and self.radius // Animate mouth open/close (scale up/down) self.setMouthOpen = function (open) { var targetScale = open ? 1.25 : 1.0; tween.stop(self, { scaleX: true, scaleY: true }); tween(self, { scaleX: targetScale, scaleY: targetScale }, { duration: 180, easing: tween.easeOut }); }; // For update, nothing needed (position is set by game) self.update = function () {}; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x3a7eea }); /**** * Game Code ****/ // Slider assets // Collectible: Green circle // Obstacle: Red rectangles // Character: The player avatar, a bright ellipse // Game area var GAME_W = 2048; var GAME_H = 2732; // Player setup var player = new Player(); player.x = GAME_W / 2; player.y = GAME_H * 0.8; player.scaleX = 1.0; player.scaleY = 1.0; game.addChild(player); // Apply selected skin if not default if (typeof selectedSkin !== 'undefined' && selectedSkin !== 'player') { player.removeChildren(); var newAsset = player.attachAsset(selectedSkin, { anchorX: 0.5, anchorY: 0.5 }); player.radius = newAsset.width / 2; } // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Arrays for obstacles, collectibles, fireballs, and powerups var obstacles = []; var collectibles = []; var fireballs = []; var fireballPowerups = []; // Fireball powerup state var fireballActive = false; var fireballTimer = 0; var fireballShootTicks = 0; // Game state var score = 0; var ticks = 0; var gameSpeed = 12; // Initial speed var spawnInterval = 60; // Frames between spawns (1 sec at 60fps) var collectibleInterval = 180; // Frames between collectibles // For collision detection function circleRectIntersect(cx, cy, cr, rx, ry, rw, rh) { // Find closest point to circle within rectangle var closestX = Math.max(rx - rw / 2, Math.min(cx, rx + rw / 2)); var closestY = Math.max(ry - rh / 2, Math.min(cy, ry + rh / 2)); var dx = cx - closestX; var dy = cy - closestY; return dx * dx + dy * dy < cr * cr; } function circleCircleIntersect(x1, y1, r1, x2, y2, r2) { var dx = x1 - x2; var dy = y1 - y2; var distSq = dx * dx + dy * dy; var rSum = r1 + r2; return distSq < rSum * rSum; } // Slider control variables var sliderBar = null; var sliderKnob = null; var sliderDragging = false; var sliderBarWidth = GAME_W; // Make the slider bar as wide as the game area var sliderBarHeight = 32; var sliderKnobRadius = 70; var sliderBarY = GAME_H - 220; // Place slider above bottom, visible on all screens // Create slider bar and knob using dedicated assets sliderBar = LK.getAsset('slider_bar', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: sliderBarY }); sliderKnob = LK.getAsset('slider_knob', { anchorX: 0.5, anchorY: 0.5, x: GAME_W / 2, y: sliderBarY }); sliderKnob.width = sliderKnobRadius * 2; sliderKnob.height = sliderKnobRadius * 2; game.addChild(sliderBar); game.addChild(sliderKnob); // Helper to clamp knob position function clampSliderKnobX(x) { // Allow knob to reach the very left and right edges of the bar var minX = sliderBar.x - sliderBarWidth / 2 + sliderKnobRadius; var maxX = sliderBar.x + sliderBarWidth / 2 - sliderKnobRadius; // If the knob is wider than the bar, clamp to center if (minX > maxX) { return sliderBar.x; } return Math.max(minX, Math.min(maxX, x)); } // Set initial knob position sliderKnob.x = GAME_W / 2; // Update player position from slider function updatePlayerFromSlider() { // Map knob X to player X, allowing player to reach the very left and right edges var minX = player.radius; var maxX = GAME_W - player.radius; var knobMinX = sliderBar.x - sliderBarWidth / 2 + sliderKnobRadius; var knobMaxX = sliderBar.x + sliderBarWidth / 2 - sliderKnobRadius; // If the knob is wider than the bar, set t to 0.5 (center) var t = knobMaxX === knobMinX ? 0.5 : (sliderKnob.x - knobMinX) / (knobMaxX - knobMinX); player.x = minX + t * (maxX - minX); // Player Y is fixed player.y = GAME_H * 0.8; } // Touch/mouse events for slider game.down = function (x, y, obj) { // Only start drag if touch is near knob var dx = x - sliderKnob.x; var dy = y - sliderKnob.y; if (dx * dx + dy * dy <= sliderKnobRadius * sliderKnobRadius * 1.2) { sliderDragging = true; // Move knob immediately sliderKnob.x = clampSliderKnobX(x); updatePlayerFromSlider(); } }; game.move = function (x, y, obj) { if (sliderDragging) { sliderKnob.x = clampSliderKnobX(x); updatePlayerFromSlider(); } }; game.up = function (x, y, obj) { sliderDragging = false; }; // Remove facekit control entirely // Spawning function spawnObstacle() { var obs = new Obstacle(); // Random X, avoid edges var margin = 80; obs.x = margin + Math.random() * (GAME_W - 2 * margin); obs.y = -obs.height / 2; obs.speed = gameSpeed; obstacles.push(obs); game.addChild(obs); } function spawnCollectible() { var col = new Collectible(); var margin = 80; col.x = margin + Math.random() * (GAME_W - 2 * margin); col.y = -col.radius; col.speed = gameSpeed; collectibles.push(col); game.addChild(col); } // Fireball powerup spawner (rare) function spawnFireballPowerup() { var p = new FireballPowerup(); var margin = 80; p.x = margin + Math.random() * (GAME_W - 2 * margin); p.y = -p.radius; p.speed = gameSpeed; fireballPowerups.push(p); game.addChild(p); } // Difficulty scaling function updateDifficulty() { // Every 7 points, increase speed and spawn rate (harder) var level = Math.floor(score / 7); gameSpeed = 14 + level * 2.5; // Start faster, scale faster spawnInterval = Math.max(18, 48 - level * 5); // Min ~0.3s, faster spawn } // Main update loop game.update = function () { ticks++; // Slider control updatePlayerFromSlider(); // --- Fireball Powerup Spawning (rare, every 10 seconds) --- if (ticks % (60 * 10) === 0) { if (Math.random() < 0.7) { // 70% chance every 10s spawnFireballPowerup(); } } // Spawn obstacles if (ticks % spawnInterval === 0) { spawnObstacle(); } // Spawn collectibles if (ticks % collectibleInterval === 0) { spawnCollectible(); } // --- Fireball Powerup Update & Collision --- for (var fpi = fireballPowerups.length - 1; fpi >= 0; fpi--) { var p = fireballPowerups[fpi]; p.speed = gameSpeed; p.update(); // Collect powerup if (circleCircleIntersect(player.x, player.y, player.radius * player.scaleX, p.x, p.y, p.radius)) { fireballActive = true; // 5-10 seconds random duration fireballTimer = 60 * (5 + Math.floor(Math.random() * 6)); fireballShootTicks = 0; LK.effects.flashObject(player, 0xffa500, 400); p.destroy(); fireballPowerups.splice(fpi, 1); continue; } // Off screen if (p.y - p.radius > GAME_H + 100) { p.destroy(); fireballPowerups.splice(fpi, 1); } } // --- Fireball Powerup Timer & Shooting --- if (fireballActive) { fireballTimer--; fireballShootTicks++; // Shoot fireball every 18 frames (~3.3/sec, less projectiles) if (fireballShootTicks % 18 === 0) { var fb = new Fireball(); fb.x = player.x; fb.y = player.y - player.radius * player.scaleY - 30; fireballs.push(fb); game.addChild(fb); } if (fireballTimer <= 0) { fireballActive = false; } } // --- Fireball Update & Collision with Obstacles --- for (var fbi = fireballs.length - 1; fbi >= 0; fbi--) { var fb = fireballs[fbi]; fb.update(); var destroyed = false; // Remove if off screen if (fb.y + fb.radius < -100) { fb.destroy(); fireballs.splice(fbi, 1); continue; } // Check collision with obstacles for (var oi = obstacles.length - 1; oi >= 0; oi--) { var obs = obstacles[oi]; if (circleRectIntersect(fb.x, fb.y, fb.radius, obs.x, obs.y, obs.width, obs.height)) { // Destroy both fireball and obstacle obs.destroy(); obstacles.splice(oi, 1); fb.destroy(); fireballs.splice(fbi, 1); destroyed = true; break; } } if (destroyed) continue; } // Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.speed = gameSpeed; obs.update(); // Collision with player if (circleRectIntersect(player.x, player.y, player.radius * player.scaleX, obs.x, obs.y, obs.width, obs.height)) { // Flash screen, game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Off screen if (obs.y - obs.height / 2 > GAME_H + 100) { obs.destroy(); obstacles.splice(i, 1); } } // Update collectibles for (var j = collectibles.length - 1; j >= 0; j--) { var col = collectibles[j]; col.speed = gameSpeed; col.update(); // Collect if (circleCircleIntersect(player.x, player.y, player.radius * player.scaleX, col.x, col.y, col.radius)) { score += 1; LK.setScore(score); scoreTxt.setText(score); updateDifficulty(); // Update allTimePoints in storage if (!storage.allTimePoints) storage.allTimePoints = 0; storage.allTimePoints = storage.allTimePoints + 1; // Flash green LK.effects.flashObject(player, 0x7ed957, 300); col.destroy(); collectibles.splice(j, 1); // Win condition if (score >= 50) { LK.showYouWin(); return; } } // Off screen if (col.y - col.radius > GAME_H + 100) { col.destroy(); collectibles.splice(j, 1); } } }; // Initial score LK.setScore(0); scoreTxt.setText(0); // Place score text at top center, avoid top left 100x100 scoreTxt.y = 0; scoreTxt.x = 0; // Skin shop feature removed // No drag/move events needed; all control is via facekit // End of MVP
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Collectible class: moves downward, destroys itself off screen
var Collectible = Container.expand(function () {
var self = Container.call(this);
var colAsset = self.attachAsset('collectible', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = colAsset.width / 2;
self.speed = 12; // Will be set by game
self.update = function () {
self.y += self.speed;
};
return self;
});
// Fireball projectile: moves upward, destroys itself off screen or on hit
var Fireball = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('skin_gold', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
self.radius = asset.width / 2;
self.speed = -36; // Fast upward
self.update = function () {
self.y += self.speed;
};
return self;
});
// FireballPowerup: when collected, enables fireball shooting for a duration
var FireballPowerup = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('fireball_powerup', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
self.radius = asset.width / 2;
self.speed = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Obstacle class: moves downward, destroys itself off screen
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
self.speed = 12; // Will be set by game
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player class: follows face position, grows when mouth is open
var Player = Container.expand(function () {
var self = Container.call(this);
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = playerAsset.width / 2;
// For collision, always use self.x, self.y, and self.radius
// Animate mouth open/close (scale up/down)
self.setMouthOpen = function (open) {
var targetScale = open ? 1.25 : 1.0;
tween.stop(self, {
scaleX: true,
scaleY: true
});
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 180,
easing: tween.easeOut
});
};
// For update, nothing needed (position is set by game)
self.update = function () {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3a7eea
});
/****
* Game Code
****/
// Slider assets
// Collectible: Green circle
// Obstacle: Red rectangles
// Character: The player avatar, a bright ellipse
// Game area
var GAME_W = 2048;
var GAME_H = 2732;
// Player setup
var player = new Player();
player.x = GAME_W / 2;
player.y = GAME_H * 0.8;
player.scaleX = 1.0;
player.scaleY = 1.0;
game.addChild(player);
// Apply selected skin if not default
if (typeof selectedSkin !== 'undefined' && selectedSkin !== 'player') {
player.removeChildren();
var newAsset = player.attachAsset(selectedSkin, {
anchorX: 0.5,
anchorY: 0.5
});
player.radius = newAsset.width / 2;
}
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Arrays for obstacles, collectibles, fireballs, and powerups
var obstacles = [];
var collectibles = [];
var fireballs = [];
var fireballPowerups = [];
// Fireball powerup state
var fireballActive = false;
var fireballTimer = 0;
var fireballShootTicks = 0;
// Game state
var score = 0;
var ticks = 0;
var gameSpeed = 12; // Initial speed
var spawnInterval = 60; // Frames between spawns (1 sec at 60fps)
var collectibleInterval = 180; // Frames between collectibles
// For collision detection
function circleRectIntersect(cx, cy, cr, rx, ry, rw, rh) {
// Find closest point to circle within rectangle
var closestX = Math.max(rx - rw / 2, Math.min(cx, rx + rw / 2));
var closestY = Math.max(ry - rh / 2, Math.min(cy, ry + rh / 2));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < cr * cr;
}
function circleCircleIntersect(x1, y1, r1, x2, y2, r2) {
var dx = x1 - x2;
var dy = y1 - y2;
var distSq = dx * dx + dy * dy;
var rSum = r1 + r2;
return distSq < rSum * rSum;
}
// Slider control variables
var sliderBar = null;
var sliderKnob = null;
var sliderDragging = false;
var sliderBarWidth = GAME_W; // Make the slider bar as wide as the game area
var sliderBarHeight = 32;
var sliderKnobRadius = 70;
var sliderBarY = GAME_H - 220; // Place slider above bottom, visible on all screens
// Create slider bar and knob using dedicated assets
sliderBar = LK.getAsset('slider_bar', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: sliderBarY
});
sliderKnob = LK.getAsset('slider_knob', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_W / 2,
y: sliderBarY
});
sliderKnob.width = sliderKnobRadius * 2;
sliderKnob.height = sliderKnobRadius * 2;
game.addChild(sliderBar);
game.addChild(sliderKnob);
// Helper to clamp knob position
function clampSliderKnobX(x) {
// Allow knob to reach the very left and right edges of the bar
var minX = sliderBar.x - sliderBarWidth / 2 + sliderKnobRadius;
var maxX = sliderBar.x + sliderBarWidth / 2 - sliderKnobRadius;
// If the knob is wider than the bar, clamp to center
if (minX > maxX) {
return sliderBar.x;
}
return Math.max(minX, Math.min(maxX, x));
}
// Set initial knob position
sliderKnob.x = GAME_W / 2;
// Update player position from slider
function updatePlayerFromSlider() {
// Map knob X to player X, allowing player to reach the very left and right edges
var minX = player.radius;
var maxX = GAME_W - player.radius;
var knobMinX = sliderBar.x - sliderBarWidth / 2 + sliderKnobRadius;
var knobMaxX = sliderBar.x + sliderBarWidth / 2 - sliderKnobRadius;
// If the knob is wider than the bar, set t to 0.5 (center)
var t = knobMaxX === knobMinX ? 0.5 : (sliderKnob.x - knobMinX) / (knobMaxX - knobMinX);
player.x = minX + t * (maxX - minX);
// Player Y is fixed
player.y = GAME_H * 0.8;
}
// Touch/mouse events for slider
game.down = function (x, y, obj) {
// Only start drag if touch is near knob
var dx = x - sliderKnob.x;
var dy = y - sliderKnob.y;
if (dx * dx + dy * dy <= sliderKnobRadius * sliderKnobRadius * 1.2) {
sliderDragging = true;
// Move knob immediately
sliderKnob.x = clampSliderKnobX(x);
updatePlayerFromSlider();
}
};
game.move = function (x, y, obj) {
if (sliderDragging) {
sliderKnob.x = clampSliderKnobX(x);
updatePlayerFromSlider();
}
};
game.up = function (x, y, obj) {
sliderDragging = false;
};
// Remove facekit control entirely
// Spawning
function spawnObstacle() {
var obs = new Obstacle();
// Random X, avoid edges
var margin = 80;
obs.x = margin + Math.random() * (GAME_W - 2 * margin);
obs.y = -obs.height / 2;
obs.speed = gameSpeed;
obstacles.push(obs);
game.addChild(obs);
}
function spawnCollectible() {
var col = new Collectible();
var margin = 80;
col.x = margin + Math.random() * (GAME_W - 2 * margin);
col.y = -col.radius;
col.speed = gameSpeed;
collectibles.push(col);
game.addChild(col);
}
// Fireball powerup spawner (rare)
function spawnFireballPowerup() {
var p = new FireballPowerup();
var margin = 80;
p.x = margin + Math.random() * (GAME_W - 2 * margin);
p.y = -p.radius;
p.speed = gameSpeed;
fireballPowerups.push(p);
game.addChild(p);
}
// Difficulty scaling
function updateDifficulty() {
// Every 7 points, increase speed and spawn rate (harder)
var level = Math.floor(score / 7);
gameSpeed = 14 + level * 2.5; // Start faster, scale faster
spawnInterval = Math.max(18, 48 - level * 5); // Min ~0.3s, faster spawn
}
// Main update loop
game.update = function () {
ticks++;
// Slider control
updatePlayerFromSlider();
// --- Fireball Powerup Spawning (rare, every 10 seconds) ---
if (ticks % (60 * 10) === 0) {
if (Math.random() < 0.7) {
// 70% chance every 10s
spawnFireballPowerup();
}
}
// Spawn obstacles
if (ticks % spawnInterval === 0) {
spawnObstacle();
}
// Spawn collectibles
if (ticks % collectibleInterval === 0) {
spawnCollectible();
}
// --- Fireball Powerup Update & Collision ---
for (var fpi = fireballPowerups.length - 1; fpi >= 0; fpi--) {
var p = fireballPowerups[fpi];
p.speed = gameSpeed;
p.update();
// Collect powerup
if (circleCircleIntersect(player.x, player.y, player.radius * player.scaleX, p.x, p.y, p.radius)) {
fireballActive = true;
// 5-10 seconds random duration
fireballTimer = 60 * (5 + Math.floor(Math.random() * 6));
fireballShootTicks = 0;
LK.effects.flashObject(player, 0xffa500, 400);
p.destroy();
fireballPowerups.splice(fpi, 1);
continue;
}
// Off screen
if (p.y - p.radius > GAME_H + 100) {
p.destroy();
fireballPowerups.splice(fpi, 1);
}
}
// --- Fireball Powerup Timer & Shooting ---
if (fireballActive) {
fireballTimer--;
fireballShootTicks++;
// Shoot fireball every 18 frames (~3.3/sec, less projectiles)
if (fireballShootTicks % 18 === 0) {
var fb = new Fireball();
fb.x = player.x;
fb.y = player.y - player.radius * player.scaleY - 30;
fireballs.push(fb);
game.addChild(fb);
}
if (fireballTimer <= 0) {
fireballActive = false;
}
}
// --- Fireball Update & Collision with Obstacles ---
for (var fbi = fireballs.length - 1; fbi >= 0; fbi--) {
var fb = fireballs[fbi];
fb.update();
var destroyed = false;
// Remove if off screen
if (fb.y + fb.radius < -100) {
fb.destroy();
fireballs.splice(fbi, 1);
continue;
}
// Check collision with obstacles
for (var oi = obstacles.length - 1; oi >= 0; oi--) {
var obs = obstacles[oi];
if (circleRectIntersect(fb.x, fb.y, fb.radius, obs.x, obs.y, obs.width, obs.height)) {
// Destroy both fireball and obstacle
obs.destroy();
obstacles.splice(oi, 1);
fb.destroy();
fireballs.splice(fbi, 1);
destroyed = true;
break;
}
}
if (destroyed) continue;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.speed = gameSpeed;
obs.update();
// Collision with player
if (circleRectIntersect(player.x, player.y, player.radius * player.scaleX, obs.x, obs.y, obs.width, obs.height)) {
// Flash screen, game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Off screen
if (obs.y - obs.height / 2 > GAME_H + 100) {
obs.destroy();
obstacles.splice(i, 1);
}
}
// Update collectibles
for (var j = collectibles.length - 1; j >= 0; j--) {
var col = collectibles[j];
col.speed = gameSpeed;
col.update();
// Collect
if (circleCircleIntersect(player.x, player.y, player.radius * player.scaleX, col.x, col.y, col.radius)) {
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
updateDifficulty();
// Update allTimePoints in storage
if (!storage.allTimePoints) storage.allTimePoints = 0;
storage.allTimePoints = storage.allTimePoints + 1;
// Flash green
LK.effects.flashObject(player, 0x7ed957, 300);
col.destroy();
collectibles.splice(j, 1);
// Win condition
if (score >= 50) {
LK.showYouWin();
return;
}
}
// Off screen
if (col.y - col.radius > GAME_H + 100) {
col.destroy();
collectibles.splice(j, 1);
}
}
};
// Initial score
LK.setScore(0);
scoreTxt.setText(0);
// Place score text at top center, avoid top left 100x100
scoreTxt.y = 0;
scoreTxt.x = 0;
// Skin shop feature removed
// No drag/move events needed; all control is via facekit
// End of MVP