/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Fragment: Collectible fragments var Fragment = Container.expand(function () { var self = Container.call(this); var frag = self.attachAsset('fragment', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); // Animate scale for a subtle pulse function pulseUp() { tween(frag, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.sineInOut, onFinish: pulseDown }); } function pulseDown() { tween(frag, { scaleX: 1.0, scaleY: 1.0 }, { duration: 600, easing: tween.sineInOut, onFinish: pulseUp }); } frag.scaleX = frag.scaleY = 1.0; pulseUp(); // Movement speed (downward) self.speedY = 6; // For collision self.radius = frag.width * 0.5; self.update = function () { self.y += self.speedY; }; return self; }); // NullEntity: The player-controlled "null" entity. var NullEntity = Container.expand(function () { var self = Container.call(this); var entity = self.attachAsset('nullEntity', { anchorX: 0.5, anchorY: 0.5, alpha: 0.18 }); // Add a subtle glow effect by animating alpha tween(entity, { alpha: 0.32 }, { duration: 1200, easing: tween.sineInOut, onFinish: function onFinish() { tween(entity, { alpha: 0.18 }, { duration: 1200, easing: tween.sineInOut, onFinish: function onFinish() { // Loop tween(entity, { alpha: 0.32 }, { duration: 1200, easing: tween.sineInOut, onFinish: arguments.callee }); } }); } }); // For collision, use self's position and size self.radius = entity.width * 0.5; // No update needed; movement is handled by drag/tap return self; }); // Obstacle: Abstract void obstacles to avoid var Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5, alpha: 0.32 }); // Randomize rotation for abstractness obs.rotation = (Math.random() - 0.5) * 0.7; // Movement speed (downward) self.speedY = 7 + Math.random() * 3; // For collision self.width = obs.width; self.height = obs.height; self.update = function () { self.y += self.speedY; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 // Deep void }); /**** * Game Code ****/ // --- Game State --- /* We use minimalist shapes for the "null" entity, obstacles, and collectible fragments. - nullEntity: a faint ellipse, representing the player. - obstacle: a semi-transparent box, representing void obstacles. - fragment: a small glowing ellipse, representing fragments to collect. - messageBg: a translucent box for cryptic message popups. */ var nullEntity = new NullEntity(); game.addChild(nullEntity); // Start position: center horizontally, 90% down vertically nullEntity.x = 2048 / 2; nullEntity.y = 2732 * 0.9; // Arrays for obstacles and fragments var obstacles = []; var fragments = []; // Level and progress var level = 1; var fragmentsCollected = 0; var fragmentsToNext = 5; var showingMessage = false; // Cryptic messages for fragments var messages = ["Nothing is the beginning of everything.", "In emptiness, potential awaits.", "Absence shapes the void.", "Meaning emerges from nothing.", "The null is not empty, but full of possibility."]; // --- GUI: Progress Display --- var progressTxt = new Text2('Fragments: 0/5', { size: 90, fill: 0xCCCCCC }); progressTxt.anchor.set(0.5, 0); LK.gui.top.addChild(progressTxt); // --- Message Popup --- var messageContainer = new Container(); var messageBg = LK.getAsset('messageBg', { anchorX: 0.5, anchorY: 0.5, alpha: 0.82 }); messageContainer.addChild(messageBg); var messageTxt = new Text2('', { size: 70, fill: 0xE0E0E0, align: "center", wordWrap: true, wordWrapWidth: 800 }); messageTxt.anchor.set(0.5, 0.5); messageTxt.x = 0; messageTxt.y = 0; messageContainer.addChild(messageTxt); messageContainer.visible = false; messageContainer.x = 2048 / 2; messageContainer.y = 2732 / 2; game.addChild(messageContainer); // --- Slide Bar Controls --- // Slide bar dimensions and position var slideBarWidth = 1200; var slideBarHeight = 60; var slideBarY = 2732 - 220; var slideBarX = (2048 - slideBarWidth) / 2; // Create slide bar background var slideBarBg = LK.getAsset('messageBg', { width: slideBarWidth, height: slideBarHeight, anchorX: 0, anchorY: 0.5, alpha: 0.32 }); slideBarBg.x = slideBarX; slideBarBg.y = slideBarY; // Create slide knob var knobRadius = 70; var slideKnob = LK.getAsset('nullEntity', { width: knobRadius * 2, height: knobRadius * 2, anchorX: 0.5, anchorY: 0.5, alpha: 0.22 }); slideKnob.x = slideBarX + slideBarWidth / 2; slideKnob.y = slideBarY + slideBarHeight / 2; // Add to game game.addChild(slideBarBg); game.addChild(slideKnob); // Helper: Clamp nullEntity inside game bounds (with margin) function clampNullEntity() { var margin = 80; var r = nullEntity.radius; if (nullEntity.x < margin + r) nullEntity.x = margin + r; if (nullEntity.x > 2048 - margin - r) nullEntity.x = 2048 - margin - r; if (nullEntity.y < margin + r) nullEntity.y = margin + r; if (nullEntity.y > 2732 - margin - r) nullEntity.y = 2732 - margin - r; } // --- Slide Bar Event Handlers --- var sliding = false; function slideBarContains(x, y) { return x >= slideBarX && x <= slideBarX + slideBarWidth && y >= slideBarY && y <= slideBarY + slideBarHeight; } function updateNullFromSlider(x) { // Clamp x to bar var px = Math.max(slideBarX, Math.min(x, slideBarX + slideBarWidth)); // Map to game X range (with margin) var margin = 80 + nullEntity.radius; var minX = margin; var maxX = 2048 - margin; var t = (px - slideBarX) / slideBarWidth; nullEntity.x = minX + t * (maxX - minX); clampNullEntity(); // Move knob slideKnob.x = px; } game.move = function (x, y, obj) { if (showingMessage) return; if (sliding) { updateNullFromSlider(x); } }; game.down = function (x, y, obj) { if (showingMessage) { // Hide message on tap messageContainer.visible = false; showingMessage = false; return; } // Start sliding if touch is on slide bar if (slideBarContains(x, y)) { sliding = true; updateNullFromSlider(x); return; } }; game.up = function (x, y, obj) { sliding = false; }; // Sync knob to nullEntity X at start and after level/message function syncKnobToNull() { var margin = 80 + nullEntity.radius; var minX = margin; var maxX = 2048 - margin; var t = (nullEntity.x - minX) / (maxX - minX); slideKnob.x = slideBarX + t * slideBarWidth; } // Initial sync syncKnobToNull(); // Also call syncKnobToNull after level up and after message is hidden var _oldNextLevel = nextLevel; nextLevel = function nextLevel() { _oldNextLevel(); // After message, sync knob LK.setTimeout(syncKnobToNull, 100); }; var _oldMessageHide = game.down; game.down = function (x, y, obj) { if (showingMessage) { messageContainer.visible = false; showingMessage = false; LK.setTimeout(syncKnobToNull, 100); return; } if (slideBarContains(x, y)) { sliding = true; updateNullFromSlider(x); return; } }; // --- Collision Helpers --- 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; } // --- Spawning Obstacles and Fragments --- var obstacleTimer = 0; var fragmentTimer = 0; function spawnObstacle() { var obs = new Obstacle(); obs.x = 200 + Math.random() * (2048 - 400); obs.y = -80; obstacles.push(obs); game.addChild(obs); } function spawnFragment() { var frag = new Fragment(); frag.x = 200 + Math.random() * (2048 - 400); frag.y = -60; fragments.push(frag); game.addChild(frag); } // --- Level Progression --- function nextLevel() { level += 1; fragmentsCollected = 0; fragmentsToNext = 5 + level * 2; progressTxt.setText('Fragments: 0/' + fragmentsToNext); // Show cryptic message var msg = messages[(level - 2) % messages.length] || "The void deepens."; messageTxt.setText(msg); messageContainer.visible = true; showingMessage = true; // Increase difficulty: spawn rates obstacleTimer = 0; fragmentTimer = 0; } // --- Game Update Loop --- game.update = function () { if (showingMessage) return; // Spawn obstacles obstacleTimer++; var obsInterval = Math.max(38 - level * 2, 16); if (obstacleTimer >= obsInterval) { spawnObstacle(); obstacleTimer = 0; } // Spawn fragments fragmentTimer++; var fragInterval = Math.max(120 - level * 8, 40); if (fragmentTimer >= fragInterval) { spawnFragment(); fragmentTimer = 0; } // Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off screen if (obs.y - obs.height / 2 > 2732 + 100) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision with nullEntity if (circleRectIntersect(nullEntity.x, nullEntity.y, nullEntity.radius * 0.8, obs.x, obs.y, obs.width, obs.height)) { // Flash and game over LK.effects.flashScreen(0xffffff, 900); LK.showGameOver(); return; } } // Update fragments for (var j = fragments.length - 1; j >= 0; j--) { var frag = fragments[j]; frag.update(); // Remove if off screen if (frag.y - frag.radius > 2732 + 80) { frag.destroy(); fragments.splice(j, 1); continue; } // Collision with nullEntity if (circleCircleIntersect(nullEntity.x, nullEntity.y, nullEntity.radius * 0.8, frag.x, frag.y, frag.radius)) { // Collect fragment fragmentsCollected += 1; progressTxt.setText('Fragments: ' + fragmentsCollected + '/' + fragmentsToNext); // Subtle flash LK.effects.flashObject(nullEntity, 0x8ecae6, 400); frag.destroy(); fragments.splice(j, 1); // Level up if (fragmentsCollected >= fragmentsToNext) { nextLevel(); } continue; } } }; // --- Initial Progress Text --- progressTxt.setText('Fragments: 0/' + fragmentsToNext); // --- Initial Message (Level 1) --- messageTxt.setText("Guide the null. Collect fragments. Avoid the void."); messageContainer.visible = true; showingMessage = true;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fragment: Collectible fragments
var Fragment = Container.expand(function () {
var self = Container.call(this);
var frag = self.attachAsset('fragment', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
// Animate scale for a subtle pulse
function pulseUp() {
tween(frag, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.sineInOut,
onFinish: pulseDown
});
}
function pulseDown() {
tween(frag, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.sineInOut,
onFinish: pulseUp
});
}
frag.scaleX = frag.scaleY = 1.0;
pulseUp();
// Movement speed (downward)
self.speedY = 6;
// For collision
self.radius = frag.width * 0.5;
self.update = function () {
self.y += self.speedY;
};
return self;
});
// NullEntity: The player-controlled "null" entity.
var NullEntity = Container.expand(function () {
var self = Container.call(this);
var entity = self.attachAsset('nullEntity', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.18
});
// Add a subtle glow effect by animating alpha
tween(entity, {
alpha: 0.32
}, {
duration: 1200,
easing: tween.sineInOut,
onFinish: function onFinish() {
tween(entity, {
alpha: 0.18
}, {
duration: 1200,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Loop
tween(entity, {
alpha: 0.32
}, {
duration: 1200,
easing: tween.sineInOut,
onFinish: arguments.callee
});
}
});
}
});
// For collision, use self's position and size
self.radius = entity.width * 0.5;
// No update needed; movement is handled by drag/tap
return self;
});
// Obstacle: Abstract void obstacles to avoid
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.32
});
// Randomize rotation for abstractness
obs.rotation = (Math.random() - 0.5) * 0.7;
// Movement speed (downward)
self.speedY = 7 + Math.random() * 3;
// For collision
self.width = obs.width;
self.height = obs.height;
self.update = function () {
self.y += self.speedY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111111 // Deep void
});
/****
* Game Code
****/
// --- Game State ---
/*
We use minimalist shapes for the "null" entity, obstacles, and collectible fragments.
- nullEntity: a faint ellipse, representing the player.
- obstacle: a semi-transparent box, representing void obstacles.
- fragment: a small glowing ellipse, representing fragments to collect.
- messageBg: a translucent box for cryptic message popups.
*/
var nullEntity = new NullEntity();
game.addChild(nullEntity);
// Start position: center horizontally, 90% down vertically
nullEntity.x = 2048 / 2;
nullEntity.y = 2732 * 0.9;
// Arrays for obstacles and fragments
var obstacles = [];
var fragments = [];
// Level and progress
var level = 1;
var fragmentsCollected = 0;
var fragmentsToNext = 5;
var showingMessage = false;
// Cryptic messages for fragments
var messages = ["Nothing is the beginning of everything.", "In emptiness, potential awaits.", "Absence shapes the void.", "Meaning emerges from nothing.", "The null is not empty, but full of possibility."];
// --- GUI: Progress Display ---
var progressTxt = new Text2('Fragments: 0/5', {
size: 90,
fill: 0xCCCCCC
});
progressTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(progressTxt);
// --- Message Popup ---
var messageContainer = new Container();
var messageBg = LK.getAsset('messageBg', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.82
});
messageContainer.addChild(messageBg);
var messageTxt = new Text2('', {
size: 70,
fill: 0xE0E0E0,
align: "center",
wordWrap: true,
wordWrapWidth: 800
});
messageTxt.anchor.set(0.5, 0.5);
messageTxt.x = 0;
messageTxt.y = 0;
messageContainer.addChild(messageTxt);
messageContainer.visible = false;
messageContainer.x = 2048 / 2;
messageContainer.y = 2732 / 2;
game.addChild(messageContainer);
// --- Slide Bar Controls ---
// Slide bar dimensions and position
var slideBarWidth = 1200;
var slideBarHeight = 60;
var slideBarY = 2732 - 220;
var slideBarX = (2048 - slideBarWidth) / 2;
// Create slide bar background
var slideBarBg = LK.getAsset('messageBg', {
width: slideBarWidth,
height: slideBarHeight,
anchorX: 0,
anchorY: 0.5,
alpha: 0.32
});
slideBarBg.x = slideBarX;
slideBarBg.y = slideBarY;
// Create slide knob
var knobRadius = 70;
var slideKnob = LK.getAsset('nullEntity', {
width: knobRadius * 2,
height: knobRadius * 2,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.22
});
slideKnob.x = slideBarX + slideBarWidth / 2;
slideKnob.y = slideBarY + slideBarHeight / 2;
// Add to game
game.addChild(slideBarBg);
game.addChild(slideKnob);
// Helper: Clamp nullEntity inside game bounds (with margin)
function clampNullEntity() {
var margin = 80;
var r = nullEntity.radius;
if (nullEntity.x < margin + r) nullEntity.x = margin + r;
if (nullEntity.x > 2048 - margin - r) nullEntity.x = 2048 - margin - r;
if (nullEntity.y < margin + r) nullEntity.y = margin + r;
if (nullEntity.y > 2732 - margin - r) nullEntity.y = 2732 - margin - r;
}
// --- Slide Bar Event Handlers ---
var sliding = false;
function slideBarContains(x, y) {
return x >= slideBarX && x <= slideBarX + slideBarWidth && y >= slideBarY && y <= slideBarY + slideBarHeight;
}
function updateNullFromSlider(x) {
// Clamp x to bar
var px = Math.max(slideBarX, Math.min(x, slideBarX + slideBarWidth));
// Map to game X range (with margin)
var margin = 80 + nullEntity.radius;
var minX = margin;
var maxX = 2048 - margin;
var t = (px - slideBarX) / slideBarWidth;
nullEntity.x = minX + t * (maxX - minX);
clampNullEntity();
// Move knob
slideKnob.x = px;
}
game.move = function (x, y, obj) {
if (showingMessage) return;
if (sliding) {
updateNullFromSlider(x);
}
};
game.down = function (x, y, obj) {
if (showingMessage) {
// Hide message on tap
messageContainer.visible = false;
showingMessage = false;
return;
}
// Start sliding if touch is on slide bar
if (slideBarContains(x, y)) {
sliding = true;
updateNullFromSlider(x);
return;
}
};
game.up = function (x, y, obj) {
sliding = false;
};
// Sync knob to nullEntity X at start and after level/message
function syncKnobToNull() {
var margin = 80 + nullEntity.radius;
var minX = margin;
var maxX = 2048 - margin;
var t = (nullEntity.x - minX) / (maxX - minX);
slideKnob.x = slideBarX + t * slideBarWidth;
}
// Initial sync
syncKnobToNull();
// Also call syncKnobToNull after level up and after message is hidden
var _oldNextLevel = nextLevel;
nextLevel = function nextLevel() {
_oldNextLevel();
// After message, sync knob
LK.setTimeout(syncKnobToNull, 100);
};
var _oldMessageHide = game.down;
game.down = function (x, y, obj) {
if (showingMessage) {
messageContainer.visible = false;
showingMessage = false;
LK.setTimeout(syncKnobToNull, 100);
return;
}
if (slideBarContains(x, y)) {
sliding = true;
updateNullFromSlider(x);
return;
}
};
// --- Collision Helpers ---
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;
}
// --- Spawning Obstacles and Fragments ---
var obstacleTimer = 0;
var fragmentTimer = 0;
function spawnObstacle() {
var obs = new Obstacle();
obs.x = 200 + Math.random() * (2048 - 400);
obs.y = -80;
obstacles.push(obs);
game.addChild(obs);
}
function spawnFragment() {
var frag = new Fragment();
frag.x = 200 + Math.random() * (2048 - 400);
frag.y = -60;
fragments.push(frag);
game.addChild(frag);
}
// --- Level Progression ---
function nextLevel() {
level += 1;
fragmentsCollected = 0;
fragmentsToNext = 5 + level * 2;
progressTxt.setText('Fragments: 0/' + fragmentsToNext);
// Show cryptic message
var msg = messages[(level - 2) % messages.length] || "The void deepens.";
messageTxt.setText(msg);
messageContainer.visible = true;
showingMessage = true;
// Increase difficulty: spawn rates
obstacleTimer = 0;
fragmentTimer = 0;
}
// --- Game Update Loop ---
game.update = function () {
if (showingMessage) return;
// Spawn obstacles
obstacleTimer++;
var obsInterval = Math.max(38 - level * 2, 16);
if (obstacleTimer >= obsInterval) {
spawnObstacle();
obstacleTimer = 0;
}
// Spawn fragments
fragmentTimer++;
var fragInterval = Math.max(120 - level * 8, 40);
if (fragmentTimer >= fragInterval) {
spawnFragment();
fragmentTimer = 0;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with nullEntity
if (circleRectIntersect(nullEntity.x, nullEntity.y, nullEntity.radius * 0.8, obs.x, obs.y, obs.width, obs.height)) {
// Flash and game over
LK.effects.flashScreen(0xffffff, 900);
LK.showGameOver();
return;
}
}
// Update fragments
for (var j = fragments.length - 1; j >= 0; j--) {
var frag = fragments[j];
frag.update();
// Remove if off screen
if (frag.y - frag.radius > 2732 + 80) {
frag.destroy();
fragments.splice(j, 1);
continue;
}
// Collision with nullEntity
if (circleCircleIntersect(nullEntity.x, nullEntity.y, nullEntity.radius * 0.8, frag.x, frag.y, frag.radius)) {
// Collect fragment
fragmentsCollected += 1;
progressTxt.setText('Fragments: ' + fragmentsCollected + '/' + fragmentsToNext);
// Subtle flash
LK.effects.flashObject(nullEntity, 0x8ecae6, 400);
frag.destroy();
fragments.splice(j, 1);
// Level up
if (fragmentsCollected >= fragmentsToNext) {
nextLevel();
}
continue;
}
}
};
// --- Initial Progress Text ---
progressTxt.setText('Fragments: 0/' + fragmentsToNext);
// --- Initial Message (Level 1) ---
messageTxt.setText("Guide the null. Collect fragments. Avoid the void.");
messageContainer.visible = true;
showingMessage = true;