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