User prompt
I'm working on a mobile rhythm game made with JavaScript and Upit Engine. The game has red, green, and black orbs that fall vertically from the top. At the bottom of the screen, there’s a targetZone where players must tap the orbs to score. There’s also a character (ninja, wizard, or swordmaster) with a special ability. Please help fix and improve the following issues in my current code: Orbs are not clickable in the target zone. 👉 Fix: Orbs should only be tappable when they are within the targetZone rectangle. Make sure the click detection is constrained to that area (X and Y bounds). Character ability is triggering when tapping the target zone. 👉 Fix: Ability should only activate when tapping directly on the character sprite, not when tapping the targetZone or screen. Swordmaster’s clone ability is not working. 👉 Fix: The clone spawns correctly, but it does not auto-hit orbs. Make the clone auto-hit the nearest orb inside the targetZone every 100ms for 1 second. TargetZone visibility is too low. 👉 Fix: Increase the visibility (e.g., set alpha to 0.5) and optionally give it a clearer color like dark blue for better UX. Additional Notes: Orbs fall straight down (this part is already correct). The game runs at ~60 FPS. The code is written in Upit’s framework with Container, LK.init.shape, and LK.getAsset. Please fix these problems and give me a corrected code block that I can directly paste into Upit Studio. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please update the game logic for a more intuitive tap-timing rhythm style: 1. Modify the orb movement: - All orbs should fall straight down from top to bottom (no target-based vector movement). - Remove all references to orb.targetX and orb.targetY. - Instead, orbs just increase their y position by `orb.speed` each frame. 2. Modify the target zone: - Make the targetZone much taller (e.g., height: 300) and stretch it across the full bottom of the screen. - Place the targetZone at the bottom of the screen (e.g., y: 2300 or 2400 depending on canvas size). 3. Modify the hit detection: - Players can only successfully hit orbs if they are within the targetZone bounds. - Tapping anywhere outside the zone does nothing. - Taps must be on orbs that are inside the vertical range of the targetZone (collision check). 4. Update swordmaster's clone AI: - The clone should only auto-hit orbs when they are inside the targetZone. - The clone should behave like a player: look for orbs inside the zone and auto-hit one every 100ms. 5. Bonus: Make orb speed slightly slower (e.g., speed: 4). This turns the game into a clearer rhythm-timing challenge like Friday Night Funkin' or Piano Tiles. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In this Upit rhythm game code, please change the swordmaster character's ability. Instead of "dual strike", make it so that when the swordmaster uses their ability, it spawns a temporary clone of themselves. This clone should appear next to the original swordmaster, automatically hit orbs for 1 second (as if it perfectly taps the correct ones), and then disappear. The clone should visually look like the swordmaster and help the player for 1 second. Update only the related parts (the ability and orb interaction logic), and do not affect other characters. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In this Upit game code, the character selection screen shows the characters (ninja, wizard, swordmaster), but tapping on them doesn’t trigger the selection function. Please fix the input detection so that tapping on the characters correctly selects them using selectCharacter(type) as intended. Do not change unrelated parts of the game.
User prompt
Redesign the gameplay to be more like Beat Saber. - Orbs should move toward the player from the top (not lanes) - Spawning should match BPM of the background music - Tapping or swiping should break orbs (use color to determine side or action) - Add simple feedback effects like screen shake or color flash - Allow player to use abilities like time-slow or auto-hit You can refactor the orb system and remove lane dependency if needed. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Scene 1 – Character Selection Screen: The player starts in a mystical dojo-like space with soft ambient music playing. On screen are 3 animated character avatars. Player must tap on one to begin the rhythm game. Character Options (Selectable): 🔹 Blue Mage • Appearance: Tall figure wearing a glowing blue robe, wide wizard hat, and holding a crystal staff. Mystical blue eyes, long white beard. • Animation: Calm idle floating motion, magic particles flowing around • Ability: Can cast “Ice Freeze” every 10 seconds to slow down incoming beat orbs for 3 seconds • Difficulty: Medium • Tagline: “Slow the rhythm, control the chaos.” 🔴 Dual Blade Swordmaster • Appearance: White torn martial arts outfit, red belt, bandaged arms, wild hair, two glowing swords • Animation: Breathing heavily, does quick sword swings while idle • Ability: Can slash both left and right lanes at the same time (multi-touch bonus), 2x combo window • Difficulty: Hard • Tagline: “Two blades. One rhythm.” ⚫ Shadow Ninja • Appearance: Full black suit, masked face, glowing dagger in hand, red eyes • Animation: Quick shadow dashes, fades in/out • Ability: Increased tap/slash speed; auto-slice chain combo when timing is perfect • Difficulty: Easy to pick, hard to master • Tagline: “Strike with silence. Dance with speed.” Gameplay Transition: Once the player selects a character, fade-in transition with music rising leads into rhythm gameplay arena (Scene 2). The player’s class ability is activated and UI adapts accordingly.
User prompt
Scene 1 – Character Selection Screen: The player starts in a mystical dojo-like space with soft ambient music playing. On screen are 3 animated character avatars. Player must tap on one to begin the rhythm game. Character Options (Selectable): 🔹 Blue Mage • Appearance: Tall figure wearing a glowing blue robe, wide wizard hat, and holding a crystal staff. Mystical blue eyes, long white beard. • Animation: Calm idle floating motion, magic particles flowing around • Ability: Can cast “Ice Freeze” every 10 seconds to slow down incoming beat orbs for 3 seconds • Difficulty: Medium • Tagline: “Slow the rhythm, control the chaos.” 🔴 Dual Blade Swordmaster • Appearance: White torn martial arts outfit, red belt, bandaged arms, wild hair, two glowing swords • Animation: Breathing heavily, does quick sword swings while idle • Ability: Can slash both left and right lanes at the same time (multi-touch bonus), 2x combo window • Difficulty: Hard • Tagline: “Two blades. One rhythm.” ⚫ Shadow Ninja • Appearance: Full black suit, masked face, glowing dagger in hand, red eyes • Animation: Quick shadow dashes, fades in/out • Ability: Increased tap/slash speed; auto-slice chain combo when timing is perfect • Difficulty: Easy to pick, hard to master • Tagline: “Strike with silence. Dance with speed.” Gameplay Transition: Once the player selects a character, fade-in transition with music rising leads into rhythm gameplay arena (Scene 2). The player’s class ability is activated and UI adapts accordingly. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Scene 1 – Character Selection Screen: The player starts in a mystical dojo-like space with soft ambient music playing. On screen are 3 animated character avatars. Player must tap on one to begin the rhythm game. Character Options (Selectable): 🔹 Blue Mage • Appearance: Tall figure wearing a glowing blue robe, wide wizard hat, and holding a crystal staff. Mystical blue eyes, long white beard. • Animation: Calm idle floating motion, magic particles flowing around • Ability: Can cast “Ice Freeze” every 10 seconds to slow down incoming beat orbs for 3 seconds • Difficulty: Medium • Tagline: “Slow the rhythm, control the chaos.” 🔴 Dual Blade Swordmaster • Appearance: White torn martial arts outfit, red belt, bandaged arms, wild hair, two glowing swords • Animation: Breathing heavily, does quick sword swings while idle • Ability: Can slash both left and right lanes at the same time (multi-touch bonus), 2x combo window • Difficulty: Hard • Tagline: “Two blades. One rhythm.” ⚫ Shadow Ninja • Appearance: Full black suit, masked face, glowing dagger in hand, red eyes • Animation: Quick shadow dashes, fades in/out • Ability: Increased tap/slash speed; auto-slice chain combo when timing is perfect • Difficulty: Easy to pick, hard to master • Tagline: “Strike with silence. Dance with speed.” Gameplay Transition: Once the player selects a character, fade-in transition with music rising leads into rhythm gameplay arena (Scene 2). The player’s class ability is activated and UI adapts accordingly.
User prompt
Scene 1 – Character Selection Screen: The player starts in a mystical dojo-like space with soft ambient music playing. On screen are 3 animated character avatars. Player must tap on one to begin the rhythm game. Character Options (Selectable): 🔹 Blue Mage • Appearance: Tall figure wearing a glowing blue robe, wide wizard hat, and holding a crystal staff. Mystical blue eyes, long white beard. • Animation: Calm idle floating motion, magic particles flowing around • Ability: Can cast “Ice Freeze” every 10 seconds to slow down incoming beat orbs for 3 seconds • Difficulty: Medium • Tagline: “Slow the rhythm, control the chaos.” 🔴 Dual Blade Swordmaster • Appearance: White torn martial arts outfit, red belt, bandaged arms, wild hair, two glowing swords • Animation: Breathing heavily, does quick sword swings while idle • Ability: Can slash both left and right lanes at the same time (multi-touch bonus), 2x combo window • Difficulty: Hard • Tagline: “Two blades. One rhythm.” ⚫ Shadow Ninja • Appearance: Full black suit, masked face, glowing dagger in hand, red eyes • Animation: Quick shadow dashes, fades in/out • Ability: Increased tap/slash speed; auto-slice chain combo when timing is perfect • Difficulty: Easy to pick, hard to master • Tagline: “Strike with silence. Dance with speed.” Gameplay Transition: Once the player selects a character, fade-in transition with music rising leads into rhythm gameplay arena (Scene 2). The player’s class ability is activated and UI adapts accordingly. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Warriors: Beat Battle Arena
Initial prompt
Game Type: Music rhythm-based action game with fantasy RPG elements Visual Style: 3D stylized / vibrant / slightly cartoonish Theme: Players choose one of 3 classes — Ninja, Wizard, or Swordmaster — and enter a rhythm battle arena where colorful beat orbs come toward the player in sync with popular music tracks. Mechanics: • Notes come at the player in 3 lanes, color-coded: • 🔴 Red = hit left side • 🟢 Green = hit right side • ⚫ Black = bomb — must be dodged or ignored (touch = instant fail) • Orbs move faster as music progresses • Player interacts by tapping or slashing based on character’s ability Classes & Abilities: • Ninja: Fast dashes, quicker tap/slash speed, special move: “Shadow Slice” (auto-hits a fast series of beats) • Wizard: Normal speed, casts freeze spell every 10 seconds to slow incoming beats for 3 seconds • Swordmaster: Dual-wielding – can tap both left and right lanes at once (multi-touch), higher combo multiplier Game Objective: • Survive the full duration of the song without hitting bombs • Score is based on accuracy, combo, and class skill use • Unlock skins and new songs after each win Popular Track Suggestions: • “Blinding Lights” by The Weeknd • “Levitating” by Dua Lipa • “Believer” by Imagine Dragons • “Bad Guy” by Billie Eilish Interface Notes: • Color-coded lanes with beatballs moving toward player • Touch screen input or FaceKit interaction (head tilt to dodge bombs?) • Energy bar depletes if notes missed • Flashy effects for class abilities, visual feedback on perfect/good/miss
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BeatOrb = Container.expand(function (orbType, lane) { var self = Container.call(this); self.orbType = orbType; self.lane = lane; self.speed = 4; self.hit = false; var orbGraphics = self.attachAsset(orbType, { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { var speedMultiplier = 1; if (selectedCharacter && selectedCharacter.freezeActive) { speedMultiplier = 0.3; } self.y += self.speed * speedMultiplier; }; return self; }); var Character = Container.expand(function (characterType) { var self = Container.call(this); self.type = characterType; self.abilityReady = true; self.abilityCooldown = 0; self.freezeActive = false; self.freezeTime = 0; var characterGraphics = self.attachAsset(characterType, { anchorX: 0.5, anchorY: 0.5 }); self.useAbility = function () { if (!self.abilityReady) return false; LK.getSound('ability').play(); if (self.type === 'ninja') { // Shadow Slice: Auto-hit next 3 orbs comboMode = 3; LK.effects.flashObject(self, 0x9932cc, 500); } else if (self.type === 'wizard') { // Time Freeze: Slow orbs for 3 seconds self.freezeActive = true; self.freezeTime = 3000; LK.effects.flashObject(self, 0x00ffff, 3000); } else if (self.type === 'swordmaster') { // Dual Strike: Next hit scores double dualStrikeActive = true; LK.effects.flashObject(self, 0xffd700, 1000); } self.abilityReady = false; self.abilityCooldown = 10000; // 10 second cooldown return true; }; self.update = function () { if (!self.abilityReady) { self.abilityCooldown -= 16; // ~60fps if (self.abilityCooldown <= 0) { self.abilityReady = true; } } if (self.freezeActive) { self.freezeTime -= 16; if (self.freezeTime <= 0) { self.freezeActive = false; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ // Game state var gameState = 'classSelection'; // 'classSelection', 'playing', 'gameOver' var selectedCharacter = null; var score = 0; var combo = 0; var maxCombo = 0; var lives = 3; var comboMode = 0; // Ninja ability counter var dualStrikeActive = false; // Swordmaster ability var songProgress = 0; var songDuration = 120000; // 2 minutes // Game objects var orbs = []; var lanes = []; var characters = {}; var targetZone = null; // UI elements var scoreText = null; var comboText = null; var livesText = null; var abilityText = null; var instructionText = null; // Lane positions var lanePositions = [2048 * 0.25, 2048 * 0.5, 2048 * 0.75]; var targetY = 2200; // Initialize lanes for (var i = 0; i < 3; i++) { var lane = game.addChild(LK.getAsset('lane', { anchorX: 0.5, anchorY: 0, x: lanePositions[i], y: 200 })); lanes.push(lane); } // Target zone targetZone = game.addChild(LK.getAsset('targetZone', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: targetY, alpha: 0.3 })); // Character selection cards var characterCards = {}; // Create character selection cards with animations function createCharacterCard(type, x, y, title, tagline, difficulty, description) { var card = game.addChild(new Container()); card.x = x; card.y = y; // Character avatar var character = card.addChild(new Character(type)); character.y = -100; // Title text var titleText = card.addChild(new Text2(title, { size: 45, fill: 0xFFFFFF })); titleText.anchor.set(0.5, 0.5); titleText.y = 50; // Tagline var taglineText = card.addChild(new Text2(tagline, { size: 30, fill: 0xCCCCCC })); taglineText.anchor.set(0.5, 0.5); taglineText.y = 90; // Difficulty var difficultyColor = difficulty === 'Easy' ? 0x44ff44 : difficulty === 'Medium' ? 0xffff44 : 0xff4444; var difficultyText = card.addChild(new Text2('Difficulty: ' + difficulty, { size: 25, fill: difficultyColor })); difficultyText.anchor.set(0.5, 0.5); difficultyText.y = 130; // Description var descText = card.addChild(new Text2(description, { size: 22, fill: 0xAAAAAAA })); descText.anchor.set(0.5, 0.5); descText.y = 170; // Add floating animation var originalY = card.y; tween(card, { y: originalY - 20 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { tween(card, { y: originalY + 20 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { // Restart animation loop tween(card, { y: originalY }, { duration: 100 }); } }); } }); card.characterType = type; characterCards[type] = card; return card; } // Create character cards createCharacterCard('ninja', 2048 * 0.2, 1200, 'Shadow Ninja', 'Strike with silence. Dance with speed.', 'Easy', 'Auto-combo chains\nIncreased tap speed'); createCharacterCard('wizard', 2048 * 0.5, 1200, 'Blue Mage', 'Slow the rhythm, control the chaos.', 'Medium', 'Freeze time ability\nSlows incoming orbs'); createCharacterCard('swordmaster', 2048 * 0.8, 1200, 'Dual Blade Swordmaster', 'Two blades. One rhythm.', 'Hard', 'Multi-lane attacks\n2x combo multiplier'); // Store references for selection characters.ninja = characterCards.ninja.children[0]; characters.wizard = characterCards.wizard.children[0]; characters.swordmaster = characterCards.swordmaster.children[0]; // UI Setup scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.topLeft.addChild(scoreText); scoreText.x = 120; scoreText.y = 20; comboText = new Text2('Combo: 0', { size: 50, fill: 0xFFFF00 }); comboText.anchor.set(0.5, 0); LK.gui.top.addChild(comboText); comboText.y = 80; livesText = new Text2('Lives: 3', { size: 50, fill: 0xFF4444 }); livesText.anchor.set(1, 0); LK.gui.topRight.addChild(livesText); livesText.x = -20; livesText.y = 20; abilityText = new Text2('Ability Ready!', { size: 40, fill: 0x00FF00 }); abilityText.anchor.set(0.5, 1); LK.gui.bottom.addChild(abilityText); abilityText.y = -100; instructionText = new Text2('Welcome to Rhythm Warriors!\nChoose your character to begin the beat battle', { size: 60, fill: 0xFFFFFF }); instructionText.anchor.set(0.5, 0.5); LK.gui.center.addChild(instructionText); instructionText.y = -300; // Input handling game.down = function (x, y, obj) { if (gameState === 'classSelection') { // Check character card selection with expanded hit areas var hitPoint = { x: x, y: y, width: 1, height: 1 }; // Check each character card for (var cardType in characterCards) { var card = characterCards[cardType]; var cardBounds = { x: card.x - 150, y: card.y - 200, width: 300, height: 400 }; if (x >= cardBounds.x && x <= cardBounds.x + cardBounds.width && y >= cardBounds.y && y <= cardBounds.y + cardBounds.height) { // Add selection animation tween(card, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { selectCharacter(cardType); } }); break; } } } else if (gameState === 'playing') { // Handle orb hits and abilities if (y > 2000) { // Ability zone if (selectedCharacter && selectedCharacter.abilityReady) { selectedCharacter.useAbility(); updateAbilityText(); } } else { // Determine which lane was tapped var tapLane = -1; if (x < 2048 * 0.375) tapLane = 0; // Left lane else if (x < 2048 * 0.625) tapLane = 1; // Middle lane else tapLane = 2; // Right lane handleOrbHit(tapLane, x < 2048 * 0.5 ? 'red' : 'green'); } } }; function selectCharacter(type) { selectedCharacter = characters[type]; // Animate card selection var selectedCard = characterCards[type]; // Hide unselected cards with fade out for (var cardType in characterCards) { if (cardType !== type) { var card = characterCards[cardType]; tween(card, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { card.visible = false; } }); } } // Animate selected character to center tween(selectedCard, { x: 2048 / 2, y: 1000, scaleX: 1.2, scaleY: 1.2 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { // Move character to game position selectedCharacter.x = 2048 / 2; selectedCharacter.y = 2400; // Hide card and show only character selectedCard.visible = false; selectedCharacter.visible = true; // Hide instruction text with fade tween(instructionText, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { instructionText.visible = false; } }); // Start the game gameState = 'playing'; LK.playMusic('bgtrack'); LK.effects.flashScreen(0x00ff00, 500); } }); } function handleOrbHit(lane, expectedType) { var hitOrb = null; var bestDistance = Infinity; // Find closest orb in target zone for this lane for (var i = 0; i < orbs.length; i++) { var orb = orbs[i]; if (orb.lane === lane && !orb.hit) { var distance = Math.abs(orb.y - targetY); if (distance < 150 && distance < bestDistance) { hitOrb = orb; bestDistance = distance; } } } if (hitOrb) { if (hitOrb.orbType === 'blackBomb') { // Hit a bomb - lose life lives--; combo = 0; LK.getSound('bomb').play(); LK.effects.flashScreen(0xff0000, 300); hitOrb.hit = true; if (lives <= 0) { endGame(); return; } } else if (hitOrb.orbType === 'redOrb' && expectedType === 'red' || hitOrb.orbType === 'greenOrb' && expectedType === 'green' || comboMode > 0) { // Successful hit var points = 100; if (comboMode > 0) { comboMode--; points *= 2; // Ninja bonus } if (dualStrikeActive) { points *= 2; // Swordmaster bonus dualStrikeActive = false; } combo++; if (combo > maxCombo) maxCombo = combo; // Combo multiplier points *= Math.min(combo, 10); score += points; LK.getSound('hit').play(); LK.effects.flashObject(hitOrb, 0xffffff, 200); hitOrb.hit = true; } else { // Wrong color hit combo = 0; LK.getSound('miss').play(); } } else { // Missed - no orb in range combo = 0; LK.getSound('miss').play(); } updateUI(); } function spawnOrb() { var lane = Math.floor(Math.random() * 3); var orbTypes = ['redOrb', 'greenOrb', 'blackBomb']; var weights = [0.4, 0.4, 0.2]; // 40% red, 40% green, 20% bomb var rand = Math.random(); var orbType = 'redOrb'; if (rand < weights[0]) orbType = 'redOrb';else if (rand < weights[0] + weights[1]) orbType = 'greenOrb';else orbType = 'blackBomb'; var orb = new BeatOrb(orbType, lane); orb.x = lanePositions[lane]; orb.y = 100; orbs.push(orb); game.addChild(orb); } function updateUI() { scoreText.setText('Score: ' + score); comboText.setText('Combo: ' + combo); livesText.setText('Lives: ' + lives); LK.setScore(score); } function updateAbilityText() { if (!selectedCharacter) return; if (selectedCharacter.abilityReady) { abilityText.setText('Ability Ready!'); abilityText.tint = 0x00ff00; } else { var cooldownSeconds = Math.ceil(selectedCharacter.abilityCooldown / 1000); abilityText.setText('Ability: ' + cooldownSeconds + 's'); abilityText.tint = 0xff4444; } } function endGame() { gameState = 'gameOver'; // Save high score var highScore = storage.highScore || 0; if (score > highScore) { storage.highScore = score; } // Save stats storage.gamesPlayed = (storage.gamesPlayed || 0) + 1; if (maxCombo > (storage.maxCombo || 0)) { storage.maxCombo = maxCombo; } LK.showGameOver(); } function checkWinCondition() { if (songProgress >= songDuration) { // Survived the full song! var bonusScore = lives * 1000 + maxCombo * 100; score += bonusScore; LK.setScore(score); // Save completion storage.songsCompleted = (storage.songsCompleted || 0) + 1; LK.showYouWin(); } } // Main game update loop game.update = function () { if (gameState !== 'playing') return; songProgress += 16; // ~60fps // Update character abilities if (selectedCharacter) { selectedCharacter.update(); updateAbilityText(); } // Spawn orbs based on music tempo if (LK.ticks % 40 === 0) { // Spawn every ~0.67 seconds spawnOrb(); } // Update orbs for (var i = orbs.length - 1; i >= 0; i--) { var orb = orbs[i]; // Remove hit orbs after animation if (orb.hit) { orb.alpha -= 0.1; if (orb.alpha <= 0) { orb.destroy(); orbs.splice(i, 1); } continue; } // Check if orb passed target zone (miss) if (orb.y > targetY + 200) { if (orb.orbType !== 'blackBomb') { // Missed a good orb combo = 0; updateUI(); } orb.destroy(); orbs.splice(i, 1); continue; } // Check bomb collision with target zone if (orb.orbType === 'blackBomb' && orb.y >= targetY - 50 && orb.y <= targetY + 50) { // Bomb reached target zone without being avoided lives--; combo = 0; LK.getSound('bomb').play(); LK.effects.flashScreen(0xff0000, 300); orb.destroy(); orbs.splice(i, 1); if (lives <= 0) { endGame(); return; } updateUI(); } } // Check win condition checkWinCondition(); };
===================================================================
--- original.js
+++ change.js
@@ -127,18 +127,83 @@
x: 2048 / 2,
y: targetY,
alpha: 0.3
}));
-// Create character selection
-characters.ninja = game.addChild(new Character('ninja'));
-characters.ninja.x = 2048 * 0.25;
-characters.ninja.y = 1000;
-characters.wizard = game.addChild(new Character('wizard'));
-characters.wizard.x = 2048 * 0.5;
-characters.wizard.y = 1000;
-characters.swordmaster = game.addChild(new Character('swordmaster'));
-characters.swordmaster.x = 2048 * 0.75;
-characters.swordmaster.y = 1000;
+// Character selection cards
+var characterCards = {};
+// Create character selection cards with animations
+function createCharacterCard(type, x, y, title, tagline, difficulty, description) {
+ var card = game.addChild(new Container());
+ card.x = x;
+ card.y = y;
+ // Character avatar
+ var character = card.addChild(new Character(type));
+ character.y = -100;
+ // Title text
+ var titleText = card.addChild(new Text2(title, {
+ size: 45,
+ fill: 0xFFFFFF
+ }));
+ titleText.anchor.set(0.5, 0.5);
+ titleText.y = 50;
+ // Tagline
+ var taglineText = card.addChild(new Text2(tagline, {
+ size: 30,
+ fill: 0xCCCCCC
+ }));
+ taglineText.anchor.set(0.5, 0.5);
+ taglineText.y = 90;
+ // Difficulty
+ var difficultyColor = difficulty === 'Easy' ? 0x44ff44 : difficulty === 'Medium' ? 0xffff44 : 0xff4444;
+ var difficultyText = card.addChild(new Text2('Difficulty: ' + difficulty, {
+ size: 25,
+ fill: difficultyColor
+ }));
+ difficultyText.anchor.set(0.5, 0.5);
+ difficultyText.y = 130;
+ // Description
+ var descText = card.addChild(new Text2(description, {
+ size: 22,
+ fill: 0xAAAAAAA
+ }));
+ descText.anchor.set(0.5, 0.5);
+ descText.y = 170;
+ // Add floating animation
+ var originalY = card.y;
+ tween(card, {
+ y: originalY - 20
+ }, {
+ duration: 2000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ tween(card, {
+ y: originalY + 20
+ }, {
+ duration: 2000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ // Restart animation loop
+ tween(card, {
+ y: originalY
+ }, {
+ duration: 100
+ });
+ }
+ });
+ }
+ });
+ card.characterType = type;
+ characterCards[type] = card;
+ return card;
+}
+// Create character cards
+createCharacterCard('ninja', 2048 * 0.2, 1200, 'Shadow Ninja', 'Strike with silence. Dance with speed.', 'Easy', 'Auto-combo chains\nIncreased tap speed');
+createCharacterCard('wizard', 2048 * 0.5, 1200, 'Blue Mage', 'Slow the rhythm, control the chaos.', 'Medium', 'Freeze time ability\nSlows incoming orbs');
+createCharacterCard('swordmaster', 2048 * 0.8, 1200, 'Dual Blade Swordmaster', 'Two blades. One rhythm.', 'Hard', 'Multi-lane attacks\n2x combo multiplier');
+// Store references for selection
+characters.ninja = characterCards.ninja.children[0];
+characters.wizard = characterCards.wizard.children[0];
+characters.swordmaster = characterCards.swordmaster.children[0];
// UI Setup
scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
@@ -168,39 +233,48 @@
});
abilityText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(abilityText);
abilityText.y = -100;
-instructionText = new Text2('Choose Your Class!\nNinja: Speed & Combos\nWizard: Time Control\nSwordmaster: Multi-Strike', {
- size: 50,
+instructionText = new Text2('Welcome to Rhythm Warriors!\nChoose your character to begin the beat battle', {
+ size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
+instructionText.y = -300;
// Input handling
game.down = function (x, y, obj) {
if (gameState === 'classSelection') {
- // Check character selection
- if (characters.ninja.intersects({
+ // Check character card selection with expanded hit areas
+ var hitPoint = {
x: x,
y: y,
width: 1,
height: 1
- })) {
- selectCharacter('ninja');
- } else if (characters.wizard.intersects({
- x: x,
- y: y,
- width: 1,
- height: 1
- })) {
- selectCharacter('wizard');
- } else if (characters.swordmaster.intersects({
- x: x,
- y: y,
- width: 1,
- height: 1
- })) {
- selectCharacter('swordmaster');
+ };
+ // Check each character card
+ for (var cardType in characterCards) {
+ var card = characterCards[cardType];
+ var cardBounds = {
+ x: card.x - 150,
+ y: card.y - 200,
+ width: 300,
+ height: 400
+ };
+ if (x >= cardBounds.x && x <= cardBounds.x + cardBounds.width && y >= cardBounds.y && y <= cardBounds.y + cardBounds.height) {
+ // Add selection animation
+ tween(card, {
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 200,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ selectCharacter(cardType);
+ }
+ });
+ break;
+ }
}
} else if (gameState === 'playing') {
// Handle orb hits and abilities
if (y > 2000) {
@@ -220,24 +294,58 @@
}
};
function selectCharacter(type) {
selectedCharacter = characters[type];
- gameState = 'playing';
- // Hide unselected characters
- for (var charType in characters) {
- if (charType !== type) {
- characters[charType].visible = false;
+ // Animate card selection
+ var selectedCard = characterCards[type];
+ // Hide unselected cards with fade out
+ for (var cardType in characterCards) {
+ if (cardType !== type) {
+ var card = characterCards[cardType];
+ tween(card, {
+ alpha: 0,
+ scaleX: 0.8,
+ scaleY: 0.8
+ }, {
+ duration: 500,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ card.visible = false;
+ }
+ });
}
}
- // Position selected character
- selectedCharacter.x = 2048 / 2;
- selectedCharacter.y = 2400;
- // Hide instruction text
- instructionText.visible = false;
- // Start music and orb spawning
- LK.playMusic('bgtrack');
- // Flash screen to indicate game start
- LK.effects.flashScreen(0x00ff00, 500);
+ // Animate selected character to center
+ tween(selectedCard, {
+ x: 2048 / 2,
+ y: 1000,
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 800,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Move character to game position
+ selectedCharacter.x = 2048 / 2;
+ selectedCharacter.y = 2400;
+ // Hide card and show only character
+ selectedCard.visible = false;
+ selectedCharacter.visible = true;
+ // Hide instruction text with fade
+ tween(instructionText, {
+ alpha: 0
+ }, {
+ duration: 300,
+ onFinish: function onFinish() {
+ instructionText.visible = false;
+ }
+ });
+ // Start the game
+ gameState = 'playing';
+ LK.playMusic('bgtrack');
+ LK.effects.flashScreen(0x00ff00, 500);
+ }
+ });
}
function handleOrbHit(lane, expectedType) {
var hitOrb = null;
var bestDistance = Infinity;
A ninja wearing tight black clothes, purple scarf, masked face, white skin, glowing purple eyes, slim and agile body, simple background, front-facing character with no background, standing pose, 2D game character. In-Game asset. 2d. High contrast. No shadows
A white-clothed male samurai with torn clothes, long gray hair tied back, pale skin, red belt on his waist, holding two swords, standing confidently, simple pose, no background, front-facing, 2D game character. In-Game asset. 2d. High contrast. No shadows
A female mage with pale skin, long white hair, wearing a long blue robe and a pointed blue hat, holding a glowing icy staff with a blue crystal, snowflake patterns on robe, cold expression, simple background, front-facing, no background, 2D game character. In-Game asset. 2d. High contrast. No shadows
Green goblin without backround. In-Game asset. 2d. High contrast. No shadows
Red wild monster. In-Game asset. 2d. High contrast. No shadows
Black monster , white eyes looks like bomb. In-Game asset. 2d. High contrast. No shadows