Ошибка The reference script missing если синглет вызывает метод другого синглета из внешей библиотеки

в данный момент я учусь писать игру под Unity, уже есть некоторый опыт в программировании и я решил попрактиковаться с выносом части функционала во внешнюю библиотеку чтобы в будущем было удобнее обслуживать возможный проект не занимаясь перекомпиляцией всего кода каждый раз на каждый чих. У меня есть три синглета, один синглет внутренний - в Assembly-CSharp, назову его InternalSingleton, он нужен для постоянного доступа ко всем объектам в игре чтобы было удобнее делать сохранения в одиночной игре(например, по нажатию F5 запустить корутину или таску с сохранением состояния объектов в файл или по "приказу" от менеджера событий).

Есть ещё внешний синглет - пусть будет называться ExternalSingleton и он находится в библиотеке Plugins/ExternalSingleton.dll(библиотеку я создавал по гайду https://www.youtube.com/watch?v=CPkO1Gek8XQ), он нужен, например для режима кооперативной игры вдвоём, и в нём там какой-нибудь код например сетевая часть игры которая отвечает за формирование, отправку и получение пакетов от другого компьютера.

И наконец третий синглет для удобства управления пользовательскими событиями - сработал какой-то триггер игровой логики, какой-то игрок перешёл в другую локацию, кто-то там умер и т.д, его я не стал выносить в отдельную dll и он лежит внутри Assembly-CSharp и называется EventManagerSingleton. Менеджер событий может по каким-то условиями дёрнуть тот или иной класс(например раньше времени инициировать сохранение, кикнуть игрока, отобразить сообщение в чате и так далее)

В первых двух синглетах есть методы Awake(метод от MonoBehaviour) и Hello, второй метод просто отправляет сообщение в консоль показывая что вызов прошёл успешно без дополнительных приключений. В менеджере событий содержатся методы Awake, Start, MyEventHandler. InternalSingleton и ExternalSingleton могут отправлять события, например сохранение прошло успешно и игрока можно уведомить что его старания не уйдут в небытие или там игрок принял запрос на подключение и нужно заспаунить его модельку и выдать ему шмотки.

Код всех трёх синглетов: InternalSingleton

using UnityEngine;

public class InternalSingleton : MonoBehaviour
{
    public delegate void EventCallback(int EventCode);
    public event EventCallback Callback;

    private static InternalSingleton _Instance = null!;
    public static InternalSingleton Instance
    {
        get
        {
            if (_Instance == null)
            {   //Если null то ищём существующий экземпляр
                _Instance = FindObjectOfType<InternalSingleton>();
                if (_Instance == null)
                {   //Если таковых не нашлось то создаём новый
                    GameObject SingletonObject = new GameObject(typeof(InternalSingleton).Name);
                    _Instance = SingletonObject.AddComponent<InternalSingleton>();
                }
            }
            return _Instance;
        }
    }
    private void Awake()
    {
        Debug.Log("Awake() InternalSingleton");

        if (Instance != null & Instance != this)
        {
            Destroy(Instance);
            return;
        }
        _Instance = this;
        DontDestroyOnLoad(gameObject);

        //Подключаем обработчика событий
        _Instance.Callback += EventManagerSingleton.Instance.MyEventHandler;

        //Вызываем какое-нибудь событие
        Callback.Invoke(1);
    }
    public void Hello()
    {
        Debug.Log("Привет из InternalSingleton!");
    }
}

ExternalSingleton

using UnityEngine;

namespace ExternalSingleton
{
    public class ExternalSingleton : MonoBehaviour
    {
        public delegate void EventCallback(int EventCode);
        public event EventCallback Callback;

        private static ExternalSingleton _Instance = null!;
        public static ExternalSingleton Instance
        {
            get
            {
                if (_Instance == null)
                {
                    _Instance = FindObjectOfType<ExternalSingleton>();
                    if (_Instance == null)
                    {
                        GameObject SingletonObject = new GameObject(typeof(ExternalSingleton).Name);
                        _Instance = SingletonObject.AddComponent<ExternalSingleton>();
                    }
                }
                return _Instance;
            }
        }
        private void Awake()
        {
            Debug.Log("Awake() ExternalSingleton");

            if (Instance != null & Instance != this)
            {
                Destroy(Instance);
                return;
            }
            _Instance = this;
            DontDestroyOnLoad(gameObject);

            //Подключаем обработчика событий
            _Instance.Callback += EventManagerSingleton.Instance.MyEventHandler;

            //Вызываем какое-нибудь событие
            Callback.Invoke(2);
        }
        public void Hello()
        {
            Debug.Log("Привет из ExternalSingleton!");
        }
    }
}

EventManagerSingleton

using UnityEngine;

public class EventManagerSingleton : MonoBehaviour
{
    private int LastCode = 0;   //какая-то там переменная неважно какая
    private static EventManagerSingleton _Instance = null!;
    public static EventManagerSingleton Instance
    {
        get
        {
            if (_Instance == null)
            {
                _Instance = FindObjectOfType<EventManagerSingleton>();
                if (_Instance == null)
                {
                    GameObject SingletonObject = new GameObject(typeof(EventManagerSingleton).Name);
                    _Instance = SingletonObject.AddComponent<EventManagerSingleton>();
                }
            }
            return _Instance;
        }
    }
    private void Awake()
    {
        if (Instance != null & Instance != this)
        {
            Destroy(Instance);
            return;
        }
        _Instance = this;
        DontDestroyOnLoad(gameObject);

        Debug.Log("Awake() EventManagerSingleton");
    }
    private void Start()
    {
        //Теперь вызовем методы обоих синглетов обращаясь к их экземплярам
        Debug.Log("Вызов InternalSingleton::Hello()");
        InternalSingleton.Instance.Hello();     //<---- Вызов метода внутреннего синглета не провоцирует ничего необычного, всё работает

        Debug.Log("Вызов ExternalSingleton::Hello()");
        ExternalSingleton.ExternalSingleton.Instance.Hello();   //<---- А если вызвать метод внешнего синглета то происходит баг хотя скрипт работает вроде бы норм
    }
    public void MyEventHandler(int EventCode)
    {
        switch(EventCode)
        {
            case 1:
            {
                //Какое-то событие из внутренней либы
                LastCode = 1;   //Какой-то код
                break;
            }
            case 2:
            {
                //Какое-то событие из внешней либы
                LastCode = 2;   //Какой-то код
                break;
            }
        }
    }
}

Порядок выполнения скриптов настроен так, что метод Awake() менеджера событий всегда выполняется первым и к моменту выполнения методов других скриптов менеджер уже готов принимать события. Это сделано чтобы предотвратить возможный null при назначении обработчика событий

И всё вроде бы ничего, первый запуск проходит успешно:

введите сюда описание изображения

Однако если перезапустить(отжать и нажать кнопку Play в редакторе), то редактор Unity теряет скрипты: введите сюда описание изображения

Это поведение исправляется либо повторным импортом всех скриптов либо перезапуском редактора, что в будущем при большом размере репозитория может отнимать непозволительно много времени. meta-файлы при такое "глюке" остаются нетронутыми и сами скрипты никуда не исчезают - их можно даже из этого же редактора открыть в Visual Studio. Если отвалившийся скрипт имеет какие-то поля, назначаемые редактором то с момента начала этого бага ломается половина UI самого Unity Editor

И вот ещё я заметил, если к событиям, генерируемым ExternalSingleton не привязывать уже имеющийся обработчик событий то скрипты в редакторе unity не теряются но тогда я теряю возможность получать какие-то события от другого игрока в кооперативном режиме игры

Как это можно пофиксить без перезапуска редактора каждый раз? Я уже третий день штудирую гугл на предмет такого поведения, но все найденные случаи такого поведения имели другое происхождение. При этом если не вызывать методы внешнего синглет класса, то этих синглетов можно плодить целое море и редактору будет по барабану, такая шляпа появляется только если я обращусь к классу из собственной библиотеки при наличии назначенного обработчика для событий ExternalSingleton

Если кому нужно, вот настройки игровых объектов в сцене: введите сюда описание изображения


Ответы (0 шт):