/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Fog tile (covers unseen areas) var FogTile = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('fog', { anchorX: 0.5, anchorY: 0.5 }); self.alpha = 0.92; return self; }); // MapTile: floor, wall, exit, item var MapTile = Container.expand(function () { var self = Container.call(this); self.type = 'floor'; // default self.asset = null; self.xIndex = 0; self.yIndex = 0; self.hasItem = false; self.itemAsset = null; self.isExit = false; self.setType = function (type) { self.type = type; if (self.asset) { self.removeChild(self.asset); } if (type === 'floor') { self.asset = self.attachAsset('floor', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'wall') { self.asset = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'exit') { self.asset = self.attachAsset('exit', { anchorX: 0.5, anchorY: 0.5 }); self.isExit = true; } }; self.placeItem = function () { self.hasItem = true; if (!self.itemAsset) { self.itemAsset = self.attachAsset('item', { anchorX: 0.5, anchorY: 0.5 }); } }; self.removeItem = function () { self.hasItem = false; if (self.itemAsset) { self.removeChild(self.itemAsset); self.itemAsset = null; } }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.xIndex = 0; self.yIndex = 0; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Map generation constants var MAP_WIDTH = 20; var MAP_HEIGHT = 15; var TILE_SIZE = 160; var MAP_OFFSET_X = (2048 - MAP_WIDTH * TILE_SIZE) / 2; var MAP_OFFSET_Y = 200; // Camera variables var camera = { x: 0, y: 0, targetX: 0, targetY: 0 }; var SCREEN_CENTER_X = 2048 / 2; var SCREEN_CENTER_Y = 2732 / 2; // Generate random map function generateRandomMap() { var map = []; // Initialize with walls for (var y = 0; y < MAP_HEIGHT; y++) { map[y] = []; for (var x = 0; x < MAP_WIDTH; x++) { map[y][x] = 1; // wall } } // Create random rooms and corridors for (var y = 1; y < MAP_HEIGHT - 1; y += 2) { for (var x = 1; x < MAP_WIDTH - 1; x += 2) { map[y][x] = 0; // floor // Random corridors if (Math.random() < 0.6 && x + 2 < MAP_WIDTH - 1) { map[y][x + 1] = 0; // horizontal corridor } if (Math.random() < 0.6 && y + 2 < MAP_HEIGHT - 1) { map[y + 1][x] = 0; // vertical corridor } } } // Place exit var exitX = MAP_WIDTH - 2; var exitY = MAP_HEIGHT - 2; map[exitY][exitX] = 2; // exit // Place 5 items randomly on floor tiles var itemsPlaced = 0; while (itemsPlaced < 5) { var randX = Math.floor(Math.random() * (MAP_WIDTH - 2)) + 1; var randY = Math.floor(Math.random() * (MAP_HEIGHT - 2)) + 1; if (map[randY][randX] === 0 && !(randX === 1 && randY === 1)) { map[randY][randX] = 3; // item itemsPlaced++; } } return map; } var mapData = generateRandomMap(); // --- GLOBALS --- var mapTiles = []; // 2D array of MapTile var fogTiles = []; // 2D array of FogTile var player = null; var playerStart = { x: 1, y: 1 }; var exitPos = { x: 7, y: 9 }; var itemsTotal = 0; var itemsCollected = 0; var visionRadius = 1.5; // tiles var moveLock = false; // prevent double moves var arrowButtons = {}; var lastPlayerPos = { x: 1, y: 1 }; // --- GUI --- var itemsTxt = new Text2('Items: 0/0', { size: 90, fill: "#fff" }); itemsTxt.anchor.set(0.5, 0); LK.gui.top.addChild(itemsTxt); // --- MAP GENERATION --- for (var y = 0; y < MAP_HEIGHT; y++) { mapTiles[y] = []; fogTiles[y] = []; for (var x = 0; x < MAP_WIDTH; x++) { var tile = new MapTile(); tile.xIndex = x; tile.yIndex = y; var type = mapData[y][x]; if (type === 1) { tile.setType('wall'); } else if (type === 2) { tile.setType('exit'); exitPos.x = x; exitPos.y = y; } else { tile.setType('floor'); } if (type === 3) { tile.placeItem(); itemsTotal++; } tile.x = MAP_OFFSET_X + x * TILE_SIZE + TILE_SIZE / 2; tile.y = MAP_OFFSET_Y + y * TILE_SIZE + TILE_SIZE / 2; game.addChild(tile); mapTiles[y][x] = tile; // Fog var fog = new FogTile(); fog.x = tile.x; fog.y = tile.y; game.addChild(fog); fogTiles[y][x] = fog; } } itemsTxt.setText('Items: 0/' + itemsTotal); // --- PLAYER --- player = new Player(); player.xIndex = playerStart.x; player.yIndex = playerStart.y; player.x = MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2; player.y = MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2; game.addChild(player); // --- FOG OF WAR --- function updateFog() { for (var y = 0; y < MAP_HEIGHT; y++) { for (var x = 0; x < MAP_WIDTH; x++) { var dx = x - player.xIndex; var dy = y - player.yIndex; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= visionRadius) { // Reveal fogTiles[y][x].alpha = 0; } else if (dist <= visionRadius + 1) { // Faded memory fogTiles[y][x].alpha = 0.5; } else { // Full fog fogTiles[y][x].alpha = 0.92; } } } } updateFog(); // --- CAMERA SYSTEM --- function updateCamera() { // Calculate target camera position to center player on screen var playerWorldX = MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2; var playerWorldY = MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2; camera.targetX = SCREEN_CENTER_X - playerWorldX; camera.targetY = SCREEN_CENTER_Y - playerWorldY; // Smooth camera movement camera.x += (camera.targetX - camera.x) * 0.15; camera.y += (camera.targetY - camera.y) * 0.15; // Apply camera offset to game container game.x = camera.x; game.y = camera.y; } // --- MOVEMENT LOGIC --- function canMoveTo(x, y) { if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) return false; var tile = mapTiles[y][x]; if (tile.type === 'wall') return false; return true; } function movePlayer(dx, dy) { if (moveLock) return; var nx = player.xIndex + dx; var ny = player.yIndex + dy; if (!canMoveTo(nx, ny)) { // Bump animation var bumpX = player.x + dx * 20; var bumpY = player.y + dy * 20; moveLock = true; tween(player, { x: bumpX, y: bumpY }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(player, { x: MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2, y: MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2 }, { duration: 80, onFinish: function onFinish() { moveLock = false; } }); } }); return; } lastPlayerPos.x = player.xIndex; lastPlayerPos.y = player.yIndex; player.xIndex = nx; player.yIndex = ny; moveLock = true; tween(player, { x: MAP_OFFSET_X + nx * TILE_SIZE + TILE_SIZE / 2, y: MAP_OFFSET_Y + ny * TILE_SIZE + TILE_SIZE / 2 }, { duration: 120, easing: tween.easeInOut, onFinish: function onFinish() { moveLock = false; afterMove(); } }); updateFog(); updateCamera(); } function afterMove() { // Check for item var tile = mapTiles[player.yIndex][player.xIndex]; if (tile.hasItem) { tile.removeItem(); itemsCollected++; itemsTxt.setText('Items: ' + itemsCollected + '/' + itemsTotal); LK.effects.flashObject(player, 0xffe066, 400); } // Check for exit if (tile.isExit) { if (itemsCollected >= itemsTotal) { LK.showYouWin(); } else { // Flash exit red if not all items collected LK.effects.flashObject(tile, 0xff0000, 400); } } } // --- ARROW BUTTONS --- // Create arrow buttons in GUI layer to follow player function createGUIArrowButton(id, x, y, rotation, moveFn) { var btn = LK.getAsset(id, { anchorX: 0.5, anchorY: 0.5 }); btn.x = x; btn.y = y; btn.scaleX = 0.8; btn.scaleY = 0.8; btn.rotation = rotation; btn.alpha = 0.9; btn.interactive = true; btn.down = function () { moveFn(); }; LK.gui.bottom.addChild(btn); return btn; } // Place arrow buttons at bottom of GUI, arranged in a D-pad layout - moved higher and smaller var btnSpacing = 160; // spacing between buttons (reduced) arrowButtons.up = createGUIArrowButton('arrowUp', 0, -btnSpacing - 250, 0, function () { movePlayer(0, -1); }); arrowButtons.down = createGUIArrowButton('arrowDown', 0, btnSpacing - 250, Math.PI, function () { movePlayer(0, 1); }); arrowButtons.left = createGUIArrowButton('arrowLeft', -btnSpacing, -250, -Math.PI / 2, function () { movePlayer(-1, 0); }); arrowButtons.right = createGUIArrowButton('arrowRight', btnSpacing, -250, Math.PI / 2, function () { movePlayer(1, 0); }); // --- TOUCH DRAG MOVEMENT (OPTIONAL) --- // For MVP, only arrow buttons. (No swipe/drag movement.) // --- GAME UPDATE --- game.update = function () { // Update camera smoothly each frame updateCamera(); }; // --- GAME OVER HANDLING --- // Handled by LK.showYouWin() // --- INITIAL FOG UPDATE --- updateFog(); // --- INITIAL CAMERA SETUP --- updateCamera();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fog tile (covers unseen areas)
var FogTile = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('fog', {
anchorX: 0.5,
anchorY: 0.5
});
self.alpha = 0.92;
return self;
});
// MapTile: floor, wall, exit, item
var MapTile = Container.expand(function () {
var self = Container.call(this);
self.type = 'floor'; // default
self.asset = null;
self.xIndex = 0;
self.yIndex = 0;
self.hasItem = false;
self.itemAsset = null;
self.isExit = false;
self.setType = function (type) {
self.type = type;
if (self.asset) {
self.removeChild(self.asset);
}
if (type === 'floor') {
self.asset = self.attachAsset('floor', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'wall') {
self.asset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'exit') {
self.asset = self.attachAsset('exit', {
anchorX: 0.5,
anchorY: 0.5
});
self.isExit = true;
}
};
self.placeItem = function () {
self.hasItem = true;
if (!self.itemAsset) {
self.itemAsset = self.attachAsset('item', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.removeItem = function () {
self.hasItem = false;
if (self.itemAsset) {
self.removeChild(self.itemAsset);
self.itemAsset = null;
}
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.xIndex = 0;
self.yIndex = 0;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Map generation constants
var MAP_WIDTH = 20;
var MAP_HEIGHT = 15;
var TILE_SIZE = 160;
var MAP_OFFSET_X = (2048 - MAP_WIDTH * TILE_SIZE) / 2;
var MAP_OFFSET_Y = 200;
// Camera variables
var camera = {
x: 0,
y: 0,
targetX: 0,
targetY: 0
};
var SCREEN_CENTER_X = 2048 / 2;
var SCREEN_CENTER_Y = 2732 / 2;
// Generate random map
function generateRandomMap() {
var map = [];
// Initialize with walls
for (var y = 0; y < MAP_HEIGHT; y++) {
map[y] = [];
for (var x = 0; x < MAP_WIDTH; x++) {
map[y][x] = 1; // wall
}
}
// Create random rooms and corridors
for (var y = 1; y < MAP_HEIGHT - 1; y += 2) {
for (var x = 1; x < MAP_WIDTH - 1; x += 2) {
map[y][x] = 0; // floor
// Random corridors
if (Math.random() < 0.6 && x + 2 < MAP_WIDTH - 1) {
map[y][x + 1] = 0; // horizontal corridor
}
if (Math.random() < 0.6 && y + 2 < MAP_HEIGHT - 1) {
map[y + 1][x] = 0; // vertical corridor
}
}
}
// Place exit
var exitX = MAP_WIDTH - 2;
var exitY = MAP_HEIGHT - 2;
map[exitY][exitX] = 2; // exit
// Place 5 items randomly on floor tiles
var itemsPlaced = 0;
while (itemsPlaced < 5) {
var randX = Math.floor(Math.random() * (MAP_WIDTH - 2)) + 1;
var randY = Math.floor(Math.random() * (MAP_HEIGHT - 2)) + 1;
if (map[randY][randX] === 0 && !(randX === 1 && randY === 1)) {
map[randY][randX] = 3; // item
itemsPlaced++;
}
}
return map;
}
var mapData = generateRandomMap();
// --- GLOBALS ---
var mapTiles = []; // 2D array of MapTile
var fogTiles = []; // 2D array of FogTile
var player = null;
var playerStart = {
x: 1,
y: 1
};
var exitPos = {
x: 7,
y: 9
};
var itemsTotal = 0;
var itemsCollected = 0;
var visionRadius = 1.5; // tiles
var moveLock = false; // prevent double moves
var arrowButtons = {};
var lastPlayerPos = {
x: 1,
y: 1
};
// --- GUI ---
var itemsTxt = new Text2('Items: 0/0', {
size: 90,
fill: "#fff"
});
itemsTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(itemsTxt);
// --- MAP GENERATION ---
for (var y = 0; y < MAP_HEIGHT; y++) {
mapTiles[y] = [];
fogTiles[y] = [];
for (var x = 0; x < MAP_WIDTH; x++) {
var tile = new MapTile();
tile.xIndex = x;
tile.yIndex = y;
var type = mapData[y][x];
if (type === 1) {
tile.setType('wall');
} else if (type === 2) {
tile.setType('exit');
exitPos.x = x;
exitPos.y = y;
} else {
tile.setType('floor');
}
if (type === 3) {
tile.placeItem();
itemsTotal++;
}
tile.x = MAP_OFFSET_X + x * TILE_SIZE + TILE_SIZE / 2;
tile.y = MAP_OFFSET_Y + y * TILE_SIZE + TILE_SIZE / 2;
game.addChild(tile);
mapTiles[y][x] = tile;
// Fog
var fog = new FogTile();
fog.x = tile.x;
fog.y = tile.y;
game.addChild(fog);
fogTiles[y][x] = fog;
}
}
itemsTxt.setText('Items: 0/' + itemsTotal);
// --- PLAYER ---
player = new Player();
player.xIndex = playerStart.x;
player.yIndex = playerStart.y;
player.x = MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2;
player.y = MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2;
game.addChild(player);
// --- FOG OF WAR ---
function updateFog() {
for (var y = 0; y < MAP_HEIGHT; y++) {
for (var x = 0; x < MAP_WIDTH; x++) {
var dx = x - player.xIndex;
var dy = y - player.yIndex;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= visionRadius) {
// Reveal
fogTiles[y][x].alpha = 0;
} else if (dist <= visionRadius + 1) {
// Faded memory
fogTiles[y][x].alpha = 0.5;
} else {
// Full fog
fogTiles[y][x].alpha = 0.92;
}
}
}
}
updateFog();
// --- CAMERA SYSTEM ---
function updateCamera() {
// Calculate target camera position to center player on screen
var playerWorldX = MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2;
var playerWorldY = MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2;
camera.targetX = SCREEN_CENTER_X - playerWorldX;
camera.targetY = SCREEN_CENTER_Y - playerWorldY;
// Smooth camera movement
camera.x += (camera.targetX - camera.x) * 0.15;
camera.y += (camera.targetY - camera.y) * 0.15;
// Apply camera offset to game container
game.x = camera.x;
game.y = camera.y;
}
// --- MOVEMENT LOGIC ---
function canMoveTo(x, y) {
if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) return false;
var tile = mapTiles[y][x];
if (tile.type === 'wall') return false;
return true;
}
function movePlayer(dx, dy) {
if (moveLock) return;
var nx = player.xIndex + dx;
var ny = player.yIndex + dy;
if (!canMoveTo(nx, ny)) {
// Bump animation
var bumpX = player.x + dx * 20;
var bumpY = player.y + dy * 20;
moveLock = true;
tween(player, {
x: bumpX,
y: bumpY
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
x: MAP_OFFSET_X + player.xIndex * TILE_SIZE + TILE_SIZE / 2,
y: MAP_OFFSET_Y + player.yIndex * TILE_SIZE + TILE_SIZE / 2
}, {
duration: 80,
onFinish: function onFinish() {
moveLock = false;
}
});
}
});
return;
}
lastPlayerPos.x = player.xIndex;
lastPlayerPos.y = player.yIndex;
player.xIndex = nx;
player.yIndex = ny;
moveLock = true;
tween(player, {
x: MAP_OFFSET_X + nx * TILE_SIZE + TILE_SIZE / 2,
y: MAP_OFFSET_Y + ny * TILE_SIZE + TILE_SIZE / 2
}, {
duration: 120,
easing: tween.easeInOut,
onFinish: function onFinish() {
moveLock = false;
afterMove();
}
});
updateFog();
updateCamera();
}
function afterMove() {
// Check for item
var tile = mapTiles[player.yIndex][player.xIndex];
if (tile.hasItem) {
tile.removeItem();
itemsCollected++;
itemsTxt.setText('Items: ' + itemsCollected + '/' + itemsTotal);
LK.effects.flashObject(player, 0xffe066, 400);
}
// Check for exit
if (tile.isExit) {
if (itemsCollected >= itemsTotal) {
LK.showYouWin();
} else {
// Flash exit red if not all items collected
LK.effects.flashObject(tile, 0xff0000, 400);
}
}
}
// --- ARROW BUTTONS ---
// Create arrow buttons in GUI layer to follow player
function createGUIArrowButton(id, x, y, rotation, moveFn) {
var btn = LK.getAsset(id, {
anchorX: 0.5,
anchorY: 0.5
});
btn.x = x;
btn.y = y;
btn.scaleX = 0.8;
btn.scaleY = 0.8;
btn.rotation = rotation;
btn.alpha = 0.9;
btn.interactive = true;
btn.down = function () {
moveFn();
};
LK.gui.bottom.addChild(btn);
return btn;
}
// Place arrow buttons at bottom of GUI, arranged in a D-pad layout - moved higher and smaller
var btnSpacing = 160; // spacing between buttons (reduced)
arrowButtons.up = createGUIArrowButton('arrowUp', 0, -btnSpacing - 250, 0, function () {
movePlayer(0, -1);
});
arrowButtons.down = createGUIArrowButton('arrowDown', 0, btnSpacing - 250, Math.PI, function () {
movePlayer(0, 1);
});
arrowButtons.left = createGUIArrowButton('arrowLeft', -btnSpacing, -250, -Math.PI / 2, function () {
movePlayer(-1, 0);
});
arrowButtons.right = createGUIArrowButton('arrowRight', btnSpacing, -250, Math.PI / 2, function () {
movePlayer(1, 0);
});
// --- TOUCH DRAG MOVEMENT (OPTIONAL) ---
// For MVP, only arrow buttons. (No swipe/drag movement.)
// --- GAME UPDATE ---
game.update = function () {
// Update camera smoothly each frame
updateCamera();
};
// --- GAME OVER HANDLING ---
// Handled by LK.showYouWin()
// --- INITIAL FOG UPDATE ---
updateFog();
// --- INITIAL CAMERA SETUP ---
updateCamera();