English | Русский
Удобная и надёжная обёртка над [SerializeReference] в Unity.
StableRef делает работу с полиморфными сериализованными ссылками стабильной и комфортной: поиск по типам в селекторе инспектора, безопасные копирование/вставка и инструменты редактора для поиска и починки ссылок. Вдобавок ссылки переживают переименование классов — рядом с объектом хранится стабильный строковый ID типа, поэтому переименование или перемещение класса не разрушает уже сохранённые данные.
Развернуть
- .unitypackage — Releases
- UPM —
Window → Package Manager→+→Add package from git URL:https://github.com/SST-Systems/StableRef.gitДобавь#тегв конец URL для фиксации версии. - Вручную — склонируй или скачай, скопируй в
Assets/.
Unity 2021.3+
Стандартный [SerializeReference] хранит полное assembly-qualified имя типа. Если переименовать или переместить класс, Unity теряет ссылку и поле становится null. StableRef разделяет сериализованную идентичность и имя класса — тип получает постоянный ID через [StableTypeId], и можно переименовывать его свободно.
| Тип | Назначение |
|---|---|
StableRef<T> |
Сериализуемая обёртка для одиночной полиморфной ссылки типа T. |
StableRefList<T> |
Сериализуемый список элементов StableRef<T>. |
[StableTypeId("id")] |
Присваивает классу постоянный ID. Переименовывай класс как угодно — Unity его найдёт. |
[StableRefCategory("Path")] |
Группирует тип под подменю в инспекторном селекторе. |
[Serializable]
[StableTypeId("my-package.damage-on-hit")]
[StableRefCategory("Combat")]
public class DamageOnHit : IEffect
{
public int Amount;
}Значение [StableTypeId] должно быть уникальным в проекте. Используй строки с неймспейсом, чтобы избежать коллизий.
[Serializable]
public class ItemConfig : ScriptableObject
{
public StableRef<IEffect> OnPickup;
}[Serializable]
public class AbilityConfig : ScriptableObject
{
public StableRefList<IEffect> Effects;
}
// Перебор
foreach (var stableRef in config.Effects)
{
var effect = stableRef?.Value;
if (effect != null)
effect.Apply();
}StableRefList<T> также работает как List<T> из кода — Add, Insert, Remove, RemoveAt, RemoveAll, Clear, Contains, IndexOf, Find и т.д.:
config.Effects.Add(new DamageOnHit { Amount = 5 });
config.Effects.RemoveAll(e => e is DamageOnHit);Индексатор и foreach возвращают обёртку StableRef<T> (значение — через .Value); методы поиска и мутации работают напрямую с T. Items открывает внутренний List<StableRef<T>> для полного List-API.
Если список формируется из editor-скрипта, а не через инспектор, вызови StableRefSync.AssignIds(list) перед сохранением, чтобы новые элементы получили стабильные ID (в инспекторе это происходит автоматически при отрисовке поля).
Селектор поддерживает и закрытые generic-типы элемента. Для поля вида StableRefList<ICondition<Unit>> предлагаются открытые generic-определения, которые ему подходят (например, All<TContext>, Any<TContext>), закрытые аргументом самого поля (All<Unit>). Каждому типу-аргументу нужен свой стабильный ID — свой файл или [StableTypeId], как и любому другому типу StableRef.
[StableTypeId] — опционален. Если атрибут не указан, StableRef автоматически использует MonoScript GUID (значение поля guid из .meta-файла скрипта) в качестве стабильного идентификатора. Это значит:
- Переименование класса — безопасно. GUID привязан к файлу, а не к имени класса.
- Переименование или перемещение файла скрипта — тоже безопасно. Meta-файл переезжает вместе с ассетом, GUID не меняется.
- Удаление и повторное создание файла — ссылка теряется (резолвится в
null), но ошибка обрабатывается контролируемо. Проект продолжит работать; потерянный тип отобразится в Fix Missing Types.
Для типов, которые планируется активно рефакторить, явный [StableTypeId] надёжнее — он выживает даже при удалении и пересоздании файла скрипта.
Переход типа с авто-ID на явный [StableTypeId] безопасен и не создаёт missing-ссылок. Кастомный ID имеет приоритет, а существующие ссылки мигрируют автоматически — сохранённый ID переписывается с MonoScript-GUID на твой кастомный при следующей отрисовке поля в инспекторе (или через StableRefSync). До этого старый GUID продолжает резолвиться (файл скрипта не менялся), поэтому ничего не теряется. Если добавляешь атрибут именно ради подготовки к серьёзному рефактору (удаление и пересоздание файла) — сначала пересохрани затронутые ассеты, чтобы «застолбить» новый ID.
Важно: нельзя размещать несколько классов в одном файле скрипта. Автоматическая генерация ID опирается на MonoScript GUID, который присваивается файлу, а не классу, — при нескольких классах в одном файле генерация ID будет работать некорректно.
Все инструменты доступны через Tools → StableRef в строке меню Unity.
Find Usages (Tools/StableRef/Find Usages) — сканирует префабы, открытые сцены и скриптовые объекты и показывает все места, где используется выбранный тип. Также доступно через правый клик на ассете скрипта: Assets/Find StableRef Usages.
Fix Missing Types (Tools/StableRef/Fix Missing Types) — сканирует проект в поисках StableRef-полей, чей ID больше не соответствует ни одному известному типу. Удобно после рефакторинга — позволяет найти сломанные ссылки до того, как они превратятся в молчаливую потерю данных.
Встроенные в Unity Copy Component / Paste Component Values не всегда корректно обрабатывают данные [SerializeReference] при переносе между разными сериализованными документами (например, со сцены в префаб). Это может оставить повреждённую запись в managedReferences, из-за которой в консоли появляется ошибка вида:
Could not update a managed instance value at property path 'managedReferences[...]', with value '...'Эта ошибка может не пропадать даже после перезапуска редактора и не исчезает при откате (revert) компонента — повреждение уже записано в сериализованный файл.
Чтобы безопасно переносить значения StableRef<T> / StableRefList<T>, используй встроенное контекстное меню по правому клику вместо стандартного копирования/вставки компонента:
| Пункт меню | Где вызывать (ПКМ) | Что делает |
|---|---|---|
StableRef/Copy |
Одиночное поле StableRef<T> |
Копирует текущее значение во внутренний буфер. |
StableRef/Paste |
Одиночное поле StableRef<T> совместимого типа |
Создаёт новую managed reference в целевом поле. |
StableRef/Duplicate |
Элемент StableRef<T> внутри списка |
Вставляет копию сразу после этого элемента. |
StableRef/Copy |
Заголовок StableRefList<T> (или массив StableRef<T>) |
Копирует все элементы списка. |
StableRef/Paste/Replace или StableRef/Paste/Append |
Заголовок StableRefList<T> (или массив StableRef<T>) |
Заменяет или добавляет скопированные элементы. |
Это безопасно между GameObject'ами, префабами и сценами: вместо копирования сырых сериализованных байт создаётся новая managed reference прямо в целевом документе, поэтому managedReferences не повреждается.
При копировании компонента со StableRef-полями между сценой и префабом используй это меню именно для StableRef-полей, а не стандартный Copy Component / Paste Component Values.
Предупреждение: даже с этим меню под рукой сохраняй бдительность. Поля на основе
[SerializeReference](в том числеStableRef/StableRefList) не всегда очевидным образом копируются или переносятся даже при тривиальных встроенных операциях Unity — Duplicate, drag & drop в Hierarchy, Apply/Revert префаб-оверрайдов, слияние сцен/префабов и похожих действиях. Перед массовыми изменениями делай коммит или бэкап и проверяй результат после операции.
Распространяется под MIT License. Свободно для использования в личных и коммерческих проектах.
Автор — Egor Shesterikov.



