Extending Monster Data
Esta página documenta código interno del motor disponible exclusivamente para licenciatarios de las ediciones Source y Studio.
El modelo de datos de monstruos de MCE está diseñado para la extensión. Esta página cubre las estructuras de datos clave, cómo agregar nuevos monstruos con formas personalizadas, cómo funciona el sistema de evolución y cómo extender el modelo de forma segura sin romper los archivos de guardado existentes.
Descripción General del Modelo de Datos
MonsterEntry (ScriptableObject — uno por especie)
├── DexNumber
├── AvailableForms: List<Form>
└── DataByForm: List<DataByFormEntry> ← una entrada por forma
├── Graphics (sprites, materiales, íconos, sprites de mundo)
├── Types (FirstType, SecondType)
├── Dex (descripción, especie, altura, peso, grito)
├── Abilities (normales + ocultas)
├── Training (EVYield, CatchRate, BaseFriendship, BaseExperience, GrowthRate)
├── Breeding (EggGroups, EggCycles, BreedingData, proporciones de género)
├── Wild (probabilidad de huida, objetos retenidos)
├── Moves (por nivel, al evolucionar, movimientos huevo, otros/MT)
├── BaseStats: Dictionary<Stat, byte>
├── Evolutions: List<EvolutionData> ← polimórfico
├── MegaEvolutions: Dictionary<Item, Form>
└── Flags (IsLegendary, IsMythical, IsUltraBeast, CanLearnAnyMove)
En tiempo de ejecución, un MonsterInstance envuelve un MonsterEntry con estado específico de la instancia: nivel, IVs, EVs, HP actual, apodo, objeto retenido, amistad y la forma activa.
Agregar un Nuevo Monstruo
Paso 1: Crear el Asset MonsterEntry
Usa el menú Assets > Create > MCE > Monster Database > Monster (o clic derecho Create > MCE > Monster Database > Monster) para crear un nuevo MonsterEntry ScriptableObject.
Configura estos campos:
- DexNumber — El índice del monstruo en la pokédex.
- LocalizableName — Clave de localización (auto-completada por el botón "Auto" basada en el nombre del activo, siguiendo el patrón
Monsters/{name}). - AvailableForms — Como mínimo, agrega un activo
Form(típicamente "Normal"). El motor lee las variantes shiny automáticamente desdeShinyVersionde cada forma.
Paso 2: Configurar DataByFormEntry
Para cada forma en AvailableForms, se almacena un DataByFormEntry en la lista DataByForm. Cada entrada contiene:
Graphics
| Campo | Tipo | Propósito |
|---|---|---|
Front / Back | Material | Materiales de sprite de batalla (hembra/defecto) |
FrontMale / BackMale | Material | Materiales de variante macho (se muestran si HasMaleMaterialOverride) |
FrontShiny / BackShiny | Material | Materiales de variante shiny |
Icon / IconShiny / IconMale / IconShinyMale | Sprite | Íconos de menú/equipo |
EggMaterial / EggIcon | Material / Sprite | Visuales del huevo |
WorldIdleSprite* / WorldWalkingSprite* | Sprite / List<Sprite> | Sprites de seguidor en el mundo (4 direcciones, con variantes macho/shiny) |
Types y Dex
public MonsterType FirstType; // Tipo primario
public MonsterType SecondType; // Tipo secundario (puede ser null/None)
public string DexDescriptionKey; // Clave de localización para la entrada de la pokédex
public MonsterSpecies Species; // ej., "Monstruo Semilla"
public float Height; // En metros
public float Weight; // En kilogramos
public AudioReference Cry; // Efecto de sonido
Abilities
public List<Ability> Abilities; // Habilidades normales (1-2)
public List<Ability> HiddenAbilities; // Habilidades ocultas
Training
public List<StatByteValuePair> EVYield; // Puntos de EV otorgados al derrotar
public byte CatchRate; // 0-255
public byte BaseFriendship; // Valor inicial de amistad
public uint BaseExperience; // Producción base de XP
public GrowthRate GrowthRate; // Tipo de curva de XP
Breeding
public List<EggGroup> EggGroups; // Grupos de cría compatibles
public byte EggCycles; // Pasos para eclosionar
public List<BreedingData> BreedingData; // Qué especies pueden nacer de los huevos
public List<PrehatchCallback> PrehatchCallbacks; // Cambios de forma pre-eclosión
public bool HasBinaryGender;
public float FemaleRatio; // 0.0 a 1.0
Base Stats
public SerializableDictionary<Stat, byte> BaseStats; // HP, Atk, Def, SpA, SpD, Spe
public SerializableDictionary<Stat, uint> StatLimits; // Límites máximos (ej., Shedinja HP=1)
Asignaciones de Movimientos
Los movimientos se organizan en cuatro categorías:
| Campo | Cuándo se Aprende |
|---|---|
MovesByLevel (List<MoveLevelPair>) | Al alcanzar un nivel específico |
OnEvolutionMoves (List<Move>) | Inmediatamente después de evolucionar a esta forma |
EggMoves (List<Move>) | Heredados a través de la cría |
OtherLearnMoves (List<Move>) | MTs, tutores u otros métodos |
El flag CanLearnAnyMove omite la lista OtherLearnMoves por completo, permitiendo que el monstruo aprenda cualquier movimiento de la base de datos (usado para monstruos especiales).
El Inspector valida los movimientos en tiempo real y marca los duplicados entre categorías.
Paso 3: Usar el Asistente de Creación de Monstruos
Haz clic en el botón Open Helper en cualquier DataByFormEntry para abrir la ventana de editor Tools > MCE > Monster Creation Helper, que proporciona un flujo de trabajo optimizado para completar todos los datos requeridos.
Paso 4: Registrar en la Base de Datos
Agrega el nuevo MonsterEntry al activo MonsterDatabaseInstance para que aparezca en la pokédex y esté disponible para todos los sistemas del motor.
El Sistema de Formas
Las formas permiten que una sola especie tenga múltiples variantes visuales y mecánicas (ej., formas regionales, Mega Evoluciones, diferencias de género).
Cada Form es un ScriptableObject con:
- Un identificador de nombre
- Un flag
HasShinyVersiony una referencia a su formaShinyVersion - Un flag
IsShinyen la variante shiny
MonsterEntry.AvailableForms lista todas las formas no shiny. Las formas shiny se derivan automáticamente a través de AvailableFormsWithShinnies.
Agregar una Nueva Forma
- Crea un activo ScriptableObject
Form. - Si la forma tiene una variante shiny, crea un segundo activo
Formy vincúlalos medianteShinyVersion/IsShiny. - Agrega la forma normal a la lista
AvailableFormsdel monstruo. - Aparecerá una nueva ranura
DataByFormEntryenDataByForm— completa los datos específicos de la forma (tipos, estadísticas, sprites, movimientos, evoluciones).
El Sistema de Evolución
Arquitectura
La evolución se basa en un patrón de comando polimórfico:
EvolutionData (base abstracta)
└── TargetSpeciesAndFormEvolutionData (abstracta — agrega TargetSpecies/TargetForm/KeepShinyIfItIs)
├── EvolveByLevel
├── EvolveByFriendship
│ └── EvolveByFriendshipAtSpecificTime
├── EvolveOnItemUse
├── EvolveWhenTraded
├── EvolveOnLevelUpHoldingItem
├── EvolveOnLevelUpWhenAMoveHasBeenLearnt
└── ... (más de 30 tipos de evolución incorporados)
Cada DataByFormEntry tiene un campo List<EvolutionData> Evolutions que usa [SerializeReference] para serialización polimórfica. Esto significa que puedes combinar diferentes tipos de evolución en el Inspector.
API Abstracta de EvolutionData
Cada subclase de evolución debe implementar estos métodos:
public abstract class EvolutionData
{
// Verificar condiciones — devuelve true si se cumple la condición
public abstract bool CanEvolveAfterLevelUp(MonsterInstance monster,
DayMoment currentTime,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);
public abstract bool CanEvolveAfterBattleThroughExtraData(MonsterInstance monster,
DayMoment currentTime,
out bool consumeHeldItem);
public abstract bool CanEvolveWhenUsingItem(MonsterInstance monster,
DayMoment currentTime,
Item item,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);
public abstract bool CanEvolveWhenTrading(MonsterInstance monster,
DayMoment currentTime,
MonsterInstance otherMonster,
PlayerCharacter playerCharacter,
out bool consumeHeldItem);
// En qué evoluciona
public abstract (MonsterEntry, Form) GetTargetEvolution(MonsterInstance monster, DayMoment currentTime);
// Hook post-evolución (opcional)
public virtual void AfterEvolutionCallback(MonsterInstance evolvedMonster, ...);
// Datos de visualización en la pokédex
public abstract List<DexMonsterRelationshipData> GetDexRelationships(...);
// Copia profunda
public abstract EvolutionData Clone();
}
Tipos de Evolución Incorporados
MCE incluye más de 30 tipos de evolución. Aquí están los más utilizados:
| Clase | Disparador |
|---|---|
EvolveByLevel | El monstruo alcanza un nivel objetivo |
EvolveByLevelAtSpecificTime | Nivel + momento del día |
EvolveByLevelWhenSpecificGender | Nivel + verificación de género |
EvolveByFriendship | Se alcanza un umbral de amistad |
EvolveByFriendshipAtSpecificTime | Amistad + momento del día |
EvolveByFriendshipWithMoveOfType | Amistad + conoce un movimiento de cierto tipo |
EvolveOnItemUse | El jugador usa un objeto específico en el monstruo |
EvolveOnItemUseAtSpecificTime | Uso de objeto + momento del día |
EvolveOnItemUseOnSpecificGender | Uso de objeto + verificación de género |
EvolveOnLevelUpHoldingItem | Sube de nivel mientras retiene un objeto específico |
EvolveOnLevelUpWhenAMoveHasBeenLearnt | Sube de nivel + conoce un movimiento específico |
EvolveOnLevelUpWithOtherMonInParty | Sube de nivel + otra especie está en el equipo |
EvolveWhenTraded | Intercambiado a otro jugador |
EvolveWhenTradedHoldingItem | Intercambiado mientras retiene un objeto específico |
EvolveWhenTradedWithMonster | Intercambiado por una especie específica |
EvolveByCriticalHitCounter | Asesta N golpes críticos en una sola batalla |
EvolveByDefeatingRivals | Derrota N monstruos de una especie específica |
EvolveByUsingMoveXTimes | Usa un movimiento específico N veces |
EvolveByEvolutionCounter | Evoluciona después de un umbral de contador |
EvolveByLevelWithSceneTag | Sube de nivel en una escena con una etiqueta específica |
EvolveToRandomSpeciesByLevel | Especie objetivo aleatoria al subir de nivel |
SplitEvolutionByLevel | Evolución dual especial (crea un segundo monstruo) |
Crear un Tipo de Evolución Personalizado
Aquí hay un ejemplo de una evolución "evolucionar cuando PS está por debajo del 50%":
using System;
using System.Collections.Generic;
using UnityEngine;
using OpenMon.MCE.Characters;
using OpenMon.MCE.GameFlow;
using OpenMon.MCE.MonsterDatabase.Items;
using OpenMon.MCE.MonsterDex;
using OpenMon.MCE.UI.Dex;
using OpenMon.Foundation.Localization;
namespace OpenMon.MCE.Monster.Evolution
{
[Serializable]
public class EvolveWhenLowHP : TargetSpeciesAndFormEvolutionData
{
[SerializeField]
[Range(0f, 1f)]
public float HPThreshold = 0.5f;
public override bool CanEvolveAfterLevelUp(MonsterInstance monster,
DayMoment currentTime,
PlayerCharacter playerCharacter,
out bool consumeHeldItem)
{
consumeHeldItem = false;
uint maxHP = MonsterMathHelper.CalculateStat(monster, Stat.Hp, null);
float hpRatio = (float)monster.CurrentHP / maxHP;
return monster.AllowEvolution(this) && hpRatio <= HPThreshold;
}
// Devuelve false para disparadores que esta evolución no usa
public override bool CanEvolveAfterBattleThroughExtraData(MonsterInstance m,
DayMoment t, out bool c) { c = false; return false; }
public override bool CanEvolveWhenUsingItem(MonsterInstance m, DayMoment t,
Item i, PlayerCharacter p, out bool c) { c = false; return false; }
public override bool CanEvolveWhenTrading(MonsterInstance m, DayMoment t,
MonsterInstance o, PlayerCharacter p, out bool c) { c = false; return false; }
public override List<DexMonsterRelationshipData> GetDexRelationships(
MonsterDexEntry entry, FormDexEntry formEntry, MonsterGender gender,
ILocalizer localizer)
{
return new List<DexMonsterRelationshipData>
{
new()
{
Species = TargetSpecies,
Form = TargetForm,
Gender = gender,
Mode = DexMonsterRelationshipData.RelationShipDisplayType.Text,
Text = "HP < " + (HPThreshold * 100) + "%",
LocalizableDescriptionKey = "Dex/Evolutions/LowHP"
}
};
}
public override EvolutionData Clone() =>
new EvolveWhenLowHP
{
HPThreshold = HPThreshold,
TargetSpecies = TargetSpecies,
TargetForm = TargetForm,
KeepShinyIfItIs = KeepShinyIfItIs
};
}
}
Debido a que Evolutions usa [SerializeReference], tu nueva subclase aparecerá automáticamente en el selector desplegable de tipos del Inspector al agregar entradas de evolución.
Cómo se Activa la Evolución en Tiempo de Ejecución
El EvolutionManager ScriptableObject orquesta la evolución en tiempo de ejecución:
- Después de subir de nivel,
TriggerEvolutionsAfterLevelUp()itera todos los monstruos del equipo conNeedsLevelUpEvolutionCheckestablecido entrue. - Para cada monstruo, itera
FormData.Evolutionsy llama aCanEvolveAfterLevelUp(). - En la primera coincidencia, llama a
TriggerEvolution(), que:- Carga la escena de evolución (fundido a negro).
- Reproduce la animación de evolución (puede ser cancelada por el jugador).
- Si no se cancela, llama a
AfterEvolutionCallback()para la lógica posterior a la evolución.
- Después de la batalla,
TriggerEvolutionAfterBattleThroughExtraData()maneja las evoluciones basadas en contadores (golpes críticos, uso de movimientos, etc.).
Consideraciones de Serialización
[SerializeField] vs [SerializeReference]
[SerializeField]— Usado para tipos concretos (estadísticas, sprites, datos básicos). Unity serializa por valor.[SerializeReference]— Usado para campos polimórficos comoList<EvolutionData>,List<BreedingData>yList<PrehatchCallback>. Unity almacena el nombre del tipo concreto, permitiendo que el Inspector muestre un selector de tipos.
Migración de Guardado
Nunca renombres campos serializados sin un plan de migración. Usa [FormerlySerializedAs("OldName")] para preservar datos al renombrar. MCE ya usa este patrón extensamente (ej., FrontShinny fue renombrado a FrontShiny).
Al agregar nuevos campos a DataByFormEntry o MonsterInstance:
- Agrega el campo con un valor predeterminado sensato.
- Los activos existentes se deserializarán con el valor predeterminado — sin pérdida de datos.
- Los archivos de guardado usan serialización JSON mediante
MCESavesSerializer. Los nuevos campos seránnull/predeterminados al cargar guardados antiguos. Maneja esto en tu código con verificaciones de null o inicialización predeterminada. - Nunca elimines campos de clases serializadas. Márcalos como
[Obsolete]e ignóralos.
Extender MonsterEntry
Para agregar un nuevo campo de nivel superior al propio MonsterEntry (no datos por forma):
- Agrega el campo con
[SerializeField]enMonsterEntry.cs. - Usa
[FormerlySerializedAs]si renombras un campo existente. - Los activos de monstruo existentes se cargarán con el valor predeterminado — actualízalos en lote usando un script de editor o el Inspector.
Para agregar nuevos datos por forma, agrega campos a DataByFormEntry siguiendo el patrón de grupo/sección existente con atributos de TriInspector.