Перейти к основному содержимому

Extending Monster Data

Только для редакций Source и Studio

Эта страница описывает внутренний код движка, доступный исключительно лицензиатам редакций Source и Studio.

Модель данных монстров MCE спроектирована для расширения. Эта страница охватывает ключевые структуры данных, добавление новых монстров с пользовательскими формами, работу системы эволюции и безопасное расширение модели без нарушения существующих файлов сохранения.

Обзор модели данных​

MonsterEntry (ScriptableObject — один на вид)
├── DexNumber
├── AvailableForms: List<Form>
└── DataByForm: List<DataByFormEntry> ← одна запись на форму
├── Graphics (спрайты, материалы, иконки, мировые спрайты)
├── Types (FirstType, SecondType)
├── Dex (описание, вид, рост, вес, крик)
├── Abilities (обычные + скрытые)
├── Training (EVYield, CatchRate, BaseFriendship, BaseExperience, GrowthRate)
├── Breeding (EggGroups, EggCycles, BreedingData, соотношение полов)
├── Wild (шанс побега, удерживаемые предметы)
├── Moves (по уровню, при эволюции, яйцевые атаки, другие/TM)
├── BaseStats: Dictionary<Stat, byte>
├── Evolutions: List<EvolutionData> ← полиморфный
├── MegaEvolutions: Dictionary<Item, Form>
└── Flags (IsLegendary, IsMythical, IsUltraBeast, CanLearnAnyMove)

Во время выполнения MonsterInstance оборачивает MonsterEntry с состоянием конкретного экземпляра: уровень, IVs, EVs, текущее HP, прозвище, удерживаемый предмет, дружба и активная форма.

Добавление нового монстра​

Шаг 1: Создание актива MonsterEntry​

Используйте меню Assets > Create > MCE > Monster Database > Monster (или правый клик Create > MCE > Monster Database > Monster), чтобы создать новый MonsterEntry ScriptableObject.

Заполните следующие поля:

  • DexNumber — Индекс монстра в покедексе.
  • LocalizableName — Ключ локализации (автозаполняется кнопкой "Auto" на основе имени актива по шаблону Monsters/{name}).
  • AvailableForms — Как минимум, добавьте один актив Form (обычно "Normal"). Движок автоматически читает блестящие варианты из ShinyVersion каждой формы.

Шаг 2: Настройка DataByFormEntry​

Для каждой формы в AvailableForms запись DataByFormEntry хранится в списке DataByForm. Каждая запись содержит:

Graphics​

ПолеТипНазначение
Front / BackMaterialМатериалы боевых спрайтов (самка/по умолчанию)
FrontMale / BackMaleMaterialМатериалы для самца (показываются, если HasMaleMaterialOverride)
FrontShiny / BackShinyMaterialМатериалы блестящих вариантов
Icon / IconShiny / IconMale / IconShinyMaleSpriteИконки меню/команды
EggMaterial / EggIconMaterial / SpriteВизуализация яйца
WorldIdleSprite* / WorldWalkingSprite*Sprite / List<Sprite>Спрайты спутника в мире (4 направления, с вариантами самца/блестящего)

Types и Dex​

public MonsterType FirstType;     // Первичный тип
public MonsterType SecondType; // Вторичный тип (может быть null/None)
public string DexDescriptionKey; // Ключ локализации для записи в покедексе
public MonsterSpecies Species; // например, "Monstruo Semilla"
public float Height; // В метрах
public float Weight; // В килограммах
public AudioReference Cry; // Звуковой эффект

Abilities​

public List<Ability> Abilities;        // Обычные способности (1-2)
public List<Ability> HiddenAbilities; // Скрытые способности

Training​

public List<StatByteValuePair> EVYield;  // Очки EV, начисляемые при поражении
public byte CatchRate; // 0-255
public byte BaseFriendship; // Начальное значение дружбы
public uint BaseExperience; // Базовое количество XP
public GrowthRate GrowthRate; // Тип кривой XP

Breeding​

public List<EggGroup> EggGroups;         // Совместимые группы яиц
public byte EggCycles; // Шаги до вылупления
public List<BreedingData> BreedingData; // Какие виды могут вылупиться из яиц
public List<PrehatchCallback> PrehatchCallbacks; // Изменения формы перед вылуплением
public bool HasBinaryGender;
public float FemaleRatio; // 0.0 до 1.0

Base Stats​

public SerializableDictionary<Stat, byte> BaseStats;  // HP, Atk, Def, SpA, SpD, Spe
public SerializableDictionary<Stat, uint> StatLimits; // Жёсткие лимиты (например, Shedinja HP=1)

Назначение атак​

Атаки организованы в четыре категории:

ПолеКогда изучается
MovesByLevel (List<MoveLevelPair>)При достижении определённого уровня
OnEvolutionMoves (List<Move>)Сразу после эволюции в эту форму
EggMoves (List<Move>)Наследуется через разведение
OtherLearnMoves (List<Move>)TM, учителя или другие методы

Флаг CanLearnAnyMove полностью обходит список OtherLearnMoves, позволяя монстру выучить любую атаку в базе данных (используется для особых монстров).

Inspector проверяет атаки в реальном времени и отмечает дубликаты между категориями.

Шаг 3: Использование помощника создания монстров​

Нажмите кнопку Open Helper на любом DataByFormEntry, чтобы открыть окно редактора Tools > MCE > Monster Creation Helper, которое предоставляет оптимизированный рабочий процесс для заполнения всех необходимых данных.

Шаг 4: Регистрация в базе данных​

Добавьте новый MonsterEntry в актив MonsterDatabaseInstance, чтобы он появился в покедексе и был доступен всем системам движка.

Система форм​

Формы позволяют одному виду иметь несколько визуальных и механических вариантов (например, региональные формы, Мега Эволюции, половые различия).

Каждая Form — это ScriptableObject с:

  • Идентификатором имени
  • Флагом HasShinyVersion и ссылкой на свою форму ShinyVersion
  • Флагом IsShiny на блестящем варианте

MonsterEntry.AvailableForms перечисляет все неблестящие формы. Блестящие формы автоматически выводятся через AvailableFormsWithShinnies.

Добавление новой формы​

  1. Создайте актив ScriptableObject Form.
  2. Если у формы есть блестящий вариант, создайте второй актив Form и свяжите их через ShinyVersion / IsShiny.
  3. Добавьте обычную форму в список AvailableForms монстра.
  4. В DataByForm появится новый слот DataByFormEntry — заполните данные, специфичные для формы (типы, характеристики, спрайты, атаки, эволюции).

Система эволюции​

Архитектура​

Эволюция управляется полиморфным паттерном команды:

EvolutionData (абстрактный базовый)
└── TargetSpeciesAndFormEvolutionData (абстрактный — добавляет TargetSpecies/TargetForm/KeepShinyIfItIs)
├── EvolveByLevel
├── EvolveByFriendship
│ └── EvolveByFriendshipAtSpecificTime
├── EvolveOnItemUse
├── EvolveWhenTraded
├── EvolveOnLevelUpHoldingItem
├── EvolveOnLevelUpWhenAMoveHasBeenLearnt
└── ... (более 30 встроенных типов эволюции)

Каждый DataByFormEntry имеет поле List<EvolutionData> Evolutions, использующее [SerializeReference] для полиморфной сериализации. Это означает, что вы можете комбинировать различные типы эволюции в Inspector.

Абстрактный API EvolutionData​

Каждый подкласс эволюции должен реализовать эти методы:

public abstract class EvolutionData
{
// Проверка триггеров — возвращает true, если условие выполнено
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);

// Во что эволюционирует
public abstract (MonsterEntry, Form) GetTargetEvolution(MonsterInstance monster, DayMoment currentTime);

// Хук после эволюции (опционально)
public virtual void AfterEvolutionCallback(MonsterInstance evolvedMonster, ...);

// Данные для отображения в покедексе
public abstract List<DexMonsterRelationshipData> GetDexRelationships(...);

// Глубокое копирование
public abstract EvolutionData Clone();
}

Встроенные типы эволюции​

MCE включает более 30 типов эволюции. Вот наиболее часто используемые:

КлассТриггер
EvolveByLevelМонстр достигает целевого уровня
EvolveByLevelAtSpecificTimeУровень + время суток
EvolveByLevelWhenSpecificGenderУровень + проверка пола
EvolveByFriendshipДостигнут порог дружбы
EvolveByFriendshipAtSpecificTimeДружба + время суток
EvolveByFriendshipWithMoveOfTypeДружба + знает атаку определённого типа
EvolveOnItemUseИгрок использует предмет на монстре
EvolveOnItemUseAtSpecificTimeИспользование предмета + время суток
EvolveOnItemUseOnSpecificGenderИспользование предмета + проверка пола
EvolveOnLevelUpHoldingItemПовышение уровня с удерживаемым предметом
EvolveOnLevelUpWhenAMoveHasBeenLearntПовышение уровня + знает определённую атаку
EvolveOnLevelUpWithOtherMonInPartyПовышение уровня + другой вид в команде
EvolveWhenTradedОбмен другому игроку
EvolveWhenTradedHoldingItemОбмен с удерживаемым предметом
EvolveWhenTradedWithMonsterОбмен на определённый вид
EvolveByCriticalHitCounterНаносит N критических ударов в одном бою
EvolveByDefeatingRivalsПобеждает N монстров определённого вида
EvolveByUsingMoveXTimesИспользует определённую атаку N раз
EvolveByEvolutionCounterЭволюционирует после порога счётчика
EvolveByLevelWithSceneTagПовышение уровня в сцене с определённым тегом
EvolveToRandomSpeciesByLevelСлучайный целевой вид при повышении уровня
SplitEvolutionByLevelОсобая двойная эволюция (создаёт второго монстра)

Создание пользовательского типа эволюции​

Вот пример эволюции "эволюционировать при HP ниже 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;
}

// Возвращает false для триггеров, которые эта эволюция не использует
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
};
}
}

Поскольку Evolutions использует [SerializeReference], ваш новый подкласс автоматически появится в раскрывающемся списке выбора типов Inspector при добавлении записей эволюции.

Как запускается эволюция во время выполнения​

EvolutionManager ScriptableObject управляет эволюцией во время выполнения:

  1. После повышения уровня TriggerEvolutionsAfterLevelUp() перебирает всех монстров команды с NeedsLevelUpEvolutionCheck, установленным в true.
  2. Для каждого монстра перебирает FormData.Evolutions и вызывает CanEvolveAfterLevelUp().
  3. При первом совпадении вызывает TriggerEvolution(), который:
    • Загружает сцену эволюции (затемнение).
    • Воспроизводит анимацию эволюции (может быть отменена игроком).
    • Если не отменена, вызывает AfterEvolutionCallback() для пост-эволюционной логики.
  4. После битвы TriggerEvolutionAfterBattleThroughExtraData() обрабатывает эволюции на основе счётчиков (критические удары, использование атак и т.д.).

Сериализация​

[SerializeField] vs [SerializeReference]​

  • [SerializeField] — Используется для конкретных типов (характеристики, спрайты, базовые данные). Unity сериализует по значению.
  • [SerializeReference] — Используется для полиморфных полей, таких как List<EvolutionData>, List<BreedingData> и List<PrehatchCallback>. Unity сохраняет имя конкретного типа, позволяя Inspector показывать выборщик типов.

Миграция сохранений​

Критично

Никогда не переименовывайте сериализованные поля без плана миграции. Используйте [FormerlySerializedAs("OldName")] для сохранения данных при переименовании. MCE уже широко использует этот паттерн (например, FrontShinny был переименован в FrontShiny).

При добавлении новых полей в DataByFormEntry или MonsterInstance:

  1. Добавьте поле с разумным значением по умолчанию.
  2. Существующие активы будут десериализованы со значением по умолчанию — без потери данных.
  3. Файлы сохранений используют JSON-сериализацию через MCESavesSerializer. Новые поля будут null/по умолчанию при загрузке старых сохранений. Обрабатывайте это в коде с помощью проверок на null или инициализации по умолчанию.
  4. Никогда не удаляйте поля из сериализованных классов. Помечайте их как [Obsolete] и игнорируйте.

Расширение MonsterEntry​

Чтобы добавить новое поле верхнего уровня в сам MonsterEntry (не данные для каждой формы):

  1. Добавьте поле с [SerializeField] в MonsterEntry.cs.
  2. Используйте [FormerlySerializedAs] при переименовании существующего поля.
  3. Существующие активы монстров загрузятся со значением по умолчанию — обновите их пакетно с помощью скрипта редактора или Inspector.

Чтобы добавить новые данные для каждой формы, добавьте поля в DataByFormEntry, следуя существующему шаблону групп/секций с атрибутами TriInspector.