Code edit (1 edits merged)
Please save this source code
User prompt
Plague Doctor: Village Purge
User prompt
Make a game where we are plague doctors and travel from village to village to find sick people and execute them. There should be walking keys to move our character in the game and a start screen. Instead of a large map, there should be a spatial village map where we can move horizontally and there should be normal and sick people around. We need to find and kill sick people.put a button to execute sick villagers and we can move up and down not just left and rightThe villagers should not suddenly appear, they should already be there
User prompt
The villagers should not suddenly appear, they should already be there
User prompt
put a button to execute sick villagers and we can move up and down not just left and right
Initial prompt
Make a game where we are plague doctors and travel from village to village to find sick people and execute them. There should be walking keys to move our character in the game and a start screen. Instead of a large map, there should be a spatial village map where we can move horizontally and there should be normal and sick people around. We need to find and kill sick people.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // D-Pad Button class var DPadButton = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('dpadBtn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.bg.alpha = 0.8; // Slightly more visible self.icon = null; // Will be set by game code return self; }); // Execute Button class var ExecuteButton = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('executeBtn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3 }); self.bg.alpha = 0.95; self.label = new Text2('EXECUTE', { size: 70, fill: "#fff" }); self.label.anchor.set(0.5, 0.5); self.addChild(self.label); self.visible = false; return self; }); // House class var House = Container.expand(function () { var self = Container.call(this); // Use a placeholder house image; you can swap the asset id for your own house image self.sprite = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 2.2 }); // For collision and placement, use the sprite's width/height self.getBounds = function () { return { x: self.x - self.sprite.width / 2, y: self.y - self.sprite.height / 2, width: self.sprite.width, height: self.sprite.height }; }; return self; }); // Plague Doctor (player) class var PlagueDoctor = Container.expand(function () { var self = Container.call(this); var doctorSprite = self.attachAsset('plagueDoctor', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 18; // Movement speed per tick // For collision box, use the asset's width/height self.getBounds = function () { return { x: self.x - doctorSprite.width / 2, y: self.y - doctorSprite.height / 2, width: doctorSprite.width, height: doctorSprite.height }; }; return self; }); // Villager class var Villager = Container.expand(function () { var self = Container.call(this); self.isSick = false; self.isAlive = true; self.init = function (isSick) { self.isSick = isSick; self.isAlive = true; self.removeChildren(); var assetId = isSick ? 'villagerSick' : 'villagerHealthy'; self.sprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); }; self.getBounds = function () { return { x: self.x - self.sprite.width / 2, y: self.y - self.sprite.height / 2, width: self.sprite.width, height: self.sprite.height }; }; self.execute = function () { self.isAlive = false; // Animate fade out and shrink tween(self.sprite, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 400, easing: tween.cubicIn, onFinish: function onFinish() { self.visible = false; } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf2e5bc }); /**** * Game Code ****/ // Background image for the village map (should be at least VILLAGE_WIDTH x VILLAGE_HEIGHT) // D-pad buttons // "Execute" button // Sick villager // Healthy villager // Plague Doctor (player) // --- Game constants --- var VILLAGE_WIDTH = 6144; // Expanded map width (3x standard screen width) var VILLAGE_HEIGHT = 4096; // Expanded map height (2x standard screen height) var NUM_VILLAGERS = 40; // More villagers for the larger map var NUM_SICK = 15; // More sick villagers var VILLAGER_MIN_DIST = 220; // Minimum distance between villagers // --- Game state --- var player; var villagers = []; var sickLeft = NUM_SICK; var executeBtn; var dpadBtns = {}; var dpadState = { up: false, down: false, left: false, right: false }; var cameraX = 0; // For horizontal scrolling var cameraY = 0; // For vertical scrolling var playerWorldX = 0; // Player's true position in world coordinates var playerWorldY = 0; // Player's true position in world coordinates var draggingDPad = null; var canExecuteVillager = null; // Reference to sick villager in range // --- GUI elements --- var sickLeftTxt = new Text2('Sick left: ' + sickLeft, { size: 90, fill: 0xB16262 }); sickLeftTxt.anchor.set(0.5, 0); // Place at top center, but not in top 100px LK.gui.top.addChild(sickLeftTxt); sickLeftTxt.y = 110; // --- Game Start Screen Overlay --- var startScreenOverlay = new Container(); // Add a start screen image to the start screen overlay var overlayStartImg = LK.getAsset('startScreenImg', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1, x: 2048 / 2, y: 900 }); overlayStartImg.alpha = 1; startScreenOverlay.addChild(overlayStartImg); // Add a background image to the start screen overlay (behind the start image) var overlayBgImg = LK.getAsset('villageBg', { anchorX: 0.5, anchorY: 0.5, scaleX: 2048 / 30, scaleY: 2732 / 30, x: 2048 / 2, y: 2732 / 2 }); overlayBgImg.alpha = 0.98; startScreenOverlay.addChild(overlayBgImg); // Add a semi-transparent circle overlay for contrast var overlayBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 30, scaleY: 30, x: 2048 / 2, y: 2732 / 2 }); overlayBg.alpha = 0.92; startScreenOverlay.addChild(overlayBg); // Add a large plague doctor image to the start screen var overlayDoctor = LK.getAsset('plagueDoctor', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.2, scaleY: 3.2, x: 2048 / 2, y: 600 }); startScreenOverlay.addChild(overlayDoctor); var titleText = new Text2('Plague Doctor', { size: 180, fill: "#222" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 900; startScreenOverlay.addChild(titleText); var subtitleText = new Text2('Hunt down and execute sick villagers', { size: 80, fill: "#444" }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1100; startScreenOverlay.addChild(subtitleText); var startBtn = LK.getAsset('executeBtn', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, x: 2048 / 2, y: 1600 }); startScreenOverlay.addChild(startBtn); var startBtnLabel = new Text2('START', { size: 110, fill: "#fff" }); startBtnLabel.anchor.set(0.5, 0.5); startBtnLabel.x = 2048 / 2; startBtnLabel.y = 1600; startScreenOverlay.addChild(startBtnLabel); var startScreenActive = true; game.addChild(startScreenOverlay); // Helper functions function clamp(val, min, max) { if (val < min) return min; if (val > max) return max; return val; } // Axis-aligned bounding box collision function aabbIntersect(a, b) { return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y; } // --- Add background image --- var backgroundImage = LK.getAsset('villageBg', { anchorX: 0, anchorY: 0, x: 0, y: 0, scaleX: 1, scaleY: 1 }); backgroundImage.width = VILLAGE_WIDTH; backgroundImage.height = VILLAGE_HEIGHT; game.addChild(backgroundImage); // --- Map and villagers setup --- // Array to hold house objects var houses = []; // Starting position is the center of the map (no offset needed for scrolling map) var mapCenterX = VILLAGE_WIDTH / 2; var mapCenterY = VILLAGE_HEIGHT / 2; // Check if a position is clear for placing objects (no collision with existing objects) function isPositionClear(x, y, radius, objectArrays) { // Check distance from map center (player start) if (Math.abs(x - mapCenterX) < 300 && Math.abs(y - mapCenterY) < 300) { return false; } // Check distance from existing objects for (var i = 0; i < objectArrays.length; i++) { var objects = objectArrays[i]; for (var j = 0; j < objects.length; j++) { var obj = objects[j]; var dx = x - obj.x; var dy = y - obj.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < radius) { return false; } } } return true; } // Place objects randomly on the map function placeObjects(objectClass, count, minDistance, container) { for (var i = 0; i < count; i++) { var object = new objectClass(); // Try to find a valid position var x, y; var attempts = 0; var placed = false; while (!placed && attempts < 50) { // Random position with padding from edges x = 200 + Math.random() * (VILLAGE_WIDTH - 400); y = 200 + Math.random() * (VILLAGE_HEIGHT - 400); if (isPositionClear(x, y, minDistance, [villagers, houses])) { object.x = x; object.y = y; // Store original position for camera calculations object.x0 = x; object.y0 = y; game.addChild(object); container.push(object); placed = true; } attempts++; } } } // Place villagers randomly, but not too close to each other or the player start function placeVillagers() { villagers = []; var placed = 0; var sickPlaced = 0; while (placed < NUM_VILLAGERS) { var isSick = sickPlaced < NUM_SICK ? true : false; var villager = new Villager(); villager.init(isSick); // Random position in map bounds with padding from edges var x = 200 + Math.random() * (VILLAGE_WIDTH - 400); var y = 200 + Math.random() * (VILLAGE_HEIGHT - 400); if (isPositionClear(x, y, VILLAGER_MIN_DIST, [villagers])) { villager.x = x; villager.y = y; // Store original position for camera calculations villager.x0 = x; villager.y0 = y; game.addChild(villager); villagers.push(villager); if (isSick) sickPlaced++; placed++; } } } // --- Player setup --- player = new PlagueDoctor(); playerWorldX = mapCenterX; // Initialize player world coordinates at map center playerWorldY = mapCenterY; // Initialize player world coordinates at map center player.x = mapCenterX; // Start player at map center player.y = mapCenterY; // Start player at map center game.addChild(player); // --- Houses --- var NUM_HOUSES = 18; var HOUSE_MIN_DIST = 420; placeObjects(House, NUM_HOUSES, HOUSE_MIN_DIST, houses); // --- Villagers --- placeVillagers(); // --- Execute Button --- // Place execute button in the bottom left corner of the screen executeBtn = new ExecuteButton(); executeBtn.x = executeBtn.width * 0.7; // Position with some padding from left edge executeBtn.y = LK.gui.bottomLeft.height - executeBtn.height * 0.7; // Position with some padding from bottom LK.gui.bottomLeft.addChild(executeBtn); // --- D-Pad Controls (fit to screen, always visible in bottom middle using LK.gui.bottom) --- var dpadSize = Math.floor(LK.gui.bottom.width * 0.24); // Larger buttons (24% of screen width) if (dpadSize < 120) dpadSize = 120; // Larger minimum size if (dpadSize > 220) dpadSize = 220; // Larger maximum size // Further increase margin for more space between keys var dpadMargin = Math.floor(dpadSize * 1.6); // Adjust spacing relative to larger buttons // Place the D-pad cluster in the bottom middle of the screen, moved higher up var dpadCenterX = LK.gui.bottom.width / 2; // Center horizontally var dpadCenterY = LK.gui.bottom.height - dpadSize * 3.8; // Position higher above bottom function createDPadBtns() { // Set spacing between buttons var dpadSpacing = dpadMargin * 1.4; // Adjusted spacing for larger buttons // Up var btnUp = new DPadButton(); btnUp.x = dpadCenterX; btnUp.y = dpadCenterY - dpadSpacing; btnUp.icon = new Text2('▲', { size: Math.floor(dpadSize * 0.5), fill: "#222" }); btnUp.icon.anchor.set(0.5, 0.5); btnUp.addChild(btnUp.icon); LK.gui.bottom.addChild(btnUp); dpadBtns['up'] = btnUp; // Down var btnDown = new DPadButton(); btnDown.x = dpadCenterX; btnDown.y = dpadCenterY + dpadSpacing; btnDown.icon = new Text2('▼', { size: Math.floor(dpadSize * 0.5), fill: "#222" }); btnDown.icon.anchor.set(0.5, 0.5); btnDown.addChild(btnDown.icon); LK.gui.bottom.addChild(btnDown); dpadBtns['down'] = btnDown; // Left var btnLeft = new DPadButton(); btnLeft.x = dpadCenterX - dpadSpacing; btnLeft.y = dpadCenterY; btnLeft.icon = new Text2('◀', { size: Math.floor(dpadSize * 0.5), fill: "#222" }); btnLeft.icon.anchor.set(0.5, 0.5); btnLeft.addChild(btnLeft.icon); LK.gui.bottom.addChild(btnLeft); dpadBtns['left'] = btnLeft; // Right var btnRight = new DPadButton(); btnRight.x = dpadCenterX + dpadSpacing; btnRight.y = dpadCenterY; btnRight.icon = new Text2('▶', { size: Math.floor(dpadSize * 0.5), fill: "#222" }); btnRight.icon.anchor.set(0.5, 0.5); btnRight.addChild(btnRight.icon); LK.gui.bottom.addChild(btnRight); dpadBtns['right'] = btnRight; } createDPadBtns(); // --- Start screen overlay event handling --- startScreenOverlay.down = function (x, y, obj) { // Check if start button is pressed var btnX = 2048 / 2; var btnY = 1600; var btnW = startBtn.width * 0.5; var btnH = startBtn.height * 0.5; if (x >= btnX - btnW && x <= btnX + btnW && y >= btnY - btnH && y <= btnY + btnH) { // Hide overlay, show UI, enable game startScreenOverlay.visible = false; startScreenActive = false; } }; // --- D-Pad event handling --- function dpadBtnForPos(x, y) { var r = dpadSize / 2; for (var dir in dpadBtns) { var btn = dpadBtns[dir]; var bx = btn.x; var by = btn.y; if ((x - bx) * (x - bx) + (y - by) * (y - by) < r * r) { return dir; } } return null; } // D-Pad touch/mouse down LK.gui.bottom.down = function (x, y, obj) { var dir = dpadBtnForPos(x, y); if (dir) { dpadState[dir] = true; draggingDPad = dir; tween(dpadBtns[dir].bg, { alpha: 1 }, { duration: 80 }); } }; // D-Pad touch/mouse up LK.gui.bottom.up = function (x, y, obj) { if (draggingDPad) { dpadState[draggingDPad] = false; tween(dpadBtns[draggingDPad].bg, { alpha: 0.7 }, { duration: 120 }); draggingDPad = null; } }; // D-Pad move (drag) LK.gui.bottom.move = function (x, y, obj) { if (draggingDPad) { var dir = dpadBtnForPos(x, y); if (dir === draggingDPad) { if (!dpadState[dir]) { dpadState[dir] = true; tween(dpadBtns[dir].bg, { alpha: 1 }, { duration: 80 }); } } else { if (dpadState[draggingDPad]) { dpadState[draggingDPad] = false; tween(dpadBtns[draggingDPad].bg, { alpha: 0.7 }, { duration: 120 }); } } } }; // --- Execute Button event handling --- LK.gui.bottomLeft.down = function (x, y, obj) { // Check if the click is within the execute button bounds if (executeBtn.visible && x >= executeBtn.x - executeBtn.width / 2 && x <= executeBtn.x + executeBtn.width / 2 && y >= executeBtn.y - executeBtn.height / 2 && y <= executeBtn.y + executeBtn.height / 2) { if (canExecuteVillager && canExecuteVillager.isAlive) { canExecuteVillager.execute(); sickLeft--; sickLeftTxt.setText('Sick left: ' + sickLeft); executeBtn.visible = false; canExecuteVillager = null; // Win condition if (sickLeft === 0) { LK.showYouWin(); } } } }; // --- Main game move handler (for dragging player, not used here) --- game.move = function (x, y, obj) { // No drag-to-move, only D-pad }; // --- Main game update loop --- game.update = function () { // If start screen is active, block all game logic and hide UI if (startScreenActive) { // Hide all game objects except overlay for (var i = 0; i < game.children.length; ++i) { var obj = game.children[i]; if (obj !== startScreenOverlay) obj.visible = false;else obj.visible = true; } // Hide execute button and sick left text executeBtn.visible = false; sickLeftTxt.visible = false; // Hide D-pad for (var dir in dpadBtns) { dpadBtns[dir].visible = false; } return; } else { // Show UI when game is started sickLeftTxt.visible = true; for (var dir in dpadBtns) { dpadBtns[dir].visible = true; } } // --- Player movement --- var dx = 0, dy = 0; if (dpadState.left) dx -= 1; if (dpadState.right) dx += 1; if (dpadState.up) dy -= 1; if (dpadState.down) dy += 1; if (dx !== 0 || dy !== 0) { var len = Math.sqrt(dx * dx + dy * dy); if (len > 0) { dx /= len; dy /= len; } // Calculate new position before actually moving var newWorldX = playerWorldX + dx * player.speed; var newWorldY = playerWorldY + dy * player.speed; // Store player's world coordinates separately from rendering position playerWorldX = newWorldX; playerWorldY = newWorldY; } // Clamp player to world coordinates map bounds with consistent padding on all sides playerWorldX = clamp(playerWorldX, 60, VILLAGE_WIDTH - 60); playerWorldY = clamp(playerWorldY, 60, VILLAGE_HEIGHT - 60); // --- Camera scrolling (horizontal and vertical) --- var screenCenterX = 2048 / 2; // Use fixed resolution width var screenCenterY = 2732 / 2; // Use fixed resolution height // Calculate camera position to center player cameraX = playerWorldX - screenCenterX; cameraY = playerWorldY - screenCenterY; // Clamp camera to map bounds cameraX = clamp(cameraX, 0, VILLAGE_WIDTH - 2048); cameraY = clamp(cameraY, 0, VILLAGE_HEIGHT - 2732); // Move background image with camera (always at the back) backgroundImage.x = -cameraX; backgroundImage.y = -cameraY; // Move all game children (player, villagers) relative to camera position for (var i = 0; i < game.children.length; ++i) { var obj = game.children[i]; obj.visible = true; if (obj === player) { // Position player on screen based on world coordinates obj.x = playerWorldX - cameraX; obj.y = playerWorldY - cameraY; } else if (obj instanceof Villager || obj instanceof House) { // Position objects on screen based on their world coordinates // Make sure we're using the stored world coordinates consistently obj.x = obj.x0 - cameraX; obj.y = obj.y0 - cameraY; // Hide objects that are completely outside the visible area if (obj.x < -300 || obj.x > 2348 || obj.y < -300 || obj.y > 3032) { obj.visible = false; } } } // --- Villager proximity check --- canExecuteVillager = null; for (var i = 0; i < villagers.length; ++i) { var villager = villagers[i]; if (!villager.isAlive || !villager.isSick) continue; // Use world coordinates for collision var px = playerWorldX; var py = playerWorldY; var vx = villager.x0; // Always use stored world coordinates var vy = villager.y0; // Always use stored world coordinates var dist = Math.sqrt((px - vx) * (px - vx) + (py - vy) * (py - vy)); if (dist < 140) { canExecuteVillager = villager; break; } } // --- Execute button visibility --- if (canExecuteVillager && canExecuteVillager.isAlive) { executeBtn.visible = true; } else { executeBtn.visible = false; } }; // --- Store original world positions for villagers (for camera) --- function storeVillagerPositions() { // Store villager positions for (var i = 0; i < villagers.length; ++i) { villagers[i].x0 = villagers[i].x; villagers[i].y0 = villagers[i].y; } } // Store initial positions storeVillagerPositions(); // --- Initial camera position --- cameraX = mapCenterX - 2048 / 2; // Start camera centered on the player using fixed width cameraY = mapCenterY - 2732 / 2; // Start camera centered on the player using fixed height for (var i = 0; i < game.children.length; ++i) { var obj = game.children[i]; if (obj === player) { obj.x = player.x - cameraX; obj.y = player.y - cameraY; } else if (obj instanceof Villager) { obj.x = obj.x0 - cameraX; obj.y = obj.y0 - cameraY; } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// D-Pad Button class
var DPadButton = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('dpadBtn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.bg.alpha = 0.8; // Slightly more visible
self.icon = null; // Will be set by game code
return self;
});
// Execute Button class
var ExecuteButton = Container.expand(function () {
var self = Container.call(this);
self.bg = self.attachAsset('executeBtn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
self.bg.alpha = 0.95;
self.label = new Text2('EXECUTE', {
size: 70,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
self.visible = false;
return self;
});
// House class
var House = Container.expand(function () {
var self = Container.call(this);
// Use a placeholder house image; you can swap the asset id for your own house image
self.sprite = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
// For collision and placement, use the sprite's width/height
self.getBounds = function () {
return {
x: self.x - self.sprite.width / 2,
y: self.y - self.sprite.height / 2,
width: self.sprite.width,
height: self.sprite.height
};
};
return self;
});
// Plague Doctor (player) class
var PlagueDoctor = Container.expand(function () {
var self = Container.call(this);
var doctorSprite = self.attachAsset('plagueDoctor', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18; // Movement speed per tick
// For collision box, use the asset's width/height
self.getBounds = function () {
return {
x: self.x - doctorSprite.width / 2,
y: self.y - doctorSprite.height / 2,
width: doctorSprite.width,
height: doctorSprite.height
};
};
return self;
});
// Villager class
var Villager = Container.expand(function () {
var self = Container.call(this);
self.isSick = false;
self.isAlive = true;
self.init = function (isSick) {
self.isSick = isSick;
self.isAlive = true;
self.removeChildren();
var assetId = isSick ? 'villagerSick' : 'villagerHealthy';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
self.getBounds = function () {
return {
x: self.x - self.sprite.width / 2,
y: self.y - self.sprite.height / 2,
width: self.sprite.width,
height: self.sprite.height
};
};
self.execute = function () {
self.isAlive = false;
// Animate fade out and shrink
tween(self.sprite, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf2e5bc
});
/****
* Game Code
****/
// Background image for the village map (should be at least VILLAGE_WIDTH x VILLAGE_HEIGHT)
// D-pad buttons
// "Execute" button
// Sick villager
// Healthy villager
// Plague Doctor (player)
// --- Game constants ---
var VILLAGE_WIDTH = 6144; // Expanded map width (3x standard screen width)
var VILLAGE_HEIGHT = 4096; // Expanded map height (2x standard screen height)
var NUM_VILLAGERS = 40; // More villagers for the larger map
var NUM_SICK = 15; // More sick villagers
var VILLAGER_MIN_DIST = 220; // Minimum distance between villagers
// --- Game state ---
var player;
var villagers = [];
var sickLeft = NUM_SICK;
var executeBtn;
var dpadBtns = {};
var dpadState = {
up: false,
down: false,
left: false,
right: false
};
var cameraX = 0; // For horizontal scrolling
var cameraY = 0; // For vertical scrolling
var playerWorldX = 0; // Player's true position in world coordinates
var playerWorldY = 0; // Player's true position in world coordinates
var draggingDPad = null;
var canExecuteVillager = null; // Reference to sick villager in range
// --- GUI elements ---
var sickLeftTxt = new Text2('Sick left: ' + sickLeft, {
size: 90,
fill: 0xB16262
});
sickLeftTxt.anchor.set(0.5, 0);
// Place at top center, but not in top 100px
LK.gui.top.addChild(sickLeftTxt);
sickLeftTxt.y = 110;
// --- Game Start Screen Overlay ---
var startScreenOverlay = new Container();
// Add a start screen image to the start screen overlay
var overlayStartImg = LK.getAsset('startScreenImg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1,
x: 2048 / 2,
y: 900
});
overlayStartImg.alpha = 1;
startScreenOverlay.addChild(overlayStartImg);
// Add a background image to the start screen overlay (behind the start image)
var overlayBgImg = LK.getAsset('villageBg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2048 / 30,
scaleY: 2732 / 30,
x: 2048 / 2,
y: 2732 / 2
});
overlayBgImg.alpha = 0.98;
startScreenOverlay.addChild(overlayBgImg);
// Add a semi-transparent circle overlay for contrast
var overlayBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 30,
scaleY: 30,
x: 2048 / 2,
y: 2732 / 2
});
overlayBg.alpha = 0.92;
startScreenOverlay.addChild(overlayBg);
// Add a large plague doctor image to the start screen
var overlayDoctor = LK.getAsset('plagueDoctor', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.2,
scaleY: 3.2,
x: 2048 / 2,
y: 600
});
startScreenOverlay.addChild(overlayDoctor);
var titleText = new Text2('Plague Doctor', {
size: 180,
fill: "#222"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 900;
startScreenOverlay.addChild(titleText);
var subtitleText = new Text2('Hunt down and execute sick villagers', {
size: 80,
fill: "#444"
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1100;
startScreenOverlay.addChild(subtitleText);
var startBtn = LK.getAsset('executeBtn', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
x: 2048 / 2,
y: 1600
});
startScreenOverlay.addChild(startBtn);
var startBtnLabel = new Text2('START', {
size: 110,
fill: "#fff"
});
startBtnLabel.anchor.set(0.5, 0.5);
startBtnLabel.x = 2048 / 2;
startBtnLabel.y = 1600;
startScreenOverlay.addChild(startBtnLabel);
var startScreenActive = true;
game.addChild(startScreenOverlay);
// Helper functions
function clamp(val, min, max) {
if (val < min) return min;
if (val > max) return max;
return val;
}
// Axis-aligned bounding box collision
function aabbIntersect(a, b) {
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
}
// --- Add background image ---
var backgroundImage = LK.getAsset('villageBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
backgroundImage.width = VILLAGE_WIDTH;
backgroundImage.height = VILLAGE_HEIGHT;
game.addChild(backgroundImage);
// --- Map and villagers setup ---
// Array to hold house objects
var houses = [];
// Starting position is the center of the map (no offset needed for scrolling map)
var mapCenterX = VILLAGE_WIDTH / 2;
var mapCenterY = VILLAGE_HEIGHT / 2;
// Check if a position is clear for placing objects (no collision with existing objects)
function isPositionClear(x, y, radius, objectArrays) {
// Check distance from map center (player start)
if (Math.abs(x - mapCenterX) < 300 && Math.abs(y - mapCenterY) < 300) {
return false;
}
// Check distance from existing objects
for (var i = 0; i < objectArrays.length; i++) {
var objects = objectArrays[i];
for (var j = 0; j < objects.length; j++) {
var obj = objects[j];
var dx = x - obj.x;
var dy = y - obj.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < radius) {
return false;
}
}
}
return true;
}
// Place objects randomly on the map
function placeObjects(objectClass, count, minDistance, container) {
for (var i = 0; i < count; i++) {
var object = new objectClass();
// Try to find a valid position
var x, y;
var attempts = 0;
var placed = false;
while (!placed && attempts < 50) {
// Random position with padding from edges
x = 200 + Math.random() * (VILLAGE_WIDTH - 400);
y = 200 + Math.random() * (VILLAGE_HEIGHT - 400);
if (isPositionClear(x, y, minDistance, [villagers, houses])) {
object.x = x;
object.y = y;
// Store original position for camera calculations
object.x0 = x;
object.y0 = y;
game.addChild(object);
container.push(object);
placed = true;
}
attempts++;
}
}
}
// Place villagers randomly, but not too close to each other or the player start
function placeVillagers() {
villagers = [];
var placed = 0;
var sickPlaced = 0;
while (placed < NUM_VILLAGERS) {
var isSick = sickPlaced < NUM_SICK ? true : false;
var villager = new Villager();
villager.init(isSick);
// Random position in map bounds with padding from edges
var x = 200 + Math.random() * (VILLAGE_WIDTH - 400);
var y = 200 + Math.random() * (VILLAGE_HEIGHT - 400);
if (isPositionClear(x, y, VILLAGER_MIN_DIST, [villagers])) {
villager.x = x;
villager.y = y;
// Store original position for camera calculations
villager.x0 = x;
villager.y0 = y;
game.addChild(villager);
villagers.push(villager);
if (isSick) sickPlaced++;
placed++;
}
}
}
// --- Player setup ---
player = new PlagueDoctor();
playerWorldX = mapCenterX; // Initialize player world coordinates at map center
playerWorldY = mapCenterY; // Initialize player world coordinates at map center
player.x = mapCenterX; // Start player at map center
player.y = mapCenterY; // Start player at map center
game.addChild(player);
// --- Houses ---
var NUM_HOUSES = 18;
var HOUSE_MIN_DIST = 420;
placeObjects(House, NUM_HOUSES, HOUSE_MIN_DIST, houses);
// --- Villagers ---
placeVillagers();
// --- Execute Button ---
// Place execute button in the bottom left corner of the screen
executeBtn = new ExecuteButton();
executeBtn.x = executeBtn.width * 0.7; // Position with some padding from left edge
executeBtn.y = LK.gui.bottomLeft.height - executeBtn.height * 0.7; // Position with some padding from bottom
LK.gui.bottomLeft.addChild(executeBtn);
// --- D-Pad Controls (fit to screen, always visible in bottom middle using LK.gui.bottom) ---
var dpadSize = Math.floor(LK.gui.bottom.width * 0.24); // Larger buttons (24% of screen width)
if (dpadSize < 120) dpadSize = 120; // Larger minimum size
if (dpadSize > 220) dpadSize = 220; // Larger maximum size
// Further increase margin for more space between keys
var dpadMargin = Math.floor(dpadSize * 1.6); // Adjust spacing relative to larger buttons
// Place the D-pad cluster in the bottom middle of the screen, moved higher up
var dpadCenterX = LK.gui.bottom.width / 2; // Center horizontally
var dpadCenterY = LK.gui.bottom.height - dpadSize * 3.8; // Position higher above bottom
function createDPadBtns() {
// Set spacing between buttons
var dpadSpacing = dpadMargin * 1.4; // Adjusted spacing for larger buttons
// Up
var btnUp = new DPadButton();
btnUp.x = dpadCenterX;
btnUp.y = dpadCenterY - dpadSpacing;
btnUp.icon = new Text2('▲', {
size: Math.floor(dpadSize * 0.5),
fill: "#222"
});
btnUp.icon.anchor.set(0.5, 0.5);
btnUp.addChild(btnUp.icon);
LK.gui.bottom.addChild(btnUp);
dpadBtns['up'] = btnUp;
// Down
var btnDown = new DPadButton();
btnDown.x = dpadCenterX;
btnDown.y = dpadCenterY + dpadSpacing;
btnDown.icon = new Text2('▼', {
size: Math.floor(dpadSize * 0.5),
fill: "#222"
});
btnDown.icon.anchor.set(0.5, 0.5);
btnDown.addChild(btnDown.icon);
LK.gui.bottom.addChild(btnDown);
dpadBtns['down'] = btnDown;
// Left
var btnLeft = new DPadButton();
btnLeft.x = dpadCenterX - dpadSpacing;
btnLeft.y = dpadCenterY;
btnLeft.icon = new Text2('◀', {
size: Math.floor(dpadSize * 0.5),
fill: "#222"
});
btnLeft.icon.anchor.set(0.5, 0.5);
btnLeft.addChild(btnLeft.icon);
LK.gui.bottom.addChild(btnLeft);
dpadBtns['left'] = btnLeft;
// Right
var btnRight = new DPadButton();
btnRight.x = dpadCenterX + dpadSpacing;
btnRight.y = dpadCenterY;
btnRight.icon = new Text2('▶', {
size: Math.floor(dpadSize * 0.5),
fill: "#222"
});
btnRight.icon.anchor.set(0.5, 0.5);
btnRight.addChild(btnRight.icon);
LK.gui.bottom.addChild(btnRight);
dpadBtns['right'] = btnRight;
}
createDPadBtns();
// --- Start screen overlay event handling ---
startScreenOverlay.down = function (x, y, obj) {
// Check if start button is pressed
var btnX = 2048 / 2;
var btnY = 1600;
var btnW = startBtn.width * 0.5;
var btnH = startBtn.height * 0.5;
if (x >= btnX - btnW && x <= btnX + btnW && y >= btnY - btnH && y <= btnY + btnH) {
// Hide overlay, show UI, enable game
startScreenOverlay.visible = false;
startScreenActive = false;
}
};
// --- D-Pad event handling ---
function dpadBtnForPos(x, y) {
var r = dpadSize / 2;
for (var dir in dpadBtns) {
var btn = dpadBtns[dir];
var bx = btn.x;
var by = btn.y;
if ((x - bx) * (x - bx) + (y - by) * (y - by) < r * r) {
return dir;
}
}
return null;
}
// D-Pad touch/mouse down
LK.gui.bottom.down = function (x, y, obj) {
var dir = dpadBtnForPos(x, y);
if (dir) {
dpadState[dir] = true;
draggingDPad = dir;
tween(dpadBtns[dir].bg, {
alpha: 1
}, {
duration: 80
});
}
};
// D-Pad touch/mouse up
LK.gui.bottom.up = function (x, y, obj) {
if (draggingDPad) {
dpadState[draggingDPad] = false;
tween(dpadBtns[draggingDPad].bg, {
alpha: 0.7
}, {
duration: 120
});
draggingDPad = null;
}
};
// D-Pad move (drag)
LK.gui.bottom.move = function (x, y, obj) {
if (draggingDPad) {
var dir = dpadBtnForPos(x, y);
if (dir === draggingDPad) {
if (!dpadState[dir]) {
dpadState[dir] = true;
tween(dpadBtns[dir].bg, {
alpha: 1
}, {
duration: 80
});
}
} else {
if (dpadState[draggingDPad]) {
dpadState[draggingDPad] = false;
tween(dpadBtns[draggingDPad].bg, {
alpha: 0.7
}, {
duration: 120
});
}
}
}
};
// --- Execute Button event handling ---
LK.gui.bottomLeft.down = function (x, y, obj) {
// Check if the click is within the execute button bounds
if (executeBtn.visible && x >= executeBtn.x - executeBtn.width / 2 && x <= executeBtn.x + executeBtn.width / 2 && y >= executeBtn.y - executeBtn.height / 2 && y <= executeBtn.y + executeBtn.height / 2) {
if (canExecuteVillager && canExecuteVillager.isAlive) {
canExecuteVillager.execute();
sickLeft--;
sickLeftTxt.setText('Sick left: ' + sickLeft);
executeBtn.visible = false;
canExecuteVillager = null;
// Win condition
if (sickLeft === 0) {
LK.showYouWin();
}
}
}
};
// --- Main game move handler (for dragging player, not used here) ---
game.move = function (x, y, obj) {
// No drag-to-move, only D-pad
};
// --- Main game update loop ---
game.update = function () {
// If start screen is active, block all game logic and hide UI
if (startScreenActive) {
// Hide all game objects except overlay
for (var i = 0; i < game.children.length; ++i) {
var obj = game.children[i];
if (obj !== startScreenOverlay) obj.visible = false;else obj.visible = true;
}
// Hide execute button and sick left text
executeBtn.visible = false;
sickLeftTxt.visible = false;
// Hide D-pad
for (var dir in dpadBtns) {
dpadBtns[dir].visible = false;
}
return;
} else {
// Show UI when game is started
sickLeftTxt.visible = true;
for (var dir in dpadBtns) {
dpadBtns[dir].visible = true;
}
}
// --- Player movement ---
var dx = 0,
dy = 0;
if (dpadState.left) dx -= 1;
if (dpadState.right) dx += 1;
if (dpadState.up) dy -= 1;
if (dpadState.down) dy += 1;
if (dx !== 0 || dy !== 0) {
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
}
// Calculate new position before actually moving
var newWorldX = playerWorldX + dx * player.speed;
var newWorldY = playerWorldY + dy * player.speed;
// Store player's world coordinates separately from rendering position
playerWorldX = newWorldX;
playerWorldY = newWorldY;
}
// Clamp player to world coordinates map bounds with consistent padding on all sides
playerWorldX = clamp(playerWorldX, 60, VILLAGE_WIDTH - 60);
playerWorldY = clamp(playerWorldY, 60, VILLAGE_HEIGHT - 60);
// --- Camera scrolling (horizontal and vertical) ---
var screenCenterX = 2048 / 2; // Use fixed resolution width
var screenCenterY = 2732 / 2; // Use fixed resolution height
// Calculate camera position to center player
cameraX = playerWorldX - screenCenterX;
cameraY = playerWorldY - screenCenterY;
// Clamp camera to map bounds
cameraX = clamp(cameraX, 0, VILLAGE_WIDTH - 2048);
cameraY = clamp(cameraY, 0, VILLAGE_HEIGHT - 2732);
// Move background image with camera (always at the back)
backgroundImage.x = -cameraX;
backgroundImage.y = -cameraY;
// Move all game children (player, villagers) relative to camera position
for (var i = 0; i < game.children.length; ++i) {
var obj = game.children[i];
obj.visible = true;
if (obj === player) {
// Position player on screen based on world coordinates
obj.x = playerWorldX - cameraX;
obj.y = playerWorldY - cameraY;
} else if (obj instanceof Villager || obj instanceof House) {
// Position objects on screen based on their world coordinates
// Make sure we're using the stored world coordinates consistently
obj.x = obj.x0 - cameraX;
obj.y = obj.y0 - cameraY;
// Hide objects that are completely outside the visible area
if (obj.x < -300 || obj.x > 2348 || obj.y < -300 || obj.y > 3032) {
obj.visible = false;
}
}
}
// --- Villager proximity check ---
canExecuteVillager = null;
for (var i = 0; i < villagers.length; ++i) {
var villager = villagers[i];
if (!villager.isAlive || !villager.isSick) continue;
// Use world coordinates for collision
var px = playerWorldX;
var py = playerWorldY;
var vx = villager.x0; // Always use stored world coordinates
var vy = villager.y0; // Always use stored world coordinates
var dist = Math.sqrt((px - vx) * (px - vx) + (py - vy) * (py - vy));
if (dist < 140) {
canExecuteVillager = villager;
break;
}
}
// --- Execute button visibility ---
if (canExecuteVillager && canExecuteVillager.isAlive) {
executeBtn.visible = true;
} else {
executeBtn.visible = false;
}
};
// --- Store original world positions for villagers (for camera) ---
function storeVillagerPositions() {
// Store villager positions
for (var i = 0; i < villagers.length; ++i) {
villagers[i].x0 = villagers[i].x;
villagers[i].y0 = villagers[i].y;
}
}
// Store initial positions
storeVillagerPositions();
// --- Initial camera position ---
cameraX = mapCenterX - 2048 / 2; // Start camera centered on the player using fixed width
cameraY = mapCenterY - 2732 / 2; // Start camera centered on the player using fixed height
for (var i = 0; i < game.children.length; ++i) {
var obj = game.children[i];
if (obj === player) {
obj.x = player.x - cameraX;
obj.y = player.y - cameraY;
} else if (obj instanceof Villager) {
obj.x = obj.x0 - cameraX;
obj.y = obj.y0 - cameraY;
}
}
Kill button. In-Game asset. 2d. High contrast. No shadows
Palugue doctors full body pixel art. In-Game asset. 2d. High contrast. No shadows
Sick viliger pixel art full body
Not sick viliger pixel art
Pixel art death block.
Viliger house pixel art. In-Game asset. 2d. High contrast. No shadows
Background sand image pixel art. 4k In-Game asset. 2d. High contrast. No shadows