/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Enemy attributes
self.health = 100;
self.maxHealth = 100;
self.attackDamage = 10;
self.attackRange = 150;
self.attackCooldown = 0;
self.maxAttackCooldown = 60; // frames
self.moveSpeed = 3;
self.isActive = true;
// Visual representation
var enemySprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Take damage method
self.takeDamage = function (amount) {
self.health -= amount;
// Visual effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Play hit sound
LK.getSound('hit').play();
// Check if enemy is dead
if (self.health <= 0) {
self.isActive = false;
LK.getSound('enemy_die').play();
// Death animation
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from game
self.destroy();
// Increase score
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Find index and remove from array
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
}
});
}
};
// Update method - called automatically every tick
self.update = function () {
if (!self.isActive) {
return;
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move if not in attack range
if (distance > self.attackRange) {
var moveX = dx / distance * self.moveSpeed;
self.x += moveX;
// Keep on ground
self.y = groundLevel - self.height / 2;
} else {
// Try to attack player if in range
if (self.attackCooldown <= 0) {
player.takeDamage(self.attackDamage);
self.attackCooldown = self.maxAttackCooldown;
// Attack animation
tween(enemySprite, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemySprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
// Decrease attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
// Platform attributes
self.isActive = true;
// Visual representation
var platformSprite = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
// Check if player is on this platform
self.checkCollision = function (playerObj) {
if (!self.isActive) {
return false;
}
// Basic platform collision detection
var playerBottom = playerObj.y + playerObj.height / 2;
var playerTop = playerObj.y - playerObj.height / 2;
var playerLeft = playerObj.x - playerObj.width / 2;
var playerRight = playerObj.x + playerObj.width / 2;
var platformTop = self.y - self.height / 2;
var platformBottom = self.y + self.height / 2;
var platformLeft = self.x - self.width / 2;
var platformRight = self.x + self.width / 2;
// Check if player is falling onto platform
if (playerObj.velocityY > 0 && playerBottom >= platformTop && playerTop < platformTop && playerRight > platformLeft && playerLeft < platformRight) {
// Position player on top of platform
playerObj.y = platformTop - playerObj.height / 2;
playerObj.isJumping = false;
playerObj.velocityY = 0;
return true;
}
return false;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Player attributes
self.health = 100;
self.maxHealth = 100;
self.stance = 'swan'; // Default stance
self.isAttacking = false;
self.isJumping = false;
self.velocityY = 0;
self.gravity = 0.5;
self.jumpPower = -15;
self.moveSpeed = 10;
// Stats per stance
self.stanceData = {
swan: {
color: 0xFFFFFF,
attackDamage: 20,
attackRange: 150,
attackSpeed: 1.0,
moveSpeedMod: 1.2
},
crow: {
color: 0x222222,
attackDamage: 30,
attackRange: 100,
attackSpeed: 0.8,
moveSpeedMod: 1.0
},
phoenix: {
color: 0xFF3300,
attackDamage: 40,
attackRange: 80,
attackSpeed: 0.6,
moveSpeedMod: 0.8
}
};
// Create all stance sprites but only show the current stance
self.swanSprite = self.attachAsset('playerSwan', {
anchorX: 0.5,
anchorY: 0.5,
visible: true
});
self.crowSprite = self.attachAsset('playerCrow', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
self.phoenixSprite = self.attachAsset('playerPhoenix', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
// Crack overlay for health visualization
self.crackOverlay = self.attachAsset('crackOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Set hit area for touch detection
self.width = 150;
self.height = 200;
// Movement control
self.moveTo = function (x, y) {
self.targetX = x;
};
// Jump method
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.velocityY = self.jumpPower;
}
};
// Attack method
self.attack = function () {
if (!self.isAttacking) {
self.isAttacking = true;
// Different attack animation based on stance
var stanceData = self.stanceData[self.stance];
var attackDuration = 500 * (1 / stanceData.attackSpeed);
// Get the current visible sprite
var currentSprite;
if (self.stance === 'swan') {
currentSprite = self.swanSprite;
} else if (self.stance === 'crow') {
currentSprite = self.crowSprite;
} else {
currentSprite = self.phoenixSprite;
}
// Attack animation
tween(currentSprite, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: attackDuration / 2,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: attackDuration / 2,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isAttacking = false;
}
});
}
});
// Create projectile based on stance
var projectile = new Projectile(self.stance, stanceData.attackDamage);
projectile.x = self.x;
projectile.y = self.y;
game.addChild(projectile);
projectiles.push(projectile);
// Play attack sound
LK.getSound('attack').play();
// Check for enemies in range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < stanceData.attackRange) {
enemy.takeDamage(stanceData.attackDamage);
}
}
}
};
// Switch stance method
self.switchStance = function (newStance) {
if (self.stance !== newStance) {
self.stance = newStance;
// Update visibility of sprites
self.swanSprite.visible = newStance === 'swan';
self.crowSprite.visible = newStance === 'crow';
self.phoenixSprite.visible = newStance === 'phoenix';
// Play stance change sound
LK.getSound('stance_change').play();
// Visual effect for stance change
LK.effects.flashObject(self, self.stanceData[newStance].color, 500);
}
};
// Take damage method
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) {
self.health = 0;
}
// Update crack overlay based on health
var crackAlpha = 1 - self.health / self.maxHealth;
self.crackOverlay.alpha = crackAlpha;
// Visual effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Play hit sound
LK.getSound('player_hit').play();
// Check if player is dead
if (self.health <= 0) {
LK.showGameOver();
}
};
// Update method - called automatically every tick
self.update = function () {
// Apply gravity
if (self.isJumping) {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Check if landed on ground
if (self.y >= groundLevel - self.height / 2) {
self.y = groundLevel - self.height / 2;
self.isJumping = false;
self.velocityY = 0;
}
}
// Move towards target if exists
if (self.targetX !== undefined) {
var moveSpeed = self.moveSpeed * self.stanceData[self.stance].moveSpeedMod;
var dx = self.targetX - self.x;
// If close enough to target, stop moving
if (Math.abs(dx) < moveSpeed) {
self.x = self.targetX;
self.targetX = undefined;
} else {
// Move towards target
self.x += dx > 0 ? moveSpeed : -moveSpeed;
}
}
// Ensure player stays on screen
if (self.x < self.width / 2) {
self.x = self.width / 2;
} else if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
}
};
// Touch event handlers
self.down = function (x, y, obj) {
// Check if the player should attack or jump
if (y < self.y - self.height / 4) {
self.jump();
} else {
self.attack();
}
};
return self;
});
var Projectile = Container.expand(function (stanceType, damage) {
var self = Container.call(this);
// Projectile attributes
self.type = stanceType || 'swan';
self.damage = damage || 10;
self.speed = 15;
self.range = 500; // Max travel distance
self.distanceTraveled = 0;
self.isActive = true;
// Color based on stance
var colors = {
swan: 0xFFFFFF,
crow: 0x222222,
phoenix: 0xFF3300
};
// Visual representation
var projectileSprite = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
tint: colors[self.type] || 0xFFFFFF
});
// Update method - called automatically every tick
self.update = function () {
if (!self.isActive) {
return;
}
// Move forward
self.x += self.speed;
self.distanceTraveled += self.speed;
// Check if out of range
if (self.distanceTraveled >= self.range || self.x < 0 || self.x > 2048) {
self.destroy();
var index = projectiles.indexOf(self);
if (index !== -1) {
projectiles.splice(index, 1);
}
return;
}
// Check for collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy) && enemy.isActive) {
// Apply damage based on stance
enemy.takeDamage(self.damage);
// Destroy projectile
self.destroy();
var index = projectiles.indexOf(self);
if (index !== -1) {
projectiles.splice(index, 1);
}
break;
}
}
};
return self;
});
var StanceButton = Container.expand(function (stanceType) {
var self = Container.call(this);
// Button attributes
self.type = stanceType;
// Colors based on stance
var colors = {
swan: 0xFFFFFF,
crow: 0x222222,
phoenix: 0xFF3300
};
// Visual representation
var buttonSprite = self.attachAsset(stanceType === 'swan' ? 'playerSwan' : stanceType === 'crow' ? 'playerCrow' : 'playerPhoenix', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Highlight when selected
self.setSelected = function (isSelected) {
if (isSelected) {
tween(buttonSprite, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(buttonSprite, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 300,
easing: tween.easeOut
});
}
};
// Touch event handler
self.down = function (x, y, obj) {
// Switch player stance
player.switchStance(self.type);
// Update UI buttons
updateStanceButtons();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111827
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var platforms = [];
var projectiles = [];
var stanceButtons = {};
var groundLevel = 2500; // Y position of ground
var enemySpawnTimer = 0;
var maxEnemies = 5;
var gameStarted = false;
var difficultyLevel = 1;
var difficultyTimer = 0;
var maxDifficultyLevel = 10;
// Background
var background = game.addChild(LK.getAsset('background', {
x: 0,
y: 0,
anchorX: 0,
anchorY: 0
}));
// Initialize score display
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize health display
var healthTxt = new Text2('Health: 100%', {
size: 80,
fill: 0xFFFFFF
});
healthTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(healthTxt);
// Create and position stance buttons
function createStanceButtons() {
var stanceTypes = ['swan', 'crow', 'phoenix'];
var buttonSpacing = 180;
var startX = 2048 / 2 - (stanceTypes.length - 1) * buttonSpacing / 2;
for (var i = 0; i < stanceTypes.length; i++) {
var button = new StanceButton(stanceTypes[i]);
button.x = startX + i * buttonSpacing;
button.y = 2732 - 150;
game.addChild(button);
stanceButtons[stanceTypes[i]] = button;
}
// Set initial selection
updateStanceButtons();
}
// Update stance button visuals
function updateStanceButtons() {
for (var type in stanceButtons) {
stanceButtons[type].setSelected(type === player.stance);
}
}
// Create player
function createPlayer() {
player = new Player();
player.x = 2048 / 2;
player.y = groundLevel - player.height / 2;
game.addChild(player);
}
// Create enemy
function createEnemy() {
var enemy = new Enemy();
// Position enemy on edge of screen
enemy.x = Math.random() < 0.5 ? 100 : 2048 - 100;
enemy.y = groundLevel - enemy.height / 2;
// Scale difficulty
enemy.health = 50 + difficultyLevel * 10;
enemy.maxHealth = enemy.health;
enemy.attackDamage = 5 + difficultyLevel * 2;
enemy.moveSpeed = 2 + difficultyLevel * 0.3;
game.addChild(enemy);
enemies.push(enemy);
}
// Create platforms
function createPlatforms() {
var numPlatforms = 5;
var platformWidth = 300;
for (var i = 0; i < numPlatforms; i++) {
var platform = new Platform();
platform.x = 300 + i * 350;
platform.y = groundLevel - 300 - i * 100;
platform.width = platformWidth;
platform.height = 50;
game.addChild(platform);
platforms.push(platform);
}
}
// Initialize game
function initGame() {
// Reset game state
LK.setScore(0);
enemies = [];
platforms = [];
projectiles = [];
difficultyLevel = 1;
difficultyTimer = 0;
// Create player
createPlayer();
// Create platforms
createPlatforms();
// Create stance buttons
createStanceButtons();
// Start music
LK.playMusic('ballet_theme');
gameStarted = true;
}
// Event handlers
game.down = function (x, y, obj) {
// Start game if not started
if (!gameStarted) {
initGame();
return;
}
// Move player when clicking on game area (not on player or buttons)
var isButtonClick = false;
for (var type in stanceButtons) {
if (stanceButtons[type].intersects({
x: x,
y: y
})) {
isButtonClick = true;
break;
}
}
if (!isButtonClick && !player.intersects({
x: x,
y: y
})) {
player.moveTo(x);
}
};
// Game update loop
game.update = function () {
if (!gameStarted) {
// Show title screen if game not started
if (!titleShown) {
var titleText = new Text2('Ballet of Shadows', {
size: 150,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 200;
game.addChild(titleText);
var startText = new Text2('Tap to Begin', {
size: 100,
fill: 0xAAAAAA
});
startText.anchor.set(0.5, 0.5);
startText.x = 2048 / 2;
startText.y = 2732 / 2 + 200;
game.addChild(startText);
titleShown = true;
}
return;
}
// Update health display
healthTxt.setText('Health: ' + player.health + '%');
// Spawn enemies on timer
enemySpawnTimer++;
if (enemySpawnTimer >= 120 && enemies.length < maxEnemies) {
// Every 2 seconds (120 frames at 60fps)
createEnemy();
enemySpawnTimer = 0;
}
// Increase difficulty over time
difficultyTimer++;
if (difficultyTimer >= 1800 && difficultyLevel < maxDifficultyLevel) {
// Every 30 seconds
difficultyLevel++;
difficultyTimer = 0;
// Increase max enemies based on difficulty
maxEnemies = 5 + Math.floor(difficultyLevel / 2);
}
// Platform collision detection
if (player.isJumping) {
for (var i = 0; i < platforms.length; i++) {
platforms[i].checkCollision(player);
}
}
// Check for game win condition (example: score 1000)
if (LK.getScore() >= 1000) {
LK.showYouWin();
}
};
// Start with title screen
var titleShown = false;
// Play background music
LK.playMusic('ballet_theme', {
fade: {
start: 0,
end: 0.8,
duration: 1000
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Enemy attributes
self.health = 100;
self.maxHealth = 100;
self.attackDamage = 10;
self.attackRange = 150;
self.attackCooldown = 0;
self.maxAttackCooldown = 60; // frames
self.moveSpeed = 3;
self.isActive = true;
// Visual representation
var enemySprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Take damage method
self.takeDamage = function (amount) {
self.health -= amount;
// Visual effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Play hit sound
LK.getSound('hit').play();
// Check if enemy is dead
if (self.health <= 0) {
self.isActive = false;
LK.getSound('enemy_die').play();
// Death animation
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove from game
self.destroy();
// Increase score
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Find index and remove from array
var index = enemies.indexOf(self);
if (index !== -1) {
enemies.splice(index, 1);
}
}
});
}
};
// Update method - called automatically every tick
self.update = function () {
if (!self.isActive) {
return;
}
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move if not in attack range
if (distance > self.attackRange) {
var moveX = dx / distance * self.moveSpeed;
self.x += moveX;
// Keep on ground
self.y = groundLevel - self.height / 2;
} else {
// Try to attack player if in range
if (self.attackCooldown <= 0) {
player.takeDamage(self.attackDamage);
self.attackCooldown = self.maxAttackCooldown;
// Attack animation
tween(enemySprite, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemySprite, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
// Decrease attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
// Platform attributes
self.isActive = true;
// Visual representation
var platformSprite = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
// Check if player is on this platform
self.checkCollision = function (playerObj) {
if (!self.isActive) {
return false;
}
// Basic platform collision detection
var playerBottom = playerObj.y + playerObj.height / 2;
var playerTop = playerObj.y - playerObj.height / 2;
var playerLeft = playerObj.x - playerObj.width / 2;
var playerRight = playerObj.x + playerObj.width / 2;
var platformTop = self.y - self.height / 2;
var platformBottom = self.y + self.height / 2;
var platformLeft = self.x - self.width / 2;
var platformRight = self.x + self.width / 2;
// Check if player is falling onto platform
if (playerObj.velocityY > 0 && playerBottom >= platformTop && playerTop < platformTop && playerRight > platformLeft && playerLeft < platformRight) {
// Position player on top of platform
playerObj.y = platformTop - playerObj.height / 2;
playerObj.isJumping = false;
playerObj.velocityY = 0;
return true;
}
return false;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Player attributes
self.health = 100;
self.maxHealth = 100;
self.stance = 'swan'; // Default stance
self.isAttacking = false;
self.isJumping = false;
self.velocityY = 0;
self.gravity = 0.5;
self.jumpPower = -15;
self.moveSpeed = 10;
// Stats per stance
self.stanceData = {
swan: {
color: 0xFFFFFF,
attackDamage: 20,
attackRange: 150,
attackSpeed: 1.0,
moveSpeedMod: 1.2
},
crow: {
color: 0x222222,
attackDamage: 30,
attackRange: 100,
attackSpeed: 0.8,
moveSpeedMod: 1.0
},
phoenix: {
color: 0xFF3300,
attackDamage: 40,
attackRange: 80,
attackSpeed: 0.6,
moveSpeedMod: 0.8
}
};
// Create all stance sprites but only show the current stance
self.swanSprite = self.attachAsset('playerSwan', {
anchorX: 0.5,
anchorY: 0.5,
visible: true
});
self.crowSprite = self.attachAsset('playerCrow', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
self.phoenixSprite = self.attachAsset('playerPhoenix', {
anchorX: 0.5,
anchorY: 0.5,
visible: false
});
// Crack overlay for health visualization
self.crackOverlay = self.attachAsset('crackOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Set hit area for touch detection
self.width = 150;
self.height = 200;
// Movement control
self.moveTo = function (x, y) {
self.targetX = x;
};
// Jump method
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.velocityY = self.jumpPower;
}
};
// Attack method
self.attack = function () {
if (!self.isAttacking) {
self.isAttacking = true;
// Different attack animation based on stance
var stanceData = self.stanceData[self.stance];
var attackDuration = 500 * (1 / stanceData.attackSpeed);
// Get the current visible sprite
var currentSprite;
if (self.stance === 'swan') {
currentSprite = self.swanSprite;
} else if (self.stance === 'crow') {
currentSprite = self.crowSprite;
} else {
currentSprite = self.phoenixSprite;
}
// Attack animation
tween(currentSprite, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: attackDuration / 2,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentSprite, {
scaleX: 1,
scaleY: 1
}, {
duration: attackDuration / 2,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isAttacking = false;
}
});
}
});
// Create projectile based on stance
var projectile = new Projectile(self.stance, stanceData.attackDamage);
projectile.x = self.x;
projectile.y = self.y;
game.addChild(projectile);
projectiles.push(projectile);
// Play attack sound
LK.getSound('attack').play();
// Check for enemies in range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < stanceData.attackRange) {
enemy.takeDamage(stanceData.attackDamage);
}
}
}
};
// Switch stance method
self.switchStance = function (newStance) {
if (self.stance !== newStance) {
self.stance = newStance;
// Update visibility of sprites
self.swanSprite.visible = newStance === 'swan';
self.crowSprite.visible = newStance === 'crow';
self.phoenixSprite.visible = newStance === 'phoenix';
// Play stance change sound
LK.getSound('stance_change').play();
// Visual effect for stance change
LK.effects.flashObject(self, self.stanceData[newStance].color, 500);
}
};
// Take damage method
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) {
self.health = 0;
}
// Update crack overlay based on health
var crackAlpha = 1 - self.health / self.maxHealth;
self.crackOverlay.alpha = crackAlpha;
// Visual effect
LK.effects.flashObject(self, 0xFF0000, 300);
// Play hit sound
LK.getSound('player_hit').play();
// Check if player is dead
if (self.health <= 0) {
LK.showGameOver();
}
};
// Update method - called automatically every tick
self.update = function () {
// Apply gravity
if (self.isJumping) {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Check if landed on ground
if (self.y >= groundLevel - self.height / 2) {
self.y = groundLevel - self.height / 2;
self.isJumping = false;
self.velocityY = 0;
}
}
// Move towards target if exists
if (self.targetX !== undefined) {
var moveSpeed = self.moveSpeed * self.stanceData[self.stance].moveSpeedMod;
var dx = self.targetX - self.x;
// If close enough to target, stop moving
if (Math.abs(dx) < moveSpeed) {
self.x = self.targetX;
self.targetX = undefined;
} else {
// Move towards target
self.x += dx > 0 ? moveSpeed : -moveSpeed;
}
}
// Ensure player stays on screen
if (self.x < self.width / 2) {
self.x = self.width / 2;
} else if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
}
};
// Touch event handlers
self.down = function (x, y, obj) {
// Check if the player should attack or jump
if (y < self.y - self.height / 4) {
self.jump();
} else {
self.attack();
}
};
return self;
});
var Projectile = Container.expand(function (stanceType, damage) {
var self = Container.call(this);
// Projectile attributes
self.type = stanceType || 'swan';
self.damage = damage || 10;
self.speed = 15;
self.range = 500; // Max travel distance
self.distanceTraveled = 0;
self.isActive = true;
// Color based on stance
var colors = {
swan: 0xFFFFFF,
crow: 0x222222,
phoenix: 0xFF3300
};
// Visual representation
var projectileSprite = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5,
tint: colors[self.type] || 0xFFFFFF
});
// Update method - called automatically every tick
self.update = function () {
if (!self.isActive) {
return;
}
// Move forward
self.x += self.speed;
self.distanceTraveled += self.speed;
// Check if out of range
if (self.distanceTraveled >= self.range || self.x < 0 || self.x > 2048) {
self.destroy();
var index = projectiles.indexOf(self);
if (index !== -1) {
projectiles.splice(index, 1);
}
return;
}
// Check for collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (self.intersects(enemy) && enemy.isActive) {
// Apply damage based on stance
enemy.takeDamage(self.damage);
// Destroy projectile
self.destroy();
var index = projectiles.indexOf(self);
if (index !== -1) {
projectiles.splice(index, 1);
}
break;
}
}
};
return self;
});
var StanceButton = Container.expand(function (stanceType) {
var self = Container.call(this);
// Button attributes
self.type = stanceType;
// Colors based on stance
var colors = {
swan: 0xFFFFFF,
crow: 0x222222,
phoenix: 0xFF3300
};
// Visual representation
var buttonSprite = self.attachAsset(stanceType === 'swan' ? 'playerSwan' : stanceType === 'crow' ? 'playerCrow' : 'playerPhoenix', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Highlight when selected
self.setSelected = function (isSelected) {
if (isSelected) {
tween(buttonSprite, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 300,
easing: tween.easeOut
});
} else {
tween(buttonSprite, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 300,
easing: tween.easeOut
});
}
};
// Touch event handler
self.down = function (x, y, obj) {
// Switch player stance
player.switchStance(self.type);
// Update UI buttons
updateStanceButtons();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x111827
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var platforms = [];
var projectiles = [];
var stanceButtons = {};
var groundLevel = 2500; // Y position of ground
var enemySpawnTimer = 0;
var maxEnemies = 5;
var gameStarted = false;
var difficultyLevel = 1;
var difficultyTimer = 0;
var maxDifficultyLevel = 10;
// Background
var background = game.addChild(LK.getAsset('background', {
x: 0,
y: 0,
anchorX: 0,
anchorY: 0
}));
// Initialize score display
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize health display
var healthTxt = new Text2('Health: 100%', {
size: 80,
fill: 0xFFFFFF
});
healthTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(healthTxt);
// Create and position stance buttons
function createStanceButtons() {
var stanceTypes = ['swan', 'crow', 'phoenix'];
var buttonSpacing = 180;
var startX = 2048 / 2 - (stanceTypes.length - 1) * buttonSpacing / 2;
for (var i = 0; i < stanceTypes.length; i++) {
var button = new StanceButton(stanceTypes[i]);
button.x = startX + i * buttonSpacing;
button.y = 2732 - 150;
game.addChild(button);
stanceButtons[stanceTypes[i]] = button;
}
// Set initial selection
updateStanceButtons();
}
// Update stance button visuals
function updateStanceButtons() {
for (var type in stanceButtons) {
stanceButtons[type].setSelected(type === player.stance);
}
}
// Create player
function createPlayer() {
player = new Player();
player.x = 2048 / 2;
player.y = groundLevel - player.height / 2;
game.addChild(player);
}
// Create enemy
function createEnemy() {
var enemy = new Enemy();
// Position enemy on edge of screen
enemy.x = Math.random() < 0.5 ? 100 : 2048 - 100;
enemy.y = groundLevel - enemy.height / 2;
// Scale difficulty
enemy.health = 50 + difficultyLevel * 10;
enemy.maxHealth = enemy.health;
enemy.attackDamage = 5 + difficultyLevel * 2;
enemy.moveSpeed = 2 + difficultyLevel * 0.3;
game.addChild(enemy);
enemies.push(enemy);
}
// Create platforms
function createPlatforms() {
var numPlatforms = 5;
var platformWidth = 300;
for (var i = 0; i < numPlatforms; i++) {
var platform = new Platform();
platform.x = 300 + i * 350;
platform.y = groundLevel - 300 - i * 100;
platform.width = platformWidth;
platform.height = 50;
game.addChild(platform);
platforms.push(platform);
}
}
// Initialize game
function initGame() {
// Reset game state
LK.setScore(0);
enemies = [];
platforms = [];
projectiles = [];
difficultyLevel = 1;
difficultyTimer = 0;
// Create player
createPlayer();
// Create platforms
createPlatforms();
// Create stance buttons
createStanceButtons();
// Start music
LK.playMusic('ballet_theme');
gameStarted = true;
}
// Event handlers
game.down = function (x, y, obj) {
// Start game if not started
if (!gameStarted) {
initGame();
return;
}
// Move player when clicking on game area (not on player or buttons)
var isButtonClick = false;
for (var type in stanceButtons) {
if (stanceButtons[type].intersects({
x: x,
y: y
})) {
isButtonClick = true;
break;
}
}
if (!isButtonClick && !player.intersects({
x: x,
y: y
})) {
player.moveTo(x);
}
};
// Game update loop
game.update = function () {
if (!gameStarted) {
// Show title screen if game not started
if (!titleShown) {
var titleText = new Text2('Ballet of Shadows', {
size: 150,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 200;
game.addChild(titleText);
var startText = new Text2('Tap to Begin', {
size: 100,
fill: 0xAAAAAA
});
startText.anchor.set(0.5, 0.5);
startText.x = 2048 / 2;
startText.y = 2732 / 2 + 200;
game.addChild(startText);
titleShown = true;
}
return;
}
// Update health display
healthTxt.setText('Health: ' + player.health + '%');
// Spawn enemies on timer
enemySpawnTimer++;
if (enemySpawnTimer >= 120 && enemies.length < maxEnemies) {
// Every 2 seconds (120 frames at 60fps)
createEnemy();
enemySpawnTimer = 0;
}
// Increase difficulty over time
difficultyTimer++;
if (difficultyTimer >= 1800 && difficultyLevel < maxDifficultyLevel) {
// Every 30 seconds
difficultyLevel++;
difficultyTimer = 0;
// Increase max enemies based on difficulty
maxEnemies = 5 + Math.floor(difficultyLevel / 2);
}
// Platform collision detection
if (player.isJumping) {
for (var i = 0; i < platforms.length; i++) {
platforms[i].checkCollision(player);
}
}
// Check for game win condition (example: score 1000)
if (LK.getScore() >= 1000) {
LK.showYouWin();
}
};
// Start with title screen
var titleShown = false;
// Play background music
LK.playMusic('ballet_theme', {
fade: {
start: 0,
end: 0.8,
duration: 1000
}
});