/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var DPad = Container.expand(function () {
var self = Container.call(this);
// Create the D-Pad background with increased size
var padBg = LK.getAsset('player', {
width: 250,
height: 250,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333,
alpha: 0.6
});
// Create the directional buttons with increased size
var upButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
upButton.y = -80;
var downButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
downButton.y = 80;
var leftButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
leftButton.x = -80;
var rightButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
rightButton.x = 80;
// Add all components to the container
self.addChild(padBg);
self.addChild(upButton);
self.addChild(downButton);
self.addChild(leftButton);
self.addChild(rightButton);
// Direction state
self.direction = {
up: false,
down: false,
left: false,
right: false
};
// Make the entire D-Pad container interactive to capture all touch events
self.interactive = true;
// Buttons don't need to be individually interactive when container handles events
// Highlight function for visual feedback
function highlightButton(button) {
tween(button, {
tint: 0x00FFFF
}, {
duration: 100,
easing: tween.easeOut
});
}
function unhighlightButton(button) {
tween(button, {
tint: 0x666666
}, {
duration: 100,
easing: tween.easeIn
});
}
// Event handlers
self.down = function (x, y, obj) {
// Convert global coordinates to local coordinates
var localPos = {
x: x - self.x,
y: y - self.y
};
// Check which button was pressed using more generous hit areas
if (Math.abs(localPos.x) < 40 && localPos.y < -30) {
self.direction.up = true;
highlightButton(upButton);
} else if (Math.abs(localPos.x) < 40 && localPos.y > 30) {
self.direction.down = true;
highlightButton(downButton);
} else if (localPos.x < -30 && Math.abs(localPos.y) < 40) {
self.direction.left = true;
highlightButton(leftButton);
} else if (localPos.x > 30 && Math.abs(localPos.y) < 40) {
self.direction.right = true;
highlightButton(rightButton);
}
};
self.up = function (x, y, obj) {
// Reset all directions when touch ends
self.direction.up = false;
self.direction.down = false;
self.direction.left = false;
self.direction.right = false;
// Reset all button appearances
unhighlightButton(upButton);
unhighlightButton(downButton);
unhighlightButton(leftButton);
unhighlightButton(rightButton);
};
self.move = function (x, y, obj) {
// Convert global coordinates to local coordinates
var localPos = {
x: x - self.x,
y: y - self.y
};
// Update direction based on touch position with improved detection
self.direction.up = Math.abs(localPos.x) < 40 && localPos.y < -30;
self.direction.down = Math.abs(localPos.x) < 40 && localPos.y > 30;
self.direction.left = localPos.x < -30 && Math.abs(localPos.y) < 40;
self.direction.right = localPos.x > 30 && Math.abs(localPos.y) < 40;
// Update button appearances
if (self.direction.up) highlightButton(upButton);else unhighlightButton(upButton);
if (self.direction.down) highlightButton(downButton);else unhighlightButton(downButton);
if (self.direction.left) highlightButton(leftButton);else unhighlightButton(leftButton);
if (self.direction.right) highlightButton(rightButton);else unhighlightButton(rightButton);
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Make the enemy graphic much bigger
enemyGraphic.scale.x = 2.2;
enemyGraphic.scale.y = 2.2;
self.health = 20;
self.maxHealth = 20;
self.speed = 1.5;
self.damage = 5;
// Health bar
self.healthBar = new Container();
self.healthBar.y = -50;
self.healthBarBg = LK.getAsset('player', {
width: 70,
height: 8,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.healthBarFill = LK.getAsset('player', {
width: 70,
height: 8,
anchorX: 0,
anchorY: 0.5,
tint: 0xFF0000
});
self.healthBarFill.x = -35;
self.healthBar.addChild(self.healthBarBg);
self.healthBar.addChild(self.healthBarFill);
self.addChild(self.healthBar);
self.attackCooldown = 0;
self.attackCooldownMax = 30; // Frames between attacks
self.experienceValue = 1;
self.hitByKatana = false;
self.katanaHitTimer = 0;
self.update = function () {
if (player.isDead) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Normalize and apply speed
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Update health bar
self.healthBarFill.width = 70 * (self.health / self.maxHealth);
// Attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Decrement katana hit timer if active
if (self.katanaHitTimer > 0) {
self.katanaHitTimer--;
if (self.katanaHitTimer === 0) {
self.hitByKatana = false;
}
}
// Attack player if touching and not recently hit by katana
if (self.intersects(player) && self.attackCooldown === 0 && !self.hitByKatana) {
player.takeDamage(self.damage);
self.attackCooldown = self.attackCooldownMax;
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.health = Math.max(0, self.health);
// Flash white when damaged
tween(enemyGraphic, {
tint: 0xFFFFFF
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemyGraphic, {
tint: 0xff2222 // Return to original enemy color
}, {
duration: 50,
easing: tween.easeIn
});
}
});
// Create explosion effect
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Add a small bounce effect using tween
tween(self, {
y: self.y - 10
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 10
}, {
duration: 100,
easing: tween.easeIn
});
}
});
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Spawn experience gem
var expGem = new ExperienceGem();
expGem.value = self.experienceValue;
expGem.x = self.x;
expGem.y = self.y;
game.addChild(expGem);
experienceGems.push(expGem);
// Remove from enemies array and destroy
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
self.destroy();
// Increment kill count
killCount++;
killCountText.setText("Kills: " + killCount);
};
return self;
});
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0x2222ff; // Blue tint
// Override default properties
self.health = 50;
self.maxHealth = 50;
self.speed = 1.5; // Slower than regular enemies
self.damage = 15; // More damage
// Larger hitbox
self.scale.x = 2.8;
self.scale.y = 2.8;
return self;
});
var RangedEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0xff22ff; // Purple tint
// Override default properties
self.health = 15;
self.maxHealth = 15;
self.speed = 1.8;
self.damage = 8;
self.shootRange = 300; // Range at which enemy will stop and shoot
self.projectileSpeed = 8;
self.attackCooldownMax = 90; // Slower attacks
// Override update method to add shooting behavior
var originalUpdate = self.update;
self.update = function () {
if (player.isDead) return;
// Calculate distance to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If in range, stop and shoot
if (distance <= self.shootRange && self.attackCooldown === 0) {
// Shoot at player
var projectile = new EnemyProjectile();
projectile.x = self.x;
projectile.y = self.y;
// Direction toward player
if (distance > 0) {
projectile.direction = {
x: dx / distance,
y: dy / distance
};
}
projectile.speed = self.projectileSpeed;
projectile.damage = self.damage;
game.addChild(projectile);
enemyProjectiles.push(projectile);
self.attackCooldown = self.attackCooldownMax;
} else {
// Move only if not in range or on cooldown
if (distance > self.shootRange || self.attackCooldown > 0) {
// Use original movement logic
originalUpdate.call(self);
return;
}
}
// Update cooldowns and other logic
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update health bar
self.healthBarFill.width = 70 * (self.health / self.maxHealth);
// Decrement katana hit timer if active
if (self.katanaHitTimer > 0) {
self.katanaHitTimer--;
if (self.katanaHitTimer === 0) {
self.hitByKatana = false;
}
}
};
return self;
});
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0x22ff22; // Green tint
// Override default properties
self.health = 10;
self.maxHealth = 10;
self.speed = 4; // Faster than regular enemies
self.damage = 5; // Less damage
// Slightly smaller hitbox, but still bigger than before
self.scale.x = 1.2;
self.scale.y = 1.2;
return self;
});
var EnemyProjectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphic = self.attachAsset('shuriken', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff00ff
});
self.direction = {
x: 0,
y: 0
};
self.speed = 8;
self.damage = 5;
self.update = function () {
// Move in direction
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Rotate for effect
projectileGraphic.rotation += 0.1;
// Check if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
var index = enemyProjectiles.indexOf(self);
if (index !== -1) {
enemyProjectiles.splice(index, 1);
}
return;
}
// Check for player collision
if (self.intersects(player) && !player.isDead) {
player.takeDamage(self.damage);
// Remove projectile
self.destroy();
var index = enemyProjectiles.indexOf(self);
if (index !== -1) {
enemyProjectiles.splice(index, 1);
}
}
};
return self;
});
var ExperienceGem = Container.expand(function () {
var self = Container.call(this);
var gemGraphic = self.attachAsset('expGem', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.speed = 0;
self.magnetRange = 200;
self.isMovingToPlayer = false;
self.update = function () {
if (player.isDead) return;
// Calculate distance to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If close to player, move toward player (magnetic effect)
if (distance < self.magnetRange || self.isMovingToPlayer) {
self.isMovingToPlayer = true;
self.speed += 0.2;
self.speed = Math.min(self.speed, 15);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// If touching player, give experience
if (self.intersects(player)) {
player.gainExperience(self.value);
// Remove from array and destroy
var index = experienceGems.indexOf(self);
if (index !== -1) {
experienceGems.splice(index, 1);
}
self.destroy();
}
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFA500
});
self.lifetime = 0;
self.maxLifetime = 15; // Short lifetime for quick explosion effect
self.update = function () {
self.lifetime++;
// Scale up and fade out
var progress = self.lifetime / self.maxLifetime;
explosionGraphic.scale.x = 1 + progress;
explosionGraphic.scale.y = 1 + progress;
explosionGraphic.alpha = 1 - progress;
// Destroy when animation complete
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
return self;
});
var Katana = Container.expand(function () {
var self = Container.call(this);
var katanaGraphic = self.attachAsset('katana', {
anchorX: 0,
anchorY: 0.5
});
self.damage = 20;
self.rotationSpeed = 0.25;
self.attackCooldown = 0;
self.attackCooldownMax = 8; // Frames between attacks
self.level = 1;
// Attack cooldown indicator
self.cooldownBar = new Container();
self.cooldownBar.y = 15;
self.cooldownBarBg = LK.getAsset('player', {
width: 150,
height: 5,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.cooldownBarFill = LK.getAsset('player', {
width: 150,
height: 5,
anchorX: 0,
anchorY: 0.5,
tint: 0x00FFFF
});
self.cooldownBarFill.x = -75;
self.cooldownBar.addChild(self.cooldownBarBg);
self.cooldownBar.addChild(self.cooldownBarFill);
self.addChild(self.cooldownBar);
self.update = function () {
if (player.isDead) return;
// Rotate around player
self.rotation += self.rotationSpeed;
// Attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update cooldown bar
var readyPercent = 1 - self.attackCooldown / self.attackCooldownMax;
self.cooldownBarFill.width = 150 * readyPercent;
// Modify color based on readiness
if (readyPercent === 1) {
self.cooldownBarFill.tint = 0x00FF00; // Green when ready
} else {
self.cooldownBarFill.tint = 0x00FFFF; // Cyan when charging
}
// Check for enemies in range and attack
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy) && self.attackCooldown === 0) {
enemy.takeDamage(self.damage);
self.attackCooldown = self.attackCooldownMax;
LK.getSound('slash').play();
// Mark enemy as being hit by katana - this will be used to prevent damage to player
enemy.hitByKatana = true;
enemy.katanaHitTimer = 30; // Frames of immunity after being hit
// Don't break, allow the katana to hit multiple enemies in a single swing
}
}
};
self.levelUp = function () {
self.level += 1;
self.damage += 5;
self.attackCooldownMax = Math.max(10, self.attackCooldownMax - 2);
};
return self;
});
var LevelUpEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphic = self.attachAsset('levelUpIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.lifetime = 0;
self.maxLifetime = 60; // 1 second at 60fps
self.update = function () {
self.lifetime++;
// Scale up and fade out
var progress = self.lifetime / self.maxLifetime;
effectGraphic.scale.x = 1 + progress;
effectGraphic.scale.y = 1 + progress;
effectGraphic.alpha = 0.7 * (1 - progress);
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerBody = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.health = 150;
self.maxHealth = 150;
self.level = 1;
self.experience = 0;
self.experienceToNextLevel = 10;
self.isDead = false;
// Health display
self.healthBar = new Container();
self.healthBar.y = -70;
self.healthBarBg = LK.getAsset('player', {
width: 100,
height: 10,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.healthBarFill = LK.getAsset('player', {
width: 100,
height: 10,
anchorX: 0,
anchorY: 0.5,
tint: 0x00FF00
});
self.healthBarFill.x = -50;
self.healthBar.addChild(self.healthBarBg);
self.healthBar.addChild(self.healthBarFill);
self.addChild(self.healthBar);
self.update = function () {
if (self.isDead) return;
// Update health bar
self.healthBarFill.width = 100 * (self.health / self.maxHealth);
// Keep player within bounds when using D-Pad
self.x = Math.max(50, Math.min(2048 - 50, self.x));
self.y = Math.max(50, Math.min(2732 - 50, self.y));
};
self.takeDamage = function (amount) {
if (self.isDead) return;
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
self.die();
} else {
// Flash red when damaged
LK.effects.flashObject(playerBody, 0xFF0000, 200);
}
};
self.gainExperience = function (amount) {
if (self.isDead) return;
self.experience += amount;
if (self.experience >= self.experienceToNextLevel) {
self.levelUp();
}
};
self.levelUp = function () {
self.level += 1;
self.experience = 0;
self.experienceToNextLevel = Math.floor(self.experienceToNextLevel * 1.3);
self.maxHealth += 10;
self.health = self.maxHealth;
// Level up effects
var levelUpEffect = new LevelUpEffect();
levelUpEffect.x = self.x;
levelUpEffect.y = self.y;
game.addChild(levelUpEffect);
LK.getSound('levelup').play();
};
self.die = function () {
if (self.isDead) return;
self.isDead = true;
LK.getSound('death').play();
LK.effects.flashObject(playerBody, 0xFF0000, 1000);
// Game over after a short delay
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
};
return self;
});
var Shuriken = Container.expand(function () {
var self = Container.call(this);
var shurikenGraphic = self.attachAsset('shuriken', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.speed = 10;
self.direction = {
x: 0,
y: 0
};
self.pierceCount = 1; // How many enemies it can hit before disappearing
self.hitEnemies = []; // Track which enemies we've already hit
self.level = 1;
self.update = function () {
// Move in direction
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Rotate the shuriken for effect
shurikenGraphic.rotation += 0.2;
// Check if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
var index = shurikens.indexOf(self);
if (index !== -1) {
shurikens.splice(index, 1);
}
return;
}
// Check for enemy collisions
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Skip enemies we've already hit
if (self.hitEnemies.indexOf(enemy) !== -1) {
continue;
}
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage);
LK.getSound('hit').play();
self.hitEnemies.push(enemy);
// If we've hit enough enemies, destroy this shuriken
if (self.hitEnemies.length >= self.pierceCount) {
self.destroy();
var index = shurikens.indexOf(self);
if (index !== -1) {
shurikens.splice(index, 1);
}
return;
}
}
}
};
self.levelUp = function () {
self.level += 1;
self.damage += 10;
self.pierceCount += 2;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x003300
});
/****
* Game Code
****/
// Global variables
var player;
var katana;
var shurikens = [];
var enemies = [];
var experienceGems = [];
var enemyProjectiles = [];
var dragPosition = null;
var dpad;
var movementSpeed = 8;
var gameTime = 0;
var shurikenTimer = 0;
var enemySpawnTimer = 0;
var waveNumber = 1;
var killCount = 0;
// UI elements
var timeText;
var waveText;
var killCountText;
var levelText;
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// Initialize katana
katana = new Katana();
player.addChild(katana);
// Initialize D-Pad
dpad = new DPad();
dpad.x = 200;
dpad.y = 2732 - 200;
game.addChild(dpad);
// Initialize UI
timeText = new Text2('Time: 0s', {
size: 60,
fill: 0xFFFFFF
});
timeText.anchor.set(1, 0);
LK.gui.topRight.addChild(timeText);
waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
killCountText = new Text2('Kills: 0', {
size: 60,
fill: 0xFFFFFF
});
killCountText.anchor.set(0, 0);
LK.gui.topRight.addChild(killCountText);
killCountText.y = 70;
levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelText);
levelText.x = 120; // Avoid platform level menu icon area
// Spawn initial enemies
spawnEnemyWave(5);
// Play background music
LK.playMusic('bgmusic');
// Handle player movement
game.down = function (x, y, obj) {
dragPosition = {
x: x,
y: y
};
};
game.move = function (x, y, obj) {
if (dragPosition) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
dragPosition = null;
};
// Main game update loop
game.update = function () {
if (player.isDead) return;
// Update game time (in seconds)
gameTime += 1 / 60;
timeText.setText('Time: ' + Math.floor(gameTime) + 's');
// Update level text
levelText.setText('Level: ' + player.level);
// Handle D-Pad movement
if (dpad.direction.up) {
player.y -= movementSpeed;
}
if (dpad.direction.down) {
player.y += movementSpeed;
}
if (dpad.direction.left) {
player.x -= movementSpeed;
}
if (dpad.direction.right) {
player.x += movementSpeed;
}
// Keep player within bounds
player.x = Math.max(50, Math.min(2048 - 50, player.x));
player.y = Math.max(50, Math.min(2732 - 50, player.y));
// Shuriken spawning logic
shurikenTimer++;
if (shurikenTimer >= 180) {
// Every 3 seconds (60fps * 3)
spawnShuriken();
shurikenTimer = 0;
}
// Enemy spawning logic
enemySpawnTimer++;
var spawnRate = Math.max(400 - waveNumber * 10, 120); // Slower spawns to make game easier
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Wave progression
if (Math.floor(gameTime) % 30 === 0 && Math.floor(gameTime) > 0 && Math.floor(gameTime / 30) > waveNumber - 1) {
waveNumber++;
waveText.setText('Wave: ' + waveNumber);
spawnEnemyWave(5 + waveNumber);
}
};
// Helper functions
function spawnShuriken() {
for (var i = 0; i < (player.level > 5 ? 2 : 1); i++) {
var shuriken = new Shuriken();
shuriken.x = player.x;
shuriken.y = player.y;
// Choose a random enemy to target, or random direction if no enemies
if (enemies.length > 0) {
var targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
var dx = targetEnemy.x - player.x;
var dy = targetEnemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
shuriken.direction = {
x: dx / distance,
y: dy / distance
};
} else {
// Random direction if enemy is directly on top of player
var angle = Math.random() * Math.PI * 2;
shuriken.direction = {
x: Math.cos(angle),
y: Math.sin(angle)
};
}
} else {
// Random direction if no enemies
var angle = Math.random() * Math.PI * 2;
shuriken.direction = {
x: Math.cos(angle),
y: Math.sin(angle)
};
}
game.addChild(shuriken);
shurikens.push(shuriken);
LK.getSound('throw').play();
}
}
function spawnEnemy() {
var enemy;
// Choose random enemy type with increasing difficulty by wave
var randType = Math.random();
if (waveNumber >= 3 && randType < 0.25) {
// 25% chance for ranged enemies after wave 3
enemy = new RangedEnemy();
} else if (waveNumber >= 2 && randType < 0.5) {
// 25% chance for tank enemies after wave 2
enemy = new TankEnemy();
} else if (randType < 0.75) {
// 25% chance for fast enemies
enemy = new FastEnemy();
} else {
// 25% chance for regular enemies
enemy = new Enemy();
}
// Scale enemy stats with wave number (reduced scaling for easier gameplay)
enemy.health *= 1 + waveNumber * 0.1;
enemy.maxHealth = enemy.health;
enemy.speed *= 1 + waveNumber * 0.03;
enemy.damage *= 1 + waveNumber * 0.05;
enemy.experienceValue = 2 + Math.floor(waveNumber / 2);
// Spawn outside the screen
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -100;
break;
case 1:
// Right
enemy.x = 2148;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2832;
break;
case 3:
// Left
enemy.x = -100;
enemy.y = Math.random() * 2732;
break;
}
game.addChild(enemy);
enemies.push(enemy);
}
function spawnEnemyWave(count) {
// Add more enemies as game progresses
var actualCount = count + Math.floor(waveNumber / 2);
// Special wave types based on wave number
if (waveNumber % 5 === 0) {
// Boss wave - add a few super tank enemies
var bossCount = Math.min(3, Math.floor(waveNumber / 5));
for (var i = 0; i < bossCount; i++) {
var bossEnemy = new TankEnemy();
bossEnemy.health = 100 + waveNumber * 10;
bossEnemy.maxHealth = bossEnemy.health;
bossEnemy.damage *= 1.5;
bossEnemy.scale.x = 2.5;
bossEnemy.scale.y = 2.5;
bossEnemy.experienceValue = 5 + Math.floor(waveNumber / 2);
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
bossEnemy.x = Math.random() * 2048;
bossEnemy.y = -100;
break;
case 1:
bossEnemy.x = 2148;
bossEnemy.y = Math.random() * 2732;
break;
case 2:
bossEnemy.x = Math.random() * 2048;
bossEnemy.y = 2832;
break;
case 3:
bossEnemy.x = -100;
bossEnemy.y = Math.random() * 2732;
break;
}
game.addChild(bossEnemy);
enemies.push(bossEnemy);
}
// Add remaining enemies
for (var i = 0; i < actualCount - bossCount; i++) {
spawnEnemy();
}
} else if (waveNumber % 3 === 0) {
// Swarm wave - lots of fast enemies
for (var i = 0; i < actualCount * 1.5; i++) {
var fastEnemy = new FastEnemy();
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
fastEnemy.x = Math.random() * 2048;
fastEnemy.y = -100;
break;
case 1:
fastEnemy.x = 2148;
fastEnemy.y = Math.random() * 2732;
break;
case 2:
fastEnemy.x = Math.random() * 2048;
fastEnemy.y = 2832;
break;
case 3:
fastEnemy.x = -100;
fastEnemy.y = Math.random() * 2732;
break;
}
game.addChild(fastEnemy);
enemies.push(fastEnemy);
}
} else if (waveNumber % 2 === 0) {
// Ranged wave - more ranged enemies
for (var i = 0; i < actualCount; i++) {
if (i < actualCount / 2) {
var rangedEnemy = new RangedEnemy();
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
rangedEnemy.x = Math.random() * 2048;
rangedEnemy.y = -100;
break;
case 1:
rangedEnemy.x = 2148;
rangedEnemy.y = Math.random() * 2732;
break;
case 2:
rangedEnemy.x = Math.random() * 2048;
rangedEnemy.y = 2832;
break;
case 3:
rangedEnemy.x = -100;
rangedEnemy.y = Math.random() * 2732;
break;
}
game.addChild(rangedEnemy);
enemies.push(rangedEnemy);
} else {
spawnEnemy();
}
}
} else {
// Normal wave - random enemies
for (var i = 0; i < actualCount; i++) {
spawnEnemy();
}
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var DPad = Container.expand(function () {
var self = Container.call(this);
// Create the D-Pad background with increased size
var padBg = LK.getAsset('player', {
width: 250,
height: 250,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x333333,
alpha: 0.6
});
// Create the directional buttons with increased size
var upButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
upButton.y = -80;
var downButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
downButton.y = 80;
var leftButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
leftButton.x = -80;
var rightButton = LK.getAsset('player', {
width: 85,
height: 85,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x666666
});
rightButton.x = 80;
// Add all components to the container
self.addChild(padBg);
self.addChild(upButton);
self.addChild(downButton);
self.addChild(leftButton);
self.addChild(rightButton);
// Direction state
self.direction = {
up: false,
down: false,
left: false,
right: false
};
// Make the entire D-Pad container interactive to capture all touch events
self.interactive = true;
// Buttons don't need to be individually interactive when container handles events
// Highlight function for visual feedback
function highlightButton(button) {
tween(button, {
tint: 0x00FFFF
}, {
duration: 100,
easing: tween.easeOut
});
}
function unhighlightButton(button) {
tween(button, {
tint: 0x666666
}, {
duration: 100,
easing: tween.easeIn
});
}
// Event handlers
self.down = function (x, y, obj) {
// Convert global coordinates to local coordinates
var localPos = {
x: x - self.x,
y: y - self.y
};
// Check which button was pressed using more generous hit areas
if (Math.abs(localPos.x) < 40 && localPos.y < -30) {
self.direction.up = true;
highlightButton(upButton);
} else if (Math.abs(localPos.x) < 40 && localPos.y > 30) {
self.direction.down = true;
highlightButton(downButton);
} else if (localPos.x < -30 && Math.abs(localPos.y) < 40) {
self.direction.left = true;
highlightButton(leftButton);
} else if (localPos.x > 30 && Math.abs(localPos.y) < 40) {
self.direction.right = true;
highlightButton(rightButton);
}
};
self.up = function (x, y, obj) {
// Reset all directions when touch ends
self.direction.up = false;
self.direction.down = false;
self.direction.left = false;
self.direction.right = false;
// Reset all button appearances
unhighlightButton(upButton);
unhighlightButton(downButton);
unhighlightButton(leftButton);
unhighlightButton(rightButton);
};
self.move = function (x, y, obj) {
// Convert global coordinates to local coordinates
var localPos = {
x: x - self.x,
y: y - self.y
};
// Update direction based on touch position with improved detection
self.direction.up = Math.abs(localPos.x) < 40 && localPos.y < -30;
self.direction.down = Math.abs(localPos.x) < 40 && localPos.y > 30;
self.direction.left = localPos.x < -30 && Math.abs(localPos.y) < 40;
self.direction.right = localPos.x > 30 && Math.abs(localPos.y) < 40;
// Update button appearances
if (self.direction.up) highlightButton(upButton);else unhighlightButton(upButton);
if (self.direction.down) highlightButton(downButton);else unhighlightButton(downButton);
if (self.direction.left) highlightButton(leftButton);else unhighlightButton(leftButton);
if (self.direction.right) highlightButton(rightButton);else unhighlightButton(rightButton);
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Make the enemy graphic much bigger
enemyGraphic.scale.x = 2.2;
enemyGraphic.scale.y = 2.2;
self.health = 20;
self.maxHealth = 20;
self.speed = 1.5;
self.damage = 5;
// Health bar
self.healthBar = new Container();
self.healthBar.y = -50;
self.healthBarBg = LK.getAsset('player', {
width: 70,
height: 8,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.healthBarFill = LK.getAsset('player', {
width: 70,
height: 8,
anchorX: 0,
anchorY: 0.5,
tint: 0xFF0000
});
self.healthBarFill.x = -35;
self.healthBar.addChild(self.healthBarBg);
self.healthBar.addChild(self.healthBarFill);
self.addChild(self.healthBar);
self.attackCooldown = 0;
self.attackCooldownMax = 30; // Frames between attacks
self.experienceValue = 1;
self.hitByKatana = false;
self.katanaHitTimer = 0;
self.update = function () {
if (player.isDead) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Normalize and apply speed
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Update health bar
self.healthBarFill.width = 70 * (self.health / self.maxHealth);
// Attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Decrement katana hit timer if active
if (self.katanaHitTimer > 0) {
self.katanaHitTimer--;
if (self.katanaHitTimer === 0) {
self.hitByKatana = false;
}
}
// Attack player if touching and not recently hit by katana
if (self.intersects(player) && self.attackCooldown === 0 && !self.hitByKatana) {
player.takeDamage(self.damage);
self.attackCooldown = self.attackCooldownMax;
}
};
self.takeDamage = function (amount) {
self.health -= amount;
self.health = Math.max(0, self.health);
// Flash white when damaged
tween(enemyGraphic, {
tint: 0xFFFFFF
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemyGraphic, {
tint: 0xff2222 // Return to original enemy color
}, {
duration: 50,
easing: tween.easeIn
});
}
});
// Create explosion effect
var explosion = new Explosion();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Add a small bounce effect using tween
tween(self, {
y: self.y - 10
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + 10
}, {
duration: 100,
easing: tween.easeIn
});
}
});
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Spawn experience gem
var expGem = new ExperienceGem();
expGem.value = self.experienceValue;
expGem.x = self.x;
expGem.y = self.y;
game.addChild(expGem);
experienceGems.push(expGem);
// Remove from enemies array and destroy
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
self.destroy();
// Increment kill count
killCount++;
killCountText.setText("Kills: " + killCount);
};
return self;
});
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0x2222ff; // Blue tint
// Override default properties
self.health = 50;
self.maxHealth = 50;
self.speed = 1.5; // Slower than regular enemies
self.damage = 15; // More damage
// Larger hitbox
self.scale.x = 2.8;
self.scale.y = 2.8;
return self;
});
var RangedEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0xff22ff; // Purple tint
// Override default properties
self.health = 15;
self.maxHealth = 15;
self.speed = 1.8;
self.damage = 8;
self.shootRange = 300; // Range at which enemy will stop and shoot
self.projectileSpeed = 8;
self.attackCooldownMax = 90; // Slower attacks
// Override update method to add shooting behavior
var originalUpdate = self.update;
self.update = function () {
if (player.isDead) return;
// Calculate distance to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If in range, stop and shoot
if (distance <= self.shootRange && self.attackCooldown === 0) {
// Shoot at player
var projectile = new EnemyProjectile();
projectile.x = self.x;
projectile.y = self.y;
// Direction toward player
if (distance > 0) {
projectile.direction = {
x: dx / distance,
y: dy / distance
};
}
projectile.speed = self.projectileSpeed;
projectile.damage = self.damage;
game.addChild(projectile);
enemyProjectiles.push(projectile);
self.attackCooldown = self.attackCooldownMax;
} else {
// Move only if not in range or on cooldown
if (distance > self.shootRange || self.attackCooldown > 0) {
// Use original movement logic
originalUpdate.call(self);
return;
}
}
// Update cooldowns and other logic
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update health bar
self.healthBarFill.width = 70 * (self.health / self.maxHealth);
// Decrement katana hit timer if active
if (self.katanaHitTimer > 0) {
self.katanaHitTimer--;
if (self.katanaHitTimer === 0) {
self.hitByKatana = false;
}
}
};
return self;
});
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Override enemy appearance
self.getChildAt(0).tint = 0x22ff22; // Green tint
// Override default properties
self.health = 10;
self.maxHealth = 10;
self.speed = 4; // Faster than regular enemies
self.damage = 5; // Less damage
// Slightly smaller hitbox, but still bigger than before
self.scale.x = 1.2;
self.scale.y = 1.2;
return self;
});
var EnemyProjectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphic = self.attachAsset('shuriken', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff00ff
});
self.direction = {
x: 0,
y: 0
};
self.speed = 8;
self.damage = 5;
self.update = function () {
// Move in direction
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Rotate for effect
projectileGraphic.rotation += 0.1;
// Check if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
var index = enemyProjectiles.indexOf(self);
if (index !== -1) {
enemyProjectiles.splice(index, 1);
}
return;
}
// Check for player collision
if (self.intersects(player) && !player.isDead) {
player.takeDamage(self.damage);
// Remove projectile
self.destroy();
var index = enemyProjectiles.indexOf(self);
if (index !== -1) {
enemyProjectiles.splice(index, 1);
}
}
};
return self;
});
var ExperienceGem = Container.expand(function () {
var self = Container.call(this);
var gemGraphic = self.attachAsset('expGem', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.speed = 0;
self.magnetRange = 200;
self.isMovingToPlayer = false;
self.update = function () {
if (player.isDead) return;
// Calculate distance to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If close to player, move toward player (magnetic effect)
if (distance < self.magnetRange || self.isMovingToPlayer) {
self.isMovingToPlayer = true;
self.speed += 0.2;
self.speed = Math.min(self.speed, 15);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
// If touching player, give experience
if (self.intersects(player)) {
player.gainExperience(self.value);
// Remove from array and destroy
var index = experienceGems.indexOf(self);
if (index !== -1) {
experienceGems.splice(index, 1);
}
self.destroy();
}
};
return self;
});
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGraphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFA500
});
self.lifetime = 0;
self.maxLifetime = 15; // Short lifetime for quick explosion effect
self.update = function () {
self.lifetime++;
// Scale up and fade out
var progress = self.lifetime / self.maxLifetime;
explosionGraphic.scale.x = 1 + progress;
explosionGraphic.scale.y = 1 + progress;
explosionGraphic.alpha = 1 - progress;
// Destroy when animation complete
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
return self;
});
var Katana = Container.expand(function () {
var self = Container.call(this);
var katanaGraphic = self.attachAsset('katana', {
anchorX: 0,
anchorY: 0.5
});
self.damage = 20;
self.rotationSpeed = 0.25;
self.attackCooldown = 0;
self.attackCooldownMax = 8; // Frames between attacks
self.level = 1;
// Attack cooldown indicator
self.cooldownBar = new Container();
self.cooldownBar.y = 15;
self.cooldownBarBg = LK.getAsset('player', {
width: 150,
height: 5,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.cooldownBarFill = LK.getAsset('player', {
width: 150,
height: 5,
anchorX: 0,
anchorY: 0.5,
tint: 0x00FFFF
});
self.cooldownBarFill.x = -75;
self.cooldownBar.addChild(self.cooldownBarBg);
self.cooldownBar.addChild(self.cooldownBarFill);
self.addChild(self.cooldownBar);
self.update = function () {
if (player.isDead) return;
// Rotate around player
self.rotation += self.rotationSpeed;
// Attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Update cooldown bar
var readyPercent = 1 - self.attackCooldown / self.attackCooldownMax;
self.cooldownBarFill.width = 150 * readyPercent;
// Modify color based on readiness
if (readyPercent === 1) {
self.cooldownBarFill.tint = 0x00FF00; // Green when ready
} else {
self.cooldownBarFill.tint = 0x00FFFF; // Cyan when charging
}
// Check for enemies in range and attack
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy) && self.attackCooldown === 0) {
enemy.takeDamage(self.damage);
self.attackCooldown = self.attackCooldownMax;
LK.getSound('slash').play();
// Mark enemy as being hit by katana - this will be used to prevent damage to player
enemy.hitByKatana = true;
enemy.katanaHitTimer = 30; // Frames of immunity after being hit
// Don't break, allow the katana to hit multiple enemies in a single swing
}
}
};
self.levelUp = function () {
self.level += 1;
self.damage += 5;
self.attackCooldownMax = Math.max(10, self.attackCooldownMax - 2);
};
return self;
});
var LevelUpEffect = Container.expand(function () {
var self = Container.call(this);
var effectGraphic = self.attachAsset('levelUpIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.lifetime = 0;
self.maxLifetime = 60; // 1 second at 60fps
self.update = function () {
self.lifetime++;
// Scale up and fade out
var progress = self.lifetime / self.maxLifetime;
effectGraphic.scale.x = 1 + progress;
effectGraphic.scale.y = 1 + progress;
effectGraphic.alpha = 0.7 * (1 - progress);
if (self.lifetime >= self.maxLifetime) {
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerBody = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.health = 150;
self.maxHealth = 150;
self.level = 1;
self.experience = 0;
self.experienceToNextLevel = 10;
self.isDead = false;
// Health display
self.healthBar = new Container();
self.healthBar.y = -70;
self.healthBarBg = LK.getAsset('player', {
width: 100,
height: 10,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.healthBarFill = LK.getAsset('player', {
width: 100,
height: 10,
anchorX: 0,
anchorY: 0.5,
tint: 0x00FF00
});
self.healthBarFill.x = -50;
self.healthBar.addChild(self.healthBarBg);
self.healthBar.addChild(self.healthBarFill);
self.addChild(self.healthBar);
self.update = function () {
if (self.isDead) return;
// Update health bar
self.healthBarFill.width = 100 * (self.health / self.maxHealth);
// Keep player within bounds when using D-Pad
self.x = Math.max(50, Math.min(2048 - 50, self.x));
self.y = Math.max(50, Math.min(2732 - 50, self.y));
};
self.takeDamage = function (amount) {
if (self.isDead) return;
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
self.die();
} else {
// Flash red when damaged
LK.effects.flashObject(playerBody, 0xFF0000, 200);
}
};
self.gainExperience = function (amount) {
if (self.isDead) return;
self.experience += amount;
if (self.experience >= self.experienceToNextLevel) {
self.levelUp();
}
};
self.levelUp = function () {
self.level += 1;
self.experience = 0;
self.experienceToNextLevel = Math.floor(self.experienceToNextLevel * 1.3);
self.maxHealth += 10;
self.health = self.maxHealth;
// Level up effects
var levelUpEffect = new LevelUpEffect();
levelUpEffect.x = self.x;
levelUpEffect.y = self.y;
game.addChild(levelUpEffect);
LK.getSound('levelup').play();
};
self.die = function () {
if (self.isDead) return;
self.isDead = true;
LK.getSound('death').play();
LK.effects.flashObject(playerBody, 0xFF0000, 1000);
// Game over after a short delay
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
};
return self;
});
var Shuriken = Container.expand(function () {
var self = Container.call(this);
var shurikenGraphic = self.attachAsset('shuriken', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 15;
self.speed = 10;
self.direction = {
x: 0,
y: 0
};
self.pierceCount = 1; // How many enemies it can hit before disappearing
self.hitEnemies = []; // Track which enemies we've already hit
self.level = 1;
self.update = function () {
// Move in direction
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Rotate the shuriken for effect
shurikenGraphic.rotation += 0.2;
// Check if off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
var index = shurikens.indexOf(self);
if (index !== -1) {
shurikens.splice(index, 1);
}
return;
}
// Check for enemy collisions
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Skip enemies we've already hit
if (self.hitEnemies.indexOf(enemy) !== -1) {
continue;
}
if (self.intersects(enemy)) {
enemy.takeDamage(self.damage);
LK.getSound('hit').play();
self.hitEnemies.push(enemy);
// If we've hit enough enemies, destroy this shuriken
if (self.hitEnemies.length >= self.pierceCount) {
self.destroy();
var index = shurikens.indexOf(self);
if (index !== -1) {
shurikens.splice(index, 1);
}
return;
}
}
}
};
self.levelUp = function () {
self.level += 1;
self.damage += 10;
self.pierceCount += 2;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x003300
});
/****
* Game Code
****/
// Global variables
var player;
var katana;
var shurikens = [];
var enemies = [];
var experienceGems = [];
var enemyProjectiles = [];
var dragPosition = null;
var dpad;
var movementSpeed = 8;
var gameTime = 0;
var shurikenTimer = 0;
var enemySpawnTimer = 0;
var waveNumber = 1;
var killCount = 0;
// UI elements
var timeText;
var waveText;
var killCountText;
var levelText;
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// Initialize katana
katana = new Katana();
player.addChild(katana);
// Initialize D-Pad
dpad = new DPad();
dpad.x = 200;
dpad.y = 2732 - 200;
game.addChild(dpad);
// Initialize UI
timeText = new Text2('Time: 0s', {
size: 60,
fill: 0xFFFFFF
});
timeText.anchor.set(1, 0);
LK.gui.topRight.addChild(timeText);
waveText = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
killCountText = new Text2('Kills: 0', {
size: 60,
fill: 0xFFFFFF
});
killCountText.anchor.set(0, 0);
LK.gui.topRight.addChild(killCountText);
killCountText.y = 70;
levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0, 0);
LK.gui.topLeft.addChild(levelText);
levelText.x = 120; // Avoid platform level menu icon area
// Spawn initial enemies
spawnEnemyWave(5);
// Play background music
LK.playMusic('bgmusic');
// Handle player movement
game.down = function (x, y, obj) {
dragPosition = {
x: x,
y: y
};
};
game.move = function (x, y, obj) {
if (dragPosition) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
dragPosition = null;
};
// Main game update loop
game.update = function () {
if (player.isDead) return;
// Update game time (in seconds)
gameTime += 1 / 60;
timeText.setText('Time: ' + Math.floor(gameTime) + 's');
// Update level text
levelText.setText('Level: ' + player.level);
// Handle D-Pad movement
if (dpad.direction.up) {
player.y -= movementSpeed;
}
if (dpad.direction.down) {
player.y += movementSpeed;
}
if (dpad.direction.left) {
player.x -= movementSpeed;
}
if (dpad.direction.right) {
player.x += movementSpeed;
}
// Keep player within bounds
player.x = Math.max(50, Math.min(2048 - 50, player.x));
player.y = Math.max(50, Math.min(2732 - 50, player.y));
// Shuriken spawning logic
shurikenTimer++;
if (shurikenTimer >= 180) {
// Every 3 seconds (60fps * 3)
spawnShuriken();
shurikenTimer = 0;
}
// Enemy spawning logic
enemySpawnTimer++;
var spawnRate = Math.max(400 - waveNumber * 10, 120); // Slower spawns to make game easier
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Wave progression
if (Math.floor(gameTime) % 30 === 0 && Math.floor(gameTime) > 0 && Math.floor(gameTime / 30) > waveNumber - 1) {
waveNumber++;
waveText.setText('Wave: ' + waveNumber);
spawnEnemyWave(5 + waveNumber);
}
};
// Helper functions
function spawnShuriken() {
for (var i = 0; i < (player.level > 5 ? 2 : 1); i++) {
var shuriken = new Shuriken();
shuriken.x = player.x;
shuriken.y = player.y;
// Choose a random enemy to target, or random direction if no enemies
if (enemies.length > 0) {
var targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
var dx = targetEnemy.x - player.x;
var dy = targetEnemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
shuriken.direction = {
x: dx / distance,
y: dy / distance
};
} else {
// Random direction if enemy is directly on top of player
var angle = Math.random() * Math.PI * 2;
shuriken.direction = {
x: Math.cos(angle),
y: Math.sin(angle)
};
}
} else {
// Random direction if no enemies
var angle = Math.random() * Math.PI * 2;
shuriken.direction = {
x: Math.cos(angle),
y: Math.sin(angle)
};
}
game.addChild(shuriken);
shurikens.push(shuriken);
LK.getSound('throw').play();
}
}
function spawnEnemy() {
var enemy;
// Choose random enemy type with increasing difficulty by wave
var randType = Math.random();
if (waveNumber >= 3 && randType < 0.25) {
// 25% chance for ranged enemies after wave 3
enemy = new RangedEnemy();
} else if (waveNumber >= 2 && randType < 0.5) {
// 25% chance for tank enemies after wave 2
enemy = new TankEnemy();
} else if (randType < 0.75) {
// 25% chance for fast enemies
enemy = new FastEnemy();
} else {
// 25% chance for regular enemies
enemy = new Enemy();
}
// Scale enemy stats with wave number (reduced scaling for easier gameplay)
enemy.health *= 1 + waveNumber * 0.1;
enemy.maxHealth = enemy.health;
enemy.speed *= 1 + waveNumber * 0.03;
enemy.damage *= 1 + waveNumber * 0.05;
enemy.experienceValue = 2 + Math.floor(waveNumber / 2);
// Spawn outside the screen
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -100;
break;
case 1:
// Right
enemy.x = 2148;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2832;
break;
case 3:
// Left
enemy.x = -100;
enemy.y = Math.random() * 2732;
break;
}
game.addChild(enemy);
enemies.push(enemy);
}
function spawnEnemyWave(count) {
// Add more enemies as game progresses
var actualCount = count + Math.floor(waveNumber / 2);
// Special wave types based on wave number
if (waveNumber % 5 === 0) {
// Boss wave - add a few super tank enemies
var bossCount = Math.min(3, Math.floor(waveNumber / 5));
for (var i = 0; i < bossCount; i++) {
var bossEnemy = new TankEnemy();
bossEnemy.health = 100 + waveNumber * 10;
bossEnemy.maxHealth = bossEnemy.health;
bossEnemy.damage *= 1.5;
bossEnemy.scale.x = 2.5;
bossEnemy.scale.y = 2.5;
bossEnemy.experienceValue = 5 + Math.floor(waveNumber / 2);
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
bossEnemy.x = Math.random() * 2048;
bossEnemy.y = -100;
break;
case 1:
bossEnemy.x = 2148;
bossEnemy.y = Math.random() * 2732;
break;
case 2:
bossEnemy.x = Math.random() * 2048;
bossEnemy.y = 2832;
break;
case 3:
bossEnemy.x = -100;
bossEnemy.y = Math.random() * 2732;
break;
}
game.addChild(bossEnemy);
enemies.push(bossEnemy);
}
// Add remaining enemies
for (var i = 0; i < actualCount - bossCount; i++) {
spawnEnemy();
}
} else if (waveNumber % 3 === 0) {
// Swarm wave - lots of fast enemies
for (var i = 0; i < actualCount * 1.5; i++) {
var fastEnemy = new FastEnemy();
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
fastEnemy.x = Math.random() * 2048;
fastEnemy.y = -100;
break;
case 1:
fastEnemy.x = 2148;
fastEnemy.y = Math.random() * 2732;
break;
case 2:
fastEnemy.x = Math.random() * 2048;
fastEnemy.y = 2832;
break;
case 3:
fastEnemy.x = -100;
fastEnemy.y = Math.random() * 2732;
break;
}
game.addChild(fastEnemy);
enemies.push(fastEnemy);
}
} else if (waveNumber % 2 === 0) {
// Ranged wave - more ranged enemies
for (var i = 0; i < actualCount; i++) {
if (i < actualCount / 2) {
var rangedEnemy = new RangedEnemy();
// Spawn at random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
rangedEnemy.x = Math.random() * 2048;
rangedEnemy.y = -100;
break;
case 1:
rangedEnemy.x = 2148;
rangedEnemy.y = Math.random() * 2732;
break;
case 2:
rangedEnemy.x = Math.random() * 2048;
rangedEnemy.y = 2832;
break;
case 3:
rangedEnemy.x = -100;
rangedEnemy.y = Math.random() * 2732;
break;
}
game.addChild(rangedEnemy);
enemies.push(rangedEnemy);
} else {
spawnEnemy();
}
}
} else {
// Normal wave - random enemies
for (var i = 0; i < actualCount; i++) {
spawnEnemy();
}
}
}
a ninja star. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
green exp diamond
a katana. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
monster. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
ninja. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
yellow arrow. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat