/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
self.init = function (elementId, elementName, rarity, assetId, multiplier) {
// Obtenemos el color directamente de la configuración global
var rColor = GAME_CONFIG.RARITIES[rarity] ? GAME_CONFIG.RARITIES[rarity].colorNum : 0xffffff;
// 1. El Marco (Aura de fondo)
var frame = self.attachAsset('bg_character', {
anchorX: 0.5,
anchorY: 0.55
});
frame.scale.set(0.75, 1);
frame.tint = rColor;
frame.alpha = 0.6; // Resplandor sutil
// 2. El Gráfico del Personaje
var graphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
graphic.scale.set(0.5, 0.5);
// 3. El Nombre
var nameText = new Text2(elementName, {
size: 50,
fill: '#ffffff'
});
nameText.anchor.set(0.5, 0);
nameText.y = 130;
self.addChild(nameText);
// 4. La Rareza
var rarityText = new Text2('[' + rarity + ']', {
size: 45,
fill: rColor
});
rarityText.anchor.set(0.5, 0);
rarityText.y = 170;
self.addChild(rarityText);
// Guardamos referencias para modificarlas fácilmente en la galería
self.frameObj = frame;
self.graphicObj = graphic;
self.nameObj = nameText;
self.rarityObj = rarityText;
};
return self;
});
var FloatingText = Container.expand(function () {
var self = Container.call(this);
self.init = function (textValue, startX, startY, color) {
self.x = startX;
self.y = startY;
var txt = new Text2(textValue, {
size: 48,
fill: color || '#ffffff'
});
txt.anchor.set(0.5, 0.5);
self.addChild(txt);
tween(self, {
y: self.y - 150,
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Clases de UI: Income Level (AHORA MÁS GRANDE)
var IncomeLevel = Container.expand(function () {
var self = Container.call(this);
self.levelIndex = 0;
self.baseIncome = 0;
self.baseCost = 0;
self.currentCost = 0;
self.init = function (levelIndex, baseIncome) {
self.levelIndex = levelIndex;
self.baseIncome = baseIncome;
self.baseCost = 50 * (levelIndex + 1);
var bg = self.attachAsset('upgrade_button', {
anchorX: 0.5,
anchorY: 0.5
});
// Escala aumentada de 1.5 a 2.0
bg.scale.set(2.0, 2.0);
// Textos aumentados y reposicionados
var levelText = new Text2('', {
size: 38,
fill: '#ffffff'
});
levelText.anchor.set(0.5, 0.5);
levelText.y = -25;
self.addChild(levelText);
self.levelText = levelText;
var costText = new Text2('', {
size: 30,
fill: '#ffffff'
});
costText.anchor.set(0.5, 0.5);
costText.y = 25;
self.addChild(costText);
self.costText = costText;
};
self.updateData = function (purchases) {
self.currentCost = Math.floor(self.baseCost * Math.pow(1.5, purchases));
self.costText.setText('Cost: ' + self.currentCost);
self.levelText.setText('Lvl ' + (self.levelIndex + 1) + ' (x' + purchases + '): +' + self.baseIncome + '/t');
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x6f6fdb
});
/****
* Game Code
****/
var GAME_CONFIG = {
BASE_GACHA_COST: 1000,
GACHA_COST_MULTIPLIER: 1.10,
FPS: 60,
SAVE_INTERVAL: 300,
MAX_EQUIPPED: 6,
ORBIT_RADIUS: 400,
ORBIT_SPEED: 0.008,
// NUEVO: Diccionario centralizado de propiedades por rareza
RARITIES: {
'Common': {
colorNum: 0xFFFFFF,
colorStr: '#FFFFFF',
scale: 0.70
},
'Uncommon': {
colorNum: 0x007700,
colorStr: '#00FF00',
scale: 0.70
},
'Rare': {
colorNum: 0x0078a4,
colorStr: '#00BFFF',
scale: 0.70
},
'Ultra-Rare': {
colorNum: 0x8A2BE2,
colorStr: '#8A2BE2',
scale: 0.70
},
'Special': {
colorNum: 0x1d6a60,
colorStr: '#FF1493',
scale: 1.0
},
'Legendary': {
colorNum: 0xd0b100,
colorStr: '#FFD700',
scale: 1.0
},
'Unique': {
colorNum: 0xFF0000,
colorStr: '#FF0000',
scale: 1.0
}
}
};
var characterDatabase = [{
id: 0,
name: 'Fire',
rarity: 'Common',
asset: 'fire_common',
multiplier: 2
}, {
id: 1,
name: 'Water',
rarity: 'Common',
asset: 'water_common',
multiplier: 2
}, {
id: 2,
name: 'Earth',
rarity: 'Common',
asset: 'earth_common',
multiplier: 2
}, {
id: 3,
name: 'Wind',
rarity: 'Common',
asset: 'wind_common',
multiplier: 2
}, {
id: 4,
name: 'Lightning',
rarity: 'Uncommon',
asset: 'lightning_uncommon',
multiplier: 3
}, {
id: 5,
name: 'Ice',
rarity: 'Uncommon',
asset: 'ice_uncommon',
multiplier: 3
}, {
id: 6,
name: 'Nature',
rarity: 'Uncommon',
asset: 'nature_uncommon',
multiplier: 3
}, {
id: 7,
name: 'Metal',
rarity: 'Uncommon',
asset: 'metal_uncommon',
multiplier: 3
}, {
id: 8,
name: 'Sand',
rarity: 'Uncommon',
asset: 'sand_uncommon',
multiplier: 3
}, {
id: 9,
name: 'Steam',
rarity: 'Uncommon',
asset: 'steam_uncommon',
multiplier: 3
}, {
id: 10,
name: 'Magma',
rarity: 'Rare',
asset: 'magma_rare',
multiplier: 4
}, {
id: 11,
name: 'Frost',
rarity: 'Rare',
asset: 'frost_rare',
multiplier: 4
}, {
id: 12,
name: 'Crystal',
rarity: 'Rare',
asset: 'crystal_rare',
multiplier: 4
}, {
id: 13,
name: 'Storm',
rarity: 'Rare',
asset: 'storm_rare',
multiplier: 4
}, {
id: 14,
name: 'Coral',
rarity: 'Rare',
asset: 'coral_rare',
multiplier: 4
}, {
id: 15,
name: 'Forest',
rarity: 'Rare',
asset: 'forest_rare',
multiplier: 4
}, {
id: 16,
name: 'Shadow',
rarity: 'Rare',
asset: 'shadow_rare',
multiplier: 4
}, {
id: 17,
name: 'Plasma',
rarity: 'Ultra-Rare',
asset: 'plasma_ultra',
multiplier: 5
}, {
id: 18,
name: 'Abyss',
rarity: 'Ultra-Rare',
asset: 'abyss_ultra',
multiplier: 5
}, {
id: 19,
name: 'Aurora',
rarity: 'Ultra-Rare',
asset: 'aurora_ultra',
multiplier: 5
}, {
id: 20,
name: 'Inferno',
rarity: 'Ultra-Rare',
asset: 'inferno_ultra',
multiplier: 5
}, {
id: 21,
name: 'Glacier',
rarity: 'Ultra-Rare',
asset: 'glacier_ultra',
multiplier: 5
}, {
id: 22,
name: 'Mirage',
rarity: 'Ultra-Rare',
asset: 'mirage_ultra',
multiplier: 5
}, {
id: 23,
name: 'Celestial',
rarity: 'Legendary',
asset: 'celestial_legendary',
multiplier: 7
}, {
id: 24,
name: 'Cosmic',
rarity: 'Legendary',
asset: 'cosmic_legendary',
multiplier: 7
}, {
id: 25,
name: 'Void',
rarity: 'Legendary',
asset: 'void_legendary',
multiplier: 7
}, {
id: 26,
name: 'Radiance',
rarity: 'Legendary',
asset: 'radiance_legendary',
multiplier: 7
}, {
id: 27,
name: 'Eclipse',
rarity: 'Legendary',
asset: 'eclipse_legendary',
multiplier: 7
}, {
id: 28,
name: 'Genesis',
rarity: 'Unique',
asset: 'genesis_unique',
multiplier: 10
}, {
id: 29,
name: 'Apocalypse',
rarity: 'Unique',
asset: 'apocalypse_unique',
multiplier: 10
}, {
id: 30,
name: 'Paradox',
rarity: 'Unique',
asset: 'paradox_unique',
multiplier: 10
}, {
id: 31,
name: 'Nova',
rarity: 'Special',
asset: 'nova_special',
multiplier: 6
}, {
id: 32,
name: 'Nebula',
rarity: 'Special',
asset: 'nebula_special',
multiplier: 6
}, {
id: 33,
name: 'Titan',
rarity: 'Special',
asset: 'titan_special',
multiplier: 6
}, {
id: 34,
name: 'Phantom',
rarity: 'Special',
asset: 'phantom_special',
multiplier: 6
}, {
id: 35,
name: 'Sphinx',
rarity: 'Special',
asset: 'sphinx_special',
multiplier: 6
}, {
id: 36,
name: 'Oracle',
rarity: 'Special',
asset: 'oracle_special',
multiplier: 6
}, {
id: 37,
name: 'Dust',
rarity: 'Uncommon',
asset: 'dust_uncommon',
multiplier: 3
}, {
id: 38,
name: 'Spore',
rarity: 'Uncommon',
asset: 'spore_uncommon',
multiplier: 3
}, {
id: 39,
name: 'Tectonic',
rarity: 'Rare',
asset: 'tectonic_rare',
multiplier: 4
}, {
id: 40,
name: 'Biosphere',
rarity: 'Ultra-Rare',
asset: 'biosphere_ultra',
multiplier: 5
}, {
id: 41,
name: 'Pulsar',
rarity: 'Special',
asset: 'pulsar_special',
multiplier: 6
}, {
id: 42,
name: 'Antimatter',
rarity: 'Legendary',
asset: 'antimatter_legendary',
multiplier: 7
}, {
id: 43,
name: 'Singularity',
rarity: 'Legendary',
asset: 'singularity_legendary',
multiplier: 7
}, {
id: 44,
name: 'Entropy',
rarity: 'Unique',
asset: 'entropy_unique',
multiplier: 10
}];
// Definimos los dropRates base SIN los IDs hardcodeados
var dropRates = [{
rarity: 'Common',
chance: 45,
ids: []
}, {
rarity: 'Uncommon',
chance: 20,
ids: []
}, {
rarity: 'Rare',
chance: 15,
ids: []
}, {
rarity: 'Ultra-Rare',
chance: 10,
ids: []
}, {
rarity: 'Special',
chance: 6,
ids: []
}, {
rarity: 'Legendary',
chance: 3,
ids: []
}, {
rarity: 'Unique',
chance: 1,
ids: []
}];
var galleryDisplayOrder = [];
// Función para inicializar los datos dinámicamente
function initializeGameData() {
// 1. Llenar los IDs del Gacha automáticamente
for (var i = 0; i < characterDatabase.length; i++) {
var charData = characterDatabase[i];
for (var j = 0; j < dropRates.length; j++) {
if (dropRates[j].rarity === charData.rarity) {
dropRates[j].ids.push(charData.id);
break;
}
}
}
// 2. Generar el orden de la Galería (Por Rareza y luego por ID)
var rarityWeights = {
'Common': 1,
'Uncommon': 2,
'Rare': 3,
'Ultra-Rare': 4,
'Special': 5,
'Legendary': 6,
'Unique': 7
};
// Clonamos la base de datos para no alterar la original y la ordenamos
var sortedCharacters = characterDatabase.slice().sort(function (a, b) {
if (rarityWeights[a.rarity] !== rarityWeights[b.rarity]) {
return rarityWeights[a.rarity] - rarityWeights[b.rarity];
}
return a.id - b.id;
});
// Extraemos solo los IDs ordenados para la galería
for (var k = 0; k < sortedCharacters.length; k++) {
galleryDisplayOrder.push(sortedCharacters[k].id);
}
}
// Ejecutamos la inicialización
initializeGameData();
// Función de ayuda para obtener IDs por rareza (Útil para el Pity System)
function getIdsByRarity(targetRarity) {
for (var i = 0; i < dropRates.length; i++) {
if (dropRates[i].rarity === targetRarity) {
return dropRates[i].ids;
}
}
return [];
}
var gameState = {
credits: 0,
incomePerTick: 1,
collectedCharacters: {},
totalCharactersCollected: 0,
incomeLevels: [],
tickCounter: 0,
equippedCharacters: [],
pityCounter: 0 // Contador de Lástima
};
// Estado temporal para controlar la vista de la galería
var galleryState = {
currentPage: 0,
itemsPerPage: 9,
// Cuadrícula de 3x3
selectedElementId: 0
};
// --- Funciones Base de Estado ---
function getGachaCost() {
return Math.floor(GAME_CONFIG.BASE_GACHA_COST * Math.pow(GAME_CONFIG.GACHA_COST_MULTIPLIER, gameState.totalCharactersCollected));
}
function recalculateIncome() {
// Recalcula incomePerTick desde cero a partir del estado real del juego.
// Esto evita que el income guardado se desincronice con los personajes y upgrades.
var total = 1; // Base income
// Sumar aportes de personajes colectados
for (var charId in gameState.collectedCharacters) {
if (gameState.collectedCharacters[charId]) {
var cData = characterDatabase[parseInt(charId)];
if (cData) {
total += cData.multiplier;
}
}
}
// Sumar aportes de upgrades comprados
// Cada upgrade de nivel i comprado N veces aporta baseIncome * N
// baseIncome por nivel: [1, 2, 3, 5, 10, 20, 50, 100, 200, 300]
var baseIncomes = [1, 2, 3, 5, 10, 20, 50, 100, 200, 300];
for (var lvl = 0; lvl < gameState.incomeLevels.length; lvl++) {
total += baseIncomes[lvl] * gameState.incomeLevels[lvl];
}
gameState.incomePerTick = total;
}
function loadGame() {
gameState.collectedCharacters = {};
gameState.incomeLevels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
gameState.equippedCharacters = [];
if (storage.credits !== undefined) {
gameState.credits = storage.credits;
gameState.totalCharactersCollected = storage.totalCharactersCollected;
gameState.tickCounter = storage.tickCounter || 0;
gameState.pityCounter = storage.pityCounter || 0;
var collectedIds = (storage.collectedCharacterIds || '').split(',');
for (var i = 0; i < collectedIds.length; i++) {
if (collectedIds[i] !== '') {
gameState.collectedCharacters[parseInt(collectedIds[i])] = true;
}
}
var upgradedLevels = (storage.upgradedLevelCounts || '').split(',');
for (var j = 0; j < upgradedLevels.length; j++) {
if (upgradedLevels[j] !== '') {
gameState.incomeLevels[j] = parseInt(upgradedLevels[j]) || 0;
}
}
var equippedStr = storage.equippedCharactersIds || '';
if (equippedStr !== '') {
var eqSplit = equippedStr.split(',');
for (var k = 0; k < eqSplit.length; k++) {
if (eqSplit[k] !== '') {
var eqId = parseInt(eqSplit[k]);
// Solo restaurar si el ID existe en la DB y fue colectado
if (characterDatabase[eqId] && gameState.collectedCharacters[eqId]) {
gameState.equippedCharacters.push(eqId);
}
}
}
}
// Recalcular income desde cero en lugar de confiar en el valor guardado
recalculateIncome();
}
}
function saveGame() {
storage.credits = gameState.credits;
// incomePerTick NO se guarda: se recalcula al cargar desde personajes y upgrades
storage.totalCharactersCollected = gameState.totalCharactersCollected;
storage.tickCounter = gameState.tickCounter;
storage.pityCounter = gameState.pityCounter;
var collectedIds = '';
for (var charId in gameState.collectedCharacters) {
if (gameState.collectedCharacters[charId]) {
collectedIds += charId + ',';
}
}
storage.collectedCharacterIds = collectedIds;
var upgradedLevels = '';
for (var i = 0; i < gameState.incomeLevels.length; i++) {
upgradedLevels += gameState.incomeLevels[i] + ',';
}
storage.upgradedLevelCounts = upgradedLevels;
var equippedIds = '';
for (var e = 0; e < gameState.equippedCharacters.length; e++) {
equippedIds += gameState.equippedCharacters[e] + ',';
}
storage.equippedCharactersIds = equippedIds;
}
loadGame();
// --- Play Background Music ---
LK.playMusic('background_music', {
loop: true
});
// --- UI Construction ---
var creditsText = new Text2('Credits: 0', {
size: 100,
fill: '#FFD700'
});
creditsText.anchor.set(0.5, 0);
creditsText.x = 1024;
creditsText.y = 50;
game.addChild(creditsText);
var incomeText = new Text2('Income: 1/tick', {
size: 80,
fill: '#00FF00'
});
incomeText.anchor.set(0.5, 0);
incomeText.x = 1024;
incomeText.y = 160;
game.addChild(incomeText);
var collectionText = new Text2('Collection: 0/' + characterDatabase.length, {
size: 60,
fill: '#87CEEB'
});
collectionText.anchor.set(0.5, 0);
collectionText.x = 1024;
collectionText.y = 250;
game.addChild(collectionText);
// --- SISTEMA DE ÓRBITA (A LA DERECHA) ---
var orbitContainer = new Container();
orbitContainer.x = 1536; // 3/4 de la pantalla
orbitContainer.y = 1366; // Centro vertical
game.addChild(orbitContainer);
var orbitingSprites = [];
var orbitAngle = 0;
function updateEquippedDisplay() {
while (orbitContainer.children.length > 0) {
orbitContainer.children[0].destroy();
}
orbitingSprites = [];
for (var i = 0; i < gameState.equippedCharacters.length; i++) {
var charId = gameState.equippedCharacters[i];
var charData = characterDatabase[charId];
var sprite = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
sprite.scale.set(0.55, 0.55);
orbitContainer.addChild(sprite);
orbitingSprites.push(sprite);
}
}
updateEquippedDisplay();
// --- Manual Clicker Core (A LA DERECHA) ---
var coreGraphic = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
var coreButton = new Container();
coreButton.addChild(coreGraphic);
coreButton.x = 1536; // 3/4 de la pantalla
coreButton.y = 1366; // Centro vertical
game.addChild(coreButton);
var coreText = new Text2('TAP', {
size: 50,
fill: '#ffffff'
});
coreText.anchor.set(0.5, 0.5);
coreButton.addChild(coreText);
// --- Botón Gacha Individual (Movido a la Izquierda) ---
var gachaBtnGraphic = LK.getAsset('gacha_button', {
anchorX: 0.5,
anchorY: 0.5
});
gachaBtnGraphic.scale.set(1.6, 1.8);
var gachaButton = new Container();
gachaButton.addChild(gachaBtnGraphic);
gachaButton.x = 600; // Antes 1024
gachaButton.y = 2500;
game.addChild(gachaButton);
var gachaText = new Text2('PULL (' + getGachaCost() + ')', {
size: 55,
fill: '#ffffff'
});
gachaText.anchor.set(0.5, 0.5);
gachaText.y = -20;
gachaButton.addChild(gachaText);
var pityText = new Text2('Pity: 0/10', {
size: 40,
fill: '#FFD700'
});
pityText.anchor.set(0.5, 0.5);
pityText.y = 30;
gachaButton.addChild(pityText);
var gachaEnabled = false;
// --- NUEVO: Botón Gacha x10 (A la Derecha) ---
var gacha10BtnGraphic = LK.getAsset('gacha_button', {
anchorX: 0.5,
anchorY: 0.5
});
gacha10BtnGraphic.scale.set(1.6, 1.8);
var gacha10Button = new Container();
gacha10Button.addChild(gacha10BtnGraphic);
gacha10Button.x = 1448;
gacha10Button.y = 2500;
game.addChild(gacha10Button);
var gacha10Text = new Text2('10x PULL', {
size: 55,
fill: '#ffffff'
});
gacha10Text.anchor.set(0.5, 0.5);
gacha10Text.y = -20;
gacha10Button.addChild(gacha10Text);
var pity10Text = new Text2('1 Guaranteed!', {
size: 40,
fill: '#00FF00'
});
pity10Text.anchor.set(0.5, 0.5);
pity10Text.y = 30;
gacha10Button.addChild(pity10Text);
var gacha10Enabled = false;
// Gallery Button
var galleryBtnGraphic = LK.getAsset('gallery_button', {
anchorX: 0.5,
anchorY: 0.5
});
var galleryButton = new Container();
galleryButton.addChild(galleryBtnGraphic);
galleryButton.x = 1800;
galleryButton.y = 500;
game.addChild(galleryButton);
var galleryText = new Text2('GALLERY', {
size: 50,
fill: '#ffffff'
});
galleryText.anchor.set(0.5, 0.5);
galleryButton.addChild(galleryText);
// Debug Reset Button
/*
var debugBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var debugButton = new Container();
debugButton.addChild(debugBtnGraphic);
debugButton.x = 300;
debugButton.y = 100;
game.addChild(debugButton);
var debugText = new Text2('RESET', {
size: 40,
fill: '#ff4444'
});
debugText.anchor.set(0.5, 0.5);
debugButton.addChild(debugText);*/
// Upgrade Levels Container (A LA IZQUIERDA Y MÁS GRANDES)
var levelContainers = [];
var levelStartY = 500; // Empezamos un poco más arriba
var levelSpacing = 180; // Más espacio vertical porque los botones crecieron
for (var i = 0; i < 10; i++) {
var levelContainer = new IncomeLevel();
levelContainer.init(i, [1, 2, 3, 5, 10, 20, 50, 100, 200, 300][i]);
levelContainer.updateData(gameState.incomeLevels[i]);
levelContainer.x = 512; // 1/4 de la pantalla hacia la izquierda
levelContainer.y = levelStartY + i * levelSpacing;
game.addChild(levelContainer);
levelContainers.push(levelContainer);
}
// Modal Variables
var modalOpen = false;
var modalContainer = null;
// --- Funciones Lógicas ---
function openGallery() {
if (modalOpen) {
return;
}
modalOpen = true;
galleryState.currentPage = 0;
galleryState.selectedElementId = 0;
// Buscar el primer personaje recolectado para mostrarlo por defecto al abrir
for (var s = 0; s < characterDatabase.length; s++) {
if (gameState.collectedCharacters[s]) {
galleryState.selectedElementId = s;
break;
}
}
modalContainer = new Container();
var modalBg = LK.getAsset('modal_bg', {
anchorX: 0.5,
anchorY: 0.5
});
modalContainer.addChild(modalBg);
modalContainer.x = 1024;
modalContainer.y = 1366;
var galleryTitle = new Text2('Element Gallery', {
size: 80,
fill: '#FFD700'
});
galleryTitle.anchor.set(0.5, 0);
galleryTitle.x = 0;
galleryTitle.y = -1100;
modalContainer.addChild(galleryTitle);
var closeBtnGraphic = LK.getAsset('modal_close_btn', {
anchorX: 0.5,
anchorY: 0.5
});
var closeBtn = new Container();
closeBtn.addChild(closeBtnGraphic);
closeBtn.x = 800;
closeBtn.y = -1050;
modalContainer.addChild(closeBtn);
var closeBtnText = new Text2('X', {
size: 60,
fill: '#ffffff'
});
closeBtnText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeBtnText);
closeBtn.down = function () {
closeModal();
LK.getSound('ui_click').play();
};
// Contenedor dinámico que se refresca
var contentContainer = new Container();
modalContainer.addChild(contentContainer);
function renderGalleryContent() {
while (contentContainer.children.length > 0) {
contentContainer.children[0].destroy();
}
var totalItems = galleryDisplayOrder.length;
var totalPages = Math.ceil(totalItems / galleryState.itemsPerPage);
var startIndex = galleryState.currentPage * galleryState.itemsPerPage;
var endIndex = Math.min(startIndex + galleryState.itemsPerPage, totalItems);
// --- PANEL IZQUIERDO: CUADRÍCULA 3x3 ---
var gridStartX = -750;
var gridStartY = -700;
var spacing = 400;
for (var i = startIndex; i < endIndex; i++) {
var localIndex = i - startIndex;
var col = localIndex % 3;
var row = Math.floor(localIndex / 3);
// AHORA BUSCAMOS EL ID BASADO EN EL NUEVO ORDEN
var actualCharId = galleryDisplayOrder[i];
var charData = characterDatabase[actualCharId];
var isCollected = gameState.collectedCharacters[actualCharId];
var displayChar = new Character();
var displayName = isCollected ? charData.name : "???";
// Siempre revelamos la rareza (Spoiler visual)
var displayRarity = charData.rarity;
displayChar.init(actualCharId, displayName, displayRarity, charData.asset, charData.multiplier);
displayChar.x = gridStartX + col * spacing;
displayChar.y = gridStartY + row * (spacing * 1.2);
// Efecto Silueta Inteligente
if (!isCollected) {
displayChar.graphicObj.tint = 0x000000; // Personaje en negro
displayChar.nameObj.fill = '#555555'; // Letras del nombre en gris
displayChar.frameObj.alpha = 0.05; // Apagamos casi todo el brillo del marco
}
// Indicador visual de elemento seleccionado
if (galleryState.selectedElementId === actualCharId) {
displayChar.scale.set(1.15, 1.15); // Ligeramente más grande al seleccionarlo
}
(function (idToSelect) {
displayChar.down = function () {
if (galleryState.selectedElementId !== idToSelect) {
galleryState.selectedElementId = idToSelect;
LK.getSound('ui_click').play();
renderGalleryContent();
}
};
})(actualCharId);
contentContainer.addChild(displayChar);
}
var pageText = new Text2('Page ' + (galleryState.currentPage + 1) + ' / ' + totalPages, {
size: 80,
fill: '#FFFFFF'
});
pageText.anchor.set(0.5, 0.5);
pageText.x = -300;
pageText.y = 800;
contentContainer.addChild(pageText);
var collectionStatus = new Text2('Collected: ' + gameState.totalCharactersCollected + '/' + characterDatabase.length, {
size: 60,
fill: '#87CEEB'
});
collectionStatus.anchor.set(0.5, 0.5);
collectionStatus.x = -300;
collectionStatus.y = 900;
contentContainer.addChild(collectionStatus);
if (galleryState.currentPage > 0) {
var prevBtn = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
prevBtn.scale.set(0.6, 0.8);
prevBtn.x = -550;
prevBtn.y = 650;
var prevTxt = new Text2('< PREV', {
size: 70,
fill: '#FFF'
});
prevTxt.anchor.set(0.5, 0.5);
prevBtn.addChild(prevTxt);
prevBtn.down = function () {
galleryState.currentPage--;
LK.getSound('ui_click').play();
renderGalleryContent();
};
contentContainer.addChild(prevBtn);
}
if (galleryState.currentPage < totalPages - 1) {
var nextBtn = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
nextBtn.scale.set(0.6, 0.8);
nextBtn.x = -50;
nextBtn.y = 650;
var nextTxt = new Text2('NEXT >', {
size: 70,
fill: '#FFF'
});
nextTxt.anchor.set(0.5, 0.5);
nextBtn.addChild(nextTxt);
nextBtn.down = function () {
galleryState.currentPage++;
LK.getSound('ui_click').play();
renderGalleryContent();
};
contentContainer.addChild(nextBtn);
}
// --- PANEL DERECHO: DETALLES Y EQUIPAR ---
var rightPanelX = 550;
var detailData = characterDatabase[galleryState.selectedElementId];
var isDetailCollected = gameState.collectedCharacters[galleryState.selectedElementId];
if (detailData) {
var bigGraphic = LK.getAsset(detailData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
bigGraphic.scale.set(1.35, 1.35);
bigGraphic.x = rightPanelX;
bigGraphic.y = -250;
if (!isDetailCollected) {
bigGraphic.tint = 0x000000;
}
contentContainer.addChild(bigGraphic);
var detailNameText = isDetailCollected ? detailData.name : "???";
var detailName = new Text2(detailNameText, {
size: 80,
fill: '#FFF'
});
detailName.anchor.set(0.5, 0);
detailName.x = rightPanelX;
detailName.y = 200;
contentContainer.addChild(detailName);
// Mapa de colores String para la UI de detalles
var rarityColorHexStr = GAME_CONFIG.RARITIES[detailData.rarity] ? GAME_CONFIG.RARITIES[detailData.rarity].colorStr : '#FFFFFF';
var detailRarity = new Text2('[' + detailData.rarity + ']', {
size: 60,
fill: rarityColorHexStr
});
detailRarity.anchor.set(0.5, 0);
detailRarity.x = rightPanelX;
detailRarity.y = 280;
contentContainer.addChild(detailRarity);
if (isDetailCollected) {
var incomeInfo = new Text2('Income Boost: +' + detailData.multiplier + '/tick', {
size: 55,
fill: '#00FF00'
});
incomeInfo.anchor.set(0.5, 0);
incomeInfo.x = rightPanelX;
incomeInfo.y = 350;
contentContainer.addChild(incomeInfo);
}
// LÓGICA DEL BOTÓN EQUIPAR
var orbitStatusText = new Text2('Orbiting: ' + gameState.equippedCharacters.length + ' / ' + GAME_CONFIG.MAX_EQUIPPED, {
size: 60,
fill: '#87CEEB'
});
orbitStatusText.anchor.set(0.5, 0);
orbitStatusText.x = rightPanelX;
orbitStatusText.y = 450;
contentContainer.addChild(orbitStatusText);
if (isDetailCollected) {
var isEquipped = gameState.equippedCharacters.indexOf(galleryState.selectedElementId) > -1;
var equipBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
equipBtnGraphic.scale.set(1.5, 1.2);
equipBtnGraphic.tint = isEquipped ? 0xFF4444 : 0x32CD32;
var equipBtn = new Container();
equipBtn.addChild(equipBtnGraphic);
equipBtn.x = rightPanelX;
equipBtn.y = 650;
var equipBtnText = new Text2(isEquipped ? 'UNEQUIP' : 'EQUIP', {
size: 50,
fill: '#ffffff'
});
equipBtnText.anchor.set(0.5, 0.5);
equipBtn.addChild(equipBtnText);
equipBtn.down = function () {
if (isEquipped) {
var index = gameState.equippedCharacters.indexOf(galleryState.selectedElementId);
gameState.equippedCharacters.splice(index, 1);
LK.getSound('ui_click').play();
} else {
if (gameState.equippedCharacters.length >= GAME_CONFIG.MAX_EQUIPPED) {
// Órbita llena: mostrar texto de aviso en lugar de desplazar silenciosamente
var fullMsg = new FloatingText('Orbit full! Unequip one first.', 1024, 1366, '#FF4444');
game.addChild(fullMsg);
return;
}
gameState.equippedCharacters.push(galleryState.selectedElementId);
LK.getSound('ui_click').play();
}
updateEquippedDisplay();
saveGame();
renderGalleryContent();
};
contentContainer.addChild(equipBtn);
} else {
var lockText = new Text2('LOCKED', {
size: 50,
fill: '#FF4444'
});
lockText.anchor.set(0.5, 0.5);
lockText.x = rightPanelX;
lockText.y = 400;
contentContainer.addChild(lockText);
}
}
}
renderGalleryContent();
game.addChild(modalContainer);
}
function closeModal() {
if (modalContainer) {
modalContainer.destroy();
modalOpen = false;
}
}
// --- Lógica de tirada de Gacha centralizada ---
// Retorna una rareza de pity (Special, Legendary o Unique)
function rollPityRarity() {
var pityRoll = Math.random() * 100;
if (pityRoll <= 60) {
return {
rarity: 'Special',
ids: getIdsByRarity('Special')
};
} else if (pityRoll <= 90) {
return {
rarity: 'Legendary',
ids: getIdsByRarity('Legendary')
};
} else {
return {
rarity: 'Unique',
ids: getIdsByRarity('Unique')
};
}
}
// Tirada normal con soft pity: a partir del pull 6, la probabilidad de rarezas altas sube gradualmente
function rollNormalRarity() {
var softBonus = Math.max(0, gameState.pityCounter - 5) * 3;
var dynamicRates = [];
for (var i = 0; i < dropRates.length; i++) {
var rate = {
rarity: dropRates[i].rarity,
ids: dropRates[i].ids,
chance: dropRates[i].chance
};
if (rate.rarity === 'Special') {
rate.chance = Math.min(30, 6 + softBonus);
} else if (rate.rarity === 'Legendary') {
rate.chance = Math.min(20, 3 + softBonus * 0.5);
} else if (rate.rarity === 'Unique') {
rate.chance = Math.min(10, 1 + softBonus * 0.25);
}
dynamicRates.push(rate);
}
var roll = Math.random() * 100;
var cumulativeChance = 0;
for (var j = 0; j < dynamicRates.length; j++) {
cumulativeChance += dynamicRates[j].chance;
if (roll <= cumulativeChance) {
return dynamicRates[j];
}
}
return dynamicRates[0];
}
// Registra un personaje obtenido y retorna si es nuevo
function registerCharacterObtained(charId) {
var charData = characterDatabase[charId];
if (!gameState.collectedCharacters[charId]) {
gameState.collectedCharacters[charId] = true;
gameState.totalCharactersCollected++;
gameState.incomePerTick += charData.multiplier;
return true;
}
return false;
}
function performGachaPull() {
var currentCost = getGachaCost();
if (gameState.credits < currentCost) {
return;
}
gameState.credits -= currentCost;
gameState.pityCounter++;
var selectedRarity = null;
// Hard Pity en 10: garantiza rareza alta
if (gameState.pityCounter >= 10) {
selectedRarity = rollPityRarity();
gameState.pityCounter = 0;
} else {
// Soft pity: probabilidades aumentan gradualmente desde pull 6
selectedRarity = rollNormalRarity();
if (selectedRarity.rarity === 'Special' || selectedRarity.rarity === 'Legendary' || selectedRarity.rarity === 'Unique') {
gameState.pityCounter = 0;
}
}
var characterIdArray = selectedRarity.ids;
var selectedCharacterId = characterIdArray[Math.floor(Math.random() * characterIdArray.length)];
var wasNew = registerCharacterObtained(selectedCharacterId);
var charData = characterDatabase[selectedCharacterId];
saveGame();
LK.getSound('gacha_pull').play();
if (wasNew) {
LK.getSound('gacha_pull_success').play();
}
modalOpen = true; // Bloquear UI durante la animación del resultado
// Consumimos los efectos visuales desde la configuración global
var currentEffect = GAME_CONFIG.RARITIES[charData.rarity] || {
colorNum: 0xFFFFFF,
scale: 0.70
};
var resultMessage = wasNew ? 'NEW: ' + charData.name + '!' : 'Duplicate: ' + charData.name;
var resultColor = wasNew ? '#2d633b' : '#b89e14';
var resultContainer = new Container();
var resultBg = LK.getAsset('pull_bg', {
anchorX: 0.5,
anchorY: 0.5
});
resultContainer.addChild(resultBg);
resultContainer.x = 1024;
resultContainer.y = 1366;
resultContainer.scale.set(0.1, 0.1);
tween(resultContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
var aura = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
aura.tint = currentEffect.colorNum;
var auraScaleFactor = currentEffect.scale * 2.5;
aura.scale.set(auraScaleFactor, auraScaleFactor);
aura.alpha = 0.5;
resultContainer.addChild(aura);
tween(aura, {
rotation: Math.PI * 2
}, {
duration: 2000
});
var resultGraphic = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
var scaleTarget = currentEffect.scale;
resultGraphic.scale.set(0.1, 0.1);
tween(resultGraphic, {
scaleX: scaleTarget + 0.05,
scaleY: scaleTarget + 0.05
}, {
duration: 250,
onFinish: function onFinish() {
tween(resultGraphic, {
scaleX: scaleTarget,
scaleY: scaleTarget
}, {
duration: 150
});
}
});
resultGraphic.y = 0;
resultContainer.addChild(resultGraphic);
var safePadding = 720 * scaleTarget / 2 + 80;
var resultTitle = new Text2(resultMessage, {
size: 150,
fill: resultColor
});
resultTitle.anchor.set(0.5, 0.5);
resultTitle.y = -safePadding;
resultContainer.addChild(resultTitle);
var rarityDisplayColor = GAME_CONFIG.RARITIES[charData.rarity] ? GAME_CONFIG.RARITIES[charData.rarity].colorStr : '#FFFFFF';
var resultRarity = new Text2('[' + charData.rarity + ']', {
size: 90,
fill: rarityDisplayColor
});
resultRarity.anchor.set(0.5, 0.5);
resultRarity.y = safePadding;
resultContainer.addChild(resultRarity);
game.addChild(resultContainer);
LK.setTimeout(function () {
tween(resultContainer, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
resultContainer.destroy();
modalOpen = false; // Liberar UI al cerrar el resultado
}
});
}, 2000);
}
function performGachaPull10() {
var singleCost = getGachaCost();
var totalCost = singleCost * 10;
if (gameState.credits < totalCost) {
return;
}
gameState.credits -= totalCost;
var pullResults = [];
// 1. Las 9 tiradas normales (con soft pity activo)
for (var i = 0; i < 9; i++) {
gameState.pityCounter++;
var selectedRarity = rollNormalRarity();
if (selectedRarity.rarity === 'Special' || selectedRarity.rarity === 'Legendary' || selectedRarity.rarity === 'Unique') {
gameState.pityCounter = 0;
}
var charIdArray = selectedRarity.ids;
var selectedCharId = charIdArray[Math.floor(Math.random() * charIdArray.length)];
pullResults.push(characterDatabase[selectedCharId]);
registerCharacterObtained(selectedCharId);
}
// 2. La 10ª tirada (Lástima Garantizada — siempre rareza alta)
var pityRarity = rollPityRarity();
var pityCharIdArray = pityRarity.ids;
var pityCharId = pityCharIdArray[Math.floor(Math.random() * pityCharIdArray.length)];
pullResults.push(characterDatabase[pityCharId]);
registerCharacterObtained(pityCharId);
// Reiniciamos el pity porque acabamos de dar uno garantizado
gameState.pityCounter = 0;
saveGame();
LK.getSound('gacha_pull').play();
showMultiPullResults(pullResults);
}
function showMultiPullResults(results) {
modalOpen = true; // Bloquea la UI de fondo
var resultContainer = new Container();
var modalBg = LK.getAsset('modal_bg', {
anchorX: 0.5,
anchorY: 0.5
});
resultContainer.addChild(modalBg);
resultContainer.x = 1024;
resultContainer.y = 1366;
resultContainer.scale.set(0.1, 0.1);
tween(resultContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
var titleText = new Text2('10x PULL RESULTS', {
size: 100,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -900;
resultContainer.addChild(titleText);
// Configuramos la cuadrícula (2 filas x 5 columnas)
var startX = -600;
var startY = -400;
var spacingX = 300;
var spacingY = 400;
for (var i = 0; i < results.length; i++) {
var charData = results[i];
var col = i % 5;
var row = Math.floor(i / 5);
// Si es de rareza alta, le ponemos un aura detrás
if (charData.rarity === 'Special' || charData.rarity === 'Legendary' || charData.rarity === 'Unique') {
var aura = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
aura.x = startX + col * spacingX;
aura.y = startY + row * spacingY;
aura.scale.set(1.5, 1.5);
aura.tint = 0xFFD700;
aura.alpha = 0.5;
resultContainer.addChild(aura);
tween(aura, {
rotation: Math.PI * 2
}, {
duration: 2000
});
}
var charGraphic = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
charGraphic.x = startX + col * spacingX;
charGraphic.y = startY + row * spacingY;
charGraphic.scale.set(0.35, 0.35); // Más pequeños para que quepan
resultContainer.addChild(charGraphic);
}
// Botón para cerrar
var closeBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var closeBtn = new Container();
closeBtn.addChild(closeBtnGraphic);
closeBtn.y = 800;
resultContainer.addChild(closeBtn);
var closeText = new Text2('CONTINUE', {
size: 60,
fill: '#FFFFFF'
});
closeText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeText);
closeBtn.down = function () {
LK.getSound('ui_click').play();
tween(resultContainer, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
resultContainer.destroy();
modalOpen = false; // Liberamos la UI
}
});
};
game.addChild(resultContainer);
}
function upgradeIncomeLevel(levelIndex) {
var level = levelContainers[levelIndex];
if (gameState.credits >= level.currentCost) {
gameState.credits -= level.currentCost;
gameState.incomeLevels[levelIndex]++;
gameState.incomePerTick += level.baseIncome;
level.updateData(gameState.incomeLevels[levelIndex]);
saveGame();
LK.getSound('level_up').play();
// Confirmar visualmente la compra
var floatUpgrade = new FloatingText('+' + level.baseIncome + '/tick', level.x + (Math.random() * 60 - 30), level.y - 60, '#00FF00');
game.addChild(floatUpgrade);
}
}
// --- Event Handlers Directos ---
gachaButton.down = function () {
if (modalOpen) {
return;
}
if (gachaEnabled) {
performGachaPull();
LK.getSound('ui_click').play();
}
};
gacha10Button.down = function () {
if (modalOpen) {
return;
}
if (gacha10Enabled) {
performGachaPull10();
}
};
galleryButton.down = function () {
if (modalOpen) {
return;
}
openGallery();
LK.getSound('ui_click').play();
};
/*
debugButton.down = function () {
if (modalOpen) {
return;
}
gameState.credits = 0;
gameState.incomePerTick = 1;
gameState.collectedCharacters = {};
gameState.totalCharactersCollected = 0;
gameState.incomeLevels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
gameState.equippedCharacters = [];
for (var i = 0; i < levelContainers.length; i++) {
levelContainers[i].updateData(0);
}
updateEquippedDisplay();
saveGame();
LK.getSound('ui_click').play();
};
*/
coreButton.down = function () {
if (modalOpen) {
return;
}
var clickGain = 1 + Math.floor(gameState.incomePerTick * 0.2);
gameState.credits += clickGain;
var floatText = new FloatingText('+' + clickGain, coreButton.x + (Math.random() * 100 - 50), coreButton.y - 150, '#00ffff');
game.addChild(floatText);
tween(coreGraphic, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 50,
onFinish: function onFinish() {
tween(coreGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 50
});
}
});
};
for (var i = 0; i < levelContainers.length; i++) {
(function (index) {
levelContainers[index].down = function () {
if (modalOpen) {
return;
}
upgradeIncomeLevel(index);
};
})(i);
}
// --- Variables para el Main Loop ---
var pulseTimer = 0;
var lastDisplayedCredits = -1;
var lastDisplayedIncome = -1;
var lastDisplayedCollection = -1;
var lastDisplayedGachaCost = -1;
var lastDisplayedPity = -1;
// --- Main Game Loop Optimizado ---
game.update = function () {
gameState.tickCounter++;
// LÓGICA DE ÓRBITA
var numOrbiting = orbitingSprites.length;
if (numOrbiting > 0) {
orbitAngle += GAME_CONFIG.ORBIT_SPEED;
var angleStep = Math.PI * 2 / numOrbiting;
for (var o = 0; o < numOrbiting; o++) {
var currentAngle = orbitAngle + o * angleStep;
orbitingSprites[o].x = Math.cos(currentAngle) * GAME_CONFIG.ORBIT_RADIUS;
orbitingSprites[o].y = Math.sin(currentAngle) * GAME_CONFIG.ORBIT_RADIUS;
//orbitingSprites[o].rotation += 0.01;
}
}
if (gameState.tickCounter % GAME_CONFIG.FPS === 0) {
gameState.credits += gameState.incomePerTick;
}
// --- Actualización de Costos y Textos ---
var currentCredits = Math.floor(gameState.credits);
var currentGachaCost = getGachaCost();
var currentGachaCost10 = currentGachaCost * 10;
// === INICIO DEL BLOQUE FALTANTE ===
if (currentCredits !== lastDisplayedCredits) {
creditsText.setText('Credits: ' + currentCredits);
lastDisplayedCredits = currentCredits;
}
if (gameState.incomePerTick !== lastDisplayedIncome) {
incomeText.setText('Income: ' + gameState.incomePerTick + '/tick');
lastDisplayedIncome = gameState.incomePerTick;
}
if (gameState.totalCharactersCollected !== lastDisplayedCollection) {
// Usamos characterDatabase.length para que se actualice solo si agregas más en el futuro
collectionText.setText('Collection: ' + gameState.totalCharactersCollected + '/' + characterDatabase.length);
lastDisplayedCollection = gameState.totalCharactersCollected;
}
if (currentGachaCost !== lastDisplayedGachaCost || gameState.pityCounter !== lastDisplayedPity) {
// Actualiza textos Botón x1
gachaText.setText('PULL (' + currentGachaCost + ')');
if (gameState.pityCounter >= 9) {
pityText.setText('Pity: READY!');
pityText.fill = '#00FF00';
} else if (gameState.pityCounter >= 6) {
// Soft pity activo: probabilidades subiendo
pityText.setText('Pity: ' + gameState.pityCounter + "/10 \u2191");
pityText.fill = '#FFA500';
} else {
pityText.setText('Pity: ' + gameState.pityCounter + '/10');
pityText.fill = '#FFD700';
}
// Actualiza textos Botón x10
gacha10Text.setText('10x (' + currentGachaCost10 + ')');
lastDisplayedGachaCost = currentGachaCost;
lastDisplayedPity = gameState.pityCounter;
}
// --- Control Visual de Botones Disponibles ---
gachaEnabled = gameState.credits >= currentGachaCost;
gacha10Enabled = gameState.credits >= currentGachaCost10;
// Efectos del Botón x1
if (gachaEnabled) {
gachaBtnGraphic.tint = 0xFF1493;
gachaText.tint = 0xFFFFFF;
pulseTimer += 0.05;
var scaleX = 1.6 + Math.sin(pulseTimer) * 0.05;
var scaleY = 1.8 + Math.sin(pulseTimer) * 0.05;
gachaBtnGraphic.scale.set(scaleX, scaleY);
} else {
gachaBtnGraphic.tint = 0x883366;
gachaText.tint = 0xAAAAAA;
gachaBtnGraphic.scale.set(1.6, 1.8);
}
// Efectos del Botón x10
if (gacha10Enabled) {
gacha10BtnGraphic.tint = 0xFF1493;
gacha10Text.tint = 0xFFFFFF;
var scaleX10 = 1.6 + Math.cos(pulseTimer) * 0.05; // Usamos cos para desfasar el pulso visual
var scaleY10 = 1.8 + Math.cos(pulseTimer) * 0.05;
gacha10BtnGraphic.scale.set(scaleX10, scaleY10);
} else {
gacha10BtnGraphic.tint = 0x883366;
gacha10Text.tint = 0xAAAAAA;
gacha10BtnGraphic.scale.set(1.6, 1.8);
}
for (var j = 0; j < levelContainers.length; j++) {
var level = levelContainers[j];
if (gameState.credits >= level.currentCost) {
level.children[0].tint = 0xffffff;
} else {
level.children[0].tint = 0x525252;
}
}
if (gameState.tickCounter % GAME_CONFIG.SAVE_INTERVAL === 0) {
saveGame();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
self.init = function (elementId, elementName, rarity, assetId, multiplier) {
// Obtenemos el color directamente de la configuración global
var rColor = GAME_CONFIG.RARITIES[rarity] ? GAME_CONFIG.RARITIES[rarity].colorNum : 0xffffff;
// 1. El Marco (Aura de fondo)
var frame = self.attachAsset('bg_character', {
anchorX: 0.5,
anchorY: 0.55
});
frame.scale.set(0.75, 1);
frame.tint = rColor;
frame.alpha = 0.6; // Resplandor sutil
// 2. El Gráfico del Personaje
var graphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
graphic.scale.set(0.5, 0.5);
// 3. El Nombre
var nameText = new Text2(elementName, {
size: 50,
fill: '#ffffff'
});
nameText.anchor.set(0.5, 0);
nameText.y = 130;
self.addChild(nameText);
// 4. La Rareza
var rarityText = new Text2('[' + rarity + ']', {
size: 45,
fill: rColor
});
rarityText.anchor.set(0.5, 0);
rarityText.y = 170;
self.addChild(rarityText);
// Guardamos referencias para modificarlas fácilmente en la galería
self.frameObj = frame;
self.graphicObj = graphic;
self.nameObj = nameText;
self.rarityObj = rarityText;
};
return self;
});
var FloatingText = Container.expand(function () {
var self = Container.call(this);
self.init = function (textValue, startX, startY, color) {
self.x = startX;
self.y = startY;
var txt = new Text2(textValue, {
size: 48,
fill: color || '#ffffff'
});
txt.anchor.set(0.5, 0.5);
self.addChild(txt);
tween(self, {
y: self.y - 150,
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Clases de UI: Income Level (AHORA MÁS GRANDE)
var IncomeLevel = Container.expand(function () {
var self = Container.call(this);
self.levelIndex = 0;
self.baseIncome = 0;
self.baseCost = 0;
self.currentCost = 0;
self.init = function (levelIndex, baseIncome) {
self.levelIndex = levelIndex;
self.baseIncome = baseIncome;
self.baseCost = 50 * (levelIndex + 1);
var bg = self.attachAsset('upgrade_button', {
anchorX: 0.5,
anchorY: 0.5
});
// Escala aumentada de 1.5 a 2.0
bg.scale.set(2.0, 2.0);
// Textos aumentados y reposicionados
var levelText = new Text2('', {
size: 38,
fill: '#ffffff'
});
levelText.anchor.set(0.5, 0.5);
levelText.y = -25;
self.addChild(levelText);
self.levelText = levelText;
var costText = new Text2('', {
size: 30,
fill: '#ffffff'
});
costText.anchor.set(0.5, 0.5);
costText.y = 25;
self.addChild(costText);
self.costText = costText;
};
self.updateData = function (purchases) {
self.currentCost = Math.floor(self.baseCost * Math.pow(1.5, purchases));
self.costText.setText('Cost: ' + self.currentCost);
self.levelText.setText('Lvl ' + (self.levelIndex + 1) + ' (x' + purchases + '): +' + self.baseIncome + '/t');
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x6f6fdb
});
/****
* Game Code
****/
var GAME_CONFIG = {
BASE_GACHA_COST: 1000,
GACHA_COST_MULTIPLIER: 1.10,
FPS: 60,
SAVE_INTERVAL: 300,
MAX_EQUIPPED: 6,
ORBIT_RADIUS: 400,
ORBIT_SPEED: 0.008,
// NUEVO: Diccionario centralizado de propiedades por rareza
RARITIES: {
'Common': {
colorNum: 0xFFFFFF,
colorStr: '#FFFFFF',
scale: 0.70
},
'Uncommon': {
colorNum: 0x007700,
colorStr: '#00FF00',
scale: 0.70
},
'Rare': {
colorNum: 0x0078a4,
colorStr: '#00BFFF',
scale: 0.70
},
'Ultra-Rare': {
colorNum: 0x8A2BE2,
colorStr: '#8A2BE2',
scale: 0.70
},
'Special': {
colorNum: 0x1d6a60,
colorStr: '#FF1493',
scale: 1.0
},
'Legendary': {
colorNum: 0xd0b100,
colorStr: '#FFD700',
scale: 1.0
},
'Unique': {
colorNum: 0xFF0000,
colorStr: '#FF0000',
scale: 1.0
}
}
};
var characterDatabase = [{
id: 0,
name: 'Fire',
rarity: 'Common',
asset: 'fire_common',
multiplier: 2
}, {
id: 1,
name: 'Water',
rarity: 'Common',
asset: 'water_common',
multiplier: 2
}, {
id: 2,
name: 'Earth',
rarity: 'Common',
asset: 'earth_common',
multiplier: 2
}, {
id: 3,
name: 'Wind',
rarity: 'Common',
asset: 'wind_common',
multiplier: 2
}, {
id: 4,
name: 'Lightning',
rarity: 'Uncommon',
asset: 'lightning_uncommon',
multiplier: 3
}, {
id: 5,
name: 'Ice',
rarity: 'Uncommon',
asset: 'ice_uncommon',
multiplier: 3
}, {
id: 6,
name: 'Nature',
rarity: 'Uncommon',
asset: 'nature_uncommon',
multiplier: 3
}, {
id: 7,
name: 'Metal',
rarity: 'Uncommon',
asset: 'metal_uncommon',
multiplier: 3
}, {
id: 8,
name: 'Sand',
rarity: 'Uncommon',
asset: 'sand_uncommon',
multiplier: 3
}, {
id: 9,
name: 'Steam',
rarity: 'Uncommon',
asset: 'steam_uncommon',
multiplier: 3
}, {
id: 10,
name: 'Magma',
rarity: 'Rare',
asset: 'magma_rare',
multiplier: 4
}, {
id: 11,
name: 'Frost',
rarity: 'Rare',
asset: 'frost_rare',
multiplier: 4
}, {
id: 12,
name: 'Crystal',
rarity: 'Rare',
asset: 'crystal_rare',
multiplier: 4
}, {
id: 13,
name: 'Storm',
rarity: 'Rare',
asset: 'storm_rare',
multiplier: 4
}, {
id: 14,
name: 'Coral',
rarity: 'Rare',
asset: 'coral_rare',
multiplier: 4
}, {
id: 15,
name: 'Forest',
rarity: 'Rare',
asset: 'forest_rare',
multiplier: 4
}, {
id: 16,
name: 'Shadow',
rarity: 'Rare',
asset: 'shadow_rare',
multiplier: 4
}, {
id: 17,
name: 'Plasma',
rarity: 'Ultra-Rare',
asset: 'plasma_ultra',
multiplier: 5
}, {
id: 18,
name: 'Abyss',
rarity: 'Ultra-Rare',
asset: 'abyss_ultra',
multiplier: 5
}, {
id: 19,
name: 'Aurora',
rarity: 'Ultra-Rare',
asset: 'aurora_ultra',
multiplier: 5
}, {
id: 20,
name: 'Inferno',
rarity: 'Ultra-Rare',
asset: 'inferno_ultra',
multiplier: 5
}, {
id: 21,
name: 'Glacier',
rarity: 'Ultra-Rare',
asset: 'glacier_ultra',
multiplier: 5
}, {
id: 22,
name: 'Mirage',
rarity: 'Ultra-Rare',
asset: 'mirage_ultra',
multiplier: 5
}, {
id: 23,
name: 'Celestial',
rarity: 'Legendary',
asset: 'celestial_legendary',
multiplier: 7
}, {
id: 24,
name: 'Cosmic',
rarity: 'Legendary',
asset: 'cosmic_legendary',
multiplier: 7
}, {
id: 25,
name: 'Void',
rarity: 'Legendary',
asset: 'void_legendary',
multiplier: 7
}, {
id: 26,
name: 'Radiance',
rarity: 'Legendary',
asset: 'radiance_legendary',
multiplier: 7
}, {
id: 27,
name: 'Eclipse',
rarity: 'Legendary',
asset: 'eclipse_legendary',
multiplier: 7
}, {
id: 28,
name: 'Genesis',
rarity: 'Unique',
asset: 'genesis_unique',
multiplier: 10
}, {
id: 29,
name: 'Apocalypse',
rarity: 'Unique',
asset: 'apocalypse_unique',
multiplier: 10
}, {
id: 30,
name: 'Paradox',
rarity: 'Unique',
asset: 'paradox_unique',
multiplier: 10
}, {
id: 31,
name: 'Nova',
rarity: 'Special',
asset: 'nova_special',
multiplier: 6
}, {
id: 32,
name: 'Nebula',
rarity: 'Special',
asset: 'nebula_special',
multiplier: 6
}, {
id: 33,
name: 'Titan',
rarity: 'Special',
asset: 'titan_special',
multiplier: 6
}, {
id: 34,
name: 'Phantom',
rarity: 'Special',
asset: 'phantom_special',
multiplier: 6
}, {
id: 35,
name: 'Sphinx',
rarity: 'Special',
asset: 'sphinx_special',
multiplier: 6
}, {
id: 36,
name: 'Oracle',
rarity: 'Special',
asset: 'oracle_special',
multiplier: 6
}, {
id: 37,
name: 'Dust',
rarity: 'Uncommon',
asset: 'dust_uncommon',
multiplier: 3
}, {
id: 38,
name: 'Spore',
rarity: 'Uncommon',
asset: 'spore_uncommon',
multiplier: 3
}, {
id: 39,
name: 'Tectonic',
rarity: 'Rare',
asset: 'tectonic_rare',
multiplier: 4
}, {
id: 40,
name: 'Biosphere',
rarity: 'Ultra-Rare',
asset: 'biosphere_ultra',
multiplier: 5
}, {
id: 41,
name: 'Pulsar',
rarity: 'Special',
asset: 'pulsar_special',
multiplier: 6
}, {
id: 42,
name: 'Antimatter',
rarity: 'Legendary',
asset: 'antimatter_legendary',
multiplier: 7
}, {
id: 43,
name: 'Singularity',
rarity: 'Legendary',
asset: 'singularity_legendary',
multiplier: 7
}, {
id: 44,
name: 'Entropy',
rarity: 'Unique',
asset: 'entropy_unique',
multiplier: 10
}];
// Definimos los dropRates base SIN los IDs hardcodeados
var dropRates = [{
rarity: 'Common',
chance: 45,
ids: []
}, {
rarity: 'Uncommon',
chance: 20,
ids: []
}, {
rarity: 'Rare',
chance: 15,
ids: []
}, {
rarity: 'Ultra-Rare',
chance: 10,
ids: []
}, {
rarity: 'Special',
chance: 6,
ids: []
}, {
rarity: 'Legendary',
chance: 3,
ids: []
}, {
rarity: 'Unique',
chance: 1,
ids: []
}];
var galleryDisplayOrder = [];
// Función para inicializar los datos dinámicamente
function initializeGameData() {
// 1. Llenar los IDs del Gacha automáticamente
for (var i = 0; i < characterDatabase.length; i++) {
var charData = characterDatabase[i];
for (var j = 0; j < dropRates.length; j++) {
if (dropRates[j].rarity === charData.rarity) {
dropRates[j].ids.push(charData.id);
break;
}
}
}
// 2. Generar el orden de la Galería (Por Rareza y luego por ID)
var rarityWeights = {
'Common': 1,
'Uncommon': 2,
'Rare': 3,
'Ultra-Rare': 4,
'Special': 5,
'Legendary': 6,
'Unique': 7
};
// Clonamos la base de datos para no alterar la original y la ordenamos
var sortedCharacters = characterDatabase.slice().sort(function (a, b) {
if (rarityWeights[a.rarity] !== rarityWeights[b.rarity]) {
return rarityWeights[a.rarity] - rarityWeights[b.rarity];
}
return a.id - b.id;
});
// Extraemos solo los IDs ordenados para la galería
for (var k = 0; k < sortedCharacters.length; k++) {
galleryDisplayOrder.push(sortedCharacters[k].id);
}
}
// Ejecutamos la inicialización
initializeGameData();
// Función de ayuda para obtener IDs por rareza (Útil para el Pity System)
function getIdsByRarity(targetRarity) {
for (var i = 0; i < dropRates.length; i++) {
if (dropRates[i].rarity === targetRarity) {
return dropRates[i].ids;
}
}
return [];
}
var gameState = {
credits: 0,
incomePerTick: 1,
collectedCharacters: {},
totalCharactersCollected: 0,
incomeLevels: [],
tickCounter: 0,
equippedCharacters: [],
pityCounter: 0 // Contador de Lástima
};
// Estado temporal para controlar la vista de la galería
var galleryState = {
currentPage: 0,
itemsPerPage: 9,
// Cuadrícula de 3x3
selectedElementId: 0
};
// --- Funciones Base de Estado ---
function getGachaCost() {
return Math.floor(GAME_CONFIG.BASE_GACHA_COST * Math.pow(GAME_CONFIG.GACHA_COST_MULTIPLIER, gameState.totalCharactersCollected));
}
function recalculateIncome() {
// Recalcula incomePerTick desde cero a partir del estado real del juego.
// Esto evita que el income guardado se desincronice con los personajes y upgrades.
var total = 1; // Base income
// Sumar aportes de personajes colectados
for (var charId in gameState.collectedCharacters) {
if (gameState.collectedCharacters[charId]) {
var cData = characterDatabase[parseInt(charId)];
if (cData) {
total += cData.multiplier;
}
}
}
// Sumar aportes de upgrades comprados
// Cada upgrade de nivel i comprado N veces aporta baseIncome * N
// baseIncome por nivel: [1, 2, 3, 5, 10, 20, 50, 100, 200, 300]
var baseIncomes = [1, 2, 3, 5, 10, 20, 50, 100, 200, 300];
for (var lvl = 0; lvl < gameState.incomeLevels.length; lvl++) {
total += baseIncomes[lvl] * gameState.incomeLevels[lvl];
}
gameState.incomePerTick = total;
}
function loadGame() {
gameState.collectedCharacters = {};
gameState.incomeLevels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
gameState.equippedCharacters = [];
if (storage.credits !== undefined) {
gameState.credits = storage.credits;
gameState.totalCharactersCollected = storage.totalCharactersCollected;
gameState.tickCounter = storage.tickCounter || 0;
gameState.pityCounter = storage.pityCounter || 0;
var collectedIds = (storage.collectedCharacterIds || '').split(',');
for (var i = 0; i < collectedIds.length; i++) {
if (collectedIds[i] !== '') {
gameState.collectedCharacters[parseInt(collectedIds[i])] = true;
}
}
var upgradedLevels = (storage.upgradedLevelCounts || '').split(',');
for (var j = 0; j < upgradedLevels.length; j++) {
if (upgradedLevels[j] !== '') {
gameState.incomeLevels[j] = parseInt(upgradedLevels[j]) || 0;
}
}
var equippedStr = storage.equippedCharactersIds || '';
if (equippedStr !== '') {
var eqSplit = equippedStr.split(',');
for (var k = 0; k < eqSplit.length; k++) {
if (eqSplit[k] !== '') {
var eqId = parseInt(eqSplit[k]);
// Solo restaurar si el ID existe en la DB y fue colectado
if (characterDatabase[eqId] && gameState.collectedCharacters[eqId]) {
gameState.equippedCharacters.push(eqId);
}
}
}
}
// Recalcular income desde cero en lugar de confiar en el valor guardado
recalculateIncome();
}
}
function saveGame() {
storage.credits = gameState.credits;
// incomePerTick NO se guarda: se recalcula al cargar desde personajes y upgrades
storage.totalCharactersCollected = gameState.totalCharactersCollected;
storage.tickCounter = gameState.tickCounter;
storage.pityCounter = gameState.pityCounter;
var collectedIds = '';
for (var charId in gameState.collectedCharacters) {
if (gameState.collectedCharacters[charId]) {
collectedIds += charId + ',';
}
}
storage.collectedCharacterIds = collectedIds;
var upgradedLevels = '';
for (var i = 0; i < gameState.incomeLevels.length; i++) {
upgradedLevels += gameState.incomeLevels[i] + ',';
}
storage.upgradedLevelCounts = upgradedLevels;
var equippedIds = '';
for (var e = 0; e < gameState.equippedCharacters.length; e++) {
equippedIds += gameState.equippedCharacters[e] + ',';
}
storage.equippedCharactersIds = equippedIds;
}
loadGame();
// --- Play Background Music ---
LK.playMusic('background_music', {
loop: true
});
// --- UI Construction ---
var creditsText = new Text2('Credits: 0', {
size: 100,
fill: '#FFD700'
});
creditsText.anchor.set(0.5, 0);
creditsText.x = 1024;
creditsText.y = 50;
game.addChild(creditsText);
var incomeText = new Text2('Income: 1/tick', {
size: 80,
fill: '#00FF00'
});
incomeText.anchor.set(0.5, 0);
incomeText.x = 1024;
incomeText.y = 160;
game.addChild(incomeText);
var collectionText = new Text2('Collection: 0/' + characterDatabase.length, {
size: 60,
fill: '#87CEEB'
});
collectionText.anchor.set(0.5, 0);
collectionText.x = 1024;
collectionText.y = 250;
game.addChild(collectionText);
// --- SISTEMA DE ÓRBITA (A LA DERECHA) ---
var orbitContainer = new Container();
orbitContainer.x = 1536; // 3/4 de la pantalla
orbitContainer.y = 1366; // Centro vertical
game.addChild(orbitContainer);
var orbitingSprites = [];
var orbitAngle = 0;
function updateEquippedDisplay() {
while (orbitContainer.children.length > 0) {
orbitContainer.children[0].destroy();
}
orbitingSprites = [];
for (var i = 0; i < gameState.equippedCharacters.length; i++) {
var charId = gameState.equippedCharacters[i];
var charData = characterDatabase[charId];
var sprite = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
sprite.scale.set(0.55, 0.55);
orbitContainer.addChild(sprite);
orbitingSprites.push(sprite);
}
}
updateEquippedDisplay();
// --- Manual Clicker Core (A LA DERECHA) ---
var coreGraphic = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
var coreButton = new Container();
coreButton.addChild(coreGraphic);
coreButton.x = 1536; // 3/4 de la pantalla
coreButton.y = 1366; // Centro vertical
game.addChild(coreButton);
var coreText = new Text2('TAP', {
size: 50,
fill: '#ffffff'
});
coreText.anchor.set(0.5, 0.5);
coreButton.addChild(coreText);
// --- Botón Gacha Individual (Movido a la Izquierda) ---
var gachaBtnGraphic = LK.getAsset('gacha_button', {
anchorX: 0.5,
anchorY: 0.5
});
gachaBtnGraphic.scale.set(1.6, 1.8);
var gachaButton = new Container();
gachaButton.addChild(gachaBtnGraphic);
gachaButton.x = 600; // Antes 1024
gachaButton.y = 2500;
game.addChild(gachaButton);
var gachaText = new Text2('PULL (' + getGachaCost() + ')', {
size: 55,
fill: '#ffffff'
});
gachaText.anchor.set(0.5, 0.5);
gachaText.y = -20;
gachaButton.addChild(gachaText);
var pityText = new Text2('Pity: 0/10', {
size: 40,
fill: '#FFD700'
});
pityText.anchor.set(0.5, 0.5);
pityText.y = 30;
gachaButton.addChild(pityText);
var gachaEnabled = false;
// --- NUEVO: Botón Gacha x10 (A la Derecha) ---
var gacha10BtnGraphic = LK.getAsset('gacha_button', {
anchorX: 0.5,
anchorY: 0.5
});
gacha10BtnGraphic.scale.set(1.6, 1.8);
var gacha10Button = new Container();
gacha10Button.addChild(gacha10BtnGraphic);
gacha10Button.x = 1448;
gacha10Button.y = 2500;
game.addChild(gacha10Button);
var gacha10Text = new Text2('10x PULL', {
size: 55,
fill: '#ffffff'
});
gacha10Text.anchor.set(0.5, 0.5);
gacha10Text.y = -20;
gacha10Button.addChild(gacha10Text);
var pity10Text = new Text2('1 Guaranteed!', {
size: 40,
fill: '#00FF00'
});
pity10Text.anchor.set(0.5, 0.5);
pity10Text.y = 30;
gacha10Button.addChild(pity10Text);
var gacha10Enabled = false;
// Gallery Button
var galleryBtnGraphic = LK.getAsset('gallery_button', {
anchorX: 0.5,
anchorY: 0.5
});
var galleryButton = new Container();
galleryButton.addChild(galleryBtnGraphic);
galleryButton.x = 1800;
galleryButton.y = 500;
game.addChild(galleryButton);
var galleryText = new Text2('GALLERY', {
size: 50,
fill: '#ffffff'
});
galleryText.anchor.set(0.5, 0.5);
galleryButton.addChild(galleryText);
// Debug Reset Button
/*
var debugBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var debugButton = new Container();
debugButton.addChild(debugBtnGraphic);
debugButton.x = 300;
debugButton.y = 100;
game.addChild(debugButton);
var debugText = new Text2('RESET', {
size: 40,
fill: '#ff4444'
});
debugText.anchor.set(0.5, 0.5);
debugButton.addChild(debugText);*/
// Upgrade Levels Container (A LA IZQUIERDA Y MÁS GRANDES)
var levelContainers = [];
var levelStartY = 500; // Empezamos un poco más arriba
var levelSpacing = 180; // Más espacio vertical porque los botones crecieron
for (var i = 0; i < 10; i++) {
var levelContainer = new IncomeLevel();
levelContainer.init(i, [1, 2, 3, 5, 10, 20, 50, 100, 200, 300][i]);
levelContainer.updateData(gameState.incomeLevels[i]);
levelContainer.x = 512; // 1/4 de la pantalla hacia la izquierda
levelContainer.y = levelStartY + i * levelSpacing;
game.addChild(levelContainer);
levelContainers.push(levelContainer);
}
// Modal Variables
var modalOpen = false;
var modalContainer = null;
// --- Funciones Lógicas ---
function openGallery() {
if (modalOpen) {
return;
}
modalOpen = true;
galleryState.currentPage = 0;
galleryState.selectedElementId = 0;
// Buscar el primer personaje recolectado para mostrarlo por defecto al abrir
for (var s = 0; s < characterDatabase.length; s++) {
if (gameState.collectedCharacters[s]) {
galleryState.selectedElementId = s;
break;
}
}
modalContainer = new Container();
var modalBg = LK.getAsset('modal_bg', {
anchorX: 0.5,
anchorY: 0.5
});
modalContainer.addChild(modalBg);
modalContainer.x = 1024;
modalContainer.y = 1366;
var galleryTitle = new Text2('Element Gallery', {
size: 80,
fill: '#FFD700'
});
galleryTitle.anchor.set(0.5, 0);
galleryTitle.x = 0;
galleryTitle.y = -1100;
modalContainer.addChild(galleryTitle);
var closeBtnGraphic = LK.getAsset('modal_close_btn', {
anchorX: 0.5,
anchorY: 0.5
});
var closeBtn = new Container();
closeBtn.addChild(closeBtnGraphic);
closeBtn.x = 800;
closeBtn.y = -1050;
modalContainer.addChild(closeBtn);
var closeBtnText = new Text2('X', {
size: 60,
fill: '#ffffff'
});
closeBtnText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeBtnText);
closeBtn.down = function () {
closeModal();
LK.getSound('ui_click').play();
};
// Contenedor dinámico que se refresca
var contentContainer = new Container();
modalContainer.addChild(contentContainer);
function renderGalleryContent() {
while (contentContainer.children.length > 0) {
contentContainer.children[0].destroy();
}
var totalItems = galleryDisplayOrder.length;
var totalPages = Math.ceil(totalItems / galleryState.itemsPerPage);
var startIndex = galleryState.currentPage * galleryState.itemsPerPage;
var endIndex = Math.min(startIndex + galleryState.itemsPerPage, totalItems);
// --- PANEL IZQUIERDO: CUADRÍCULA 3x3 ---
var gridStartX = -750;
var gridStartY = -700;
var spacing = 400;
for (var i = startIndex; i < endIndex; i++) {
var localIndex = i - startIndex;
var col = localIndex % 3;
var row = Math.floor(localIndex / 3);
// AHORA BUSCAMOS EL ID BASADO EN EL NUEVO ORDEN
var actualCharId = galleryDisplayOrder[i];
var charData = characterDatabase[actualCharId];
var isCollected = gameState.collectedCharacters[actualCharId];
var displayChar = new Character();
var displayName = isCollected ? charData.name : "???";
// Siempre revelamos la rareza (Spoiler visual)
var displayRarity = charData.rarity;
displayChar.init(actualCharId, displayName, displayRarity, charData.asset, charData.multiplier);
displayChar.x = gridStartX + col * spacing;
displayChar.y = gridStartY + row * (spacing * 1.2);
// Efecto Silueta Inteligente
if (!isCollected) {
displayChar.graphicObj.tint = 0x000000; // Personaje en negro
displayChar.nameObj.fill = '#555555'; // Letras del nombre en gris
displayChar.frameObj.alpha = 0.05; // Apagamos casi todo el brillo del marco
}
// Indicador visual de elemento seleccionado
if (galleryState.selectedElementId === actualCharId) {
displayChar.scale.set(1.15, 1.15); // Ligeramente más grande al seleccionarlo
}
(function (idToSelect) {
displayChar.down = function () {
if (galleryState.selectedElementId !== idToSelect) {
galleryState.selectedElementId = idToSelect;
LK.getSound('ui_click').play();
renderGalleryContent();
}
};
})(actualCharId);
contentContainer.addChild(displayChar);
}
var pageText = new Text2('Page ' + (galleryState.currentPage + 1) + ' / ' + totalPages, {
size: 80,
fill: '#FFFFFF'
});
pageText.anchor.set(0.5, 0.5);
pageText.x = -300;
pageText.y = 800;
contentContainer.addChild(pageText);
var collectionStatus = new Text2('Collected: ' + gameState.totalCharactersCollected + '/' + characterDatabase.length, {
size: 60,
fill: '#87CEEB'
});
collectionStatus.anchor.set(0.5, 0.5);
collectionStatus.x = -300;
collectionStatus.y = 900;
contentContainer.addChild(collectionStatus);
if (galleryState.currentPage > 0) {
var prevBtn = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
prevBtn.scale.set(0.6, 0.8);
prevBtn.x = -550;
prevBtn.y = 650;
var prevTxt = new Text2('< PREV', {
size: 70,
fill: '#FFF'
});
prevTxt.anchor.set(0.5, 0.5);
prevBtn.addChild(prevTxt);
prevBtn.down = function () {
galleryState.currentPage--;
LK.getSound('ui_click').play();
renderGalleryContent();
};
contentContainer.addChild(prevBtn);
}
if (galleryState.currentPage < totalPages - 1) {
var nextBtn = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
nextBtn.scale.set(0.6, 0.8);
nextBtn.x = -50;
nextBtn.y = 650;
var nextTxt = new Text2('NEXT >', {
size: 70,
fill: '#FFF'
});
nextTxt.anchor.set(0.5, 0.5);
nextBtn.addChild(nextTxt);
nextBtn.down = function () {
galleryState.currentPage++;
LK.getSound('ui_click').play();
renderGalleryContent();
};
contentContainer.addChild(nextBtn);
}
// --- PANEL DERECHO: DETALLES Y EQUIPAR ---
var rightPanelX = 550;
var detailData = characterDatabase[galleryState.selectedElementId];
var isDetailCollected = gameState.collectedCharacters[galleryState.selectedElementId];
if (detailData) {
var bigGraphic = LK.getAsset(detailData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
bigGraphic.scale.set(1.35, 1.35);
bigGraphic.x = rightPanelX;
bigGraphic.y = -250;
if (!isDetailCollected) {
bigGraphic.tint = 0x000000;
}
contentContainer.addChild(bigGraphic);
var detailNameText = isDetailCollected ? detailData.name : "???";
var detailName = new Text2(detailNameText, {
size: 80,
fill: '#FFF'
});
detailName.anchor.set(0.5, 0);
detailName.x = rightPanelX;
detailName.y = 200;
contentContainer.addChild(detailName);
// Mapa de colores String para la UI de detalles
var rarityColorHexStr = GAME_CONFIG.RARITIES[detailData.rarity] ? GAME_CONFIG.RARITIES[detailData.rarity].colorStr : '#FFFFFF';
var detailRarity = new Text2('[' + detailData.rarity + ']', {
size: 60,
fill: rarityColorHexStr
});
detailRarity.anchor.set(0.5, 0);
detailRarity.x = rightPanelX;
detailRarity.y = 280;
contentContainer.addChild(detailRarity);
if (isDetailCollected) {
var incomeInfo = new Text2('Income Boost: +' + detailData.multiplier + '/tick', {
size: 55,
fill: '#00FF00'
});
incomeInfo.anchor.set(0.5, 0);
incomeInfo.x = rightPanelX;
incomeInfo.y = 350;
contentContainer.addChild(incomeInfo);
}
// LÓGICA DEL BOTÓN EQUIPAR
var orbitStatusText = new Text2('Orbiting: ' + gameState.equippedCharacters.length + ' / ' + GAME_CONFIG.MAX_EQUIPPED, {
size: 60,
fill: '#87CEEB'
});
orbitStatusText.anchor.set(0.5, 0);
orbitStatusText.x = rightPanelX;
orbitStatusText.y = 450;
contentContainer.addChild(orbitStatusText);
if (isDetailCollected) {
var isEquipped = gameState.equippedCharacters.indexOf(galleryState.selectedElementId) > -1;
var equipBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
equipBtnGraphic.scale.set(1.5, 1.2);
equipBtnGraphic.tint = isEquipped ? 0xFF4444 : 0x32CD32;
var equipBtn = new Container();
equipBtn.addChild(equipBtnGraphic);
equipBtn.x = rightPanelX;
equipBtn.y = 650;
var equipBtnText = new Text2(isEquipped ? 'UNEQUIP' : 'EQUIP', {
size: 50,
fill: '#ffffff'
});
equipBtnText.anchor.set(0.5, 0.5);
equipBtn.addChild(equipBtnText);
equipBtn.down = function () {
if (isEquipped) {
var index = gameState.equippedCharacters.indexOf(galleryState.selectedElementId);
gameState.equippedCharacters.splice(index, 1);
LK.getSound('ui_click').play();
} else {
if (gameState.equippedCharacters.length >= GAME_CONFIG.MAX_EQUIPPED) {
// Órbita llena: mostrar texto de aviso en lugar de desplazar silenciosamente
var fullMsg = new FloatingText('Orbit full! Unequip one first.', 1024, 1366, '#FF4444');
game.addChild(fullMsg);
return;
}
gameState.equippedCharacters.push(galleryState.selectedElementId);
LK.getSound('ui_click').play();
}
updateEquippedDisplay();
saveGame();
renderGalleryContent();
};
contentContainer.addChild(equipBtn);
} else {
var lockText = new Text2('LOCKED', {
size: 50,
fill: '#FF4444'
});
lockText.anchor.set(0.5, 0.5);
lockText.x = rightPanelX;
lockText.y = 400;
contentContainer.addChild(lockText);
}
}
}
renderGalleryContent();
game.addChild(modalContainer);
}
function closeModal() {
if (modalContainer) {
modalContainer.destroy();
modalOpen = false;
}
}
// --- Lógica de tirada de Gacha centralizada ---
// Retorna una rareza de pity (Special, Legendary o Unique)
function rollPityRarity() {
var pityRoll = Math.random() * 100;
if (pityRoll <= 60) {
return {
rarity: 'Special',
ids: getIdsByRarity('Special')
};
} else if (pityRoll <= 90) {
return {
rarity: 'Legendary',
ids: getIdsByRarity('Legendary')
};
} else {
return {
rarity: 'Unique',
ids: getIdsByRarity('Unique')
};
}
}
// Tirada normal con soft pity: a partir del pull 6, la probabilidad de rarezas altas sube gradualmente
function rollNormalRarity() {
var softBonus = Math.max(0, gameState.pityCounter - 5) * 3;
var dynamicRates = [];
for (var i = 0; i < dropRates.length; i++) {
var rate = {
rarity: dropRates[i].rarity,
ids: dropRates[i].ids,
chance: dropRates[i].chance
};
if (rate.rarity === 'Special') {
rate.chance = Math.min(30, 6 + softBonus);
} else if (rate.rarity === 'Legendary') {
rate.chance = Math.min(20, 3 + softBonus * 0.5);
} else if (rate.rarity === 'Unique') {
rate.chance = Math.min(10, 1 + softBonus * 0.25);
}
dynamicRates.push(rate);
}
var roll = Math.random() * 100;
var cumulativeChance = 0;
for (var j = 0; j < dynamicRates.length; j++) {
cumulativeChance += dynamicRates[j].chance;
if (roll <= cumulativeChance) {
return dynamicRates[j];
}
}
return dynamicRates[0];
}
// Registra un personaje obtenido y retorna si es nuevo
function registerCharacterObtained(charId) {
var charData = characterDatabase[charId];
if (!gameState.collectedCharacters[charId]) {
gameState.collectedCharacters[charId] = true;
gameState.totalCharactersCollected++;
gameState.incomePerTick += charData.multiplier;
return true;
}
return false;
}
function performGachaPull() {
var currentCost = getGachaCost();
if (gameState.credits < currentCost) {
return;
}
gameState.credits -= currentCost;
gameState.pityCounter++;
var selectedRarity = null;
// Hard Pity en 10: garantiza rareza alta
if (gameState.pityCounter >= 10) {
selectedRarity = rollPityRarity();
gameState.pityCounter = 0;
} else {
// Soft pity: probabilidades aumentan gradualmente desde pull 6
selectedRarity = rollNormalRarity();
if (selectedRarity.rarity === 'Special' || selectedRarity.rarity === 'Legendary' || selectedRarity.rarity === 'Unique') {
gameState.pityCounter = 0;
}
}
var characterIdArray = selectedRarity.ids;
var selectedCharacterId = characterIdArray[Math.floor(Math.random() * characterIdArray.length)];
var wasNew = registerCharacterObtained(selectedCharacterId);
var charData = characterDatabase[selectedCharacterId];
saveGame();
LK.getSound('gacha_pull').play();
if (wasNew) {
LK.getSound('gacha_pull_success').play();
}
modalOpen = true; // Bloquear UI durante la animación del resultado
// Consumimos los efectos visuales desde la configuración global
var currentEffect = GAME_CONFIG.RARITIES[charData.rarity] || {
colorNum: 0xFFFFFF,
scale: 0.70
};
var resultMessage = wasNew ? 'NEW: ' + charData.name + '!' : 'Duplicate: ' + charData.name;
var resultColor = wasNew ? '#2d633b' : '#b89e14';
var resultContainer = new Container();
var resultBg = LK.getAsset('pull_bg', {
anchorX: 0.5,
anchorY: 0.5
});
resultContainer.addChild(resultBg);
resultContainer.x = 1024;
resultContainer.y = 1366;
resultContainer.scale.set(0.1, 0.1);
tween(resultContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
var aura = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
aura.tint = currentEffect.colorNum;
var auraScaleFactor = currentEffect.scale * 2.5;
aura.scale.set(auraScaleFactor, auraScaleFactor);
aura.alpha = 0.5;
resultContainer.addChild(aura);
tween(aura, {
rotation: Math.PI * 2
}, {
duration: 2000
});
var resultGraphic = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
var scaleTarget = currentEffect.scale;
resultGraphic.scale.set(0.1, 0.1);
tween(resultGraphic, {
scaleX: scaleTarget + 0.05,
scaleY: scaleTarget + 0.05
}, {
duration: 250,
onFinish: function onFinish() {
tween(resultGraphic, {
scaleX: scaleTarget,
scaleY: scaleTarget
}, {
duration: 150
});
}
});
resultGraphic.y = 0;
resultContainer.addChild(resultGraphic);
var safePadding = 720 * scaleTarget / 2 + 80;
var resultTitle = new Text2(resultMessage, {
size: 150,
fill: resultColor
});
resultTitle.anchor.set(0.5, 0.5);
resultTitle.y = -safePadding;
resultContainer.addChild(resultTitle);
var rarityDisplayColor = GAME_CONFIG.RARITIES[charData.rarity] ? GAME_CONFIG.RARITIES[charData.rarity].colorStr : '#FFFFFF';
var resultRarity = new Text2('[' + charData.rarity + ']', {
size: 90,
fill: rarityDisplayColor
});
resultRarity.anchor.set(0.5, 0.5);
resultRarity.y = safePadding;
resultContainer.addChild(resultRarity);
game.addChild(resultContainer);
LK.setTimeout(function () {
tween(resultContainer, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
resultContainer.destroy();
modalOpen = false; // Liberar UI al cerrar el resultado
}
});
}, 2000);
}
function performGachaPull10() {
var singleCost = getGachaCost();
var totalCost = singleCost * 10;
if (gameState.credits < totalCost) {
return;
}
gameState.credits -= totalCost;
var pullResults = [];
// 1. Las 9 tiradas normales (con soft pity activo)
for (var i = 0; i < 9; i++) {
gameState.pityCounter++;
var selectedRarity = rollNormalRarity();
if (selectedRarity.rarity === 'Special' || selectedRarity.rarity === 'Legendary' || selectedRarity.rarity === 'Unique') {
gameState.pityCounter = 0;
}
var charIdArray = selectedRarity.ids;
var selectedCharId = charIdArray[Math.floor(Math.random() * charIdArray.length)];
pullResults.push(characterDatabase[selectedCharId]);
registerCharacterObtained(selectedCharId);
}
// 2. La 10ª tirada (Lástima Garantizada — siempre rareza alta)
var pityRarity = rollPityRarity();
var pityCharIdArray = pityRarity.ids;
var pityCharId = pityCharIdArray[Math.floor(Math.random() * pityCharIdArray.length)];
pullResults.push(characterDatabase[pityCharId]);
registerCharacterObtained(pityCharId);
// Reiniciamos el pity porque acabamos de dar uno garantizado
gameState.pityCounter = 0;
saveGame();
LK.getSound('gacha_pull').play();
showMultiPullResults(pullResults);
}
function showMultiPullResults(results) {
modalOpen = true; // Bloquea la UI de fondo
var resultContainer = new Container();
var modalBg = LK.getAsset('modal_bg', {
anchorX: 0.5,
anchorY: 0.5
});
resultContainer.addChild(modalBg);
resultContainer.x = 1024;
resultContainer.y = 1366;
resultContainer.scale.set(0.1, 0.1);
tween(resultContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
var titleText = new Text2('10x PULL RESULTS', {
size: 100,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -900;
resultContainer.addChild(titleText);
// Configuramos la cuadrícula (2 filas x 5 columnas)
var startX = -600;
var startY = -400;
var spacingX = 300;
var spacingY = 400;
for (var i = 0; i < results.length; i++) {
var charData = results[i];
var col = i % 5;
var row = Math.floor(i / 5);
// Si es de rareza alta, le ponemos un aura detrás
if (charData.rarity === 'Special' || charData.rarity === 'Legendary' || charData.rarity === 'Unique') {
var aura = LK.getAsset('core_crystal', {
anchorX: 0.5,
anchorY: 0.5
});
aura.x = startX + col * spacingX;
aura.y = startY + row * spacingY;
aura.scale.set(1.5, 1.5);
aura.tint = 0xFFD700;
aura.alpha = 0.5;
resultContainer.addChild(aura);
tween(aura, {
rotation: Math.PI * 2
}, {
duration: 2000
});
}
var charGraphic = LK.getAsset(charData.asset, {
anchorX: 0.5,
anchorY: 0.5
});
charGraphic.x = startX + col * spacingX;
charGraphic.y = startY + row * spacingY;
charGraphic.scale.set(0.35, 0.35); // Más pequeños para que quepan
resultContainer.addChild(charGraphic);
}
// Botón para cerrar
var closeBtnGraphic = LK.getAsset('button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var closeBtn = new Container();
closeBtn.addChild(closeBtnGraphic);
closeBtn.y = 800;
resultContainer.addChild(closeBtn);
var closeText = new Text2('CONTINUE', {
size: 60,
fill: '#FFFFFF'
});
closeText.anchor.set(0.5, 0.5);
closeBtn.addChild(closeText);
closeBtn.down = function () {
LK.getSound('ui_click').play();
tween(resultContainer, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
onFinish: function onFinish() {
resultContainer.destroy();
modalOpen = false; // Liberamos la UI
}
});
};
game.addChild(resultContainer);
}
function upgradeIncomeLevel(levelIndex) {
var level = levelContainers[levelIndex];
if (gameState.credits >= level.currentCost) {
gameState.credits -= level.currentCost;
gameState.incomeLevels[levelIndex]++;
gameState.incomePerTick += level.baseIncome;
level.updateData(gameState.incomeLevels[levelIndex]);
saveGame();
LK.getSound('level_up').play();
// Confirmar visualmente la compra
var floatUpgrade = new FloatingText('+' + level.baseIncome + '/tick', level.x + (Math.random() * 60 - 30), level.y - 60, '#00FF00');
game.addChild(floatUpgrade);
}
}
// --- Event Handlers Directos ---
gachaButton.down = function () {
if (modalOpen) {
return;
}
if (gachaEnabled) {
performGachaPull();
LK.getSound('ui_click').play();
}
};
gacha10Button.down = function () {
if (modalOpen) {
return;
}
if (gacha10Enabled) {
performGachaPull10();
}
};
galleryButton.down = function () {
if (modalOpen) {
return;
}
openGallery();
LK.getSound('ui_click').play();
};
/*
debugButton.down = function () {
if (modalOpen) {
return;
}
gameState.credits = 0;
gameState.incomePerTick = 1;
gameState.collectedCharacters = {};
gameState.totalCharactersCollected = 0;
gameState.incomeLevels = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
gameState.equippedCharacters = [];
for (var i = 0; i < levelContainers.length; i++) {
levelContainers[i].updateData(0);
}
updateEquippedDisplay();
saveGame();
LK.getSound('ui_click').play();
};
*/
coreButton.down = function () {
if (modalOpen) {
return;
}
var clickGain = 1 + Math.floor(gameState.incomePerTick * 0.2);
gameState.credits += clickGain;
var floatText = new FloatingText('+' + clickGain, coreButton.x + (Math.random() * 100 - 50), coreButton.y - 150, '#00ffff');
game.addChild(floatText);
tween(coreGraphic, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 50,
onFinish: function onFinish() {
tween(coreGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 50
});
}
});
};
for (var i = 0; i < levelContainers.length; i++) {
(function (index) {
levelContainers[index].down = function () {
if (modalOpen) {
return;
}
upgradeIncomeLevel(index);
};
})(i);
}
// --- Variables para el Main Loop ---
var pulseTimer = 0;
var lastDisplayedCredits = -1;
var lastDisplayedIncome = -1;
var lastDisplayedCollection = -1;
var lastDisplayedGachaCost = -1;
var lastDisplayedPity = -1;
// --- Main Game Loop Optimizado ---
game.update = function () {
gameState.tickCounter++;
// LÓGICA DE ÓRBITA
var numOrbiting = orbitingSprites.length;
if (numOrbiting > 0) {
orbitAngle += GAME_CONFIG.ORBIT_SPEED;
var angleStep = Math.PI * 2 / numOrbiting;
for (var o = 0; o < numOrbiting; o++) {
var currentAngle = orbitAngle + o * angleStep;
orbitingSprites[o].x = Math.cos(currentAngle) * GAME_CONFIG.ORBIT_RADIUS;
orbitingSprites[o].y = Math.sin(currentAngle) * GAME_CONFIG.ORBIT_RADIUS;
//orbitingSprites[o].rotation += 0.01;
}
}
if (gameState.tickCounter % GAME_CONFIG.FPS === 0) {
gameState.credits += gameState.incomePerTick;
}
// --- Actualización de Costos y Textos ---
var currentCredits = Math.floor(gameState.credits);
var currentGachaCost = getGachaCost();
var currentGachaCost10 = currentGachaCost * 10;
// === INICIO DEL BLOQUE FALTANTE ===
if (currentCredits !== lastDisplayedCredits) {
creditsText.setText('Credits: ' + currentCredits);
lastDisplayedCredits = currentCredits;
}
if (gameState.incomePerTick !== lastDisplayedIncome) {
incomeText.setText('Income: ' + gameState.incomePerTick + '/tick');
lastDisplayedIncome = gameState.incomePerTick;
}
if (gameState.totalCharactersCollected !== lastDisplayedCollection) {
// Usamos characterDatabase.length para que se actualice solo si agregas más en el futuro
collectionText.setText('Collection: ' + gameState.totalCharactersCollected + '/' + characterDatabase.length);
lastDisplayedCollection = gameState.totalCharactersCollected;
}
if (currentGachaCost !== lastDisplayedGachaCost || gameState.pityCounter !== lastDisplayedPity) {
// Actualiza textos Botón x1
gachaText.setText('PULL (' + currentGachaCost + ')');
if (gameState.pityCounter >= 9) {
pityText.setText('Pity: READY!');
pityText.fill = '#00FF00';
} else if (gameState.pityCounter >= 6) {
// Soft pity activo: probabilidades subiendo
pityText.setText('Pity: ' + gameState.pityCounter + "/10 \u2191");
pityText.fill = '#FFA500';
} else {
pityText.setText('Pity: ' + gameState.pityCounter + '/10');
pityText.fill = '#FFD700';
}
// Actualiza textos Botón x10
gacha10Text.setText('10x (' + currentGachaCost10 + ')');
lastDisplayedGachaCost = currentGachaCost;
lastDisplayedPity = gameState.pityCounter;
}
// --- Control Visual de Botones Disponibles ---
gachaEnabled = gameState.credits >= currentGachaCost;
gacha10Enabled = gameState.credits >= currentGachaCost10;
// Efectos del Botón x1
if (gachaEnabled) {
gachaBtnGraphic.tint = 0xFF1493;
gachaText.tint = 0xFFFFFF;
pulseTimer += 0.05;
var scaleX = 1.6 + Math.sin(pulseTimer) * 0.05;
var scaleY = 1.8 + Math.sin(pulseTimer) * 0.05;
gachaBtnGraphic.scale.set(scaleX, scaleY);
} else {
gachaBtnGraphic.tint = 0x883366;
gachaText.tint = 0xAAAAAA;
gachaBtnGraphic.scale.set(1.6, 1.8);
}
// Efectos del Botón x10
if (gacha10Enabled) {
gacha10BtnGraphic.tint = 0xFF1493;
gacha10Text.tint = 0xFFFFFF;
var scaleX10 = 1.6 + Math.cos(pulseTimer) * 0.05; // Usamos cos para desfasar el pulso visual
var scaleY10 = 1.8 + Math.cos(pulseTimer) * 0.05;
gacha10BtnGraphic.scale.set(scaleX10, scaleY10);
} else {
gacha10BtnGraphic.tint = 0x883366;
gacha10Text.tint = 0xAAAAAA;
gacha10BtnGraphic.scale.set(1.6, 1.8);
}
for (var j = 0; j < levelContainers.length; j++) {
var level = levelContainers[j];
if (gameState.credits >= level.currentCost) {
level.children[0].tint = 0xffffff;
} else {
level.children[0].tint = 0x525252;
}
}
if (gameState.tickCounter % GAME_CONFIG.SAVE_INTERVAL === 0) {
saveGame();
}
};
Crea un rectángulo para asset de botón. Debe ser: color amarillo, rojo, verde, azul, gris, rosa dispuestos en forma diagonal irregular por el rectángulo. Los colores deben ser planos, y los bordes redondeados. In-Game asset. 2d. High contrast. No shadows. anime. simple
Crea un rectángulo para asset de botón. Debe ser: color rojo escarlata con borde en forma de rectángulo. Los colores deben ser planos, y los bordes redondeados. In-Game asset. 2d. High contrast. No shadows. anime. simple
Crea un rectángulo para asset de botón. Debe ser: color verde esmeralda con borde en forma de rectángulo. Los colores deben ser planos, y los bordes redondeados. In-Game asset. 2d. High contrast. No shadows. anime. simple
Crea un fondo de textura verde tipo anime. In-Game asset. 2d. High contrast. No shadows. Background. anime