/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Crewmate/Impostor player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Role: 'crewmate' or 'impostor'
self.role = 'crewmate';
self.isAlive = true;
self.isLocal = false; // Only one player is local (user-controlled)
self.playerId = 0; // Unique id
// Attach asset based on role
self.setRole = function (role) {
self.role = role;
if (self.sprite) {
self.removeChild(self.sprite);
}
var assetId = role === 'impostor' ? 'impostor' : 'crewmate';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Make impostor 2x bigger, and make crewmate (player) 2x bigger as well
if (role === 'impostor' || role === 'crewmate') {
self.sprite.scaleX = 2;
self.sprite.scaleY = 2;
} else {
self.sprite.scaleX = 1;
self.sprite.scaleY = 1;
}
self.facing = 1; // 1 = right, -1 = left
self.sprite.scaleX *= self.facing;
};
// Show dead body
self.showDeadBody = function () {
if (self.sprite) self.removeChild(self.sprite);
self.sprite = self.attachAsset('deadbody', {
anchorX: 0.5,
anchorY: 0.5
});
};
// For touch feedback
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 200);
};
// For movement tween
self.moveTo = function (x, y, duration) {
tween(self, {
x: x,
y: y
}, {
duration: duration || 200,
easing: tween.easeInOut
});
};
// For kill animation
self.kill = function () {
self.isAlive = false;
self.showDeadBody();
tween(self, {
alpha: 0.7
}, {
duration: 400
});
};
// For respawn (not used in MVP)
self.respawn = function () {
self.isAlive = true;
self.setRole(self.role);
self.alpha = 1;
};
// Set initial role
self.setRole('crewmate');
return self;
});
// Task class
var Task = Container.expand(function () {
var self = Container.call(this);
self.completed = false;
self.taskId = 0;
self.sprite = self.attachAsset('task', {
anchorX: 0.5,
anchorY: 0.5
});
// Mark as completed
self.complete = function () {
self.completed = true;
tween(self.sprite, {
alpha: 0.3
}, {
duration: 300
});
};
return self;
});
// Button class (for tasks and emergency)
var UIButton = Container.expand(function () {
var self = Container.call(this);
self.label = '';
self.onPress = null;
self.type = 'button'; // 'button' or 'emergency'
self.bg = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
self.text = new Text2('', {
size: 60,
fill: 0x222222
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
self.setType = function (type) {
self.type = type;
if (type === 'emergency') {
self.removeChild(self.bg);
self.bg = self.attachAsset('emergency', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChildAt(self.bg, 0);
}
};
self.setLabel = function (label) {
self.label = label;
self.text.setText(label);
};
// Touch event
self.down = function (x, y, obj) {
if (self.onPress && typeof self.onPress === 'function') {
self.onPress();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222b38
});
/****
* Game Code
****/
// --- GAME STATE ---
var player; // The local player (user)
var impostor; // The AI chasing impostor
var tasks = []; // All task objects
var completedTasks = 0;
var totalTasks = 5;
var taskTimers = []; // Timers for each task
var taskInProgress = null; // Currently active task
var taskProgressBar = null; // Progress bar UI
var escapeDoor = null; // Door object
var doorOpen = false;
var escapeText = null;
var gameEnded = false;
// --- LAYOUT CONSTANTS ---
var margin = 200;
var playWidth = 2048;
var playHeight = 2732;
// --- UTILS ---
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// --- SETUP TASKS ---
tasks = [];
for (var i = 0; i < totalTasks; ++i) {
var t = new Task();
t.taskId = i;
// Place tasks in a circle further from the center, but clamp so they don't go off screen
var angle = i / totalTasks * Math.PI * 2;
var radius = 1000; // Increased from 700 to 1000
var tx = playWidth / 2 + Math.cos(angle) * radius;
var ty = playHeight / 2 + Math.sin(angle) * radius;
// Clamp so task stays fully on screen
var taskHalfW = t.sprite.width / 2;
var taskHalfH = t.sprite.height / 2;
tx = Math.max(margin + taskHalfW, Math.min(playWidth - margin - taskHalfW, tx));
ty = Math.max(margin + taskHalfH, Math.min(playHeight - margin - taskHalfH, ty));
t.x = tx;
t.y = ty;
t.completed = false;
t.interactive = true;
t.down = function (task) {
return function (x, y, obj) {
if (gameEnded) return;
if (task.completed) return;
if (taskInProgress) return; // Only one at a time
// Move player instantly to the task if not close enough
var dx = player.x - task.x;
var dy = player.y - task.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 180) {
// Too far, do not start task
LK.effects.flashObject(player, 0xff0000, 200);
return;
}
startTask(task);
};
}(t);
tasks.push(t);
}
// --- SETUP PLAYER ---
player = new Player();
player.isLocal = true;
player.x = playWidth / 2;
player.y = playHeight - 400;
player.setRole('crewmate');
// --- SETUP IMPOSTOR ---
impostor = new Player();
impostor.setRole('impostor');
impostor.x = playWidth / 2;
impostor.y = 400;
impostor.isLocal = false;
impostor.isAlive = true;
// --- SETUP ESCAPE DOOR (hidden until all tasks complete) ---
escapeDoor = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
escapeDoor.width = 220;
escapeDoor.height = 300; // Make the button taller
escapeDoor.x = playWidth / 2;
escapeDoor.y = margin + 120; // Move the door further down
escapeDoor.visible = false;
// Add door (bottom layer)
game.addChild(escapeDoor);
// Add generators (tasks) (above door)
for (var i = 0; i < tasks.length; ++i) {
game.addChild(tasks[i]);
}
// Add player (above tasks)
game.addChild(player);
// Add impostor (top layer)
game.addChild(impostor);
// --- ESCAPE TEXT ---
escapeText = new Text2('ESCAPE!', {
size: 90,
fill: 0xFFFFFF
});
escapeText.anchor.set(0.5, 0.5);
escapeText.x = escapeDoor.x;
escapeText.y = escapeDoor.y - 110; // Place text above the door
escapeText.visible = false;
game.addChild(escapeText);
// --- TASK PROGRESS BAR (GUI) ---
taskProgressBar = new Text2('', {
size: 80,
fill: 0xF7E85C
});
taskProgressBar.anchor.set(0.5, 0);
LK.gui.top.addChild(taskProgressBar);
// --- UPDATE TASK PROGRESS BAR ---
function updateTaskProgressBar() {
taskProgressBar.setText("Generator: " + completedTasks + "/" + totalTasks);
}
updateTaskProgressBar();
// --- TASK LOGIC ---
function startTask(task) {
if (gameEnded) return;
taskInProgress = task;
// Randomize duration between 3 and 5 seconds (3000-5000 ms)
var duration = randomInt(3000, 5000);
var startTime = Date.now();
var progressText = new Text2('Fixing generator...', {
size: 80,
fill: 0xF7E85C
});
progressText.anchor.set(0.5, 0.5);
progressText.x = playWidth / 2;
progressText.y = playHeight / 2;
game.addChild(progressText);
// Add a countdown timer text
var countdownText = new Text2('', {
size: 100,
fill: 0xffffff
});
countdownText.anchor.set(0.5, 0);
countdownText.x = playWidth / 2;
countdownText.y = playHeight / 2 + 100;
game.addChild(countdownText);
// Progress bar update
var interval = LK.setInterval(function () {
var elapsed = Date.now() - startTime;
var pct = Math.min(1, elapsed / duration);
var secondsLeft = Math.ceil((duration - elapsed) / 1000);
if (secondsLeft < 0) secondsLeft = 0;
var pctDisplay = (pct * 100).toFixed(1).replace('.', ',');
progressText.setText("Fixing generator: %" + pctDisplay);
countdownText.setText("Time left: " + secondsLeft + "s");
// If player moves too far, cancel
var dx = player.x - task.x;
var dy = player.y - task.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 220) {
LK.clearInterval(interval);
game.removeChild(progressText);
game.removeChild(countdownText);
taskInProgress = null;
LK.effects.flashObject(player, 0xff0000, 200);
}
if (pct >= 1) {
LK.clearInterval(interval);
game.removeChild(progressText);
game.removeChild(countdownText);
completeTask(task);
taskInProgress = null;
}
}, 100);
taskTimers[task.taskId] = interval;
}
function completeTask(task) {
if (task.completed) return;
task.complete();
completedTasks++;
updateTaskProgressBar();
if (completedTasks >= totalTasks) {
openEscapeDoor();
}
}
// --- ESCAPE DOOR LOGIC ---
function openEscapeDoor() {
doorOpen = true;
escapeDoor.visible = true;
escapeText.visible = true;
LK.effects.flashObject(escapeDoor, 0x00ff00, 800);
}
// --- PLAYER ESCAPE LOGIC ---
function checkPlayerEscape() {
if (!doorOpen || gameEnded) return;
// If player is close to door, escape!
var dx = player.x - escapeDoor.x;
var dy = player.y - escapeDoor.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
gameEnded = true;
escapeText.setText("You Escaped!");
LK.effects.flashScreen(0x00ff00, 1200);
LK.setTimeout(function () {
LK.showYouWin();
}, 1200);
}
}
// --- IMPOSTOR AI LOGIC ---
impostor.lastX = impostor.x;
impostor.lastY = impostor.y;
impostor.speed = 2.625;
impostor.killCooldown = 0;
impostor.killDelay = 1500; // ms
function updateImpostorAI() {
if (!impostor.isAlive || !player.isAlive || gameEnded) return;
// Chase player
var dx = player.x - impostor.x;
var dy = player.y - impostor.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
var step = Math.min(impostor.speed, dist);
var prevX = impostor.x;
impostor.x += dx / dist * step;
impostor.y += dy / dist * step;
// Turning logic: face right if moving right, left if moving left
var moveDir = impostor.x - prevX;
if (moveDir !== 0) {
var newFacing = moveDir > 0 ? 1 : -1;
if (impostor.facing !== newFacing) {
impostor.facing = newFacing;
if (impostor.sprite) {
impostor.sprite.scaleX = Math.abs(impostor.sprite.scaleX) * impostor.facing;
}
}
}
}
// Kill if close and cooldown expired
if (dist < 120 && impostor.killCooldown <= 0 && player.isAlive) {
impostor.killCooldown = impostor.killDelay;
killPlayer();
}
if (impostor.killCooldown > 0) {
impostor.killCooldown -= 1000 / 60;
if (impostor.killCooldown < 0) impostor.killCooldown = 0;
}
impostor.lastX = impostor.x;
impostor.lastY = impostor.y;
}
// --- KILL PLAYER ---
function killPlayer() {
if (!player.isAlive || gameEnded) return;
// 20% chance to dodge
if (Math.random() < 0.2) {
// Dodged!
escapeText.setText("Dodged!");
escapeText.visible = true;
LK.effects.flashObject(player, 0x00ff00, 400);
// Hide text after 1 second
LK.setTimeout(function () {
if (!gameEnded) escapeText.visible = false;
}, 1000);
return;
}
player.kill();
gameEnded = true;
escapeText.setText("You were caught!");
escapeText.visible = true;
LK.effects.flashScreen(0xff0000, 1200);
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
}
// --- PLAYER MOVEMENT (TAP TO WALK) ---
var playerTarget = null;
var playerMoveSpeed = 8 / 1.15; // px per frame (1.15x slower)
game.down = function (x, y, obj) {
if (gameEnded) return;
// Set target position to where user touched, clamped to play area
var tx = Math.max(margin, Math.min(playWidth - margin, x));
var ty = Math.max(margin, Math.min(playHeight - margin, y));
playerTarget = {
x: tx,
y: ty
};
};
game.up = function (x, y, obj) {
// No-op for tap-to-move
};
game.move = function (x, y, obj) {
// No-op for tap-to-move
};
// Move player toward target in game.update
// --- ESCAPE DOOR INTERACTION ---
escapeDoor.interactive = true;
escapeDoor.down = function (x, y, obj) {
if (gameEnded) return;
checkPlayerEscape();
};
// --- MAIN GAME LOOP ---
game.update = function () {
if (gameEnded) {
return;
}
// --- PLAYER TAP-TO-MOVE LOGIC ---
if (playerTarget && player.isAlive) {
var dx = playerTarget.x - player.x;
var dy = playerTarget.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var prevX = player.x;
if (dist > playerMoveSpeed) {
player.x += dx / dist * playerMoveSpeed;
player.y += dy / dist * playerMoveSpeed;
} else {
player.x = playerTarget.x;
player.y = playerTarget.y;
playerTarget = null;
}
// Turning logic: face right if moving right, left if moving left
var moveDir = player.x - prevX;
if (moveDir !== 0) {
var newFacing = moveDir > 0 ? 1 : -1;
if (player.facing !== newFacing) {
player.facing = newFacing;
if (player.sprite) {
player.sprite.scaleX = Math.abs(player.sprite.scaleX) * player.facing;
}
}
}
}
updateImpostorAI();
checkPlayerEscape();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Crewmate/Impostor player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Role: 'crewmate' or 'impostor'
self.role = 'crewmate';
self.isAlive = true;
self.isLocal = false; // Only one player is local (user-controlled)
self.playerId = 0; // Unique id
// Attach asset based on role
self.setRole = function (role) {
self.role = role;
if (self.sprite) {
self.removeChild(self.sprite);
}
var assetId = role === 'impostor' ? 'impostor' : 'crewmate';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Make impostor 2x bigger, and make crewmate (player) 2x bigger as well
if (role === 'impostor' || role === 'crewmate') {
self.sprite.scaleX = 2;
self.sprite.scaleY = 2;
} else {
self.sprite.scaleX = 1;
self.sprite.scaleY = 1;
}
self.facing = 1; // 1 = right, -1 = left
self.sprite.scaleX *= self.facing;
};
// Show dead body
self.showDeadBody = function () {
if (self.sprite) self.removeChild(self.sprite);
self.sprite = self.attachAsset('deadbody', {
anchorX: 0.5,
anchorY: 0.5
});
};
// For touch feedback
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 200);
};
// For movement tween
self.moveTo = function (x, y, duration) {
tween(self, {
x: x,
y: y
}, {
duration: duration || 200,
easing: tween.easeInOut
});
};
// For kill animation
self.kill = function () {
self.isAlive = false;
self.showDeadBody();
tween(self, {
alpha: 0.7
}, {
duration: 400
});
};
// For respawn (not used in MVP)
self.respawn = function () {
self.isAlive = true;
self.setRole(self.role);
self.alpha = 1;
};
// Set initial role
self.setRole('crewmate');
return self;
});
// Task class
var Task = Container.expand(function () {
var self = Container.call(this);
self.completed = false;
self.taskId = 0;
self.sprite = self.attachAsset('task', {
anchorX: 0.5,
anchorY: 0.5
});
// Mark as completed
self.complete = function () {
self.completed = true;
tween(self.sprite, {
alpha: 0.3
}, {
duration: 300
});
};
return self;
});
// Button class (for tasks and emergency)
var UIButton = Container.expand(function () {
var self = Container.call(this);
self.label = '';
self.onPress = null;
self.type = 'button'; // 'button' or 'emergency'
self.bg = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
self.text = new Text2('', {
size: 60,
fill: 0x222222
});
self.text.anchor.set(0.5, 0.5);
self.addChild(self.text);
self.setType = function (type) {
self.type = type;
if (type === 'emergency') {
self.removeChild(self.bg);
self.bg = self.attachAsset('emergency', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChildAt(self.bg, 0);
}
};
self.setLabel = function (label) {
self.label = label;
self.text.setText(label);
};
// Touch event
self.down = function (x, y, obj) {
if (self.onPress && typeof self.onPress === 'function') {
self.onPress();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222b38
});
/****
* Game Code
****/
// --- GAME STATE ---
var player; // The local player (user)
var impostor; // The AI chasing impostor
var tasks = []; // All task objects
var completedTasks = 0;
var totalTasks = 5;
var taskTimers = []; // Timers for each task
var taskInProgress = null; // Currently active task
var taskProgressBar = null; // Progress bar UI
var escapeDoor = null; // Door object
var doorOpen = false;
var escapeText = null;
var gameEnded = false;
// --- LAYOUT CONSTANTS ---
var margin = 200;
var playWidth = 2048;
var playHeight = 2732;
// --- UTILS ---
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// --- SETUP TASKS ---
tasks = [];
for (var i = 0; i < totalTasks; ++i) {
var t = new Task();
t.taskId = i;
// Place tasks in a circle further from the center, but clamp so they don't go off screen
var angle = i / totalTasks * Math.PI * 2;
var radius = 1000; // Increased from 700 to 1000
var tx = playWidth / 2 + Math.cos(angle) * radius;
var ty = playHeight / 2 + Math.sin(angle) * radius;
// Clamp so task stays fully on screen
var taskHalfW = t.sprite.width / 2;
var taskHalfH = t.sprite.height / 2;
tx = Math.max(margin + taskHalfW, Math.min(playWidth - margin - taskHalfW, tx));
ty = Math.max(margin + taskHalfH, Math.min(playHeight - margin - taskHalfH, ty));
t.x = tx;
t.y = ty;
t.completed = false;
t.interactive = true;
t.down = function (task) {
return function (x, y, obj) {
if (gameEnded) return;
if (task.completed) return;
if (taskInProgress) return; // Only one at a time
// Move player instantly to the task if not close enough
var dx = player.x - task.x;
var dy = player.y - task.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 180) {
// Too far, do not start task
LK.effects.flashObject(player, 0xff0000, 200);
return;
}
startTask(task);
};
}(t);
tasks.push(t);
}
// --- SETUP PLAYER ---
player = new Player();
player.isLocal = true;
player.x = playWidth / 2;
player.y = playHeight - 400;
player.setRole('crewmate');
// --- SETUP IMPOSTOR ---
impostor = new Player();
impostor.setRole('impostor');
impostor.x = playWidth / 2;
impostor.y = 400;
impostor.isLocal = false;
impostor.isAlive = true;
// --- SETUP ESCAPE DOOR (hidden until all tasks complete) ---
escapeDoor = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5
});
escapeDoor.width = 220;
escapeDoor.height = 300; // Make the button taller
escapeDoor.x = playWidth / 2;
escapeDoor.y = margin + 120; // Move the door further down
escapeDoor.visible = false;
// Add door (bottom layer)
game.addChild(escapeDoor);
// Add generators (tasks) (above door)
for (var i = 0; i < tasks.length; ++i) {
game.addChild(tasks[i]);
}
// Add player (above tasks)
game.addChild(player);
// Add impostor (top layer)
game.addChild(impostor);
// --- ESCAPE TEXT ---
escapeText = new Text2('ESCAPE!', {
size: 90,
fill: 0xFFFFFF
});
escapeText.anchor.set(0.5, 0.5);
escapeText.x = escapeDoor.x;
escapeText.y = escapeDoor.y - 110; // Place text above the door
escapeText.visible = false;
game.addChild(escapeText);
// --- TASK PROGRESS BAR (GUI) ---
taskProgressBar = new Text2('', {
size: 80,
fill: 0xF7E85C
});
taskProgressBar.anchor.set(0.5, 0);
LK.gui.top.addChild(taskProgressBar);
// --- UPDATE TASK PROGRESS BAR ---
function updateTaskProgressBar() {
taskProgressBar.setText("Generator: " + completedTasks + "/" + totalTasks);
}
updateTaskProgressBar();
// --- TASK LOGIC ---
function startTask(task) {
if (gameEnded) return;
taskInProgress = task;
// Randomize duration between 3 and 5 seconds (3000-5000 ms)
var duration = randomInt(3000, 5000);
var startTime = Date.now();
var progressText = new Text2('Fixing generator...', {
size: 80,
fill: 0xF7E85C
});
progressText.anchor.set(0.5, 0.5);
progressText.x = playWidth / 2;
progressText.y = playHeight / 2;
game.addChild(progressText);
// Add a countdown timer text
var countdownText = new Text2('', {
size: 100,
fill: 0xffffff
});
countdownText.anchor.set(0.5, 0);
countdownText.x = playWidth / 2;
countdownText.y = playHeight / 2 + 100;
game.addChild(countdownText);
// Progress bar update
var interval = LK.setInterval(function () {
var elapsed = Date.now() - startTime;
var pct = Math.min(1, elapsed / duration);
var secondsLeft = Math.ceil((duration - elapsed) / 1000);
if (secondsLeft < 0) secondsLeft = 0;
var pctDisplay = (pct * 100).toFixed(1).replace('.', ',');
progressText.setText("Fixing generator: %" + pctDisplay);
countdownText.setText("Time left: " + secondsLeft + "s");
// If player moves too far, cancel
var dx = player.x - task.x;
var dy = player.y - task.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 220) {
LK.clearInterval(interval);
game.removeChild(progressText);
game.removeChild(countdownText);
taskInProgress = null;
LK.effects.flashObject(player, 0xff0000, 200);
}
if (pct >= 1) {
LK.clearInterval(interval);
game.removeChild(progressText);
game.removeChild(countdownText);
completeTask(task);
taskInProgress = null;
}
}, 100);
taskTimers[task.taskId] = interval;
}
function completeTask(task) {
if (task.completed) return;
task.complete();
completedTasks++;
updateTaskProgressBar();
if (completedTasks >= totalTasks) {
openEscapeDoor();
}
}
// --- ESCAPE DOOR LOGIC ---
function openEscapeDoor() {
doorOpen = true;
escapeDoor.visible = true;
escapeText.visible = true;
LK.effects.flashObject(escapeDoor, 0x00ff00, 800);
}
// --- PLAYER ESCAPE LOGIC ---
function checkPlayerEscape() {
if (!doorOpen || gameEnded) return;
// If player is close to door, escape!
var dx = player.x - escapeDoor.x;
var dy = player.y - escapeDoor.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
gameEnded = true;
escapeText.setText("You Escaped!");
LK.effects.flashScreen(0x00ff00, 1200);
LK.setTimeout(function () {
LK.showYouWin();
}, 1200);
}
}
// --- IMPOSTOR AI LOGIC ---
impostor.lastX = impostor.x;
impostor.lastY = impostor.y;
impostor.speed = 2.625;
impostor.killCooldown = 0;
impostor.killDelay = 1500; // ms
function updateImpostorAI() {
if (!impostor.isAlive || !player.isAlive || gameEnded) return;
// Chase player
var dx = player.x - impostor.x;
var dy = player.y - impostor.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
var step = Math.min(impostor.speed, dist);
var prevX = impostor.x;
impostor.x += dx / dist * step;
impostor.y += dy / dist * step;
// Turning logic: face right if moving right, left if moving left
var moveDir = impostor.x - prevX;
if (moveDir !== 0) {
var newFacing = moveDir > 0 ? 1 : -1;
if (impostor.facing !== newFacing) {
impostor.facing = newFacing;
if (impostor.sprite) {
impostor.sprite.scaleX = Math.abs(impostor.sprite.scaleX) * impostor.facing;
}
}
}
}
// Kill if close and cooldown expired
if (dist < 120 && impostor.killCooldown <= 0 && player.isAlive) {
impostor.killCooldown = impostor.killDelay;
killPlayer();
}
if (impostor.killCooldown > 0) {
impostor.killCooldown -= 1000 / 60;
if (impostor.killCooldown < 0) impostor.killCooldown = 0;
}
impostor.lastX = impostor.x;
impostor.lastY = impostor.y;
}
// --- KILL PLAYER ---
function killPlayer() {
if (!player.isAlive || gameEnded) return;
// 20% chance to dodge
if (Math.random() < 0.2) {
// Dodged!
escapeText.setText("Dodged!");
escapeText.visible = true;
LK.effects.flashObject(player, 0x00ff00, 400);
// Hide text after 1 second
LK.setTimeout(function () {
if (!gameEnded) escapeText.visible = false;
}, 1000);
return;
}
player.kill();
gameEnded = true;
escapeText.setText("You were caught!");
escapeText.visible = true;
LK.effects.flashScreen(0xff0000, 1200);
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
}
// --- PLAYER MOVEMENT (TAP TO WALK) ---
var playerTarget = null;
var playerMoveSpeed = 8 / 1.15; // px per frame (1.15x slower)
game.down = function (x, y, obj) {
if (gameEnded) return;
// Set target position to where user touched, clamped to play area
var tx = Math.max(margin, Math.min(playWidth - margin, x));
var ty = Math.max(margin, Math.min(playHeight - margin, y));
playerTarget = {
x: tx,
y: ty
};
};
game.up = function (x, y, obj) {
// No-op for tap-to-move
};
game.move = function (x, y, obj) {
// No-op for tap-to-move
};
// Move player toward target in game.update
// --- ESCAPE DOOR INTERACTION ---
escapeDoor.interactive = true;
escapeDoor.down = function (x, y, obj) {
if (gameEnded) return;
checkPlayerEscape();
};
// --- MAIN GAME LOOP ---
game.update = function () {
if (gameEnded) {
return;
}
// --- PLAYER TAP-TO-MOVE LOGIC ---
if (playerTarget && player.isAlive) {
var dx = playerTarget.x - player.x;
var dy = playerTarget.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var prevX = player.x;
if (dist > playerMoveSpeed) {
player.x += dx / dist * playerMoveSpeed;
player.y += dy / dist * playerMoveSpeed;
} else {
player.x = playerTarget.x;
player.y = playerTarget.y;
playerTarget = null;
}
// Turning logic: face right if moving right, left if moving left
var moveDir = player.x - prevX;
if (moveDir !== 0) {
var newFacing = moveDir > 0 ? 1 : -1;
if (player.facing !== newFacing) {
player.facing = newFacing;
if (player.sprite) {
player.sprite.scaleX = Math.abs(player.sprite.scaleX) * player.facing;
}
}
}
}
updateImpostorAI();
checkPlayerEscape();
};
Wooden door. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat,sharp edges,straight lines,creepy and bloody look
A skull head holding a knife . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat.dark,bloody look on knife and head,make my drawing better
A scared man,make my drawing better . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat.only head,make hands as a circle.dont add body
Cabinet,metal,bloody look,sharp edges. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "die by moonlight" and with the description "A game where player fix generators and tries to escape killer.". No text on banner,put the name on the up-left corner