/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ninja class var Ninja = Container.expand(function () { var self = Container.call(this); var ninjaGfx = self.attachAsset('ninja', { anchorX: 0.5, anchorY: 0.5 }); // 0: left wall, 1: right wall self.wallSide = 0; // Current vertical speed (for jump animation) self.vy = 0; // Is currently jumping self.isJumping = false; // Called every tick self.update = function () { // Gravity effect (for jump arc) if (self.isJumping) { self.y += self.vy; self.vy += 2.5; // gravity // If reached or passed targetY, snap to targetY and stop jumping if (self.vy > 0 && self.y >= self.targetY || self.vy < 0 && self.y <= self.targetY) { self.y = self.targetY; self.isJumping = false; self.vy = 0; } } }; // Start jump to other wall, upward by jumpHeight self.jump = function (jumpHeight, wallX) { if (self.isJumping) return; self.isJumping = true; self.wallSide = 1 - self.wallSide; self.targetY = self.y - jumpHeight; self.vy = -40; // initial jump velocity // Tween x to other wall tween(self, { x: wallX }, { duration: 120, easing: tween.cubicOut }); // Play jump sound LK.getSound('jump').play(); }; return self; }); // Obstacle class (spike, barrier, or laser) var Obstacle = Container.expand(function () { var self = Container.call(this); // type: 'spike', 'barrier', or 'laser' self.type = 'spike'; self.wallSide = 0; // 0: left, 1: right self.passed = false; // Has ninja passed this obstacle self.laserDir = 1; // Only used for laser self.laserRange = 0; // Only used for laser self.laserBaseX = 0; // Only used for laser // Set up asset self.setType = function (type) { self.type = type; if (self.asset) { self.removeChild(self.asset); } if (type === 'spike') { self.asset = self.attachAsset('spike', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'barrier') { self.asset = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'laser') { self.asset = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 40, height: 320, color: 0xff3366 }); self.laserDir = Math.random() < 0.5 ? 1 : -1; self.laserRange = 120; self.laserBaseX = 0; } }; // Called every tick self.update = function () { // Obstacles move downward as the ninja climbs self.y += obstacleSpeed; if (self.type === 'laser') { if (self.laserBaseX === 0) self.laserBaseX = self.x; self.x += self.laserDir * 8; if (Math.abs(self.x - self.laserBaseX) > self.laserRange) { self.laserDir *= -1; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Game constants // Ninja (player) - a small box, red // Wall - tall, thin, gray // Obstacle (spike) - yellow ellipse // Obstacle (barrier) - blue box // Sound for jump var wallGap = 1200; // horizontal distance between walls var wallMargin = 200; // margin from screen edge var ninjaStartY = 2000; var jumpHeight = 320; // vertical jump per tap var obstacleMinGap = 400; // min vertical gap between obstacles var obstacleMaxGap = 700; // max vertical gap between obstacles var obstacleSpeed = 8; // how fast obstacles move down var ninjaXLeft = wallMargin + 50; // center of left wall var ninjaXRight = 2048 - wallMargin - 50; // center of right wall // Walls var leftWall = LK.getAsset('wall', { anchorX: 0.5, anchorY: 0 }); leftWall.x = wallMargin + leftWall.width / 2; leftWall.y = 0; game.addChild(leftWall); var rightWall = LK.getAsset('wall', { anchorX: 0.5, anchorY: 0 }); rightWall.x = 2048 - wallMargin - rightWall.width / 2; rightWall.y = 0; game.addChild(rightWall); // Ninja var ninja = new Ninja(); ninja.x = ninjaXLeft; ninja.y = ninjaStartY; ninja.wallSide = 0; game.addChild(ninja); // Obstacles array var obstacles = []; // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // For touch input var isGameStarted = false; var isDead = false; // For obstacle generation var lastObstacleY = ninjaStartY - 500; // Helper: generate a new obstacle above the last one function spawnObstacle() { var obs = new Obstacle(); // Randomly choose type: 40% spike, 40% barrier, 20% laser var r = Math.random(); if (r < 0.4) { obs.setType('spike'); } else if (r < 0.8) { obs.setType('barrier'); } else { obs.setType('laser'); } // Alternate wall side obs.wallSide = Math.random() < 0.5 ? 0 : 1; // X position obs.x = obs.wallSide === 0 ? ninjaXLeft : ninjaXRight; // Y position: above last obstacle var gap = obstacleMinGap + Math.random() * (obstacleMaxGap - obstacleMinGap); obs.y = lastObstacleY - gap; lastObstacleY = obs.y; // For barrier, offset horizontally so it sticks out from wall if (obs.type === 'barrier') { obs.x += obs.wallSide === 0 ? 90 : -90; } // For laser, center between walls and randomize offset if (obs.type === 'laser') { obs.x = (ninjaXLeft + ninjaXRight) / 2 + (Math.random() - 0.5) * 300; obs.wallSide = -1; // not attached to a wall } obstacles.push(obs); game.addChild(obs); } // Initial obstacles for (var i = 0; i < 8; i++) { spawnObstacle(); } // Move handler: tap anywhere to jump game.down = function (x, y, obj) { if (isDead) return; if (!isGameStarted) { isGameStarted = true; } // Only allow jump if not already jumping if (!ninja.isJumping) { // Jump to other wall var targetX = ninja.wallSide === 0 ? ninjaXRight : ninjaXLeft; ninja.jump(jumpHeight, targetX); } }; // Main update loop game.update = function () { if (isDead) return; // Ninja update ninja.update(); // Move obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove obstacles that are off screen if (obs.y > 2732 + 100) { obs.destroy(); obstacles.splice(i, 1); continue; } // Check for collision (only if ninja is not jumping, i.e. landed) if (!ninja.isJumping && !obs.passed) { // Only check obstacles on the same wall as ninja if (obs.wallSide === ninja.wallSide) { // Use accurate collider: LK's .intersects() for all obstacle types if (ninja.intersects(obs)) { isDead = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } // Also check for laser collision regardless of wall side if (obs.type === 'laser') { if (ninja.intersects(obs)) { isDead = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } } // Score: passed obstacle (only once per obstacle) if (!obs.passed && ninja.y < obs.y) { obs.passed = true; score += 1; LK.setScore(score); scoreTxt.setText(score); } } // Camera: move everything down as ninja climbs if (ninja.y < 1000) { var dy = 1000 - ninja.y; ninja.y += dy; for (var i = 0; i < obstacles.length; i++) { obstacles[i].y += dy; } lastObstacleY += dy; } // Spawn new obstacles if needed while (lastObstacleY > -200) { spawnObstacle(); } }; // Reset game state on game over (handled by LK, but for safety) game.onDestroy = function () { obstacles = []; isGameStarted = false; isDead = false; score = 0; LK.setScore(0); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ninja class
var Ninja = Container.expand(function () {
var self = Container.call(this);
var ninjaGfx = self.attachAsset('ninja', {
anchorX: 0.5,
anchorY: 0.5
});
// 0: left wall, 1: right wall
self.wallSide = 0;
// Current vertical speed (for jump animation)
self.vy = 0;
// Is currently jumping
self.isJumping = false;
// Called every tick
self.update = function () {
// Gravity effect (for jump arc)
if (self.isJumping) {
self.y += self.vy;
self.vy += 2.5; // gravity
// If reached or passed targetY, snap to targetY and stop jumping
if (self.vy > 0 && self.y >= self.targetY || self.vy < 0 && self.y <= self.targetY) {
self.y = self.targetY;
self.isJumping = false;
self.vy = 0;
}
}
};
// Start jump to other wall, upward by jumpHeight
self.jump = function (jumpHeight, wallX) {
if (self.isJumping) return;
self.isJumping = true;
self.wallSide = 1 - self.wallSide;
self.targetY = self.y - jumpHeight;
self.vy = -40; // initial jump velocity
// Tween x to other wall
tween(self, {
x: wallX
}, {
duration: 120,
easing: tween.cubicOut
});
// Play jump sound
LK.getSound('jump').play();
};
return self;
});
// Obstacle class (spike, barrier, or laser)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// type: 'spike', 'barrier', or 'laser'
self.type = 'spike';
self.wallSide = 0; // 0: left, 1: right
self.passed = false; // Has ninja passed this obstacle
self.laserDir = 1; // Only used for laser
self.laserRange = 0; // Only used for laser
self.laserBaseX = 0; // Only used for laser
// Set up asset
self.setType = function (type) {
self.type = type;
if (self.asset) {
self.removeChild(self.asset);
}
if (type === 'spike') {
self.asset = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'barrier') {
self.asset = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'laser') {
self.asset = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 320,
color: 0xff3366
});
self.laserDir = Math.random() < 0.5 ? 1 : -1;
self.laserRange = 120;
self.laserBaseX = 0;
}
};
// Called every tick
self.update = function () {
// Obstacles move downward as the ninja climbs
self.y += obstacleSpeed;
if (self.type === 'laser') {
if (self.laserBaseX === 0) self.laserBaseX = self.x;
self.x += self.laserDir * 8;
if (Math.abs(self.x - self.laserBaseX) > self.laserRange) {
self.laserDir *= -1;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Game constants
// Ninja (player) - a small box, red
// Wall - tall, thin, gray
// Obstacle (spike) - yellow ellipse
// Obstacle (barrier) - blue box
// Sound for jump
var wallGap = 1200; // horizontal distance between walls
var wallMargin = 200; // margin from screen edge
var ninjaStartY = 2000;
var jumpHeight = 320; // vertical jump per tap
var obstacleMinGap = 400; // min vertical gap between obstacles
var obstacleMaxGap = 700; // max vertical gap between obstacles
var obstacleSpeed = 8; // how fast obstacles move down
var ninjaXLeft = wallMargin + 50; // center of left wall
var ninjaXRight = 2048 - wallMargin - 50; // center of right wall
// Walls
var leftWall = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0
});
leftWall.x = wallMargin + leftWall.width / 2;
leftWall.y = 0;
game.addChild(leftWall);
var rightWall = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0
});
rightWall.x = 2048 - wallMargin - rightWall.width / 2;
rightWall.y = 0;
game.addChild(rightWall);
// Ninja
var ninja = new Ninja();
ninja.x = ninjaXLeft;
ninja.y = ninjaStartY;
ninja.wallSide = 0;
game.addChild(ninja);
// Obstacles array
var obstacles = [];
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// For touch input
var isGameStarted = false;
var isDead = false;
// For obstacle generation
var lastObstacleY = ninjaStartY - 500;
// Helper: generate a new obstacle above the last one
function spawnObstacle() {
var obs = new Obstacle();
// Randomly choose type: 40% spike, 40% barrier, 20% laser
var r = Math.random();
if (r < 0.4) {
obs.setType('spike');
} else if (r < 0.8) {
obs.setType('barrier');
} else {
obs.setType('laser');
}
// Alternate wall side
obs.wallSide = Math.random() < 0.5 ? 0 : 1;
// X position
obs.x = obs.wallSide === 0 ? ninjaXLeft : ninjaXRight;
// Y position: above last obstacle
var gap = obstacleMinGap + Math.random() * (obstacleMaxGap - obstacleMinGap);
obs.y = lastObstacleY - gap;
lastObstacleY = obs.y;
// For barrier, offset horizontally so it sticks out from wall
if (obs.type === 'barrier') {
obs.x += obs.wallSide === 0 ? 90 : -90;
}
// For laser, center between walls and randomize offset
if (obs.type === 'laser') {
obs.x = (ninjaXLeft + ninjaXRight) / 2 + (Math.random() - 0.5) * 300;
obs.wallSide = -1; // not attached to a wall
}
obstacles.push(obs);
game.addChild(obs);
}
// Initial obstacles
for (var i = 0; i < 8; i++) {
spawnObstacle();
}
// Move handler: tap anywhere to jump
game.down = function (x, y, obj) {
if (isDead) return;
if (!isGameStarted) {
isGameStarted = true;
}
// Only allow jump if not already jumping
if (!ninja.isJumping) {
// Jump to other wall
var targetX = ninja.wallSide === 0 ? ninjaXRight : ninjaXLeft;
ninja.jump(jumpHeight, targetX);
}
};
// Main update loop
game.update = function () {
if (isDead) return;
// Ninja update
ninja.update();
// Move obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove obstacles that are off screen
if (obs.y > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Check for collision (only if ninja is not jumping, i.e. landed)
if (!ninja.isJumping && !obs.passed) {
// Only check obstacles on the same wall as ninja
if (obs.wallSide === ninja.wallSide) {
// Use accurate collider: LK's .intersects() for all obstacle types
if (ninja.intersects(obs)) {
isDead = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
// Also check for laser collision regardless of wall side
if (obs.type === 'laser') {
if (ninja.intersects(obs)) {
isDead = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
}
// Score: passed obstacle (only once per obstacle)
if (!obs.passed && ninja.y < obs.y) {
obs.passed = true;
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
}
}
// Camera: move everything down as ninja climbs
if (ninja.y < 1000) {
var dy = 1000 - ninja.y;
ninja.y += dy;
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].y += dy;
}
lastObstacleY += dy;
}
// Spawn new obstacles if needed
while (lastObstacleY > -200) {
spawnObstacle();
}
};
// Reset game state on game over (handled by LK, but for safety)
game.onDestroy = function () {
obstacles = [];
isGameStarted = false;
isDead = false;
score = 0;
LK.setScore(0);
};