/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fire (goal) class
var Fire = Container.expand(function () {
var self = Container.call(this);
var fire = self.attachAsset('fire', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Memory orb class
var Memory = Container.expand(function () {
var self = Container.call(this);
var orb = self.attachAsset('memory', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
return self;
});
// Platform class
var Platform = Container.expand(function () {
var self = Container.call(this);
var plat = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Water (player) class
var Water = Container.expand(function () {
var self = Container.call(this);
var water = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.onGround = false;
self.canJump = true;
self.jumpCooldown = 0;
self.moveDir = 0; // -1 left, 1 right, 0 none
self.jumpQueued = false;
// For touch controls
self.touchStartX = null;
self.touchStartY = null;
self.touching = false;
// For narrative triggers
self.lastMemoryIndex = -1;
// Methods
self.update = function () {
// Horizontal movement
var moveSpeed = 18;
self.x += self.vx;
// Gravity
self.vy += 2.2;
if (self.vy > 40) self.vy = 40;
self.y += self.vy;
// Friction
self.vx *= 0.85;
if (Math.abs(self.vx) < 0.5) self.vx = 0;
// Prevent out of bounds
if (self.x < 60) self.x = 60;
if (self.x > 2048 - 60) self.x = 2048 - 60;
if (self.y > 2732 - 60) {
self.y = 2732 - 60;
self.vy = 0;
self.onGround = true;
}
// Jump cooldown
if (self.jumpCooldown > 0) self.jumpCooldown--;
// Touch controls: moveDir and jumpQueued are set by game.down/move/up
if (self.moveDir !== 0) {
self.vx += self.moveDir * moveSpeed * 0.5;
if (self.vx > moveSpeed) self.vx = moveSpeed;
if (self.vx < -moveSpeed) self.vx = -moveSpeed;
}
// Jump
if (self.jumpQueued && self.onGround && self.jumpCooldown === 0) {
self.vy = -38;
self.onGround = false;
self.jumpCooldown = 20;
self.jumpQueued = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a237e // Deep blue
});
/****
* Game Code
****/
// Goal (Fire) - orange ellipse
// Memory orb - glowing yellow ellipse
// Platform - soft gray rectangle
// Water (player) - blue ellipse
// --- Level Data ---
var levelPlatforms = [
// [x, y, width]
[1024, 2600, 800], [600, 2200, 400], [1500, 2000, 400], [400, 1700, 400], [1200, 1400, 400], [700, 1100, 400], [1700, 900, 400], [1024, 600, 600]];
var memoryPositions = [[600, 2100], [1500, 1900], [400, 1600], [1200, 1300], [700, 1000]];
var firePosition = [1024, 500];
// --- Narrative Data ---
var memoryTexts = ["I remember the first time we met. Fire was so bright, so warm.", "We danced together under the stars, our laughter echoing.", "Sometimes, Fire would flicker, uncertain. I tried to hold on.", "We shared dreams, hopes, and gentle silences.", "But one day, Fire was gone. I felt cold, empty."];
var introText = "I am Water. I have lost my Fire. Maybe, if I follow these memories, I can find them again.";
var fireText = "There you are, Fire. But maybe, what I truly needed was to find myself.";
// --- Game State ---
var platforms = [];
var memories = [];
var fire = null;
var water = null;
var collectedMemories = 0;
var showingNarrative = false;
var narrativeBox = null;
var narrativeTimeout = null;
var canControl = true;
// --- UI ---
var memoryCounter = new Text2("Memories: 0/" + memoryTexts.length, {
size: 80,
fill: 0xFFFDE7
});
memoryCounter.anchor.set(0.5, 0);
LK.gui.top.addChild(memoryCounter);
// --- Narrative Box ---
function showNarrative(text, duration) {
if (narrativeBox) {
LK.gui.center.removeChild(narrativeBox);
narrativeBox = null;
}
showingNarrative = true;
canControl = false;
narrativeBox = new Text2(text, {
size: 70,
fill: 0xFFFDE7,
align: "center",
wordWrap: true,
wordWrapWidth: 1200
});
narrativeBox.anchor.set(0.5, 0.5);
LK.gui.center.addChild(narrativeBox);
if (narrativeTimeout) LK.clearTimeout(narrativeTimeout);
narrativeTimeout = LK.setTimeout(function () {
LK.gui.center.removeChild(narrativeBox);
narrativeBox = null;
showingNarrative = false;
canControl = true;
}, duration || 2600);
}
// --- Build Level ---
function buildLevel() {
// Platforms
for (var i = 0; i < levelPlatforms.length; i++) {
var p = new Platform();
p.x = levelPlatforms[i][0];
p.y = levelPlatforms[i][1];
// Scale platform width
var platAsset = p.children[0];
platAsset.width = levelPlatforms[i][2];
platforms.push(p);
game.addChild(p);
}
// Memories
for (var j = 0; j < memoryPositions.length; j++) {
var m = new Memory();
m.x = memoryPositions[j][0];
m.y = memoryPositions[j][1];
m.memoryIndex = j;
memories.push(m);
game.addChild(m);
}
// Fire (goal)
fire = new Fire();
fire.x = firePosition[0];
fire.y = firePosition[1];
game.addChild(fire);
// Water (player)
water = new Water();
water.x = 1024;
water.y = 2500;
game.addChild(water);
}
// --- Collision Helpers ---
function rectsIntersect(a, b) {
// a, b: Containers with anchor at 0.5,0.5
var aw = a.children[0].width,
ah = a.children[0].height;
var bw = b.children[0].width,
bh = b.children[0].height;
return Math.abs(a.x - b.x) < aw / 2 + bw / 2 && Math.abs(a.y - b.y) < ah / 2 + bh / 2;
}
// --- Platform Collision ---
function handlePlatformCollision() {
water.onGround = false;
for (var i = 0; i < platforms.length; i++) {
var plat = platforms[i];
var pw = plat.children[0].width,
ph = plat.children[0].height;
var wx = water.x,
wy = water.y;
var ww = water.children[0].width,
wh = water.children[0].height;
// Simple AABB
if (wx + ww / 2 > plat.x - pw / 2 && wx - ww / 2 < plat.x + pw / 2 && wy + wh / 2 > plat.y - ph / 2 && wy - wh / 2 < plat.y + ph / 2) {
// Check if falling onto platform (from above)
var prevY = wy - water.vy;
if (prevY + wh / 2 <= plat.y - ph / 2) {
// Land on platform
water.y = plat.y - ph / 2 - wh / 2;
water.vy = 0;
water.onGround = true;
} else if (prevY - wh / 2 >= plat.y + ph / 2) {
// Hit from below
water.y = plat.y + ph / 2 + wh / 2;
water.vy = 2;
} else {
// Side collision: push out
if (wx < plat.x) {
water.x = plat.x - pw / 2 - ww / 2;
} else {
water.x = plat.x + pw / 2 + ww / 2;
}
water.vx = 0;
}
}
}
}
// --- Memory Collection ---
function handleMemoryCollection() {
for (var i = 0; i < memories.length; i++) {
var m = memories[i];
if (!m.collected && rectsIntersect(water, m)) {
m.collected = true;
collectedMemories++;
memoryCounter.setText("Memories: " + collectedMemories + "/" + memoryTexts.length);
// Animate and remove
tween(m, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
m.destroy();
}
});
// Show narrative
showNarrative(memoryTexts[m.memoryIndex], 3200);
}
}
}
// --- Fire (Goal) ---
var reachedFire = false;
function handleFireReach() {
if (!reachedFire && rectsIntersect(water, fire)) {
reachedFire = true;
showNarrative(fireText, 3500);
LK.setTimeout(function () {
LK.showYouWin();
}, 3600);
}
}
// --- Intro ---
showNarrative(introText, 3500);
// --- Build Level ---
buildLevel();
// --- Touch Controls ---
// Touch: left half = move left, right half = move right, swipe up = jump
game.down = function (x, y, obj) {
if (!canControl) return;
if (x < 2048 / 2) {
water.moveDir = -1;
} else {
water.moveDir = 1;
}
water.touchStartX = x;
water.touchStartY = y;
water.touching = true;
};
game.move = function (x, y, obj) {
if (!canControl) return;
if (!water.touching) return;
// Detect swipe up for jump
if (water.touchStartY - y > 80) {
water.jumpQueued = true;
water.touchStartY = y; // Prevent multiple jumps per swipe
}
};
game.up = function (x, y, obj) {
if (!canControl) return;
water.moveDir = 0;
water.touching = false;
};
// --- Game Update ---
game.update = function () {
if (!water) return;
if (!canControl) return;
water.update();
handlePlatformCollision();
handleMemoryCollection();
handleFireReach();
};
// --- Reset on Game Over ---
game.on('gameover', function () {
// All state will be reset by LK
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fire (goal) class
var Fire = Container.expand(function () {
var self = Container.call(this);
var fire = self.attachAsset('fire', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Memory orb class
var Memory = Container.expand(function () {
var self = Container.call(this);
var orb = self.attachAsset('memory', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
return self;
});
// Platform class
var Platform = Container.expand(function () {
var self = Container.call(this);
var plat = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Water (player) class
var Water = Container.expand(function () {
var self = Container.call(this);
var water = self.attachAsset('water', {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.onGround = false;
self.canJump = true;
self.jumpCooldown = 0;
self.moveDir = 0; // -1 left, 1 right, 0 none
self.jumpQueued = false;
// For touch controls
self.touchStartX = null;
self.touchStartY = null;
self.touching = false;
// For narrative triggers
self.lastMemoryIndex = -1;
// Methods
self.update = function () {
// Horizontal movement
var moveSpeed = 18;
self.x += self.vx;
// Gravity
self.vy += 2.2;
if (self.vy > 40) self.vy = 40;
self.y += self.vy;
// Friction
self.vx *= 0.85;
if (Math.abs(self.vx) < 0.5) self.vx = 0;
// Prevent out of bounds
if (self.x < 60) self.x = 60;
if (self.x > 2048 - 60) self.x = 2048 - 60;
if (self.y > 2732 - 60) {
self.y = 2732 - 60;
self.vy = 0;
self.onGround = true;
}
// Jump cooldown
if (self.jumpCooldown > 0) self.jumpCooldown--;
// Touch controls: moveDir and jumpQueued are set by game.down/move/up
if (self.moveDir !== 0) {
self.vx += self.moveDir * moveSpeed * 0.5;
if (self.vx > moveSpeed) self.vx = moveSpeed;
if (self.vx < -moveSpeed) self.vx = -moveSpeed;
}
// Jump
if (self.jumpQueued && self.onGround && self.jumpCooldown === 0) {
self.vy = -38;
self.onGround = false;
self.jumpCooldown = 20;
self.jumpQueued = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a237e // Deep blue
});
/****
* Game Code
****/
// Goal (Fire) - orange ellipse
// Memory orb - glowing yellow ellipse
// Platform - soft gray rectangle
// Water (player) - blue ellipse
// --- Level Data ---
var levelPlatforms = [
// [x, y, width]
[1024, 2600, 800], [600, 2200, 400], [1500, 2000, 400], [400, 1700, 400], [1200, 1400, 400], [700, 1100, 400], [1700, 900, 400], [1024, 600, 600]];
var memoryPositions = [[600, 2100], [1500, 1900], [400, 1600], [1200, 1300], [700, 1000]];
var firePosition = [1024, 500];
// --- Narrative Data ---
var memoryTexts = ["I remember the first time we met. Fire was so bright, so warm.", "We danced together under the stars, our laughter echoing.", "Sometimes, Fire would flicker, uncertain. I tried to hold on.", "We shared dreams, hopes, and gentle silences.", "But one day, Fire was gone. I felt cold, empty."];
var introText = "I am Water. I have lost my Fire. Maybe, if I follow these memories, I can find them again.";
var fireText = "There you are, Fire. But maybe, what I truly needed was to find myself.";
// --- Game State ---
var platforms = [];
var memories = [];
var fire = null;
var water = null;
var collectedMemories = 0;
var showingNarrative = false;
var narrativeBox = null;
var narrativeTimeout = null;
var canControl = true;
// --- UI ---
var memoryCounter = new Text2("Memories: 0/" + memoryTexts.length, {
size: 80,
fill: 0xFFFDE7
});
memoryCounter.anchor.set(0.5, 0);
LK.gui.top.addChild(memoryCounter);
// --- Narrative Box ---
function showNarrative(text, duration) {
if (narrativeBox) {
LK.gui.center.removeChild(narrativeBox);
narrativeBox = null;
}
showingNarrative = true;
canControl = false;
narrativeBox = new Text2(text, {
size: 70,
fill: 0xFFFDE7,
align: "center",
wordWrap: true,
wordWrapWidth: 1200
});
narrativeBox.anchor.set(0.5, 0.5);
LK.gui.center.addChild(narrativeBox);
if (narrativeTimeout) LK.clearTimeout(narrativeTimeout);
narrativeTimeout = LK.setTimeout(function () {
LK.gui.center.removeChild(narrativeBox);
narrativeBox = null;
showingNarrative = false;
canControl = true;
}, duration || 2600);
}
// --- Build Level ---
function buildLevel() {
// Platforms
for (var i = 0; i < levelPlatforms.length; i++) {
var p = new Platform();
p.x = levelPlatforms[i][0];
p.y = levelPlatforms[i][1];
// Scale platform width
var platAsset = p.children[0];
platAsset.width = levelPlatforms[i][2];
platforms.push(p);
game.addChild(p);
}
// Memories
for (var j = 0; j < memoryPositions.length; j++) {
var m = new Memory();
m.x = memoryPositions[j][0];
m.y = memoryPositions[j][1];
m.memoryIndex = j;
memories.push(m);
game.addChild(m);
}
// Fire (goal)
fire = new Fire();
fire.x = firePosition[0];
fire.y = firePosition[1];
game.addChild(fire);
// Water (player)
water = new Water();
water.x = 1024;
water.y = 2500;
game.addChild(water);
}
// --- Collision Helpers ---
function rectsIntersect(a, b) {
// a, b: Containers with anchor at 0.5,0.5
var aw = a.children[0].width,
ah = a.children[0].height;
var bw = b.children[0].width,
bh = b.children[0].height;
return Math.abs(a.x - b.x) < aw / 2 + bw / 2 && Math.abs(a.y - b.y) < ah / 2 + bh / 2;
}
// --- Platform Collision ---
function handlePlatformCollision() {
water.onGround = false;
for (var i = 0; i < platforms.length; i++) {
var plat = platforms[i];
var pw = plat.children[0].width,
ph = plat.children[0].height;
var wx = water.x,
wy = water.y;
var ww = water.children[0].width,
wh = water.children[0].height;
// Simple AABB
if (wx + ww / 2 > plat.x - pw / 2 && wx - ww / 2 < plat.x + pw / 2 && wy + wh / 2 > plat.y - ph / 2 && wy - wh / 2 < plat.y + ph / 2) {
// Check if falling onto platform (from above)
var prevY = wy - water.vy;
if (prevY + wh / 2 <= plat.y - ph / 2) {
// Land on platform
water.y = plat.y - ph / 2 - wh / 2;
water.vy = 0;
water.onGround = true;
} else if (prevY - wh / 2 >= plat.y + ph / 2) {
// Hit from below
water.y = plat.y + ph / 2 + wh / 2;
water.vy = 2;
} else {
// Side collision: push out
if (wx < plat.x) {
water.x = plat.x - pw / 2 - ww / 2;
} else {
water.x = plat.x + pw / 2 + ww / 2;
}
water.vx = 0;
}
}
}
}
// --- Memory Collection ---
function handleMemoryCollection() {
for (var i = 0; i < memories.length; i++) {
var m = memories[i];
if (!m.collected && rectsIntersect(water, m)) {
m.collected = true;
collectedMemories++;
memoryCounter.setText("Memories: " + collectedMemories + "/" + memoryTexts.length);
// Animate and remove
tween(m, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
m.destroy();
}
});
// Show narrative
showNarrative(memoryTexts[m.memoryIndex], 3200);
}
}
}
// --- Fire (Goal) ---
var reachedFire = false;
function handleFireReach() {
if (!reachedFire && rectsIntersect(water, fire)) {
reachedFire = true;
showNarrative(fireText, 3500);
LK.setTimeout(function () {
LK.showYouWin();
}, 3600);
}
}
// --- Intro ---
showNarrative(introText, 3500);
// --- Build Level ---
buildLevel();
// --- Touch Controls ---
// Touch: left half = move left, right half = move right, swipe up = jump
game.down = function (x, y, obj) {
if (!canControl) return;
if (x < 2048 / 2) {
water.moveDir = -1;
} else {
water.moveDir = 1;
}
water.touchStartX = x;
water.touchStartY = y;
water.touching = true;
};
game.move = function (x, y, obj) {
if (!canControl) return;
if (!water.touching) return;
// Detect swipe up for jump
if (water.touchStartY - y > 80) {
water.jumpQueued = true;
water.touchStartY = y; // Prevent multiple jumps per swipe
}
};
game.up = function (x, y, obj) {
if (!canControl) return;
water.moveDir = 0;
water.touching = false;
};
// --- Game Update ---
game.update = function () {
if (!water) return;
if (!canControl) return;
water.update();
handlePlatformCollision();
handleMemoryCollection();
handleFireReach();
};
// --- Reset on Game Over ---
game.on('gameover', function () {
// All state will be reset by LK
});