User prompt
Cuando el personaje jugable es "IdolMaple", cuando se golpee a un enemigo, debe sonar el asset "hitMaple"
User prompt
Cuando el personaje jugable es "IdolMaple", cuando se golpee a un enemigo, debe sonar el asset "hitMaple"
User prompt
Cuando el personaje jugable es "IdolKasumin", cuando se golpee a un enemigo, debe sonar el asset "hitKasumin"
User prompt
Cambia las letras de las instrucciones a color negro
User prompt
Pinta las letras del menú en color negro ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Baja la posición del botón OK
User prompt
Súbelo un poco más de su posición actual, y extiendelo un poco más horizontalmente
User prompt
Baja la posición del asset "menuHowToPlay", y reducelo horizontalmente
User prompt
Haz que los enemigos se retrasen 1 solo beat en empezar a aparecer
User prompt
Cuando el personaje jugable es "IdolMaple", cuando se utilice el "idolMic" debe aparecer el asset "idolMapleAnimationUltimate"
User prompt
Haz que el asset "IdolMapleSelected" solo suene si está seleccionada "IdolMaple" y se presiona el botón play
User prompt
Haz que cuando se seleccione "IdolMaple" como personaje, suene el asset "IdolMapleSelected", y cuando se utilice el asset "idolMic" y el personaje sea "IdolMaple", suene el asset "IdolMapleUltimate"
User prompt
Haz que el tamaño de "IdolMaple" dentro del juego sea igual al de "Idol", junto con sus animaciones. Cuando ataque hacia arriba, haz que el asset "IdolMaple" use la animación "idolMapleAttackUp"
User prompt
Al presionar el botón play, si está seleccionada "idol", que carguen sus assets dentro del juego. Si está seleccionada "idolKasumin", deben cargar los assets de "idolKasumin". Si está seleccionada "IdolMaple", deben cargar los assets de "idolMaple".
User prompt
Separa a las idols en el menú de selección
User prompt
En el menú de selección de personajes, agrega al asset "IdolMaple"
User prompt
Cuando la aplicación inicia, debe sonar el asset "menuSong" solo una vez, cuando se ingresa al menú de selección de personaje no debe sonar nuevamente, y debe parar cuando se presione el botón play
User prompt
Cuando la aplicación inicia, debe sonar el asset "menuSong", y debe parar cuando se presione el botón play
User prompt
Haz que el asset "menuSong" solo suene una vez cuando inicia la aplicación
User prompt
Haz que el asset "menuSong" solo suene una vez cuando inicia la aplicación
User prompt
Haz que el asset "menuSong" solo suene una vez cuando inicia la aplicación
User prompt
Haz que el asset "menuSong" solo suene una vez cuando inicia la aplicación
User prompt
Haz que cuando entre al menú de selección de personaje, no suene el asset "menuSong"
User prompt
Haz que el asset "menuSong" suene cuando inicia la aplicación, y pare cuando se presione el botón play
User prompt
Haz que el asset "menuSong" no suene en ningún momento
/****
* 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;
if (selectedIdol === 'idol') {
attackAssetPrefix = 'idol';
} else if (selectedIdol === 'idolKasumin') {
attackAssetPrefix = 'IdolKasumin';
} else if (selectedIdol === 'IdolMaple') {
attackAssetPrefix = 'idolMaple';
} else {
attackAssetPrefix = 'idol'; // Default fallback
}
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]);
}
// Apply scaling based on selected idol for attack animations
var attackScaleX = 0.6;
var attackScaleY = 0.6;
if (selectedIdol === 'IdolMaple') {
// Scale IdolMaple attack animations to match idol size
attackScaleX = 1.44; // (240/100) * 0.6 = 1.44
attackScaleY = 1.8; // (300/100) * 0.6 = 1.8
}
var attackGraphics = idol.attachAsset(attackAssetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: attackScaleX,
scaleY: attackScaleY
});
// 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 - adjusted position and scale
var instructionsBackground = instructionsContainer.attachAsset('menuHowToPlay', {
anchorX: 0.5,
anchorY: 1,
scaleX: 1.35,
scaleY: 1.7
});
instructionsBackground.x = 1024;
instructionsBackground.y = 2000;
// Add titleGame asset to instructions screen
var instructionsTitleGraphic = instructionsContainer.attachAsset('titleGame', {
anchorX: 0.5,
anchorY: 0.5,
width: 450,
height: 450
});
instructionsTitleGraphic.x = 1024;
instructionsTitleGraphic.y = 320;
// Instructions title
var instructionsTitle = new Text2('HOW TO PLAY', {
size: 90,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
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\n• Match button color with enemy color\n\n• Hit enemies when they reach the center\n\n• Stay on beat for bonus points!\n\n• Collect combo points to unlock special attack\n\n• Don\'t let enemies reach the center!', {
size: 58,
fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
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 = 2100;
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;
// Stop menu music immediately when play button is pressed
LK.stopMusic();
menuSongPlaying = 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 if (selectedIdol === 'IdolMaple') {
// Play IdolMaple selection sound
LK.getSound('IdolMapleSelected').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;
// Select music based on selected idol and start background music
var musicToPlay;
if (selectedIdol === 'idolKasumin') {
musicToPlay = 'kasuminbgmusic';
} else if (selectedIdol === 'IdolMaple') {
musicToPlay = 'maplebgmusic';
} else {
musicToPlay = 'bgmusic'; // Default for 'idol' and any other selection
}
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]);
}
// Set the correct asset name based on selection
var idolAssetName;
if (selectedIdol === 'idol') {
idolAssetName = 'idol';
} else if (selectedIdol === 'idolKasumin') {
idolAssetName = 'idolKasumin';
} else if (selectedIdol === 'IdolMaple') {
idolAssetName = 'IdolMaple';
} else {
idolAssetName = 'idol'; // Default fallback
}
// Apply scaling based on selected idol to match sizes
var scaleX = 0.6;
var scaleY = 0.6;
if (selectedIdol === 'IdolMaple') {
// Scale IdolMaple to match idol size (idol is 240x300, IdolMaple is 100x100)
scaleX = 1.44; // (240/100) * 0.6 = 1.44
scaleY = 1.8; // (300/100) * 0.6 = 1.8
}
idol.attachAsset(idolAssetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleX,
scaleY: scaleY
});
// 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;
if (selectedIdol === 'idolKasumin') {
animationAssetName = 'kasuminAnimationUltimate';
} else if (selectedIdol === 'IdolMaple') {
animationAssetName = 'idolMapleAnimationUltimate';
} else {
animationAssetName = '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;
if (selectedIdol === 'idolKasumin') {
ultimateSound = 'kasuminUltimate';
} else if (selectedIdol === 'IdolMaple') {
ultimateSound = 'IdolMapleUltimate';
} else {
ultimateSound = '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 - only play once when application starts
if (showInstructions && !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();
// 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]);
}
// Set the correct asset name based on selection
var idolAssetName;
if (selectedIdol === 'idol') {
idolAssetName = 'idol';
} else if (selectedIdol === 'idolKasumin') {
idolAssetName = 'idolKasumin';
} else if (selectedIdol === 'IdolMaple') {
idolAssetName = 'IdolMaple';
} else {
idolAssetName = 'idol'; // Default fallback
}
// Apply scaling based on selected idol when restoring after movement
var restoreScaleX = 0.6;
var restoreScaleY = 0.6;
if (selectedIdol === 'IdolMaple') {
// Scale IdolMaple to match idol size
restoreScaleX = 1.44; // (240/100) * 0.6 = 1.44
restoreScaleY = 1.8; // (300/100) * 0.6 = 1.8
}
idol.attachAsset(idolAssetName, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: restoreScaleX,
scaleY: restoreScaleY
});
}
});
}
});
// 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
}
}
// Play hit sound based on selected character
if (selectedIdol === 'idolKasumin') {
LK.getSound('hitKasumin').play();
} else {
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 - 300; // Further left of center
idolGraphic.y = 1620; // Moved down further
idolSelectionContainer.addChild(idolGraphic);
// Second idol (center)
var idolKasuminGraphic = LK.getAsset('idolKasumin', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 250
});
idolKasuminGraphic.x = 1024; // Center
idolKasuminGraphic.y = 1620; // Moved down further
idolSelectionContainer.addChild(idolKasuminGraphic);
// Third idol (right side)
var idolMapleGraphic = LK.getAsset('IdolMaple', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 250
});
idolMapleGraphic.x = 1024 + 300; // Further right of center
idolMapleGraphic.y = 1620; // Moved down further
idolSelectionContainer.addChild(idolMapleGraphic);
// 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);
var mapleSelectionBox = LK.getAsset('menuSelectionCharacter', {
anchorX: 0.5,
anchorY: 0.5,
width: 250,
height: 300
});
mapleSelectionBox.x = idolMapleGraphic.x;
mapleSelectionBox.y = idolMapleGraphic.y;
mapleSelectionBox.alpha = 0;
idolSelectionContainer.addChild(mapleSelectionBox);
// Store references
idolSelections.push({
graphic: idolGraphic,
selectionBox: idolSelectionBox,
type: 'idol'
});
idolSelections.push({
graphic: idolKasuminGraphic,
selectionBox: kasuminSelectionBox,
type: 'idolKasumin'
});
idolSelections.push({
graphic: idolMapleGraphic,
selectionBox: mapleSelectionBox,
type: 'IdolMaple'
});
// Add touch handlers for idol selection
idolGraphic.down = function (x, y, obj) {
selectedIdol = 'idol';
// Stop any ongoing tween animations on all selection boxes
tween.stop(idolSelectionBox);
tween.stop(kasuminSelectionBox);
tween.stop(mapleSelectionBox);
// Show selection on chosen idol, hide on others
idolSelectionBox.alpha = 1;
kasuminSelectionBox.alpha = 0;
mapleSelectionBox.alpha = 0;
};
idolKasuminGraphic.down = function (x, y, obj) {
selectedIdol = 'idolKasumin';
// Stop any ongoing tween animations on all selection boxes
tween.stop(idolSelectionBox);
tween.stop(kasuminSelectionBox);
tween.stop(mapleSelectionBox);
// Show selection on chosen idol, hide on others
idolSelectionBox.alpha = 0;
kasuminSelectionBox.alpha = 1;
mapleSelectionBox.alpha = 0;
};
idolMapleGraphic.down = function (x, y, obj) {
selectedIdol = 'IdolMaple';
// Stop any ongoing tween animations on all selection boxes
tween.stop(idolSelectionBox);
tween.stop(kasuminSelectionBox);
tween.stop(mapleSelectionBox);
// Show selection on chosen idol, hide on others
idolSelectionBox.alpha = 0;
kasuminSelectionBox.alpha = 0;
mapleSelectionBox.alpha = 1;
};
// Pulsing animation for selected idol - every 1000ms (1 second)
LK.setInterval(function () {
if (gameState === 'menu') {
var activeBox;
if (selectedIdol === 'idol') {
activeBox = idolSelectionBox;
} else if (selectedIdol === 'idolKasumin') {
activeBox = kasuminSelectionBox;
} else if (selectedIdol === 'IdolMaple') {
activeBox = mapleSelectionBox;
}
if (activeBox && 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
@@ -437,9 +437,9 @@
instructionsContainer.addChild(instructionsTitle);
// Instructions text
var instructionsText = new Text2('• Tap colored buttons to defeat enemies\n\n• Match button color with enemy color\n\n• Hit enemies when they reach the center\n\n• Stay on beat for bonus points!\n\n• Collect combo points to unlock special attack\n\n• Don\'t let enemies reach the center!', {
size: 58,
- fill: 0x000000,
+ fill: 0xFFFFFF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
@@ -935,9 +935,14 @@
if (isOnBeat) {
LK.effects.flashObject(idol, 0xffff00, 200); // Golden flash for perfect timing
}
}
- LK.getSound('hit').play();
+ // Play hit sound based on selected character
+ if (selectedIdol === 'idolKasumin') {
+ LK.getSound('hitKasumin').play();
+ } else {
+ 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
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