Варианты установки обязательных полей для компонентов, созданных кодом? - PullRequest
1 голос
/ 20 мая 2019

Когда я создаю GameObject, как показано ниже, я хочу, чтобы у моего компонента были / установлены обязательные поля.

GameObject go = new GameObject("MyGameObject");
MyComponent myComponent = go.addComponent(MyComponent);

Поскольку я не могу использовать конструктор для установки приватных полей, мне также нужен установщик для этих полейили общедоступная Init функция, которая устанавливает несколько обязательных свойств.Но это позволяет изменять значения в любом месте кода, и в отличие от конструктора с перегрузками, не очевидно, что Init «нужно» или «можно» вызывать после создания для установки этих параметров.

Так что есть другиеопции, которые позволяют мне устанавливать обязательные поля при создании компонента и использовать «правильную» инкапсуляцию?

1 Ответ

3 голосов
/ 20 мая 2019

Как указано в комментариях, Unity реализовала 2 основных метода для логики инициализации MonoBehaviour: Запуск и Пробуждение .

Существуют недостатки этих 2 методов инициализации:

  • Они не могут принять ни один параметр
  • Вы не уверены в порядке их выполнения

Существует стандартный порядок выполнения и из этого вы можете быть уверены, что Start выполняется после Awake.Но если у вас есть несколько вариантов MonoBehaviours одного и того же типа, то будет нелегко отследить, какой из них выполняется первым.

Если ваша проблема - просто порядок выполнения, а у вас разные типы, вы можете просто обновить скрипт порядок выполнения .

В противном случае существуют дополнительные подходы, позволяющие избежать обоих недостатков, с использованием метода Factory внутри монобиха.

Учтите, что в этом случае порядок выполнения будет:
Пробуждение => OnEnable => Сброс => Запуск => МЕТОД ВАШЕГО ИНИЦИАТИРОВАНИЯ

ЧАСТНЫЙ ЗАВОДСКИЙ СПОСОБ

public class YourMono : MonoBehaviour
{
    //a factory method with the related gameobject and the init parameters
    public static YourMono AddWithInit(GameObject target, int yourInt, bool yourBool)
    {
        var yourMono = target.AddComponent<YourMono>();
        yourMono.InitMonoBehaviour(yourInt, yourBool);
        return yourMono;
    }

    private void InitMonoBehaviour(int yourInt, bool yourBool)
    {
        //init here
    }
}

Этот метод обеспечивает лучшую инкапсуляцию, поскольку мы можем быть уверены, что InitMonoBehaviour будет вызываться только один раз.

ФАБРИКА РАСШИРЕНИЯ

Вы также можете создать фабрику из расширения,В этом случае вы удаляете фабричный метод из класса, может быть полезно отделить фабричную логику от логики игрового процесса.

Но в этом случае вам нужно будет сделать InitMonoBehaviour внутренним и реализоватьрасширение в том же пространстве имен.
В результате InitMonoBehaviour будет немного более доступным (внутренне из того же пространства имен), поэтому он имеет более низкую инкапсуляцию.

    [Serializable]
    public class YourData
    {
    }

    namespace YourMonoNameSpace.MonoBehaviourFactory
    {
        public static class MonoFactory
        {
            public static YourMono AddYourMono(this GameObject targetObject, YourData initData)
            {
                var yourMono = targetObject.AddComponent<YourMono>();
                yourMono.InitMonoBehaviour(initData);
                return yourMono;
            }
        }
    }

    namespace YourMonoNameSpace
    {
        public class YourMono : MonoBehaviour
        {
            private YourData _componentData= null;
            internal void InitMonoBehaviour(YourData initData)
            {
                if(_componentData !=  null ) return;
                _componentData = initData;
            }
        }
    }

При этом обе эти опции также имеютнедостаток:

  • Кто-то может забыть запустить их.

Итак, если вы хотите, чтобы этот метод был «обязательным», а не необязательным, я предлагаю добавить флаг bool_Известно, чтобы никто не забыл его реализовать.

ВСТУПЛЕНИЕ УПРАВЛЕНИЯ - ВНУТРИ БУДУЩЕГО

Лично я бы реализовал логику с Scriptable Object или с Singleton , и я бы вызвал его во время пробуждения, чтобы убедиться, что инициализация вызывается все время.

public class YourMonoAutoInit : MonoBehaviour
{
    public InitLogic _initializationLogic;

    //a factory method with the related gameobject and the init parameters
    public void Awake()
    {
        //make sure we not miss initialization logic
        Assert.IsNotNull(_initializationLogic);
        InitMonoBehaviour(_initializationLogic);

    }

    private void InitMonoBehaviour(InitLogic initializationLogic)
    {
        //init here using
        int request = initializationLogic.currentRequest;
        bool playerAlive = initializationLogic.playerIsAlive;
    }
}

public class InitLogic : ScriptableObject
{
    public int currentRequest = 1;
    public bool playerIsAlive = false;
}

//this is another monobehaviour that might access the player state and change it
public class KillPlayer : MonoBehaviour
{
    public InitLogic playerState;
    public void KillThisPlayer() => playerState.playerIsAlive = false;
}

С этой последней версией yВы достигаете инверсии управления.
Поэтому вместо УСТАНОВКИ ДАННЫХ В MONOBEHAVIOUR:

//SETTING DATA FROM CLASS TO MONOBEHAVIOUR
public class OtherScript : MonoBehaviour
{
    private void CreateMonoBehaviour() => YourMono.AddWithInit(gameObject, 1, true);
}

Вы ПОЛУЧИТЕ ДАННЫЕ В МОНОВОБСТВЕННОМ ИЗ КЛАССА.

//GETTING IN MONOBEHVARIOUS FROM CLASS
public class YourOtherMono : MonoBehaviour
{
    public YourData data;

    private void Awake()
    {
        (int yourInt, bool yourBool) = data.GetData();
        //do something with the data received
    }
}
...