User prompt
Please fix the bug: 'Uncaught TypeError: player.containsPoint is not a function' in or related to this line: 'if (!isButtonClick && !player.containsPoint({' Line Number: 603
User prompt
Please fix the bug: 'Uncaught TypeError: stanceButtons[type].containsPoint is not a function' in or related to this line: 'if (stanceButtons[type].containsPoint({' Line Number: 590
Code edit (1 edits merged)
Please save this source code
User prompt
Ballet of Shadows: The Porcelain Dancer
Initial prompt
i want that game Her combat style primarily consists of actual accurate ballet moves, elegant and deadly, with a real ballerina providing the motion capture. I'm thinking there will be different forms and stances as well, similar to switching stances in games like Nioh, and they would be based on birds like the swan form--most likely the default form, slower and elegant flowing movements; the crow form--sharper, more agile, can bleed enemies; the phoenix form--more magic based with AoE fire effects and can burn enemies. Those are the main ones I have right now, but I'm open to suggestions. Each form/stance will have a special meter to where you can enter a more powered up state, and the main character's outfit and appearance will change to match the specific form's aesthetic and vibe, for example a dark, phantom-esque masquerade type outfit for the crow form. If we're going with her being a living doll, a small detail I think would be cool to incorporate is that the lower her health gets, the more cracks begin to show on her skin since she'd be made of porcelain. There may be some RPG elements like gear, but there would also be a separate glamour system so that even if you put on gear with good stats that you don't like the look of, you can change and customize her outfit and hairstyle independent of that. The world and level design of the game is inspired by real ballets, such as Swan Lake, The Nutcracker, A Midsummer Night's Dream, etc. However they would be grimdark versions, inspired by American McGee's Alice games. One idea I had was of the Swan Lake level being semi-open world, where there is the giant body of a dead swan laid out across the land as one of the big landmarks. The enemies would also be themed according to their respective worlds. You'll never see an enemy from, let's say, the Swan Lake world looking the exact same in Nutcracker world. Even if they were the same type of enemy, they would have different aesthetics and color schemes that would match the current level. There would also be platforming and traversal abilities like gliding, puzzle solving within the world, possibly Metroidvania elements where you'd need to unlock certain abilities in order to 100% explore every level. That's all I got so far. I don't even have a semblance of a plot yet right now. The current placeholder is that it's a standard self-discovery kind of plot, her going on a quest to find out who made her, what her intended purpose was, how she fits into the messed up world she's in. The plot elements of the famous ballets would be incorporated into their respective levels, but I think they would always tie into the main overarching story somehow. Again this is not a real game I'm going to develop in the future since the scope is impossible and would require a massive AAA budget, it's just all in good fun. If you're into this idea, please sound off and let's flesh it out together. I also don't have a title yet, so feel free to give me some title recommendations too. Thanks for hearing me out!
/**** * 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
}
});