/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { ending: 0, foundFragments: 0 }); /**** * Classes ****/ // --- Bullet Class --- var Bullet = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); self.speed = 38; self.dirX = 0; self.dirY = 0; self.lastX = self.x; self.lastY = self.y; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; return self; }); // --- Global arrays for enemies and bullets --- // Door class (scene transition) var Door = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5 }); self.targetScene = ""; self.highlight = null; self.showHighlight = function () { if (!self.highlight) { self.highlight = self.attachAsset('highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.10 }); } self.highlight.visible = true; }; self.hideHighlight = function () { if (self.highlight) self.highlight.visible = false; }; return self; }); // --- Enemy NPC Class (independent from other objects) --- var Enemy = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('npc', { anchorX: 0.5, anchorY: 0.5, tint: 0x8b1a1a }); self.speed = 6 + Math.random() * 4; self.lastX = self.x; self.lastY = self.y; // Each enemy is an independent NPC, not derived from other objects self.update = function () { self.lastX = self.x; self.lastY = self.y; // Move toward the player independently var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.x += self.speed * dx / dist; self.y += self.speed * dy / dist; } }; return self; }); // Memory Fragment class var MemoryFragment = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('memoryFragment', { anchorX: 0.5, anchorY: 0.5 }); self.collected = false; self.highlight = null; self.showHighlight = function () { if (!self.highlight) { self.highlight = self.attachAsset('highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.13 }); } self.highlight.visible = true; }; self.hideHighlight = function () { if (self.highlight) self.highlight.visible = false; }; return self; }); // NPC class var NPC = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('npc', { anchorX: 0.5, anchorY: 0.5 }); self.dialogue = []; self.dialogueIndex = 0; self.name = ""; self.interacted = false; self.highlight = null; // Show highlight when player is near self.showHighlight = function () { if (!self.highlight) { self.highlight = self.attachAsset('highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.18 }); } self.highlight.visible = true; }; self.hideHighlight = function () { if (self.highlight) self.highlight.visible = false; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); var sprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 18; self.targetX = self.x; self.targetY = self.y; self.moving = false; // Move towards target self.update = function () { if (!self.moving) return; var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.speed) { self.x = self.targetX; self.y = self.targetY; self.moving = false; } else { self.x += self.speed * dx / dist; self.y += self.speed * dy / dist; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xfaf7f2 }); /**** * Game Code ****/ // --- Global arrays for enemies and bullets --- // --- State --- /* We use simple shapes for the sketchbook-like look. - 'player': the protagonist, a hand-drawn ellipse. - 'npc': townsfolk, a box with a different color. - 'memoryFragment': symbolic puzzle piece, a star-shaped box. - 'door': a rectangle for scene transitions. - 'bgTown': a large, pale rectangle as the background. - 'highlight': a transparent overlay for interactable objects. */ var enemies = []; var bullets = []; var player; var npcs = []; var memoryFragments = []; var doors = []; var currentScene = "town"; var dialogueBox = null; var dialogueText = null; var choiceButtons = []; var infoText = null; var canMove = true; var lastNearObject = null; var foundFragments = storage.foundFragments || 0; var totalFragments = 3; var ending = storage.ending || 0; // --- Level Progression --- var currentLevel = 1; var enemiesDefeated = 0; var enemiesToDefeat = 10; // Level 1 goal function nextLevel() { currentLevel++; if (currentLevel === 2) { enemiesToDefeat = 20; enemiesDefeated = 0; showInfo("Level 2: Defeat 20 enemies!", 3000); // Optionally, respawn some memory fragments or change scene visuals here } else { // If more levels, add here // For now, after level 2, allow ending as before } } // --- GUI --- function showInfo(msg, duration) { if (!infoText) { infoText = new Text2(msg, { size: 80, fill: 0x444444 }); infoText.anchor.set(0.5, 0); LK.gui.top.addChild(infoText); } infoText.setText(msg); infoText.visible = true; if (duration) { LK.setTimeout(function () { if (infoText) infoText.visible = false; }, duration); } } // --- Scene Setup --- function setupTownScene() { // Clear previous game.removeChildren(); npcs = []; memoryFragments = []; doors = []; lastNearObject = null; // --- Reset level state --- enemies = []; bullets = []; if (currentLevel === 1) { enemiesToDefeat = 10; enemiesDefeated = 0; } else if (currentLevel === 2) { enemiesToDefeat = 20; enemiesDefeated = 0; } // War-torn background var warBg = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(warBg); // Overlay town sketchbook background for style var bg = LK.getAsset('bgTown', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.55 // Let the war background show through }); game.addChild(bg); // Player player = new Player(); player.x = 1024; player.y = 2000; game.addChild(player); // NPCs var npc1 = new NPC(); npc1.x = 700; npc1.y = 1500; npc1.name = "Caretaker"; npc1.dialogue = ["You look lost. The town remembers you, even if you don't remember it.", "Find the fragments of your memory. They are hidden in plain sight.", "Come back when you remember more."]; npcs.push(npc1); game.addChild(npc1); var npc2 = new NPC(); npc2.x = 1400; npc2.y = 1100; npc2.name = "Child"; npc2.dialogue = ["Will you play with me? Or are you too busy remembering?", "Sometimes, forgetting is easier than facing the truth."]; npcs.push(npc2); game.addChild(npc2); // Memory Fragments var frag1 = new MemoryFragment(); frag1.x = 400; frag1.y = 800; memoryFragments.push(frag1); game.addChild(frag1); var frag2 = new MemoryFragment(); frag2.x = 1700; frag2.y = 1800; memoryFragments.push(frag2); game.addChild(frag2); var frag3 = new MemoryFragment(); frag3.x = 1200; frag3.y = 2500; memoryFragments.push(frag3); game.addChild(frag3); // Door to ending var door = new Door(); door.x = 1024; door.y = 400; door.targetScene = "ending"; doors.push(door); game.addChild(door); // Show info showInfo("Explore the town. Tap to move. Find your memories.", 3000); } // --- Dialogue System --- function showDialogue(npc) { canMove = false; if (!dialogueBox) { dialogueBox = new Container(); var box = LK.getAsset('bgTown', { width: 1600, height: 400, color: 0xffffff, anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2200 }); box.alpha = 0.92; dialogueBox.addChild(box); dialogueText = new Text2("", { size: 70, fill: 0x222222 }); dialogueText.anchor.set(0.5, 0.5); dialogueText.x = 1024; dialogueText.y = 2200; dialogueBox.addChild(dialogueText); LK.gui.bottom.addChild(dialogueBox); } dialogueBox.visible = true; dialogueText.setText(npc.name + ": " + npc.dialogue[npc.dialogueIndex]); // Next dialogue on tap dialogueBox.down = function (x, y, obj) { npc.dialogueIndex++; if (npc.dialogueIndex >= npc.dialogue.length) { npc.dialogueIndex = 0; dialogueBox.visible = false; canMove = true; } else { dialogueText.setText(npc.name + ": " + npc.dialogue[npc.dialogueIndex]); } LK.getSound('choice').play(); }; } // --- Choice System --- function showChoice(msg, choices, onSelect) { canMove = false; // Remove old for (var i = 0; i < choiceButtons.length; ++i) { if (choiceButtons[i].parent) choiceButtons[i].parent.removeChild(choiceButtons[i]); } choiceButtons = []; // Message if (!dialogueBox) { dialogueBox = new Container(); var box = LK.getAsset('bgTown', { width: 1600, height: 400, color: 0xffffff, anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2200 }); box.alpha = 0.92; dialogueBox.addChild(box); dialogueText = new Text2("", { size: 70, fill: 0x222222 }); dialogueText.anchor.set(0.5, 0.5); dialogueText.x = 1024; dialogueText.y = 2200; dialogueBox.addChild(dialogueText); LK.gui.bottom.addChild(dialogueBox); } dialogueBox.visible = true; dialogueText.setText(msg); // Buttons for (var i = 0; i < choices.length; ++i) { var btn = new Text2(choices[i], { size: 70, fill: 0x3A3A3A }); btn.anchor.set(0.5, 0.5); btn.x = 1024; btn.y = 2400 + i * 120; btn.interactive = true; (function (idx) { btn.down = function (x, y, obj) { dialogueBox.visible = false; for (var j = 0; j < choiceButtons.length; ++j) { if (choiceButtons[j].parent) choiceButtons[j].parent.removeChild(choiceButtons[j]); } choiceButtons = []; canMove = true; LK.getSound('choice').play(); onSelect(idx); }; })(i); LK.gui.bottom.addChild(btn); choiceButtons.push(btn); } } // --- Memory Fragment Collection --- function collectFragment(fragment) { if (fragment.collected) return; fragment.collected = true; foundFragments++; storage.foundFragments = foundFragments; LK.getSound('memoryFound').play(); tween(fragment, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (fragment.parent) fragment.parent.removeChild(fragment); } }); showInfo("You found a memory fragment! (" + foundFragments + "/" + totalFragments + ")", 2000); if (foundFragments === totalFragments) { showInfo("All fragments found. The door is open.", 3000); } } // --- Ending Scene --- function setupEndingScene() { game.removeChildren(); if (dialogueBox) dialogueBox.visible = false; for (var i = 0; i < choiceButtons.length; ++i) { if (choiceButtons[i].parent) choiceButtons[i].parent.removeChild(choiceButtons[i]); } choiceButtons = []; // Background var bg = LK.getAsset('bgTown', { anchorX: 0, anchorY: 0, x: 0, y: 0, color: 0xeaeaea }); game.addChild(bg); // Ending text var endingText = new Text2("", { size: 110, fill: 0x222222 }); endingText.anchor.set(0.5, 0.5); endingText.x = 1024; endingText.y = 1200; if (foundFragments === totalFragments) { endingText.setText("You remember everything.\nThe town is at peace.\n\nThank you for playing."); storage.ending = 2; } else if (foundFragments > 0) { endingText.setText("Some memories return, but many are lost.\nThe town remains a mystery.\n\nThank you for playing."); storage.ending = 1; } else { endingText.setText("You leave, still lost.\nThe town fades behind you.\n\nThank you for playing."); storage.ending = 0; } game.addChild(endingText); // Show "You Win" after a short delay LK.setTimeout(function () { LK.showYouWin(); }, 2500); } // --- Music --- LK.playMusic('dream', { fade: { start: 0, end: 0.7, duration: 1200 } }); // --- Start Scene --- setupTownScene(); // --- Game Move Handler --- game.move = function (x, y, obj) { if (!canMove) return; // Prevent moving into top left menu area if (x < 100 && y < 100) return; // Move player player.targetX = x; player.targetY = y; player.moving = true; }; // --- Game Down Handler (for interaction) --- game.down = function (x, y, obj) { if (!canMove) return; // --- Gun shooting --- // Don't shoot if clicking in top left menu area if (!(x < 100 && y < 100)) { // Shoot a bullet towards (x, y) var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y; var dx = x - player.x; var dy = y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) { bullet.dirX = 0; bullet.dirY = -1; } else { bullet.dirX = dx / dist; bullet.dirY = dy / dist; } bullets.push(bullet); game.addChild(bullet); } // Check for nearby interactables var nearObj = null; // NPCs for (var i = 0; i < npcs.length; ++i) { var npc = npcs[i]; var dx = npc.x - player.x; var dy = npc.y - player.y; if (dx * dx + dy * dy < 300 * 300) { nearObj = npc; break; } } // Memory Fragments if (!nearObj) { for (var i = 0; i < memoryFragments.length; ++i) { var frag = memoryFragments[i]; if (frag.collected) continue; var dx = frag.x - player.x; var dy = frag.y - player.y; if (dx * dx + dy * dy < 180 * 180) { nearObj = frag; break; } } } // Door if (!nearObj && foundFragments === totalFragments) { for (var i = 0; i < doors.length; ++i) { var door = doors[i]; var dx = door.x - player.x; var dy = door.y - player.y; if (dx * dx + dy * dy < 220 * 220) { nearObj = door; break; } } } // Interact if (nearObj) { if (nearObj instanceof NPC) { showDialogue(nearObj); } else if (nearObj instanceof MemoryFragment) { collectFragment(nearObj); } else if (nearObj instanceof Door) { // Ending setupEndingScene(); } } }; // --- Enemy Spawning --- if (!game.enemySpawnTimer) { game.enemySpawnTimer = LK.setInterval(function () { if (currentScene !== "town") return; // Limit number of enemies if (enemies.length < 8 + Math.floor(foundFragments * 2)) { var enemy = new Enemy(); // Spawn at random edge var edge = Math.floor(Math.random() * 4); if (edge === 0) { // top enemy.x = Math.random() * 2048; enemy.y = -100; } else if (edge === 1) { // bottom enemy.x = Math.random() * 2048; enemy.y = 2732 + 100; } else if (edge === 2) { // left enemy.x = -100; enemy.y = Math.random() * 2732; } else { // right enemy.x = 2048 + 100; enemy.y = Math.random() * 2732; } enemies.push(enemy); game.addChild(enemy); } }, 1200); } // --- Game Update --- game.update = function () { // Player update if (player && player.update) player.update(); // --- Bullets update and cleanup --- for (var i = bullets.length - 1; i >= 0; --i) { var bullet = bullets[i]; if (bullet.update) bullet.update(); // Remove if out of bounds if (bullet.x < -200 || bullet.x > 2048 + 200 || bullet.y < -200 || bullet.y > 2732 + 200) { if (bullet.parent) bullet.parent.removeChild(bullet); bullets.splice(i, 1); continue; } } // --- Enemies update, collision with player, and bullet collision --- for (var i = enemies.length - 1; i >= 0; --i) { var enemy = enemies[i]; if (enemy.update) enemy.update(); // Check collision with player (game over) var dx = enemy.x - player.x; var dy = enemy.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 90) { // Game over LK.effects.flashScreen(0xff0000, 1200); LK.showGameOver(); return; } // Check collision with bullets for (var j = bullets.length - 1; j >= 0; --j) { var bullet = bullets[j]; var bdx = enemy.x - bullet.x; var bdy = enemy.y - bullet.y; var bdist = Math.sqrt(bdx * bdx + bdy * bdy); if (bdist < 80) { // Remove enemy and bullet if (enemy.parent) enemy.parent.removeChild(enemy); if (bullet.parent) bullet.parent.removeChild(bullet); enemies.splice(i, 1); bullets.splice(j, 1); // --- Level Progression: Count defeated enemies --- enemiesDefeated++; showInfo("Enemies defeated: " + enemiesDefeated + "/" + enemiesToDefeat, 1200); // Check for level win if (enemiesDefeated >= enemiesToDefeat) { if (currentLevel === 1) { showInfo("Level 1 complete! Advancing...", 2500); LK.setTimeout(function () { nextLevel(); }, 2000); } else if (currentLevel === 2) { showInfo("Level 2 complete! All enemies defeated!", 2500); // Optionally, allow ending or further progression // For now, open the door if all fragments are found } } break; } } } // --- Endless: Only win if all enemies are gone and all fragments found --- if (foundFragments === totalFragments && enemiesDefeated >= enemiesToDefeat && enemies.length === 0) { // Door is open, allow ending for (var i = 0; i < doors.length; ++i) { doors[i].showHighlight(); } } // Highlight nearby interactables var nearest = null; var minDist = 99999; // NPCs for (var i = 0; i < npcs.length; ++i) { var npc = npcs[i]; var dx = npc.x - player.x; var dy = npc.y - player.y; var dist = dx * dx + dy * dy; if (dist < 300 * 300 && dist < minDist) { nearest = npc; minDist = dist; } npc.hideHighlight(); } // Memory Fragments for (var i = 0; i < memoryFragments.length; ++i) { var frag = memoryFragments[i]; if (frag.collected) continue; var dx = frag.x - player.x; var dy = frag.y - player.y; var dist = dx * dx + dy * dy; if (dist < 180 * 180 && dist < minDist) { nearest = frag; minDist = dist; } frag.hideHighlight(); } // Door if (foundFragments === totalFragments && enemies.length === 0) { for (var i = 0; i < doors.length; ++i) { var door = doors[i]; var dx = door.x - player.x; var dy = door.y - player.y; var dist = dx * dx + dy * dy; if (dist < 220 * 220 && dist < minDist) { nearest = door; minDist = dist; } door.hideHighlight(); } } // Show highlight if (nearest && nearest.showHighlight) nearest.showHighlight(); // Store last near object lastNearObject = nearest; };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
ending: 0,
foundFragments: 0
});
/****
* Classes
****/
// --- Bullet Class ---
var Bullet = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.speed = 38;
self.dirX = 0;
self.dirY = 0;
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// --- Global arrays for enemies and bullets ---
// Door class (scene transition)
var Door = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetScene = "";
self.highlight = null;
self.showHighlight = function () {
if (!self.highlight) {
self.highlight = self.attachAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.10
});
}
self.highlight.visible = true;
};
self.hideHighlight = function () {
if (self.highlight) self.highlight.visible = false;
};
return self;
});
// --- Enemy NPC Class (independent from other objects) ---
var Enemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('npc', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x8b1a1a
});
self.speed = 6 + Math.random() * 4;
self.lastX = self.x;
self.lastY = self.y;
// Each enemy is an independent NPC, not derived from other objects
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Move toward the player independently
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += self.speed * dx / dist;
self.y += self.speed * dy / dist;
}
};
return self;
});
// Memory Fragment class
var MemoryFragment = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('memoryFragment', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.highlight = null;
self.showHighlight = function () {
if (!self.highlight) {
self.highlight = self.attachAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.13
});
}
self.highlight.visible = true;
};
self.hideHighlight = function () {
if (self.highlight) self.highlight.visible = false;
};
return self;
});
// NPC class
var NPC = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('npc', {
anchorX: 0.5,
anchorY: 0.5
});
self.dialogue = [];
self.dialogueIndex = 0;
self.name = "";
self.interacted = false;
self.highlight = null;
// Show highlight when player is near
self.showHighlight = function () {
if (!self.highlight) {
self.highlight = self.attachAsset('highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.18
});
}
self.highlight.visible = true;
};
self.hideHighlight = function () {
if (self.highlight) self.highlight.visible = false;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18;
self.targetX = self.x;
self.targetY = self.y;
self.moving = false;
// Move towards target
self.update = function () {
if (!self.moving) return;
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.speed) {
self.x = self.targetX;
self.y = self.targetY;
self.moving = false;
} else {
self.x += self.speed * dx / dist;
self.y += self.speed * dy / dist;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xfaf7f2
});
/****
* Game Code
****/
// --- Global arrays for enemies and bullets ---
// --- State ---
/*
We use simple shapes for the sketchbook-like look.
- 'player': the protagonist, a hand-drawn ellipse.
- 'npc': townsfolk, a box with a different color.
- 'memoryFragment': symbolic puzzle piece, a star-shaped box.
- 'door': a rectangle for scene transitions.
- 'bgTown': a large, pale rectangle as the background.
- 'highlight': a transparent overlay for interactable objects.
*/
var enemies = [];
var bullets = [];
var player;
var npcs = [];
var memoryFragments = [];
var doors = [];
var currentScene = "town";
var dialogueBox = null;
var dialogueText = null;
var choiceButtons = [];
var infoText = null;
var canMove = true;
var lastNearObject = null;
var foundFragments = storage.foundFragments || 0;
var totalFragments = 3;
var ending = storage.ending || 0;
// --- Level Progression ---
var currentLevel = 1;
var enemiesDefeated = 0;
var enemiesToDefeat = 10; // Level 1 goal
function nextLevel() {
currentLevel++;
if (currentLevel === 2) {
enemiesToDefeat = 20;
enemiesDefeated = 0;
showInfo("Level 2: Defeat 20 enemies!", 3000);
// Optionally, respawn some memory fragments or change scene visuals here
} else {
// If more levels, add here
// For now, after level 2, allow ending as before
}
}
// --- GUI ---
function showInfo(msg, duration) {
if (!infoText) {
infoText = new Text2(msg, {
size: 80,
fill: 0x444444
});
infoText.anchor.set(0.5, 0);
LK.gui.top.addChild(infoText);
}
infoText.setText(msg);
infoText.visible = true;
if (duration) {
LK.setTimeout(function () {
if (infoText) infoText.visible = false;
}, duration);
}
}
// --- Scene Setup ---
function setupTownScene() {
// Clear previous
game.removeChildren();
npcs = [];
memoryFragments = [];
doors = [];
lastNearObject = null;
// --- Reset level state ---
enemies = [];
bullets = [];
if (currentLevel === 1) {
enemiesToDefeat = 10;
enemiesDefeated = 0;
} else if (currentLevel === 2) {
enemiesToDefeat = 20;
enemiesDefeated = 0;
}
// War-torn background
var warBg = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(warBg);
// Overlay town sketchbook background for style
var bg = LK.getAsset('bgTown', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.55 // Let the war background show through
});
game.addChild(bg);
// Player
player = new Player();
player.x = 1024;
player.y = 2000;
game.addChild(player);
// NPCs
var npc1 = new NPC();
npc1.x = 700;
npc1.y = 1500;
npc1.name = "Caretaker";
npc1.dialogue = ["You look lost. The town remembers you, even if you don't remember it.", "Find the fragments of your memory. They are hidden in plain sight.", "Come back when you remember more."];
npcs.push(npc1);
game.addChild(npc1);
var npc2 = new NPC();
npc2.x = 1400;
npc2.y = 1100;
npc2.name = "Child";
npc2.dialogue = ["Will you play with me? Or are you too busy remembering?", "Sometimes, forgetting is easier than facing the truth."];
npcs.push(npc2);
game.addChild(npc2);
// Memory Fragments
var frag1 = new MemoryFragment();
frag1.x = 400;
frag1.y = 800;
memoryFragments.push(frag1);
game.addChild(frag1);
var frag2 = new MemoryFragment();
frag2.x = 1700;
frag2.y = 1800;
memoryFragments.push(frag2);
game.addChild(frag2);
var frag3 = new MemoryFragment();
frag3.x = 1200;
frag3.y = 2500;
memoryFragments.push(frag3);
game.addChild(frag3);
// Door to ending
var door = new Door();
door.x = 1024;
door.y = 400;
door.targetScene = "ending";
doors.push(door);
game.addChild(door);
// Show info
showInfo("Explore the town. Tap to move. Find your memories.", 3000);
}
// --- Dialogue System ---
function showDialogue(npc) {
canMove = false;
if (!dialogueBox) {
dialogueBox = new Container();
var box = LK.getAsset('bgTown', {
width: 1600,
height: 400,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2200
});
box.alpha = 0.92;
dialogueBox.addChild(box);
dialogueText = new Text2("", {
size: 70,
fill: 0x222222
});
dialogueText.anchor.set(0.5, 0.5);
dialogueText.x = 1024;
dialogueText.y = 2200;
dialogueBox.addChild(dialogueText);
LK.gui.bottom.addChild(dialogueBox);
}
dialogueBox.visible = true;
dialogueText.setText(npc.name + ": " + npc.dialogue[npc.dialogueIndex]);
// Next dialogue on tap
dialogueBox.down = function (x, y, obj) {
npc.dialogueIndex++;
if (npc.dialogueIndex >= npc.dialogue.length) {
npc.dialogueIndex = 0;
dialogueBox.visible = false;
canMove = true;
} else {
dialogueText.setText(npc.name + ": " + npc.dialogue[npc.dialogueIndex]);
}
LK.getSound('choice').play();
};
}
// --- Choice System ---
function showChoice(msg, choices, onSelect) {
canMove = false;
// Remove old
for (var i = 0; i < choiceButtons.length; ++i) {
if (choiceButtons[i].parent) choiceButtons[i].parent.removeChild(choiceButtons[i]);
}
choiceButtons = [];
// Message
if (!dialogueBox) {
dialogueBox = new Container();
var box = LK.getAsset('bgTown', {
width: 1600,
height: 400,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2200
});
box.alpha = 0.92;
dialogueBox.addChild(box);
dialogueText = new Text2("", {
size: 70,
fill: 0x222222
});
dialogueText.anchor.set(0.5, 0.5);
dialogueText.x = 1024;
dialogueText.y = 2200;
dialogueBox.addChild(dialogueText);
LK.gui.bottom.addChild(dialogueBox);
}
dialogueBox.visible = true;
dialogueText.setText(msg);
// Buttons
for (var i = 0; i < choices.length; ++i) {
var btn = new Text2(choices[i], {
size: 70,
fill: 0x3A3A3A
});
btn.anchor.set(0.5, 0.5);
btn.x = 1024;
btn.y = 2400 + i * 120;
btn.interactive = true;
(function (idx) {
btn.down = function (x, y, obj) {
dialogueBox.visible = false;
for (var j = 0; j < choiceButtons.length; ++j) {
if (choiceButtons[j].parent) choiceButtons[j].parent.removeChild(choiceButtons[j]);
}
choiceButtons = [];
canMove = true;
LK.getSound('choice').play();
onSelect(idx);
};
})(i);
LK.gui.bottom.addChild(btn);
choiceButtons.push(btn);
}
}
// --- Memory Fragment Collection ---
function collectFragment(fragment) {
if (fragment.collected) return;
fragment.collected = true;
foundFragments++;
storage.foundFragments = foundFragments;
LK.getSound('memoryFound').play();
tween(fragment, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (fragment.parent) fragment.parent.removeChild(fragment);
}
});
showInfo("You found a memory fragment! (" + foundFragments + "/" + totalFragments + ")", 2000);
if (foundFragments === totalFragments) {
showInfo("All fragments found. The door is open.", 3000);
}
}
// --- Ending Scene ---
function setupEndingScene() {
game.removeChildren();
if (dialogueBox) dialogueBox.visible = false;
for (var i = 0; i < choiceButtons.length; ++i) {
if (choiceButtons[i].parent) choiceButtons[i].parent.removeChild(choiceButtons[i]);
}
choiceButtons = [];
// Background
var bg = LK.getAsset('bgTown', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
color: 0xeaeaea
});
game.addChild(bg);
// Ending text
var endingText = new Text2("", {
size: 110,
fill: 0x222222
});
endingText.anchor.set(0.5, 0.5);
endingText.x = 1024;
endingText.y = 1200;
if (foundFragments === totalFragments) {
endingText.setText("You remember everything.\nThe town is at peace.\n\nThank you for playing.");
storage.ending = 2;
} else if (foundFragments > 0) {
endingText.setText("Some memories return, but many are lost.\nThe town remains a mystery.\n\nThank you for playing.");
storage.ending = 1;
} else {
endingText.setText("You leave, still lost.\nThe town fades behind you.\n\nThank you for playing.");
storage.ending = 0;
}
game.addChild(endingText);
// Show "You Win" after a short delay
LK.setTimeout(function () {
LK.showYouWin();
}, 2500);
}
// --- Music ---
LK.playMusic('dream', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
// --- Start Scene ---
setupTownScene();
// --- Game Move Handler ---
game.move = function (x, y, obj) {
if (!canMove) return;
// Prevent moving into top left menu area
if (x < 100 && y < 100) return;
// Move player
player.targetX = x;
player.targetY = y;
player.moving = true;
};
// --- Game Down Handler (for interaction) ---
game.down = function (x, y, obj) {
if (!canMove) return;
// --- Gun shooting ---
// Don't shoot if clicking in top left menu area
if (!(x < 100 && y < 100)) {
// Shoot a bullet towards (x, y)
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y;
var dx = x - player.x;
var dy = y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist === 0) {
bullet.dirX = 0;
bullet.dirY = -1;
} else {
bullet.dirX = dx / dist;
bullet.dirY = dy / dist;
}
bullets.push(bullet);
game.addChild(bullet);
}
// Check for nearby interactables
var nearObj = null;
// NPCs
for (var i = 0; i < npcs.length; ++i) {
var npc = npcs[i];
var dx = npc.x - player.x;
var dy = npc.y - player.y;
if (dx * dx + dy * dy < 300 * 300) {
nearObj = npc;
break;
}
}
// Memory Fragments
if (!nearObj) {
for (var i = 0; i < memoryFragments.length; ++i) {
var frag = memoryFragments[i];
if (frag.collected) continue;
var dx = frag.x - player.x;
var dy = frag.y - player.y;
if (dx * dx + dy * dy < 180 * 180) {
nearObj = frag;
break;
}
}
}
// Door
if (!nearObj && foundFragments === totalFragments) {
for (var i = 0; i < doors.length; ++i) {
var door = doors[i];
var dx = door.x - player.x;
var dy = door.y - player.y;
if (dx * dx + dy * dy < 220 * 220) {
nearObj = door;
break;
}
}
}
// Interact
if (nearObj) {
if (nearObj instanceof NPC) {
showDialogue(nearObj);
} else if (nearObj instanceof MemoryFragment) {
collectFragment(nearObj);
} else if (nearObj instanceof Door) {
// Ending
setupEndingScene();
}
}
};
// --- Enemy Spawning ---
if (!game.enemySpawnTimer) {
game.enemySpawnTimer = LK.setInterval(function () {
if (currentScene !== "town") return;
// Limit number of enemies
if (enemies.length < 8 + Math.floor(foundFragments * 2)) {
var enemy = new Enemy();
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// top
enemy.x = Math.random() * 2048;
enemy.y = -100;
} else if (edge === 1) {
// bottom
enemy.x = Math.random() * 2048;
enemy.y = 2732 + 100;
} else if (edge === 2) {
// left
enemy.x = -100;
enemy.y = Math.random() * 2732;
} else {
// right
enemy.x = 2048 + 100;
enemy.y = Math.random() * 2732;
}
enemies.push(enemy);
game.addChild(enemy);
}
}, 1200);
}
// --- Game Update ---
game.update = function () {
// Player update
if (player && player.update) player.update();
// --- Bullets update and cleanup ---
for (var i = bullets.length - 1; i >= 0; --i) {
var bullet = bullets[i];
if (bullet.update) bullet.update();
// Remove if out of bounds
if (bullet.x < -200 || bullet.x > 2048 + 200 || bullet.y < -200 || bullet.y > 2732 + 200) {
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(i, 1);
continue;
}
}
// --- Enemies update, collision with player, and bullet collision ---
for (var i = enemies.length - 1; i >= 0; --i) {
var enemy = enemies[i];
if (enemy.update) enemy.update();
// Check collision with player (game over)
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 90) {
// Game over
LK.effects.flashScreen(0xff0000, 1200);
LK.showGameOver();
return;
}
// Check collision with bullets
for (var j = bullets.length - 1; j >= 0; --j) {
var bullet = bullets[j];
var bdx = enemy.x - bullet.x;
var bdy = enemy.y - bullet.y;
var bdist = Math.sqrt(bdx * bdx + bdy * bdy);
if (bdist < 80) {
// Remove enemy and bullet
if (enemy.parent) enemy.parent.removeChild(enemy);
if (bullet.parent) bullet.parent.removeChild(bullet);
enemies.splice(i, 1);
bullets.splice(j, 1);
// --- Level Progression: Count defeated enemies ---
enemiesDefeated++;
showInfo("Enemies defeated: " + enemiesDefeated + "/" + enemiesToDefeat, 1200);
// Check for level win
if (enemiesDefeated >= enemiesToDefeat) {
if (currentLevel === 1) {
showInfo("Level 1 complete! Advancing...", 2500);
LK.setTimeout(function () {
nextLevel();
}, 2000);
} else if (currentLevel === 2) {
showInfo("Level 2 complete! All enemies defeated!", 2500);
// Optionally, allow ending or further progression
// For now, open the door if all fragments are found
}
}
break;
}
}
}
// --- Endless: Only win if all enemies are gone and all fragments found ---
if (foundFragments === totalFragments && enemiesDefeated >= enemiesToDefeat && enemies.length === 0) {
// Door is open, allow ending
for (var i = 0; i < doors.length; ++i) {
doors[i].showHighlight();
}
}
// Highlight nearby interactables
var nearest = null;
var minDist = 99999;
// NPCs
for (var i = 0; i < npcs.length; ++i) {
var npc = npcs[i];
var dx = npc.x - player.x;
var dy = npc.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < 300 * 300 && dist < minDist) {
nearest = npc;
minDist = dist;
}
npc.hideHighlight();
}
// Memory Fragments
for (var i = 0; i < memoryFragments.length; ++i) {
var frag = memoryFragments[i];
if (frag.collected) continue;
var dx = frag.x - player.x;
var dy = frag.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < 180 * 180 && dist < minDist) {
nearest = frag;
minDist = dist;
}
frag.hideHighlight();
}
// Door
if (foundFragments === totalFragments && enemies.length === 0) {
for (var i = 0; i < doors.length; ++i) {
var door = doors[i];
var dx = door.x - player.x;
var dy = door.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < 220 * 220 && dist < minDist) {
nearest = door;
minDist = dist;
}
door.hideHighlight();
}
}
// Show highlight
if (nearest && nearest.showHighlight) nearest.showHighlight();
// Store last near object
lastNearObject = nearest;
};
a bullet. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
an angry character that has a soldier hat on his head. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a pixel art coin. In-Game asset. 2d. High contrast. No shadows
a pixel war background. In-Game asset. 2d. High contrast. No shadows
an enemy that smiles evilly. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat