在代码创建的组件上设置必填字段的选项?

Options for setting mandatory fields on components created by code?

当我创建如下所示的 GameObject 时,我希望我的组件成为 have/set 必填字段。

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

因为我不能使用构造函数来设置私有字段,所以我需要为这些字段使用 setter 或设置多个强制属性的 public Init 函数。但这允许在代码中的任何位置更改值,并且与具有重载的构造函数不同,Init "needs to" 或 "can be" 在创建后调用以设置这些参数并不明显。

那么有没有其他选项允许我在创建组件时设置必填字段并使用 "proper" 封装?

正如评论中所述,Unity 为 MonoBehaviour 初始化逻辑实现了 2 个主要方法:Start and Awake.

这两种初始化方法都有缺点:

  • 他们不能接受任何参数
  • 你不确定他们的执行顺序

有一个标准order of execution,由此您可以确定 Start 是在 Awake 之后执行的。但是,如果您有多个相同类型的 MonoBehaviour,则很难跟踪哪个先执行。

如果您的问题只是执行顺序,并且您有不同的类型,那么您可以更新 script execution order

否则,还有其他方法可以通过在单一行为中使用工厂方法来避免这两个缺点。

考虑到在这种情况下执行顺序为:
唤醒 => OnEnable => Reset => Start => 您的初始化方法

私有工厂方法

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 只会被调用一次。

扩展工厂

您还可以从扩展创建工厂。在这种情况下,您从 class 中删除了工厂方法,这可能有助于将工厂逻辑与游戏逻辑分离。

但在这种情况下,您需要将 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;
            }
        }
    }

也就是说这两个选项都有一个缺点:

  • 有人可能会忘记启动它们。

所以,如果你希望这个方法是 "required" 而不是可选的,我建议添加一个 bool 标志 _isInitiated 以确保没有人忘记实现它。

控制反转 - 内心清醒

就我个人而言,我会使用 Scriptable Object, or with a 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;
}

使用最后一个版本,您将实现控制反转。
因此,与其将数据设置到单一行为中:

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

您将从 CLASS.

获取单变量数据
//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
    }
}