User prompt
Haz que el asset "menuSong" deje de sonar cuando se presiona el botón play
User prompt
Extiende el asset "menuHowToPlay" de manera horizontal y vertical, y el asset "titleGame" en el menú de instrucciones haz que suba un poco su posición
User prompt
Haz que las letras se vean más grandes, y agrega un salto de línea por cada instrucción
User prompt
Haz que las letras sean blancas, pero estén en estilo "Bold"
User prompt
Haz que las letras del menú de instrucciones sean negras. Solo el botón "OK" debe ser con letras blancas ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Extiendelo un poco más, de manera horizontal
User prompt
Haz que su posición sea más abajo
User prompt
Extiende mucho más el asset "menuHowToPlay" de manera vertical
User prompt
Reduce el tamaño del asset "titleGame" un 50%, solo en el menú de instrucciones ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que el asset "titleGame" se muestre en el menú de instrucciones, de la misma manera en que se ve en el menú para seleccionar el personaje
User prompt
El título HOW TO PLAY bajalo un poco de su posición actual
User prompt
Ahora, extiendelo mucho más de manera vertical
User prompt
Extiendelo un poco más horizontalmente
User prompt
Extiendelo un poco más de manera horizontal
User prompt
Extiende el asset un poco más horizontalmente, y haz que se extienda más hacia arriba, manteniendo su posición actual desde la parte inferior
User prompt
Reduce el tamaño del asset "menuHowToPlay", haz que ocupe el 80% de la pantalla horizontal, y el 30% de la pantalla en vertical. El botón "OK" Ubicalo fuera del asset, un poco más abajo
User prompt
Haz que las instrucciones tengan de fondo el asset "menuHowToPlay"
User prompt
El botón "OK" debe usar el asset "OKButton"
User prompt
Al iniciar el juego, haz que salga una pantalla para dar las indicaciones del juego, y un botón que diga "OK", y cuando se presione ese botón se muestre la pantalla del menú. El asset "menuSong" debe sonar desde que se inicia la aplicación
User prompt
Haz que la puntuación se guarde para que sea el "Final Score" en upit al finalizar la partida ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Separa un poco las flechas, manteniendo la distancia actual del margen de la parte inferior
User prompt
Haz que el botón play se oculte cuando se presione
User prompt
Haz el mismo proceso para "idolKasumin" con el asset "IdolKasuminSelected" ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Haz que ese funcionamiento sea solo cuando está "Idol" seleccionada y cuando se presiona el botón play
User prompt
En el menú, al seleccionar a "Idol" como personaje, haz que suene el assets "idolSelected", y que empiece la partida 0.5 segundos luego de que deje de sonar ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var ActionButton = Container.expand(function (color, direction, keyText) {
var self = Container.call(this);
var assetName = color + 'Button';
var buttonGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
// Add invisible larger hitbox
var hitbox = LK.getAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5,
width: 320,
height: 230
});
hitbox.alpha = 0; // Make completely invisible
hitbox.tint = 0x000000; // Ensure it's invisible
self.addChild(hitbox);
// Key text removed - button functions without text display
self.color = color;
self.direction = direction;
self.isPressed = false;
self.activate = function () {
self.isPressed = true;
buttonGraphics.scaleX = 1.17; // 0.9 * 1.3 to maintain same press effect
buttonGraphics.scaleY = 1.17;
tween(buttonGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150
});
// Button activation visual feedback only - enemy destruction handled in processInput
self.isPressed = false;
};
self.isEnemyInHitZone = function (enemy) {
var hitZoneSize = 200;
var centerX = 1024;
var centerY = 1366;
if (enemy.direction === 'up' && enemy.y > centerY - hitZoneSize && enemy.y < centerY + hitZoneSize) {
return true;
} else if (enemy.direction === 'down' && enemy.y > centerY - hitZoneSize && enemy.y < centerY + hitZoneSize) {
return true;
} else if (enemy.direction === 'left' && enemy.x > centerX - hitZoneSize && enemy.x < centerX + hitZoneSize) {
return true;
} else if (enemy.direction === 'right' && enemy.x > centerX - hitZoneSize && enemy.x < centerX + hitZoneSize) {
return true;
}
return false;
};
self.down = function (x, y, obj) {
self.activate();
processInput(self.direction);
updateUI();
};
return self;
});
var BeatIndicator = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
self.pulse = function () {
graphics.scaleX = 1.5;
graphics.scaleY = 1.5;
tween(graphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
};
return self;
});
var Enemy = Container.expand(function (color, direction) {
var self = Container.call(this);
var enemyAsset = color + 'Enemy';
var graphics = self.attachAsset(enemyAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.color = color;
self.direction = direction;
self.health = 3;
self.maxHealth = 3;
// Calculate speed based on 8 beat travel time from spawn to center (8 beats = 4000ms at 120 BPM)
// Distance from spawn to center varies by direction:
// - Up/Down: 1300 pixels (from y=-100 to y=1200 or y=2832 to y=1200)
// - Left/Right: 1124 pixels (from x=-100 to x=1024 or x=2148 to x=1024)
var distanceToCenter;
if (direction === 'up' || direction === 'down') {
distanceToCenter = 1300; // Distance from spawn edge to center
} else {
distanceToCenter = 1124; // Distance from spawn edge to center
}
// Speed = distance / time (8 beats = 4000ms = 4 seconds, but we update 60 times per second)
// So speed per frame = distance / (8 beats * 60fps) = distance / 480 frames for 8 beats
// But since we want them to arrive in 8 beats, we use 240 frames (4 seconds at 60fps)
self.speed = distanceToCenter / 240; // Speed per frame to reach center in 8 beats (4 seconds)
self.baseSpeed = self.speed;
self.rhythmMultiplier = 1;
self.targetX = 1024; // Center X
self.targetY = 1200; // Center Y (matching idol position)
self.lastY = self.y;
self.lastX = self.x;
self.pulseWithBeat = function () {
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: beatInterval / 4,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1,
scaleY: 1
}, {
duration: beatInterval / 4,
easing: tween.easeIn
});
}
});
};
self.takeDamage = function () {
self.health--;
graphics.alpha = 0.5 + self.health / self.maxHealth * 0.5;
if (self.health <= 0) {
LK.effects.flashObject(self, 0xffffff, 300);
return true; // Enemy defeated
}
return false;
};
self.update = function () {
// Calculate rhythm-based speed - enemies move in perfect sync with beat
var currentTime = Date.now();
var timeSinceLastBeat = (currentTime - lastBeatTime) % beatInterval;
var beatProgress = timeSinceLastBeat / beatInterval;
// Smooth sine wave movement that peaks on the beat for dramatic effect
var beatSyncMultiplier = 0.7 + 0.6 * Math.sin(beatProgress * 2 * Math.PI);
self.rhythmMultiplier = beatSyncMultiplier;
var currentSpeed = self.baseSpeed * self.rhythmMultiplier;
// Move toward center based on direction
if (self.direction === 'up') {
self.y += currentSpeed; // Moving down from top
} else if (self.direction === 'down') {
self.y -= currentSpeed; // Moving up from bottom
} else if (self.direction === 'left') {
self.x += currentSpeed; // Moving right from left
} else if (self.direction === 'right') {
self.x -= currentSpeed; // Moving left from right
}
};
return self;
});
var InputZone = Container.expand(function (color, direction, keyText) {
var self = Container.call(this);
var assetName = color + 'Outline';
var background = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Make background transparent to show only the outline
background.alpha = 0.3;
// Key text removed - zone functions without text display
self.color = color;
self.direction = direction;
self.active = false;
self.activate = function () {
self.active = true;
background.alpha = 0.8;
tween(background, {
alpha: 0.3
}, {
duration: 300
});
};
self.getColorValue = function () {
if (self.color === 'blue') {
return 0x0066ff;
}
if (self.color === 'yellow') {
return 0xffff00;
}
if (self.color === 'red') {
return 0xff0000;
}
if (self.color === 'green') {
return 0x00ff00;
}
return 0x0066ff;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a0d33
});
/****
* Game Code
****/
// Game state variables
var gameState = 'instructions'; // instructions, menu, playing, gameOver
var lives = 3;
var score = 0;
var combo = 0;
var bpm = 120;
var beatInterval = 60 / bpm * 1000; // milliseconds per beat (500ms at 120 BPM)
var lastBeatTime = 0;
var beatTolerance = 125; // ms tolerance for timing - adjusted for 120 BPM precision (quarter beat window)
var menuSongPlaying = false; // Flag to track if menuSong is currently playing
var blackScreenActive = false; // Flag to track when black screen is active during ultimate animation
// Game objects
var idol;
var enemies = [];
var beatIndicator;
var inputZones = [];
var actionButtons = [];
var hearts = [];
var nextBeatTime = 0;
var beatCounter = 0; // Counter to track beats for enemy spawning
var nextEnemySpawnBeat = Math.floor(Math.random() * 3) + 1; // Random interval for next enemy spawn (1-3 beats)
var gameStartTime = 0; // Track when the game started
// Enemy configuration - blue, yellow, red and green
var enemyColors = ['blue', 'yellow', 'red', 'green'];
var enemyDirections = ['left', 'right', 'up', 'down'];
var colorDirectionMap = {
'blue': 'left',
// Blue enemies come from left
'yellow': 'right',
// Yellow enemies come from right
'red': 'up',
// Red enemies come from up
'green': 'down' // Green enemies come from down
};
var keyMappings = {
'left': 'A',
'right': 'D',
'up': 'W',
'down': 'S'
};
// Initialize background
var background = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Position background to fill the screen
background.x = 0;
background.y = 0;
// Initialize idol character (will be properly set up when game starts)
idol = game.addChild(new Container());
idol.x = 1024; // Center horizontally
idol.y = 1200; // Slightly above center vertically
// Add pulse animation method to idol
idol.pulseWithBeat = function () {
tween(idol, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: beatInterval / 4,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(idol, {
scaleX: 1,
scaleY: 1
}, {
duration: beatInterval / 4,
easing: tween.easeIn
});
}
});
};
// Add attack animation method to idol
idol.playAttackAnimation = function (direction) {
// Change to attack asset based on direction and selected idol
var attackAssetPrefix = selectedIdol === 'idolKasumin' ? 'IdolKasumin' : 'idol';
var attackAssetName = attackAssetPrefix + 'Attack' + direction.charAt(0).toUpperCase() + direction.slice(1);
// Remove current graphics and add new attack graphics
if (idol.children.length > 0) {
idol.removeChild(idol.children[0]);
}
var attackGraphics = idol.attachAsset(attackAssetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Don't automatically return to original texture - let movement animation handle it
};
// Initialize beat indicator
beatIndicator = game.addChild(new BeatIndicator());
beatIndicator.x = 1024;
beatIndicator.y = 150;
// Initialize input zones - blue on left, yellow on right, red on top, green on bottom
var zonePositions = [{
x: 874,
y: 1200
}, {
x: 1174,
y: 1200
}, {
x: 1024,
y: 1040
}, {
x: 1024,
y: 1360
}];
for (var i = 0; i < 4; i++) {
var zone = game.addChild(new InputZone(enemyColors[i], enemyDirections[i], keyMappings[enemyDirections[i]]));
zone.x = zonePositions[i].x;
zone.y = zonePositions[i].y;
inputZones.push(zone);
}
// Initialize action buttons at bottom of screen - blue, yellow, red and green buttons
var buttonPositions = [{
x: 774,
y: 2400
}, {
x: 1274,
y: 2400
}, {
x: 1024,
y: 2150
}, {
x: 1024,
y: 2650
}];
for (var i = 0; i < 4; i++) {
var button = game.addChild(new ActionButton(enemyColors[i], enemyDirections[i], keyMappings[enemyDirections[i]]));
button.x = buttonPositions[i].x;
button.y = buttonPositions[i].y;
actionButtons.push(button);
}
// Initialize UI
var scoreTitle = new Text2('Score', {
size: 36,
fill: 0xFFFFFF
});
scoreTitle.anchor.set(1, 0);
scoreTitle.x = -20; // Offset from right edge
scoreTitle.y = 10;
LK.gui.topRight.addChild(scoreTitle); // Hidden on menu
var scoreText = new Text2('0', {
size: 48,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0);
scoreText.x = -20; // Offset from right edge
scoreText.y = 50; // Below the title
LK.gui.topRight.addChild(scoreText); // Hidden on menu
var comboText = new Text2('Combo: 0', {
size: 36,
fill: 0xFFFF00
});
comboText.anchor.set(0.5, 0);
LK.gui.top.addChild(comboText); // Hidden on menu
var livesText = new Text2('Lives: ' + lives, {
size: 36,
fill: 0xFFFFFF
});
livesText.anchor.set(0, 0);
livesText.x = 20; // Offset from left edge
livesText.y = 10;
// Initialize hearts display
for (var h = 0; h < lives; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 1800 + h * 80; // Position hearts horizontally with spacing in top right
heart.y = 250; // Moved further down to avoid overlapping with score
game.addChild(heart);
hearts.push(heart);
}
// Instructions screen elements
var instructionsContainer = game.addChild(new Container());
// Add menuHowToPlay background - 100% width and 80% height, positioned from bottom
var instructionsBackground = instructionsContainer.attachAsset('menuHowToPlay', {
anchorX: 0.5,
anchorY: 1,
scaleX: 1.0,
scaleY: 0.8
});
instructionsBackground.x = 1024;
instructionsBackground.y = 1566;
// Add titleGame asset to instructions screen
var instructionsTitleGraphic = instructionsContainer.attachAsset('titleGame', {
anchorX: 0.5,
anchorY: 0.5,
width: 900,
height: 900
});
instructionsTitleGraphic.x = 1024;
instructionsTitleGraphic.y = 400;
// Instructions title
var instructionsTitle = new Text2('HOW TO PLAY', {
size: 80,
fill: 0xFFFFFF
});
instructionsTitle.anchor.set(0.5, 0.5);
instructionsTitle.x = 1024;
instructionsTitle.y = 700;
instructionsContainer.addChild(instructionsTitle);
// Instructions text
var instructionsText = new Text2('• Tap colored buttons to defeat enemies\n• Match button color with enemy color\n• Hit enemies when they reach the center\n• Stay on beat for bonus points!\n• Collect combo points to unlock special attack\n• Don\'t let enemies reach the center!', {
size: 48,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1200;
instructionsContainer.addChild(instructionsText);
// OK button - positioned outside and below the asset
var okButton = instructionsContainer.addChild(new InputZone('green', 'down', 'OK'));
okButton.removeChild(okButton.children[0]); // Remove the default outline asset
okButton.attachAsset('OKButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2
});
var okButtonText = new Text2('OK', {
size: 60,
fill: 0xFFFFFF
});
okButtonText.anchor.set(0.5, 0.5);
okButton.addChild(okButtonText);
okButton.x = 1024;
okButton.y = 2000;
okButton.down = function (x, y, obj) {
gameState = 'menu';
updateVisibility();
};
// Menu elements
var menuContainer = game.addChild(new Container());
// Selection state
var selectedIdol = 'idol'; // Default selection
var idolSelections = [];
// Menu container setup
menuContainer.addChild(titleGraphic);
var playButton = menuContainer.addChild(new InputZone('green', 'down', 'PLAY')); // Using InputZone for rounded button
// Replace the background asset with the startButton asset
playButton.removeChild(playButton.children[0]); // Remove the default outline asset
playButton.attachAsset('startButton', {
// Add the startButton asset
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 2
});
var playButtonText = new Text2('▶ PLAY', {
size: 60,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButton.addChild(playButtonText);
playButton.x = 1024;
playButton.y = 1950;
playButton.down = function (x, y, obj) {
// Hide play button immediately when pressed
playButton.visible = false;
// Check if "Idol" is selected - if so, play sound and delay game start
if (selectedIdol === 'idol') {
// Play idol selection sound
LK.getSound('IdolSelected').play();
// Start game 0.5 seconds after sound finishes (assuming sound is ~2 seconds, so total delay is 2.5 seconds)
LK.setTimeout(function () {
startGame();
}, 2500); // 2.5 seconds total delay (2 seconds for sound + 0.5 seconds)
} else if (selectedIdol === 'idolKasumin') {
// Play idolKasumin selection sound
LK.getSound('IdolKasuminSelected').play();
// Start game 0.5 seconds after sound finishes (assuming sound is ~2 seconds, so total delay is 2.5 seconds)
LK.setTimeout(function () {
startGame();
}, 2500); // 2.5 seconds total delay (2 seconds for sound + 0.5 seconds)
} else {
// For other idols, start game immediately
startGame();
}
function startGame() {
gameState = 'playing';
menuContainer.visible = false;
// Stop menu music and start background music when game begins
LK.stopMusic();
menuSongPlaying = false;
// Select music based on selected idol
var musicToPlay = selectedIdol === 'idolKasumin' ? 'kasuminbgmusic' : 'bgmusic';
LK.playMusic(musicToPlay);
// Initialize beat timing synchronized with music start
var musicStartTime = Date.now();
lastBeatTime = musicStartTime;
nextBeatTime = musicStartTime + beatInterval;
beatCounter = 0;
gameStartTime = musicStartTime; // Track game start time
// Reset enemy spawn interval
nextEnemySpawnBeat = Math.floor(Math.random() * 3) + 1;
// Reset game state
lives = 3;
score = 0;
combo = 0;
// Reset idolMic to transparent when starting new game
idolMic.alpha = 0.3;
// Clear any existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Update idol asset based on selection - clear all children first
while (idol.children.length > 0) {
idol.removeChild(idol.children[0]);
}
idol.attachAsset(selectedIdol, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Update visibility to show game elements
updateVisibility();
// Update UI to reflect reset state
updateUI();
}
};
// Initialize idol microphone in bottom right corner before updateVisibility
var idolMic = game.addChild(LK.getAsset('idolMic', {
anchorX: 1,
anchorY: 1,
x: 2048 - 50,
y: 2732 - 50,
scaleX: 2,
scaleY: 2,
alpha: 0.3
}));
idolMic.visible = false; // Initially hidden, shown when game starts
// Add touch handler to idolMic for destroying all enemies
idolMic.down = function (x, y, obj) {
// Only work when idolMic is in enabled state (full opacity)
if (idolMic.alpha >= 1) {
// Set black screen active flag
blackScreenActive = true;
// Create black screen overlay
var blackOverlay = game.addChild(LK.getAsset('swordBeat', {
anchorX: 0,
anchorY: 0,
width: 2048,
height: 2732,
x: 0,
y: 0
}));
blackOverlay.tint = 0x000000;
// Choose animation asset based on selected idol
var animationAssetName = selectedIdol === 'idolKasumin' ? 'kasuminAnimationUltimate' : 'idolAnimationUltimate';
// Show ultimate animation for one second
var ultimateAnimation = game.addChild(LK.getAsset(animationAssetName, {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
// 100% of screen width
height: 1093,
// 40% of screen height (2732 * 0.40)
x: 1024,
// Center horizontally
y: 1366 // Center vertically (2732 / 2)
}));
// Play sound based on selected idol
var ultimateSound = selectedIdol === 'idolKasumin' ? 'kasuminUltimate' : 'ultimate';
LK.getSound(ultimateSound).play();
// Make the animation and black overlay disappear after 1 second
LK.setTimeout(function () {
ultimateAnimation.destroy();
blackOverlay.destroy();
// Clear black screen active flag
blackScreenActive = false;
// Show white screen flash after the animation hides
LK.effects.flashScreen(0xffffff, 300);
}, 1000);
// Destroy all enemies when white flash activates (after 1 second delay)
LK.setTimeout(function () {
for (var j = enemies.length - 1; j >= 0; j--) {
enemies[j].destroy();
enemies.splice(j, 1);
}
}, 1000);
// Reset idolMic to transparent state
tween(idolMic, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeOut
});
// Visual feedback for special attack
LK.effects.flashScreen(0xffffff, 300);
}
};
// Initially hide game elements and show menu
function updateVisibility() {
var showGame = gameState === 'playing';
var showMenu = gameState === 'menu';
var showInstructions = gameState === 'instructions';
instructionsContainer.visible = showInstructions;
menuContainer.visible = showMenu;
idol.visible = showGame;
beatIndicator.visible = showGame;
for (var i = 0; i < inputZones.length; i++) {
inputZones[i].visible = showGame;
}
for (var i = 0; i < actionButtons.length; i++) {
actionButtons[i].visible = showGame;
}
for (var i = 0; i < hearts.length; i++) {
hearts[i].visible = showGame;
}
livesText.visible = showGame;
scoreTitle.visible = showGame;
scoreText.visible = showGame;
comboText.visible = showGame;
idolMic.visible = showGame;
// Handle menu music - start from instructions or menu
if ((showInstructions || showMenu) && !menuSongPlaying) {
LK.stopMusic(); // Stop any currently playing music first
LK.playMusic('menuSong');
menuSongPlaying = true;
} else if (showGame) {
// Stop menu music when transitioning to game
LK.stopMusic();
menuSongPlaying = false;
}
}
updateVisibility();
// Start menu music immediately when application loads
LK.playMusic('menuSong');
menuSongPlaying = true;
// Input handling
var pressedKeys = {};
var inputBuffer = [];
function processInput(direction) {
// Check if input is on beat for bonus scoring
var currentTime = Date.now();
var timeToBeat = Math.abs((currentTime - lastBeatTime) % beatInterval - beatInterval / 2);
var isOnBeat = timeToBeat < beatTolerance;
var beatMultiplier = isOnBeat ? 2 : 1; // Double points for on-beat hits
// Find enemies of matching color that are in their corresponding color zones
var hitEnemy = null;
var minDistance = Infinity;
var hitZoneSize = 100; // Size of the color zone around center
var blueZoneX = 874; // Left zone X position
var yellowZoneX = 1174; // Right zone X position
var zoneY = 1200; // Zone Y position
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Check if enemy color matches the button direction and enemy is coming from correct direction
var colorMatch = false;
if (direction === 'left' && enemy.color === 'blue' && enemy.direction === 'left') {
colorMatch = true;
}
if (direction === 'right' && enemy.color === 'yellow' && enemy.direction === 'right') {
colorMatch = true;
}
if (direction === 'up' && enemy.color === 'red' && enemy.direction === 'up') {
colorMatch = true;
}
if (direction === 'down' && enemy.color === 'green' && enemy.direction === 'down') {
colorMatch = true;
}
if (colorMatch) {
// Check if enemy is within their color zone
var inColorZone = false;
var hitZoneSize = 100; // Size of the color zone
if (enemy.color === 'blue' && enemy.direction === 'left') {
// Blue zone is at x=874, y=1200
if (Math.abs(enemy.x - 874) <= hitZoneSize && Math.abs(enemy.y - 1200) <= hitZoneSize) {
inColorZone = true;
}
} else if (enemy.color === 'yellow' && enemy.direction === 'right') {
// Yellow zone is at x=1174, y=1200
if (Math.abs(enemy.x - 1174) <= hitZoneSize && Math.abs(enemy.y - 1200) <= hitZoneSize) {
inColorZone = true;
}
} else if (enemy.color === 'red' && enemy.direction === 'up') {
// Red zone is at x=1024, y=1040
if (Math.abs(enemy.x - 1024) <= hitZoneSize && Math.abs(enemy.y - 1040) <= hitZoneSize) {
inColorZone = true;
}
} else if (enemy.color === 'green' && enemy.direction === 'down') {
// Green zone is at x=1024, y=1360
if (Math.abs(enemy.x - 1024) <= hitZoneSize && Math.abs(enemy.y - 1360) <= hitZoneSize) {
inColorZone = true;
}
}
// Only allow hitting if enemy is in their color zone
if (inColorZone) {
var distance = Math.sqrt(Math.pow(enemy.x - 1024, 2) + Math.pow(enemy.y - 1200, 2));
if (distance < minDistance) {
minDistance = distance;
hitEnemy = enemy;
}
}
}
}
if (hitEnemy) {
// Play attack animation with correct asset
idol.playAttackAnimation(hitEnemy.direction);
// Animate idol movement towards enemy direction
var originalX = idol.x;
var originalY = idol.y;
var moveDistance = 50;
var targetX = originalX;
var targetY = originalY;
// Calculate movement direction based on enemy direction
if (hitEnemy.direction === 'left') {
targetX = originalX - moveDistance;
} else if (hitEnemy.direction === 'right') {
targetX = originalX + moveDistance;
} else if (hitEnemy.direction === 'up') {
targetY = originalY - moveDistance;
} else if (hitEnemy.direction === 'down') {
targetY = originalY + moveDistance;
}
// Move idol towards enemy direction
tween(idol, {
x: targetX,
y: targetY
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Always return idol to center position (1024, 1200)
tween(idol, {
x: 1024,
y: 1200
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restore original idol graphics when returning to center
if (idol.children.length > 0) {
idol.removeChild(idol.children[0]);
}
idol.attachAsset(selectedIdol, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
}
});
}
});
// Create split animation when enemy is defeated
var index = enemies.indexOf(hitEnemy);
if (index > -1) {
// Create two halves of the enemy
var leftHalf = game.addChild(LK.getAsset(hitEnemy.color + 'Enemy', {
anchorX: 1,
anchorY: 0.5
}));
var rightHalf = game.addChild(LK.getAsset(hitEnemy.color + 'Enemy', {
anchorX: 0,
anchorY: 0.5
}));
// Position halves at enemy location
leftHalf.x = hitEnemy.x;
leftHalf.y = hitEnemy.y;
rightHalf.x = hitEnemy.x;
rightHalf.y = hitEnemy.y;
// Set initial scale to show only half width
leftHalf.scaleX = 0.5;
rightHalf.scaleX = 0.5;
// Calculate time until next beat for smooth disappearance
var currentTime = Date.now();
var timeToNextBeat = beatInterval - (currentTime - lastBeatTime) % beatInterval;
// Animate halves splitting apart
tween(leftHalf, {
x: hitEnemy.x - 40,
rotation: -0.3,
alpha: 0.7
}, {
duration: timeToNextBeat,
easing: tween.easeOut
});
tween(rightHalf, {
x: hitEnemy.x + 40,
rotation: 0.3,
alpha: 0.7
}, {
duration: timeToNextBeat,
easing: tween.easeOut,
onFinish: function onFinish() {
// Smooth fade out on beat
tween(leftHalf, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
leftHalf.destroy();
}
});
tween(rightHalf, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
rightHalf.destroy();
}
});
}
});
// Remove original enemy immediately
hitEnemy.destroy();
enemies.splice(index, 1);
// Calculate score: fixed 10 points per enemy
var enemyScore = 10;
score += enemyScore * beatMultiplier;
combo++;
// Enable idolMic every 15 combo points, but only if not already enabled
if (combo % 15 === 0 && combo > 0 && idolMic.alpha < 1) {
tween(idolMic, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Check if combo reached a multiple of 25 for extra life
if (combo % 25 === 0 && combo > 0 && lives < 3) {
lives++;
// Visual feedback for gaining a life
LK.effects.flashObject(idol, 0x00ff00, 500); // Green flash for extra life
LK.effects.flashScreen(0x00ff00, 300); // Green screen flash
// Update hearts display immediately
if (lives <= hearts.length) {
hearts[lives - 1].alpha = 1; // Show the new heart
}
}
// Visual feedback for on-beat hits
if (isOnBeat) {
LK.effects.flashObject(idol, 0xffff00, 200); // Golden flash for perfect timing
}
}
LK.getSound('hit').play();
} else {
// No matching enemy found in color zone
combo = 0;
// Only reset idolMic to transparent if it hasn't reached enabled state yet
if (idolMic.alpha < 1) {
tween(idolMic, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeOut
});
}
LK.getSound('error').play();
}
// Find matching input zone and activate
for (var j = 0; j < inputZones.length; j++) {
var zone = inputZones[j];
var shouldActivate = false;
// Map button directions to color zones correctly
if (direction === 'left' && zone.color === 'blue') {
shouldActivate = true;
} // Blue button activates blue zone
else if (direction === 'right' && zone.color === 'yellow') {
shouldActivate = true;
} // Yellow button activates yellow zone
else if (direction === 'up' && zone.color === 'red') {
shouldActivate = true;
} // Red button activates red zone
else if (direction === 'down' && zone.color === 'green') {
shouldActivate = true;
} // Green button activates green zone
if (shouldActivate) {
zone.activate();
break;
}
}
// Also activate corresponding action button
for (var k = 0; k < actionButtons.length; k++) {
if (actionButtons[k].direction === direction) {
actionButtons[k].activate();
break;
}
}
updateUI();
}
function updateUI() {
scoreText.setText(score.toString());
livesText.setText('Lives: ' + lives);
comboText.setText('Combo: ' + combo);
// Update hearts display
for (var h = 0; h < hearts.length; h++) {
if (h < lives) {
hearts[h].alpha = 1; // Show heart
} else {
hearts[h].alpha = 0.3; // Dim lost hearts
}
}
}
function spawnEnemy() {
var colorIndex = Math.floor(Math.random() * enemyColors.length);
var color = enemyColors[colorIndex];
var direction = colorDirectionMap[color];
var enemy = game.addChild(new Enemy(color, direction));
// Position enemy at center of each edge based on direction
if (direction === 'up') {
enemy.x = 1024; // Center X of top edge
enemy.y = -100; // Above screen
} else if (direction === 'down') {
enemy.x = 1024; // Center X of bottom edge
enemy.y = 2832; // Below screen
} else if (direction === 'left') {
enemy.x = -100; // Left of screen
enemy.y = 1200; // Center Y of left edge
} else if (direction === 'right') {
enemy.x = 2148; // Right of screen
enemy.y = 1200; // Center Y of right edge
}
enemy.lastY = enemy.y;
enemy.lastX = enemy.x;
enemies.push(enemy);
}
// Touch controls removed - buttons now only activate when directly touched
game.down = function (x, y, obj) {
// Game down handler - buttons handle their own touch events
};
// Main game loop
game.update = function () {
if (gameState === 'instructions' || gameState === 'menu') {
updateVisibility();
return;
}
if (gameState !== 'playing') {
return;
}
var currentTime = Date.now();
// Beat tracking - maintain precise synchronization with music
if (currentTime >= nextBeatTime) {
// Calculate the exact beat time based on music start
var timeSinceMusicStart = currentTime - lastBeatTime;
var beatsElapsed = Math.floor(timeSinceMusicStart / beatInterval);
// Update to the precise beat time
lastBeatTime = lastBeatTime + (beatsElapsed + 1) * beatInterval;
nextBeatTime = lastBeatTime + beatInterval;
beatCounter += beatsElapsed + 1; // Account for any missed beats
// Visual beat feedback
beatIndicator.pulse();
// Make all enemies pulse with the beat
for (var j = 0; j < enemies.length; j++) {
enemies[j].pulseWithBeat();
}
// Make idol pulse with the beat
idol.pulseWithBeat();
// Make idolMic pulse with the beat when in enabled state (full opacity)
if (idolMic.alpha >= 1) {
tween(idolMic, {
scaleX: 2.2,
scaleY: 2.2
}, {
duration: beatInterval / 4,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(idolMic, {
scaleX: 2,
scaleY: 2
}, {
duration: beatInterval / 4,
easing: tween.easeIn
});
}
});
}
// Enemy spawning - spawn one enemy every 1-3 beats randomly for first 30 seconds, then every beat
var timeSinceGameStart = currentTime - gameStartTime;
var thirtySecondsHavePassed = timeSinceGameStart >= 30000; // 30 seconds in milliseconds
if (thirtySecondsHavePassed) {
// After 30 seconds: spawn enemy every beat (starting from beat 2)
if (beatCounter >= 2) {
spawnEnemy();
}
} else {
// First 30 seconds: spawn randomly every 1-3 beats
if (beatCounter >= 2 && beatCounter % nextEnemySpawnBeat === 0) {
spawnEnemy();
// Set next random spawn interval (1-3 beats)
nextEnemySpawnBeat = Math.floor(Math.random() * 3) + 1; // Random number between 1 and 3
}
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
// Check if enemy reached center (idol position)
var reachedCenter = false;
if (enemy.direction === 'up' && enemy.lastY < 1200 && enemy.y >= 1200) {
reachedCenter = true;
} else if (enemy.direction === 'down' && enemy.lastY > 1200 && enemy.y <= 1200) {
reachedCenter = true;
} else if (enemy.direction === 'left' && enemy.lastX < 1024 && enemy.x >= 1024) {
reachedCenter = true;
} else if (enemy.direction === 'right' && enemy.lastX > 1024 && enemy.x <= 1024) {
reachedCenter = true;
}
if (reachedCenter) {
// Only take damage if black screen is not active
if (!blackScreenActive) {
lives--;
combo = 0;
// Only reset idolMic to transparent if it hasn't reached enabled state yet
if (idolMic.alpha < 1) {
tween(idolMic, {
alpha: 0.3
}, {
duration: 300,
easing: tween.easeOut
});
}
LK.getSound('miss').play();
if (lives <= 0) {
gameState = 'gameOver';
// Save final score to storage for upit
LK.setScore(score);
LK.showGameOver();
return;
}
updateUI();
LK.effects.flashScreen(0xff0000, 500);
}
// Always destroy enemy regardless of black screen state
enemy.destroy();
enemies.splice(i, 1);
}
enemy.lastY = enemy.y;
enemy.lastX = enemy.x;
}
};
// Touch controls are already implemented above in game.down event handler
// No keyboard input needed for mobile-optimized game
;
var titleGraphic = LK.getAsset('titleGame', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
width: 900,
height: 900
});
menuContainer.addChild(titleGraphic);
// Create idol selection container
var idolSelectionContainer = menuContainer.addChild(new Container());
// Add title for idol selection
var selectIdolTitle = new Text2('Select your Idol', {
size: 72,
fill: 0xFFFFFF
});
selectIdolTitle.anchor.set(0.5, 0.5);
selectIdolTitle.x = 1024;
selectIdolTitle.y = 1350; // Position above the idols
idolSelectionContainer.addChild(selectIdolTitle);
// First idol (left side)
var idolGraphic = LK.getAsset('idol', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 250
});
idolGraphic.x = 1024 - 150; // Left of center
idolGraphic.y = 1620; // Moved down further
idolSelectionContainer.addChild(idolGraphic);
// Second idol (right side)
var idolKasuminGraphic = LK.getAsset('idolKasumin', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 250
});
idolKasuminGraphic.x = 1024 + 150; // Right of center
idolKasuminGraphic.y = 1620; // Moved down further
idolSelectionContainer.addChild(idolKasuminGraphic);
// Selection boxes using menuSelectionCharacter
var idolSelectionBox = LK.getAsset('menuSelectionCharacter', {
anchorX: 0.5,
anchorY: 0.5,
width: 250,
height: 300
});
idolSelectionBox.x = idolGraphic.x;
idolSelectionBox.y = idolGraphic.y;
idolSelectionBox.alpha = 1;
idolSelectionContainer.addChild(idolSelectionBox);
var kasuminSelectionBox = LK.getAsset('menuSelectionCharacter', {
anchorX: 0.5,
anchorY: 0.5,
width: 250,
height: 300
});
kasuminSelectionBox.x = idolKasuminGraphic.x;
kasuminSelectionBox.y = idolKasuminGraphic.y;
kasuminSelectionBox.alpha = 0;
idolSelectionContainer.addChild(kasuminSelectionBox);
// Store references
idolSelections.push({
graphic: idolGraphic,
selectionBox: idolSelectionBox,
type: 'idol'
});
idolSelections.push({
graphic: idolKasuminGraphic,
selectionBox: kasuminSelectionBox,
type: 'idolKasumin'
});
// Add touch handlers for idol selection
idolGraphic.down = function (x, y, obj) {
selectedIdol = 'idol';
// Stop any ongoing tween animations on both selection boxes
tween.stop(idolSelectionBox);
tween.stop(kasuminSelectionBox);
// Show selection on chosen idol, hide on other
idolSelectionBox.alpha = 1;
kasuminSelectionBox.alpha = 0;
};
idolKasuminGraphic.down = function (x, y, obj) {
selectedIdol = 'idolKasumin';
// Stop any ongoing tween animations on both selection boxes
tween.stop(idolSelectionBox);
tween.stop(kasuminSelectionBox);
// Show selection on chosen idol, hide on other
idolSelectionBox.alpha = 0;
kasuminSelectionBox.alpha = 1;
};
// Pulsing animation for selected idol - every 1000ms (1 second)
LK.setInterval(function () {
if (gameState === 'menu') {
var activeBox = selectedIdol === 'idol' ? idolSelectionBox : kasuminSelectionBox;
if (activeBox.alpha > 0) {
// Smooth pulse animation using tween
tween(activeBox, {
alpha: 0.3
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(activeBox, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
}
}
}, 1000);
; ===================================================================
--- original.js
+++ change.js
@@ -398,8 +398,17 @@
scaleY: 0.8
});
instructionsBackground.x = 1024;
instructionsBackground.y = 1566;
+// Add titleGame asset to instructions screen
+var instructionsTitleGraphic = instructionsContainer.attachAsset('titleGame', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 900,
+ height: 900
+});
+instructionsTitleGraphic.x = 1024;
+instructionsTitleGraphic.y = 400;
// Instructions title
var instructionsTitle = new Text2('HOW TO PLAY', {
size: 80,
fill: 0xFFFFFF
Flecha verde apuntando hacia abajo. In-Game asset. 2d. High contrast. No shadows
Flecha azul apuntando a la izquierda. In-Game asset. 2d. High contrast. No shadows
Flecha roja apuntando hacia arriba, con borde negro. In-Game asset. 2d. High contrast. No shadows
Flecha amarilla apuntando a la derecha. In-Game asset. 2d. High contrast. No shadows
Una mazmorra rocosa, sin contorno y de fondo se vean piedras algo difuminadas y una entrada a una mazmorra. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
Un panel cuadrado con contornos redondos, de color violeta. In-Game asset. 2d. High contrast. No shadows
Letras de color blanco, que digan "IDOL DUNGEON BEAT", que tengan un WordArt animado. En alta resolución para poner en una imagen 100 X 150 In-Game asset. 2d. High contrast. No shadows
espada dorada brillante, que da un toque de guerrera mágica o heroína valiente.. In-Game asset. 2d. High contrast. No shadows
Un recuadro blanco sin relleno interlineado. In-Game asset. 2d. High contrast. No shadows
Micrófono chibi, con un lazo rosado y una estrella como moño en el mango del micrófono. In-Game asset. 2d. High contrast. No shadows
Haz una animación en la que solo se vea su cara recortada, un aura de energía rosada rodeándola, y que aparezca como una viñeta que demuestre que está a punto de hacer su acción definitiva
Haz una animación en la que solo se vea su cara recortada, un aura de energía rosada rodeándola, y que aparezca como una viñeta que demuestre que está a punto de hacer su acción definitiva. Que escriba la palabra TWINKLE! abajo a la derecha. La imagen debe tener un fondo color blanco
Pintarlo de color verde
Recuadro de madera, parecido a un tablón de anuncios, animado. In-Game asset. 2d. High contrast. No shadows
Agrégale detalles en los ojos
Haz una animación simulando que ataca hacia la derecha con su escudo, y su mano y cuerpo debe seguir el movimiento del lanzamiento
Haz una animación simulando que ataca hacia abajo con su escudo, y su mano y cuerpo debe seguir el movimiento del golpe
Haz una animación en la que solo se vea su cara recortada, un aura de energía roja rodeándola, y que aparezca como una viñeta que demuestre que está a punto de hacer su acción definitiva. Que escriba la palabra SMILE! abajo a la derecha. La imagen debe tener un fondo color gris
hit
Sound effect
miss
Sound effect
error
Sound effect
bgmusic
Music
menuSong
Music
kasuminbgmusic
Music
ultimate
Sound effect
kasuminUltimate
Sound effect
IdolSelected
Sound effect
IdolKasuminSelected
Sound effect
maplebgmusic
Music
IdolMapleSelected
Sound effect
IdolMapleUltimate
Sound effect
hitKasumin
Sound effect
hitMaple
Sound effect