User prompt
make the enemies npc that are independed from the other objects
User prompt
add a war background
User prompt
also add the future to win the first level after beating 10 enemies. and after going to the next level, make the goal is to beat 20 enemies
User prompt
make it so that the game takes long and it never ends unless you beat all the enemies that are trying to attack you. give our character a gun that can shoot when we click ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Dreams of the Forgotten Town
Initial prompt
Make me a game where the player wakes up in a quiet, abandoned town with no memory of who they are. The world should feel eerie but beautiful, like a dream that's slowly turning into a nightmare. The main character doesn't speak, but the people they meet seem to know them — and they’re hiding something. Let the gameplay focus on exploration, emotional storytelling, and solving symbolic puzzles tied to the protagonist's past. Every choice the player makes, even small ones, should quietly shape the story and how it ends. Use a hand-drawn, slightly surreal art style that feels like flipping through someone’s old sketchbook. There should be no combat — tension and conflict come from atmosphere, music, and the weight of decisions. Make the soundtrack dynamic, so it responds to where the player is and what they’re feeling. Themes like memory, identity, and regret should be at the core of everything. Think of it as a haunting mix of Omori, Inside, and a forgotten fairy tale.
/**** * 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 Class --- 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.targetX = 1024; self.targetY = 2000; self.lastX = self.x; self.lastY = self.y; self.update = function () { self.lastX = self.x; self.lastY = self.y; 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; };
===================================================================
--- original.js
+++ change.js
@@ -251,15 +251,24 @@
} else if (currentLevel === 2) {
enemiesToDefeat = 20;
enemiesDefeated = 0;
}
- // Background
- var bg = LK.getAsset('bgTown', {
+ // 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;
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