/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 3;
self.speedY = (Math.random() - 0.5) * 3;
self.health = 1;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
// Bounce off screen edges
if (self.x < 30 || self.x > 2018) {
self.speedX *= -1;
}
if (self.y < 30 || self.y > 2702) {
self.speedY *= -1;
}
};
return self;
});
var FireProjectile = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('fireProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 8; // Downward speed
self.lifetime = 0;
self.gravity = 0.3;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.speedY += self.gravity; // Fire falls with gravity
self.lifetime++;
// Add fire effect with slight flickering
fireGraphics.alpha = 0.8 + Math.sin(self.lifetime * 0.3) * 0.2;
};
return self;
});
var Jar = Container.expand(function () {
var self = Container.call(this);
var jarGraphics = self.attachAsset('jar', {
anchorX: 0.5,
anchorY: 1.0
});
self.broken = false;
self.jumpTimer = Math.random() * 120 + 60; // Random jump intervals
self.moveTimer = Math.random() * 180 + 120; // Random movement intervals
self.moveDirection = Math.random() < 0.5 ? -1 : 1; // Random initial direction
self.isJumping = false;
self.originalY = 0; // Store original platform position
self.update = function () {
if (!self.broken && self.platform) {
// Update timers
self.jumpTimer--;
self.moveTimer--;
// Jump behavior
if (self.jumpTimer <= 0 && !self.isJumping) {
self.jump();
self.jumpTimer = Math.random() * 180 + 120; // Reset jump timer
}
// Movement behavior
if (self.moveTimer <= 0) {
self.moveDirection *= -1; // Change direction
self.moveTimer = Math.random() * 240 + 180; // Reset move timer
}
// Horizontal movement within platform bounds
var moveAmount = Math.sin(LK.ticks * 0.05) * 2 * self.moveDirection;
var newX = self.platform.x + moveAmount;
// Keep jar on platform
var platformWidth = self.platform.width || 200;
if (newX >= self.platform.x - platformWidth / 3 && newX <= self.platform.x + platformWidth / 3) {
self.x = newX;
} else {
self.x = self.platform.x;
}
// Keep jar positioned on platform
if (!self.isJumping) {
self.y = self.platform.y - self.platform.height / 2;
self.originalY = self.y;
}
// Fire attack if this jar can attack
if (self.canAttack) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.fireAttack();
self.attackTimer = Math.random() * 180 + 120; // Random fire intervals
}
}
}
};
self.jump = function () {
if (!self.isJumping && !self.broken) {
self.isJumping = true;
var jumpHeight = 80 + Math.random() * 40; // Random jump height
// Play jar sound when jumping
LK.getSound('jar').play();
// Jump up
tween(self, {
y: self.originalY - jumpHeight
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fall back down
tween(self, {
y: self.originalY
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
self.breakJar = function () {
if (!self.broken) {
self.broken = true;
LK.getSound('jarBreak').play();
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
// Track jars broken for level progression
jarsBrokenThisLevel++;
// Check if all jars are broken (count remaining unbroken jars)
var remainingJars = 0;
for (var k = 0; k < jars.length; k++) {
if (!jars[k].broken) {
remainingJars++;
}
}
// Level up only when all jars are broken (only 1 remaining = this jar that just broke)
if (remainingJars <= 1) {
// Level up!
currentLevel++;
jarsBrokenThisLevel = 0;
levelTxt.setText('Level ' + currentLevel);
// Flash screen gold for level up
LK.effects.flashScreen(0xFFD700, 800);
// Hide ninja temporarily for level transition
ninja.alpha = 0;
// Generate new platforms and jars for next level
LK.setTimeout(function () {
// Clear existing platforms and jars
for (var i = platforms.length - 1; i >= 0; i--) {
platforms[i].destroy();
}
for (var j = jars.length - 1; j >= 0; j--) {
if (!jars[j].broken) {
jars[j].destroy();
}
}
platforms = [];
jars = [];
// Reset ninja position to starting position
ninja.x = 1024;
ninja.y = 2500;
ninja.velocityY = 0;
ninja.onGround = false;
// Make platforms and ninja appear together
generatePlatforms();
// Fade in ninja with platforms
tween(ninja, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}, 500);
}
// Create multiple jar fragments for breaking effect
var fragments = [];
for (var i = 0; i < 4; i++) {
var fragment = LK.getAsset('jar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0x8b4513
});
fragment.x = self.x + (Math.random() - 0.5) * 20;
fragment.y = self.y + (Math.random() - 0.5) * 20;
fragments.push(fragment);
game.addChild(fragment);
// Animate each fragment
var fragmentSpeedX = (Math.random() - 0.5) * 200;
var fragmentSpeedY = -Math.random() * 150 - 50;
var fragmentRotation = (Math.random() - 0.5) * 6;
tween(fragment, {
x: fragment.x + fragmentSpeedX,
y: fragment.y + fragmentSpeedY + 100,
rotation: fragmentRotation,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
fragment.destroy();
}
});
}
// Main jar breaking animation - shake and fade
tween(jarGraphics, {
rotation: 0.3
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(jarGraphics, {
rotation: -0.3,
scaleX: 1.2,
scaleY: 0.8
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(jarGraphics, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
}
});
}
};
self.fireAttack = function () {
if (!self.broken && self.canAttack) {
var fireProjectile = new FireProjectile();
fireProjectile.x = self.x;
fireProjectile.y = self.y + 20;
fireProjectile.speedX = (Math.random() - 0.5) * 2; // Slight horizontal variation
fireProjectiles.push(fireProjectile);
game.addChild(fireProjectile);
LK.getSound('jarFire').play();
}
};
return self;
});
var Ninja = Container.expand(function () {
var self = Container.call(this);
var ninjaGraphics = self.attachAsset('ninja', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityY = 0;
self.onGround = false;
self.gravity = 0.8;
self.jumpPower = -18;
self.health = 5;
self.attackCooldown = 0;
self.update = function () {
// Apply gravity
if (!self.onGround) {
self.velocityY += self.gravity;
}
self.y += self.velocityY;
// Reset ground state
self.onGround = false;
// Check platform collisions
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
if (self.intersects(platform) && self.velocityY > 0) {
var platformTop = platform.y - platform.height / 2;
var ninjaBottom = self.y + self.height / 2;
if (ninjaBottom > platformTop - 20) {
self.y = platformTop - self.height / 2;
self.velocityY = 0;
self.onGround = true;
// Move ninja with platform
self.x += platform.moveSpeed * platform.moveDirection;
break;
}
}
}
// Fall off screen check
if (self.y > 2800) {
LK.showGameOver();
}
// Reduce attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
self.jump = function () {
if (self.onGround) {
self.velocityY = self.jumpPower;
self.onGround = false;
LK.getSound('jump').play();
}
};
self.attack = function (targetX, targetY) {
if (self.attackCooldown <= 0) {
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
projectile.speedX = dx / distance * 24;
projectile.speedY = dy / distance * 24;
projectiles.push(projectile);
game.addChild(projectile);
self.attackCooldown = 20;
LK.getSound('attack').play();
}
};
self.moveLeft = function () {
self.x -= ninjaSpeed;
if (self.x < 50) self.x = 50;
// Flip ninja to face left
ninjaGraphics.scaleX = -1;
};
self.moveRight = function () {
self.x += ninjaSpeed;
if (self.x > 1998) self.x = 1998;
// Flip ninja to face right
ninjaGraphics.scaleX = 1;
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
self.moveSpeed = Math.random() * 2 + 1; // Random speed between 1-3
self.moveDirection = Math.random() < 0.5 ? -1 : 1; // Random initial direction
self.leftBound = 100;
self.rightBound = 1948;
self.update = function () {
// Move platform horizontally
self.x += self.moveSpeed * self.moveDirection;
// Bounce off boundaries
if (self.x <= self.leftBound || self.x >= self.rightBound) {
self.moveDirection *= -1;
}
};
return self;
});
var Projectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.lifetime = 0;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifetime++;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Create background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1.0,
scaleY: 1.0
});
game.addChild(background);
var ninja = new Ninja();
var platforms = [];
var enemies = [];
var projectiles = [];
var jars = [];
var fireProjectiles = [];
var enemySpawnTimer = 0;
var difficultyTimer = 0;
var currentLevel = 1;
var jarsPerLevel = 5;
var jarsBrokenThisLevel = 0;
var touchStartX = 0;
var touchStartY = 0;
var isSwipeMove = false;
var ninjaSpeed = 8;
var ninjaMaxHealth = 5;
var ninjaCurrentHealth = 5;
// Create score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create level display
var levelTxt = new Text2('Level 1', {
size: 60,
fill: 0xFFD700
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 120;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Create health display
var healthTxt = new Text2('Health: 5', {
size: 60,
fill: 0xff0000
});
healthTxt.anchor.set(1, 0);
healthTxt.x = -20;
healthTxt.y = 20;
LK.gui.topRight.addChild(healthTxt);
// Generate random platforms
function generatePlatforms() {
platforms = [];
// Ground platform
var groundPlatform = new Platform();
groundPlatform.x = 1024;
groundPlatform.y = 2600;
groundPlatform.width = 400;
// Start platforms invisible for coordinated appearance
groundPlatform.alpha = 0;
platforms.push(groundPlatform);
game.addChild(groundPlatform);
// Animate ground platform appearance
tween(groundPlatform, {
alpha: 1
}, {
duration: 600,
easing: tween.easeInOut
});
// Random platforms - increase count with level
var platformCount = Math.min(8 + currentLevel, 15);
for (var i = 0; i < platformCount; i++) {
var platform = new Platform();
platform.x = Math.random() * 1600 + 200;
platform.y = 2400 - i * 250 - Math.random() * 100;
// Make platforms smaller at higher levels for more challenge
platform.width = Math.max(150 + Math.random() * 100 - currentLevel * 5, 80);
// Start platforms invisible for coordinated appearance
platform.alpha = 0;
platforms.push(platform);
game.addChild(platform);
// Animate platform appearance with staggered timing
tween(platform, {
alpha: 1
}, {
duration: 600,
delay: i * 100,
easing: tween.easeInOut
});
// Ensure enough jars are available for level completion
var jarChance = Math.min(0.8, 0.6 + currentLevel * 0.05);
if (Math.random() < jarChance) {
var jar = new Jar();
jar.x = platform.x;
jar.y = platform.y - platform.height / 2;
jar.platform = platform; // Assign platform reference to jar
// Start jars invisible for coordinated appearance
jar.alpha = 0;
// Randomly make one jar per few platforms a fire attacker
jar.canAttack = Math.random() < 0.3; // 30% chance
if (jar.canAttack) {
jar.attackTimer = Math.random() * 180 + 60;
}
jars.push(jar);
game.addChild(jar);
// Animate jar appearance slightly after platform
tween(jar, {
alpha: 1
}, {
duration: 400,
delay: i * 100 + 200,
easing: tween.easeInOut
});
}
}
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -30;
break;
case 1:
// Right
enemy.x = 2078;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2762;
break;
case 3:
// Left
enemy.x = -30;
enemy.y = Math.random() * 2732;
break;
}
enemies.push(enemy);
game.addChild(enemy);
}
// Initialize game elements
generatePlatforms();
ninja.x = 1024;
ninja.y = 2500;
game.addChild(ninja);
// Touch controls
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
isSwipeMove = false;
};
game.up = function (x, y, obj) {
if (!isSwipeMove) {
// If touching near ninja, jump
var dx = x - ninja.x;
var dy = y - ninja.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
ninja.jump();
} else {
// Attack in direction of touch
ninja.attack(x, y);
}
}
};
game.move = function (x, y, obj) {
var swipeDistanceX = x - touchStartX;
var swipeDistanceY = Math.abs(y - touchStartY);
// Detect horizontal swipe (minimum 50px horizontal, less than 100px vertical)
if (Math.abs(swipeDistanceX) > 50 && swipeDistanceY < 100) {
isSwipeMove = true;
if (swipeDistanceX > 0) {
// Swipe right
ninja.moveRight();
} else {
// Swipe left
ninja.moveLeft();
}
}
};
game.update = function () {
// Spawn enemies
enemySpawnTimer++;
difficultyTimer++;
var spawnRate = Math.max(180 - Math.floor(difficultyTimer / 300), 60);
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Check projectile vs enemy collisions
for (var p = projectiles.length - 1; p >= 0; p--) {
var projectile = projectiles[p];
// Remove old projectiles
if (projectile.lifetime > 180 || projectile.x < -50 || projectile.x > 2098 || projectile.y < -50 || projectile.y > 2782) {
projectile.destroy();
projectiles.splice(p, 1);
continue;
}
// Check enemy hits
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (projectile.intersects(enemy)) {
// Hit enemy
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyHit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
enemy.destroy();
enemies.splice(e, 1);
projectile.destroy();
projectiles.splice(p, 1);
break;
}
// Check jar hits
for (var j = jars.length - 1; j >= 0; j--) {
var jar = jars[j];
if (!jar.broken && projectile.intersects(jar)) {
jar.breakJar();
jars.splice(j, 1);
projectile.destroy();
projectiles.splice(p, 1);
break;
}
}
}
}
// Update jar positions to follow their platforms
for (var j = 0; j < jars.length; j++) {
var jar = jars[j];
if (!jar.broken && jar.platform) {
jar.x = jar.platform.x;
}
}
// Check ninja vs enemy collisions
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (ninja.intersects(enemy)) {
ninja.health--;
ninjaCurrentHealth = ninja.health;
healthTxt.setText('Health: ' + ninjaCurrentHealth);
LK.effects.flashObject(ninja, 0xff0000, 500);
enemy.destroy();
enemies.splice(e, 1);
if (ninja.health <= 0) {
LK.showGameOver();
}
}
}
// Update and check fire projectiles
for (var f = fireProjectiles.length - 1; f >= 0; f--) {
var fireProjectile = fireProjectiles[f];
// Remove fire projectiles that are off screen or too old
if (fireProjectile.lifetime > 300 || fireProjectile.y > 2800 || fireProjectile.x < -50 || fireProjectile.x > 2098) {
fireProjectile.destroy();
fireProjectiles.splice(f, 1);
continue;
}
// Check fire projectile vs ninja collision
if (fireProjectile.intersects(ninja)) {
ninja.health--;
ninjaCurrentHealth = ninja.health;
healthTxt.setText('Health: ' + ninjaCurrentHealth);
LK.effects.flashObject(ninja, 0xff4500, 500);
fireProjectile.destroy();
fireProjectiles.splice(f, 1);
if (ninja.health <= 0) {
LK.showGameOver();
}
}
}
// Keep ninja on screen horizontally
if (ninja.x < 40) ninja.x = 40;
if (ninja.x > 2008) ninja.x = 2008;
};
// Play background music
LK.playMusic('ninjai'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 3;
self.speedY = (Math.random() - 0.5) * 3;
self.health = 1;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
// Bounce off screen edges
if (self.x < 30 || self.x > 2018) {
self.speedX *= -1;
}
if (self.y < 30 || self.y > 2702) {
self.speedY *= -1;
}
};
return self;
});
var FireProjectile = Container.expand(function () {
var self = Container.call(this);
var fireGraphics = self.attachAsset('fireProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 8; // Downward speed
self.lifetime = 0;
self.gravity = 0.3;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.speedY += self.gravity; // Fire falls with gravity
self.lifetime++;
// Add fire effect with slight flickering
fireGraphics.alpha = 0.8 + Math.sin(self.lifetime * 0.3) * 0.2;
};
return self;
});
var Jar = Container.expand(function () {
var self = Container.call(this);
var jarGraphics = self.attachAsset('jar', {
anchorX: 0.5,
anchorY: 1.0
});
self.broken = false;
self.jumpTimer = Math.random() * 120 + 60; // Random jump intervals
self.moveTimer = Math.random() * 180 + 120; // Random movement intervals
self.moveDirection = Math.random() < 0.5 ? -1 : 1; // Random initial direction
self.isJumping = false;
self.originalY = 0; // Store original platform position
self.update = function () {
if (!self.broken && self.platform) {
// Update timers
self.jumpTimer--;
self.moveTimer--;
// Jump behavior
if (self.jumpTimer <= 0 && !self.isJumping) {
self.jump();
self.jumpTimer = Math.random() * 180 + 120; // Reset jump timer
}
// Movement behavior
if (self.moveTimer <= 0) {
self.moveDirection *= -1; // Change direction
self.moveTimer = Math.random() * 240 + 180; // Reset move timer
}
// Horizontal movement within platform bounds
var moveAmount = Math.sin(LK.ticks * 0.05) * 2 * self.moveDirection;
var newX = self.platform.x + moveAmount;
// Keep jar on platform
var platformWidth = self.platform.width || 200;
if (newX >= self.platform.x - platformWidth / 3 && newX <= self.platform.x + platformWidth / 3) {
self.x = newX;
} else {
self.x = self.platform.x;
}
// Keep jar positioned on platform
if (!self.isJumping) {
self.y = self.platform.y - self.platform.height / 2;
self.originalY = self.y;
}
// Fire attack if this jar can attack
if (self.canAttack) {
self.attackTimer--;
if (self.attackTimer <= 0) {
self.fireAttack();
self.attackTimer = Math.random() * 180 + 120; // Random fire intervals
}
}
}
};
self.jump = function () {
if (!self.isJumping && !self.broken) {
self.isJumping = true;
var jumpHeight = 80 + Math.random() * 40; // Random jump height
// Play jar sound when jumping
LK.getSound('jar').play();
// Jump up
tween(self, {
y: self.originalY - jumpHeight
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Fall back down
tween(self, {
y: self.originalY
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
}
};
self.breakJar = function () {
if (!self.broken) {
self.broken = true;
LK.getSound('jarBreak').play();
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
// Track jars broken for level progression
jarsBrokenThisLevel++;
// Check if all jars are broken (count remaining unbroken jars)
var remainingJars = 0;
for (var k = 0; k < jars.length; k++) {
if (!jars[k].broken) {
remainingJars++;
}
}
// Level up only when all jars are broken (only 1 remaining = this jar that just broke)
if (remainingJars <= 1) {
// Level up!
currentLevel++;
jarsBrokenThisLevel = 0;
levelTxt.setText('Level ' + currentLevel);
// Flash screen gold for level up
LK.effects.flashScreen(0xFFD700, 800);
// Hide ninja temporarily for level transition
ninja.alpha = 0;
// Generate new platforms and jars for next level
LK.setTimeout(function () {
// Clear existing platforms and jars
for (var i = platforms.length - 1; i >= 0; i--) {
platforms[i].destroy();
}
for (var j = jars.length - 1; j >= 0; j--) {
if (!jars[j].broken) {
jars[j].destroy();
}
}
platforms = [];
jars = [];
// Reset ninja position to starting position
ninja.x = 1024;
ninja.y = 2500;
ninja.velocityY = 0;
ninja.onGround = false;
// Make platforms and ninja appear together
generatePlatforms();
// Fade in ninja with platforms
tween(ninja, {
alpha: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}, 500);
}
// Create multiple jar fragments for breaking effect
var fragments = [];
for (var i = 0; i < 4; i++) {
var fragment = LK.getAsset('jar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0x8b4513
});
fragment.x = self.x + (Math.random() - 0.5) * 20;
fragment.y = self.y + (Math.random() - 0.5) * 20;
fragments.push(fragment);
game.addChild(fragment);
// Animate each fragment
var fragmentSpeedX = (Math.random() - 0.5) * 200;
var fragmentSpeedY = -Math.random() * 150 - 50;
var fragmentRotation = (Math.random() - 0.5) * 6;
tween(fragment, {
x: fragment.x + fragmentSpeedX,
y: fragment.y + fragmentSpeedY + 100,
rotation: fragmentRotation,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
fragment.destroy();
}
});
}
// Main jar breaking animation - shake and fade
tween(jarGraphics, {
rotation: 0.3
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(jarGraphics, {
rotation: -0.3,
scaleX: 1.2,
scaleY: 0.8
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(jarGraphics, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
}
});
}
};
self.fireAttack = function () {
if (!self.broken && self.canAttack) {
var fireProjectile = new FireProjectile();
fireProjectile.x = self.x;
fireProjectile.y = self.y + 20;
fireProjectile.speedX = (Math.random() - 0.5) * 2; // Slight horizontal variation
fireProjectiles.push(fireProjectile);
game.addChild(fireProjectile);
LK.getSound('jarFire').play();
}
};
return self;
});
var Ninja = Container.expand(function () {
var self = Container.call(this);
var ninjaGraphics = self.attachAsset('ninja', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityY = 0;
self.onGround = false;
self.gravity = 0.8;
self.jumpPower = -18;
self.health = 5;
self.attackCooldown = 0;
self.update = function () {
// Apply gravity
if (!self.onGround) {
self.velocityY += self.gravity;
}
self.y += self.velocityY;
// Reset ground state
self.onGround = false;
// Check platform collisions
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
if (self.intersects(platform) && self.velocityY > 0) {
var platformTop = platform.y - platform.height / 2;
var ninjaBottom = self.y + self.height / 2;
if (ninjaBottom > platformTop - 20) {
self.y = platformTop - self.height / 2;
self.velocityY = 0;
self.onGround = true;
// Move ninja with platform
self.x += platform.moveSpeed * platform.moveDirection;
break;
}
}
}
// Fall off screen check
if (self.y > 2800) {
LK.showGameOver();
}
// Reduce attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
self.jump = function () {
if (self.onGround) {
self.velocityY = self.jumpPower;
self.onGround = false;
LK.getSound('jump').play();
}
};
self.attack = function (targetX, targetY) {
if (self.attackCooldown <= 0) {
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
projectile.speedX = dx / distance * 24;
projectile.speedY = dy / distance * 24;
projectiles.push(projectile);
game.addChild(projectile);
self.attackCooldown = 20;
LK.getSound('attack').play();
}
};
self.moveLeft = function () {
self.x -= ninjaSpeed;
if (self.x < 50) self.x = 50;
// Flip ninja to face left
ninjaGraphics.scaleX = -1;
};
self.moveRight = function () {
self.x += ninjaSpeed;
if (self.x > 1998) self.x = 1998;
// Flip ninja to face right
ninjaGraphics.scaleX = 1;
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
self.moveSpeed = Math.random() * 2 + 1; // Random speed between 1-3
self.moveDirection = Math.random() < 0.5 ? -1 : 1; // Random initial direction
self.leftBound = 100;
self.rightBound = 1948;
self.update = function () {
// Move platform horizontally
self.x += self.moveSpeed * self.moveDirection;
// Bounce off boundaries
if (self.x <= self.leftBound || self.x >= self.rightBound) {
self.moveDirection *= -1;
}
};
return self;
});
var Projectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = 0;
self.speedY = 0;
self.lifetime = 0;
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.lifetime++;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Create background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1.0,
scaleY: 1.0
});
game.addChild(background);
var ninja = new Ninja();
var platforms = [];
var enemies = [];
var projectiles = [];
var jars = [];
var fireProjectiles = [];
var enemySpawnTimer = 0;
var difficultyTimer = 0;
var currentLevel = 1;
var jarsPerLevel = 5;
var jarsBrokenThisLevel = 0;
var touchStartX = 0;
var touchStartY = 0;
var isSwipeMove = false;
var ninjaSpeed = 8;
var ninjaMaxHealth = 5;
var ninjaCurrentHealth = 5;
// Create score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create level display
var levelTxt = new Text2('Level 1', {
size: 60,
fill: 0xFFD700
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 120;
levelTxt.y = 20;
LK.gui.topLeft.addChild(levelTxt);
// Create health display
var healthTxt = new Text2('Health: 5', {
size: 60,
fill: 0xff0000
});
healthTxt.anchor.set(1, 0);
healthTxt.x = -20;
healthTxt.y = 20;
LK.gui.topRight.addChild(healthTxt);
// Generate random platforms
function generatePlatforms() {
platforms = [];
// Ground platform
var groundPlatform = new Platform();
groundPlatform.x = 1024;
groundPlatform.y = 2600;
groundPlatform.width = 400;
// Start platforms invisible for coordinated appearance
groundPlatform.alpha = 0;
platforms.push(groundPlatform);
game.addChild(groundPlatform);
// Animate ground platform appearance
tween(groundPlatform, {
alpha: 1
}, {
duration: 600,
easing: tween.easeInOut
});
// Random platforms - increase count with level
var platformCount = Math.min(8 + currentLevel, 15);
for (var i = 0; i < platformCount; i++) {
var platform = new Platform();
platform.x = Math.random() * 1600 + 200;
platform.y = 2400 - i * 250 - Math.random() * 100;
// Make platforms smaller at higher levels for more challenge
platform.width = Math.max(150 + Math.random() * 100 - currentLevel * 5, 80);
// Start platforms invisible for coordinated appearance
platform.alpha = 0;
platforms.push(platform);
game.addChild(platform);
// Animate platform appearance with staggered timing
tween(platform, {
alpha: 1
}, {
duration: 600,
delay: i * 100,
easing: tween.easeInOut
});
// Ensure enough jars are available for level completion
var jarChance = Math.min(0.8, 0.6 + currentLevel * 0.05);
if (Math.random() < jarChance) {
var jar = new Jar();
jar.x = platform.x;
jar.y = platform.y - platform.height / 2;
jar.platform = platform; // Assign platform reference to jar
// Start jars invisible for coordinated appearance
jar.alpha = 0;
// Randomly make one jar per few platforms a fire attacker
jar.canAttack = Math.random() < 0.3; // 30% chance
if (jar.canAttack) {
jar.attackTimer = Math.random() * 180 + 60;
}
jars.push(jar);
game.addChild(jar);
// Animate jar appearance slightly after platform
tween(jar, {
alpha: 1
}, {
duration: 400,
delay: i * 100 + 200,
easing: tween.easeInOut
});
}
}
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -30;
break;
case 1:
// Right
enemy.x = 2078;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2762;
break;
case 3:
// Left
enemy.x = -30;
enemy.y = Math.random() * 2732;
break;
}
enemies.push(enemy);
game.addChild(enemy);
}
// Initialize game elements
generatePlatforms();
ninja.x = 1024;
ninja.y = 2500;
game.addChild(ninja);
// Touch controls
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
isSwipeMove = false;
};
game.up = function (x, y, obj) {
if (!isSwipeMove) {
// If touching near ninja, jump
var dx = x - ninja.x;
var dy = y - ninja.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
ninja.jump();
} else {
// Attack in direction of touch
ninja.attack(x, y);
}
}
};
game.move = function (x, y, obj) {
var swipeDistanceX = x - touchStartX;
var swipeDistanceY = Math.abs(y - touchStartY);
// Detect horizontal swipe (minimum 50px horizontal, less than 100px vertical)
if (Math.abs(swipeDistanceX) > 50 && swipeDistanceY < 100) {
isSwipeMove = true;
if (swipeDistanceX > 0) {
// Swipe right
ninja.moveRight();
} else {
// Swipe left
ninja.moveLeft();
}
}
};
game.update = function () {
// Spawn enemies
enemySpawnTimer++;
difficultyTimer++;
var spawnRate = Math.max(180 - Math.floor(difficultyTimer / 300), 60);
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Check projectile vs enemy collisions
for (var p = projectiles.length - 1; p >= 0; p--) {
var projectile = projectiles[p];
// Remove old projectiles
if (projectile.lifetime > 180 || projectile.x < -50 || projectile.x > 2098 || projectile.y < -50 || projectile.y > 2782) {
projectile.destroy();
projectiles.splice(p, 1);
continue;
}
// Check enemy hits
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (projectile.intersects(enemy)) {
// Hit enemy
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
LK.getSound('enemyHit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
enemy.destroy();
enemies.splice(e, 1);
projectile.destroy();
projectiles.splice(p, 1);
break;
}
// Check jar hits
for (var j = jars.length - 1; j >= 0; j--) {
var jar = jars[j];
if (!jar.broken && projectile.intersects(jar)) {
jar.breakJar();
jars.splice(j, 1);
projectile.destroy();
projectiles.splice(p, 1);
break;
}
}
}
}
// Update jar positions to follow their platforms
for (var j = 0; j < jars.length; j++) {
var jar = jars[j];
if (!jar.broken && jar.platform) {
jar.x = jar.platform.x;
}
}
// Check ninja vs enemy collisions
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (ninja.intersects(enemy)) {
ninja.health--;
ninjaCurrentHealth = ninja.health;
healthTxt.setText('Health: ' + ninjaCurrentHealth);
LK.effects.flashObject(ninja, 0xff0000, 500);
enemy.destroy();
enemies.splice(e, 1);
if (ninja.health <= 0) {
LK.showGameOver();
}
}
}
// Update and check fire projectiles
for (var f = fireProjectiles.length - 1; f >= 0; f--) {
var fireProjectile = fireProjectiles[f];
// Remove fire projectiles that are off screen or too old
if (fireProjectile.lifetime > 300 || fireProjectile.y > 2800 || fireProjectile.x < -50 || fireProjectile.x > 2098) {
fireProjectile.destroy();
fireProjectiles.splice(f, 1);
continue;
}
// Check fire projectile vs ninja collision
if (fireProjectile.intersects(ninja)) {
ninja.health--;
ninjaCurrentHealth = ninja.health;
healthTxt.setText('Health: ' + ninjaCurrentHealth);
LK.effects.flashObject(ninja, 0xff4500, 500);
fireProjectile.destroy();
fireProjectiles.splice(f, 1);
if (ninja.health <= 0) {
LK.showGameOver();
}
}
}
// Keep ninja on screen horizontally
if (ninja.x < 40) ninja.x = 40;
if (ninja.x > 2008) ninja.x = 2008;
};
// Play background music
LK.playMusic('ninjai');
2d anime chibi style ninja hitam jepang. In-Game asset. 2d. High contrast. No shadows
2d anime image pemandangan gunung fuji jepang zaman kuno di kejauhan. wilayah hutan dan bukit bebatuan dari dekat. langit biru cerah. In-Game asset. 2d. High contrast. No shadows
2d anime chibi evil hitam jepang. In-Game asset. 2d. High contrast. No shadows
2d anime image tanah dan bebatuan. In-Game asset. 2d. High contrast. No shadows