/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Checkpoint class: save progress var Checkpoint = Container.expand(function () { var self = Container.call(this); var cp = self.attachAsset('checkpoint', { anchorX: 0.5, anchorY: 1 }); self.activated = false; self.activate = function () { if (!self.activated) { self.activated = true; tween(cp, { tint: 0x00ff00 }, { duration: 300 }); } }; return self; }); // Platform class: static or moving platform var Platform = Container.expand(function () { var self = Container.call(this); // Attach platform asset (box shape) var plat = self.attachAsset('platform', { anchorX: 0.5, anchorY: 0.5 }); // Platform movement config self.isMoving = false; self.moveAxis = 'x'; // 'x' or 'y' self.moveFrom = 0; self.moveTo = 0; self.moveDuration = 0; self.moveDir = 1; // For moving platforms, set up tween self.startMove = function () { if (!self.isMoving) return; var prop = {}; prop[self.moveAxis] = self.moveTo; tween(self, prop, { duration: self.moveDuration, easing: tween.linear, onFinish: function onFinish() { // Swap direction var tmp = self.moveFrom; self.moveFrom = self.moveTo; self.moveTo = tmp; self.startMove(); } }); }; return self; }); // Player class: main character var Player = Container.expand(function () { var self = Container.call(this); var _char = self.attachAsset('player', { anchorX: 0.5, anchorY: 1 }); self.width = _char.width; self.height = _char.height; self.vx = 0; self.vy = 0; self.onGround = false; self.dead = false; self.jumpQueued = false; self.respawn = function (x, y) { self.x = x; self.y = y; self.vx = 0; self.vy = 0; self.dead = false; }; return self; }); // Spike class: deadly obstacle var Spike = Container.expand(function () { var self = Container.call(this); // Attach spike asset (triangle shape, but use box for now) var spike = self.attachAsset('spike', { anchorX: 0.5, anchorY: 1 }); return self; }); /**** * Initialize Game ****/ // PlayerController class: handles input and movement for the player var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // PlayerController class: handles input and movement for the player // --- Asset Initialization --- // Tween plugin for platform/character movement and effects // --- Level Data (MVP: 1 level, hardcoded) --- /* Level is an array of objects: {type: 'platform'|'spike'|'checkpoint', x, y, [w], [h], [moveAxis], [moveFrom], [moveTo], [moveDuration]} All positions are in game coordinates. */ var PlayerController = function PlayerController() { var self = {}; self.dirX = 0; self.dirY = 0; self.jumpQueued = false; self.active = false; // Called on controller down self.onDown = function (x, y) { self.active = true; self.updateInput(x, y); self.jumpQueued = false; }; // Called on controller move self.onMove = function (x, y) { if (self.active) { self.updateInput(x, y); } }; // Called on controller up self.onUp = function () { self.active = false; self.dirX = 0; self.dirY = 0; self.jumpQueued = false; }; // Called on jump tap self.onJump = function () { self.jumpQueued = true; }; // Update input direction self.updateInput = function (x, y) { var input = getControllerInput(x, y); self.dirX = input.dx / controllerRadius; self.dirY = input.dy / controllerRadius; }; // Reset jump self.consumeJump = function () { var wasQueued = self.jumpQueued; self.jumpQueued = false; return wasQueued; }; return self; }; var levelData = [ // Floor { type: 'platform', x: 1024, y: 2600, w: 1200, h: 60 }, // First jump { type: 'platform', x: 600, y: 2200, w: 400, h: 40 }, { type: 'spike', x: 800, y: 2160 }, // Second jump { type: 'platform', x: 1400, y: 2000, w: 400, h: 40 }, { type: 'spike', x: 1200, y: 1960 }, // Moving platform { type: 'platform', x: 1024, y: 1700, w: 300, h: 40, moveAxis: 'x', moveFrom: 724, moveTo: 1324, moveDuration: 1800 }, { type: 'spike', x: 1024, y: 1660 }, // Checkpoint { type: 'checkpoint', x: 1024, y: 1600 }, // Final platform { type: 'platform', x: 1024, y: 1200, w: 400, h: 40 }, // End spike { type: 'spike', x: 1024, y: 1160 }]; // --- Game State --- var platforms = []; var spikes = []; var checkpoints = []; var player = null; var currentCheckpoint = null; var deaths = 0; var levelEndY = 1100; // Y position to reach to win // --- UI --- var deathsTxt = new Text2('Deaths: 0', { size: 90, fill: "#fff" }); deathsTxt.anchor.set(0.5, 0); LK.gui.top.addChild(deathsTxt); // --- Level Construction --- function buildLevel() { // Clear previous for (var i = 0; i < platforms.length; ++i) platforms[i].destroy(); for (var i = 0; i < spikes.length; ++i) spikes[i].destroy(); for (var i = 0; i < checkpoints.length; ++i) checkpoints[i].destroy(); platforms = []; spikes = []; checkpoints = []; currentCheckpoint = null; // Build for (var i = 0; i < levelData.length; ++i) { var obj = levelData[i]; if (obj.type === 'platform') { var plat = new Platform(); plat.x = obj.x; plat.y = obj.y; plat.width = obj.w || 400; plat.height = obj.h || 40; plat.children[0].width = plat.width; plat.children[0].height = plat.height; if (obj.moveAxis) { plat.isMoving = true; plat.moveAxis = obj.moveAxis; plat.moveFrom = obj.moveFrom; plat.moveTo = obj.moveTo; plat.moveDuration = obj.moveDuration; plat[plat.moveAxis] = plat.moveFrom; plat.startMove(); } game.addChild(plat); platforms.push(plat); } else if (obj.type === 'spike') { var spike = new Spike(); spike.x = obj.x; spike.y = obj.y; game.addChild(spike); spikes.push(spike); } else if (obj.type === 'checkpoint') { var cp = new Checkpoint(); cp.x = obj.x; cp.y = obj.y; game.addChild(cp); checkpoints.push(cp); } } } // --- Player Spawn --- function spawnPlayer() { if (player) player.destroy(); player = new Player(); var spawnX = 1024, spawnY = 2500; if (currentCheckpoint) { spawnX = currentCheckpoint.x; spawnY = currentCheckpoint.y - 10; } player.respawn(spawnX, spawnY); game.addChild(player); } // --- Death/Respawn --- function killPlayer() { if (player.dead) return; player.dead = true; deaths += 1; deathsTxt.setText('Deaths: ' + deaths); LK.effects.flashScreen(0xff2222, 400); LK.setTimeout(function () { spawnPlayer(); }, 400); } // --- Win Condition --- function checkWin() { if (player.y < levelEndY) { LK.showYouWin(); } } // --- Collision Helpers --- function rectsIntersect(a, b) { return a.x - a.width / 2 < b.x + b.width / 2 && a.x + a.width / 2 > b.x - b.width / 2 && a.y - a.height < b.y && a.y > b.y - b.height; } // --- Tablet Controller (Virtual Joystick) --- // Controller state var controllerRadius = 180; // px, joystick max distance var controllerBase = null; var controllerStick = null; var controllerCenterX = 0; var controllerCenterY = 0; // Player controller instance var playerController = new PlayerController(); // Place controller in bottom left, safe from menu (not in top left 100x100) controllerCenterX = 220; controllerCenterY = 2732 - 220; // Draw controller base and stick function createControllerGraphics() { if (controllerBase) controllerBase.destroy(); if (controllerStick) controllerStick.destroy(); // Use shapes for base and stick controllerBase = LK.getAsset('platform', { width: controllerRadius * 2, height: controllerRadius * 2, color: 0x444444, anchorX: 0.5, anchorY: 0.5, x: controllerCenterX, y: controllerCenterY, alpha: 0.18 }); controllerStick = LK.getAsset('player', { width: 100, height: 100, color: 0xffffff, anchorX: 0.5, anchorY: 0.5, x: controllerCenterX, y: controllerCenterY, alpha: 0.45 }); LK.gui.bottomLeft.addChild(controllerBase); LK.gui.bottomLeft.addChild(controllerStick); } createControllerGraphics(); // Helper: get distance and angle from center function getControllerInput(x, y) { var dx = x - controllerCenterX; var dy = y - controllerCenterY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > controllerRadius) { dx = dx * controllerRadius / dist; dy = dy * controllerRadius / dist; dist = controllerRadius; } return { dx: dx, dy: dy, dist: dist }; } // Touch down: activate controller if in left-bottom quarter, else jump if in right-bottom game.down = function (x, y, obj) { // Only activate controller if touch is in left-bottom quarter (not in top left 100x100) if (x < 2048 / 2 && y > 2732 - 500 && y > 100) { playerController.onDown(x, y); var input = getControllerInput(x, y); controllerStick.x = controllerCenterX + input.dx; controllerStick.y = controllerCenterY + input.dy; } else if (x > 2048 / 2 && y > 2732 - 500) { // Right-bottom: jump playerController.onJump(); } }; game.move = function (x, y, obj) { if (playerController.active) { playerController.onMove(x, y); var input = getControllerInput(x, y); controllerStick.x = controllerCenterX + input.dx; controllerStick.y = controllerCenterY + input.dy; } }; game.up = function (x, y, obj) { playerController.onUp(); controllerStick.x = controllerCenterX; controllerStick.y = controllerCenterY; }; // --- END Tablet Controller --- // --- Physics Constants --- var GRAVITY = 2.2; var MOVE_SPEED = 18; var JUMP_VEL = -48; var MAX_FALL = 60; // --- Game Update Loop --- game.update = function () { if (!player || player.dead) return; // Horizontal movement (tablet controller) if (playerController.dirX < -0.2) { player.vx = -MOVE_SPEED * Math.min(1, Math.abs(playerController.dirX)); } else if (playerController.dirX > 0.2) { player.vx = MOVE_SPEED * Math.min(1, Math.abs(playerController.dirX)); } else { player.vx = 0; } // Jump (tablet controller) if ((playerController.consumeJump() || playerController.active && playerController.dirY < -0.5) && player.onGround) { player.vy = JUMP_VEL; player.onGround = false; } // Gravity player.vy += GRAVITY; if (player.vy > MAX_FALL) player.vy = MAX_FALL; // Save old position var oldX = player.x, oldY = player.y; // Move X player.x += player.vx; // Collide with platforms (X axis) for (var i = 0; i < platforms.length; ++i) { var plat = platforms[i]; if (rectsIntersect(player, plat)) { if (player.vx > 0) { player.x = plat.x - plat.width / 2 - player.width / 2; } else if (player.vx < 0) { player.x = plat.x + plat.width / 2 + player.width / 2; } player.vx = 0; } } // Move Y player.y += player.vy; player.onGround = false; // Collide with platforms (Y axis) for (var i = 0; i < platforms.length; ++i) { var plat = platforms[i]; if (rectsIntersect(player, plat)) { if (player.vy > 0) { player.y = plat.y - plat.height / 2; player.onGround = true; } else if (player.vy < 0) { player.y = plat.y + plat.height / 2 + player.height; } player.vy = 0; } } // Collide with spikes for (var i = 0; i < spikes.length; ++i) { var spike = spikes[i]; if (rectsIntersect(player, spike)) { killPlayer(); return; } } // Collide with checkpoints for (var i = 0; i < checkpoints.length; ++i) { var cp = checkpoints[i]; if (!cp.activated && rectsIntersect(player, cp)) { cp.activate(); currentCheckpoint = cp; } } // Out of bounds (fall) if (player.y > 2800) { killPlayer(); return; } // Win checkWin(); }; // --- Build Level and Start --- buildLevel(); spawnPlayer(); deaths = 0; deathsTxt.setText('Deaths: 0');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Checkpoint class: save progress
var Checkpoint = Container.expand(function () {
var self = Container.call(this);
var cp = self.attachAsset('checkpoint', {
anchorX: 0.5,
anchorY: 1
});
self.activated = false;
self.activate = function () {
if (!self.activated) {
self.activated = true;
tween(cp, {
tint: 0x00ff00
}, {
duration: 300
});
}
};
return self;
});
// Platform class: static or moving platform
var Platform = Container.expand(function () {
var self = Container.call(this);
// Attach platform asset (box shape)
var plat = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
// Platform movement config
self.isMoving = false;
self.moveAxis = 'x'; // 'x' or 'y'
self.moveFrom = 0;
self.moveTo = 0;
self.moveDuration = 0;
self.moveDir = 1;
// For moving platforms, set up tween
self.startMove = function () {
if (!self.isMoving) return;
var prop = {};
prop[self.moveAxis] = self.moveTo;
tween(self, prop, {
duration: self.moveDuration,
easing: tween.linear,
onFinish: function onFinish() {
// Swap direction
var tmp = self.moveFrom;
self.moveFrom = self.moveTo;
self.moveTo = tmp;
self.startMove();
}
});
};
return self;
});
// Player class: main character
var Player = Container.expand(function () {
var self = Container.call(this);
var _char = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
self.width = _char.width;
self.height = _char.height;
self.vx = 0;
self.vy = 0;
self.onGround = false;
self.dead = false;
self.jumpQueued = false;
self.respawn = function (x, y) {
self.x = x;
self.y = y;
self.vx = 0;
self.vy = 0;
self.dead = false;
};
return self;
});
// Spike class: deadly obstacle
var Spike = Container.expand(function () {
var self = Container.call(this);
// Attach spike asset (triangle shape, but use box for now)
var spike = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 1
});
return self;
});
/****
* Initialize Game
****/
// PlayerController class: handles input and movement for the player
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// PlayerController class: handles input and movement for the player
// --- Asset Initialization ---
// Tween plugin for platform/character movement and effects
// --- Level Data (MVP: 1 level, hardcoded) ---
/*
Level is an array of objects:
{type: 'platform'|'spike'|'checkpoint', x, y, [w], [h], [moveAxis], [moveFrom], [moveTo], [moveDuration]}
All positions are in game coordinates.
*/
var PlayerController = function PlayerController() {
var self = {};
self.dirX = 0;
self.dirY = 0;
self.jumpQueued = false;
self.active = false;
// Called on controller down
self.onDown = function (x, y) {
self.active = true;
self.updateInput(x, y);
self.jumpQueued = false;
};
// Called on controller move
self.onMove = function (x, y) {
if (self.active) {
self.updateInput(x, y);
}
};
// Called on controller up
self.onUp = function () {
self.active = false;
self.dirX = 0;
self.dirY = 0;
self.jumpQueued = false;
};
// Called on jump tap
self.onJump = function () {
self.jumpQueued = true;
};
// Update input direction
self.updateInput = function (x, y) {
var input = getControllerInput(x, y);
self.dirX = input.dx / controllerRadius;
self.dirY = input.dy / controllerRadius;
};
// Reset jump
self.consumeJump = function () {
var wasQueued = self.jumpQueued;
self.jumpQueued = false;
return wasQueued;
};
return self;
};
var levelData = [
// Floor
{
type: 'platform',
x: 1024,
y: 2600,
w: 1200,
h: 60
},
// First jump
{
type: 'platform',
x: 600,
y: 2200,
w: 400,
h: 40
}, {
type: 'spike',
x: 800,
y: 2160
},
// Second jump
{
type: 'platform',
x: 1400,
y: 2000,
w: 400,
h: 40
}, {
type: 'spike',
x: 1200,
y: 1960
},
// Moving platform
{
type: 'platform',
x: 1024,
y: 1700,
w: 300,
h: 40,
moveAxis: 'x',
moveFrom: 724,
moveTo: 1324,
moveDuration: 1800
}, {
type: 'spike',
x: 1024,
y: 1660
},
// Checkpoint
{
type: 'checkpoint',
x: 1024,
y: 1600
},
// Final platform
{
type: 'platform',
x: 1024,
y: 1200,
w: 400,
h: 40
},
// End spike
{
type: 'spike',
x: 1024,
y: 1160
}];
// --- Game State ---
var platforms = [];
var spikes = [];
var checkpoints = [];
var player = null;
var currentCheckpoint = null;
var deaths = 0;
var levelEndY = 1100; // Y position to reach to win
// --- UI ---
var deathsTxt = new Text2('Deaths: 0', {
size: 90,
fill: "#fff"
});
deathsTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(deathsTxt);
// --- Level Construction ---
function buildLevel() {
// Clear previous
for (var i = 0; i < platforms.length; ++i) platforms[i].destroy();
for (var i = 0; i < spikes.length; ++i) spikes[i].destroy();
for (var i = 0; i < checkpoints.length; ++i) checkpoints[i].destroy();
platforms = [];
spikes = [];
checkpoints = [];
currentCheckpoint = null;
// Build
for (var i = 0; i < levelData.length; ++i) {
var obj = levelData[i];
if (obj.type === 'platform') {
var plat = new Platform();
plat.x = obj.x;
plat.y = obj.y;
plat.width = obj.w || 400;
plat.height = obj.h || 40;
plat.children[0].width = plat.width;
plat.children[0].height = plat.height;
if (obj.moveAxis) {
plat.isMoving = true;
plat.moveAxis = obj.moveAxis;
plat.moveFrom = obj.moveFrom;
plat.moveTo = obj.moveTo;
plat.moveDuration = obj.moveDuration;
plat[plat.moveAxis] = plat.moveFrom;
plat.startMove();
}
game.addChild(plat);
platforms.push(plat);
} else if (obj.type === 'spike') {
var spike = new Spike();
spike.x = obj.x;
spike.y = obj.y;
game.addChild(spike);
spikes.push(spike);
} else if (obj.type === 'checkpoint') {
var cp = new Checkpoint();
cp.x = obj.x;
cp.y = obj.y;
game.addChild(cp);
checkpoints.push(cp);
}
}
}
// --- Player Spawn ---
function spawnPlayer() {
if (player) player.destroy();
player = new Player();
var spawnX = 1024,
spawnY = 2500;
if (currentCheckpoint) {
spawnX = currentCheckpoint.x;
spawnY = currentCheckpoint.y - 10;
}
player.respawn(spawnX, spawnY);
game.addChild(player);
}
// --- Death/Respawn ---
function killPlayer() {
if (player.dead) return;
player.dead = true;
deaths += 1;
deathsTxt.setText('Deaths: ' + deaths);
LK.effects.flashScreen(0xff2222, 400);
LK.setTimeout(function () {
spawnPlayer();
}, 400);
}
// --- Win Condition ---
function checkWin() {
if (player.y < levelEndY) {
LK.showYouWin();
}
}
// --- Collision Helpers ---
function rectsIntersect(a, b) {
return a.x - a.width / 2 < b.x + b.width / 2 && a.x + a.width / 2 > b.x - b.width / 2 && a.y - a.height < b.y && a.y > b.y - b.height;
}
// --- Tablet Controller (Virtual Joystick) ---
// Controller state
var controllerRadius = 180; // px, joystick max distance
var controllerBase = null;
var controllerStick = null;
var controllerCenterX = 0;
var controllerCenterY = 0;
// Player controller instance
var playerController = new PlayerController();
// Place controller in bottom left, safe from menu (not in top left 100x100)
controllerCenterX = 220;
controllerCenterY = 2732 - 220;
// Draw controller base and stick
function createControllerGraphics() {
if (controllerBase) controllerBase.destroy();
if (controllerStick) controllerStick.destroy();
// Use shapes for base and stick
controllerBase = LK.getAsset('platform', {
width: controllerRadius * 2,
height: controllerRadius * 2,
color: 0x444444,
anchorX: 0.5,
anchorY: 0.5,
x: controllerCenterX,
y: controllerCenterY,
alpha: 0.18
});
controllerStick = LK.getAsset('player', {
width: 100,
height: 100,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0.5,
x: controllerCenterX,
y: controllerCenterY,
alpha: 0.45
});
LK.gui.bottomLeft.addChild(controllerBase);
LK.gui.bottomLeft.addChild(controllerStick);
}
createControllerGraphics();
// Helper: get distance and angle from center
function getControllerInput(x, y) {
var dx = x - controllerCenterX;
var dy = y - controllerCenterY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > controllerRadius) {
dx = dx * controllerRadius / dist;
dy = dy * controllerRadius / dist;
dist = controllerRadius;
}
return {
dx: dx,
dy: dy,
dist: dist
};
}
// Touch down: activate controller if in left-bottom quarter, else jump if in right-bottom
game.down = function (x, y, obj) {
// Only activate controller if touch is in left-bottom quarter (not in top left 100x100)
if (x < 2048 / 2 && y > 2732 - 500 && y > 100) {
playerController.onDown(x, y);
var input = getControllerInput(x, y);
controllerStick.x = controllerCenterX + input.dx;
controllerStick.y = controllerCenterY + input.dy;
} else if (x > 2048 / 2 && y > 2732 - 500) {
// Right-bottom: jump
playerController.onJump();
}
};
game.move = function (x, y, obj) {
if (playerController.active) {
playerController.onMove(x, y);
var input = getControllerInput(x, y);
controllerStick.x = controllerCenterX + input.dx;
controllerStick.y = controllerCenterY + input.dy;
}
};
game.up = function (x, y, obj) {
playerController.onUp();
controllerStick.x = controllerCenterX;
controllerStick.y = controllerCenterY;
};
// --- END Tablet Controller ---
// --- Physics Constants ---
var GRAVITY = 2.2;
var MOVE_SPEED = 18;
var JUMP_VEL = -48;
var MAX_FALL = 60;
// --- Game Update Loop ---
game.update = function () {
if (!player || player.dead) return;
// Horizontal movement (tablet controller)
if (playerController.dirX < -0.2) {
player.vx = -MOVE_SPEED * Math.min(1, Math.abs(playerController.dirX));
} else if (playerController.dirX > 0.2) {
player.vx = MOVE_SPEED * Math.min(1, Math.abs(playerController.dirX));
} else {
player.vx = 0;
}
// Jump (tablet controller)
if ((playerController.consumeJump() || playerController.active && playerController.dirY < -0.5) && player.onGround) {
player.vy = JUMP_VEL;
player.onGround = false;
}
// Gravity
player.vy += GRAVITY;
if (player.vy > MAX_FALL) player.vy = MAX_FALL;
// Save old position
var oldX = player.x,
oldY = player.y;
// Move X
player.x += player.vx;
// Collide with platforms (X axis)
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
if (rectsIntersect(player, plat)) {
if (player.vx > 0) {
player.x = plat.x - plat.width / 2 - player.width / 2;
} else if (player.vx < 0) {
player.x = plat.x + plat.width / 2 + player.width / 2;
}
player.vx = 0;
}
}
// Move Y
player.y += player.vy;
player.onGround = false;
// Collide with platforms (Y axis)
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
if (rectsIntersect(player, plat)) {
if (player.vy > 0) {
player.y = plat.y - plat.height / 2;
player.onGround = true;
} else if (player.vy < 0) {
player.y = plat.y + plat.height / 2 + player.height;
}
player.vy = 0;
}
}
// Collide with spikes
for (var i = 0; i < spikes.length; ++i) {
var spike = spikes[i];
if (rectsIntersect(player, spike)) {
killPlayer();
return;
}
}
// Collide with checkpoints
for (var i = 0; i < checkpoints.length; ++i) {
var cp = checkpoints[i];
if (!cp.activated && rectsIntersect(player, cp)) {
cp.activate();
currentCheckpoint = cp;
}
}
// Out of bounds (fall)
if (player.y > 2800) {
killPlayer();
return;
}
// Win
checkWin();
};
// --- Build Level and Start ---
buildLevel();
spawnPlayer();
deaths = 0;
deathsTxt.setText('Deaths: 0');