/****
* 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);
};