Saltar al contenido principal

Extending Monster Data

Solo ediciones Source y Studio

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 desde ShinyVersion de cada forma.

Paso 2: Configurar DataByFormEntry​

Para cada forma en AvailableForms, se almacena un DataByFormEntry en la lista DataByForm. Cada entrada contiene:

Graphics​

CampoTipoPropósito
Front / BackMaterialMateriales de sprite de batalla (hembra/defecto)
FrontMale / BackMaleMaterialMateriales de variante macho (se muestran si HasMaleMaterialOverride)
FrontShiny / BackShinyMaterialMateriales de variante shiny
Icon / IconShiny / IconMale / IconShinyMaleSpriteÍconos de menú/equipo
EggMaterial / EggIconMaterial / SpriteVisuales 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:

CampoCuá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 HasShinyVersion y una referencia a su forma ShinyVersion
  • Un flag IsShiny en 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​

  1. Crea un activo ScriptableObject Form.
  2. Si la forma tiene una variante shiny, crea un segundo activo Form y vincúlalos mediante ShinyVersion / IsShiny.
  3. Agrega la forma normal a la lista AvailableForms del monstruo.
  4. Aparecerá una nueva ranura DataByFormEntry en DataByForm — 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:

ClaseDisparador
EvolveByLevelEl monstruo alcanza un nivel objetivo
EvolveByLevelAtSpecificTimeNivel + momento del día
EvolveByLevelWhenSpecificGenderNivel + verificación de género
EvolveByFriendshipSe alcanza un umbral de amistad
EvolveByFriendshipAtSpecificTimeAmistad + momento del día
EvolveByFriendshipWithMoveOfTypeAmistad + conoce un movimiento de cierto tipo
EvolveOnItemUseEl jugador usa un objeto específico en el monstruo
EvolveOnItemUseAtSpecificTimeUso de objeto + momento del día
EvolveOnItemUseOnSpecificGenderUso de objeto + verificación de género
EvolveOnLevelUpHoldingItemSube de nivel mientras retiene un objeto específico
EvolveOnLevelUpWhenAMoveHasBeenLearntSube de nivel + conoce un movimiento específico
EvolveOnLevelUpWithOtherMonInPartySube de nivel + otra especie está en el equipo
EvolveWhenTradedIntercambiado a otro jugador
EvolveWhenTradedHoldingItemIntercambiado mientras retiene un objeto específico
EvolveWhenTradedWithMonsterIntercambiado por una especie específica
EvolveByCriticalHitCounterAsesta N golpes críticos en una sola batalla
EvolveByDefeatingRivalsDerrota N monstruos de una especie específica
EvolveByUsingMoveXTimesUsa un movimiento específico N veces
EvolveByEvolutionCounterEvoluciona después de un umbral de contador
EvolveByLevelWithSceneTagSube de nivel en una escena con una etiqueta específica
EvolveToRandomSpeciesByLevelEspecie objetivo aleatoria al subir de nivel
SplitEvolutionByLevelEvolució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:

  1. Después de subir de nivel, TriggerEvolutionsAfterLevelUp() itera todos los monstruos del equipo con NeedsLevelUpEvolutionCheck establecido en true.
  2. Para cada monstruo, itera FormData.Evolutions y llama a CanEvolveAfterLevelUp().
  3. 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.
  4. 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 como List<EvolutionData>, List<BreedingData> y List<PrehatchCallback>. Unity almacena el nombre del tipo concreto, permitiendo que el Inspector muestre un selector de tipos.

Migración de Guardado​

Crítico

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:

  1. Agrega el campo con un valor predeterminado sensato.
  2. Los activos existentes se deserializarán con el valor predeterminado — sin pérdida de datos.
  3. Los archivos de guardado usan serialización JSON mediante MCESavesSerializer. Los nuevos campos serán null/predeterminados al cargar guardados antiguos. Maneja esto en tu código con verificaciones de null o inicialización predeterminada.
  4. 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):

  1. Agrega el campo con [SerializeField] en MonsterEntry.cs.
  2. Usa [FormerlySerializedAs] si renombras un campo existente.
  3. 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.