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

时间:2019-05-20 10:24:28

标签: unity3d encapsulation

当我创建如下所示的GameObject时,我希望我的组件具有/设置必填字段。

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

由于我不能使用构造函数来设置私有字段,因此我需要为这些字段设置setter或设置多个强制属性的公共Init函数。但是,这允许在代码中的任何地方更改值,并且与带有重载的构造函数不同,在创建这些参数后Init“需要”或“可以被调用”这一点并不明显。

那么还有其他选项可以让我在创建组件时设置必填字段并使用“适当的”封装吗?

1 个答案:

答案 0 :(得分:3)

正如评论中所述,Unity为MonoBehaviour初始化逻辑实现了2种主要方法:StartAwake

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

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

有一个标准的order of execution,从中可以确保在唤醒后执行启动。但是,如果您有多个相同类型的MonoBehaviours,那么跟踪哪个首先执行将不容易。

如果您的问题只是执行顺序,而您有不同的类型,则只需更新script execution order

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

请考虑在这种情况下执行顺序为:
唤醒 => 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;
            }
        }
    }

这表示这两个选项也都有缺点:

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

因此,如果您希望这种方法是“必需的”而不是可选的,我建议添加一个布尔标志_isInitiated,以确保没有人忘记实现它。

控制权的反转-内部唤醒

我个人会使用Scriptable ObjectSingleton来实现逻辑,我会在清醒时调用它以确保初始化始终被调用。

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);
}

您将从班级获得单项数据。

//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
    }
}
相关问题