首页 文章

为什么Unity忽略了非静态公共字段的初始化值?

提问于
浏览
37

我正在使用InvokeRepeating()在游戏中调用方法 . 我在其中一个 GameObject 类的 Start() 方法中调用 InvokeRepeating() . 要为 InvokeRepeating() 设置 repeatRate 参数,我将其传递给名为 secondsBetweenBombDrops 的公共字段 .

Unity忽略了我在代码中为 secondsBetweenBombDrops 指定的值,而在使用静态修饰符声明 secondsBetweenBombDrops 时使用了一些默认值(即1):

public float secondsBetweenBombDrops = 10f;
void Start() {
    InvokeRepeating("DropBomb", 1f, secondsBetweenBombDrops);
}

但是,一旦我将 static 修饰符添加到 secondsBetweenBombDrops ,代码就会按预期运行,并使用正确的值10:

public static float secondsBetweenBombDrops = 10f;
void Start() {
    InvokeRepeating("DropBomb", 1f, secondsBetweenBombDrops);
}

为什么此字段需要 static 修饰符才能使用适当的值?

在Unity检查器中,脚本组件显示 secondsBetweenBombDrops 为1.无论是在游戏开始时实例化预制还是在游戏运行时创建预制实例,都会出现此默认值1 .

1 回答

  • 36

    序列化的双刃剑

    Unity希望让每个人都能更轻松,包括编码知识有限的人(初学者,设计师) .

    为了帮助他们,Unity在检查器中显示数据 . 这允许编码器编码和设计者通过调整值来设计,而无需打开MonoDevelop / IDE .

    有两种方法可以在检查器中显示值:

    public int myVar = 10;
    [SerializeField] private int myOtherVar = 0; // Can also be protected
    

    第二个更好,因为它符合封装原则(变量是私有的/受保护的,并通过方法或属性进行修改) .

    在编辑器中显示变量时,脚本中给出的值仅在拖动脚本时使用 . 然后,Unity会对这些值进行序列化,而不再关心任何脚本修改 . 例如,如果事件之后脚本内的 myVar 设置为20,则会导致混淆,因此不会使用它 . 序列化写在场景文件中 .

    示例中的两行完全以相同的方式工作 .

    可能的解决方案

    通过在脚本组件的设置轮上按Reset,可以让Unity在脚本中考虑新值 . 这也将重置组件的所有其他变量,因此只有这样做才有意义 .

    将变量设为私有并省略属性 [SerializeField] 将禁用序列化过程,因此Unity将不再在场景文件中查找要显示的值 - 而是由脚本在运行时创建该值 .

    向Unity添加组件时,会创建组件类型的新对象 . 显示的值是该对象的序列化值 . 因此,只能显示成员值而不能显示静态变量,因为它们不可序列化 . (这是一个.NET规范,并不是Unity特有的 . )因为Unity does not serialize static fields,这就是为什么添加 static 修饰符似乎可以解决问题 .

    解释OP

    在OP案例中,根据注释,您的公共字段在编辑器中显示值1 . 您认为这个值是默认值,当它实际上是您最初声明时最有可能给该字段的值 . 在您将脚本添加为组件之后,您将值设置为10并认为它是错误的,因为它仍然使用值1.您现在应该理解它按照设计正常工作 .

    Unity序列化了什么?

    默认情况下,Unity将序列化并显示值类型(int,float,enum等)以及string,array,List和MonoBehaviour . (可以使用编辑器脚本修改它们的外观,但这是偏离主题的 . )

    下列:

    public class NonMonoBehaviourClass{
       public int myVar;
    }
    

    默认情况下未序列化 . 这也是.NET规范 . Unity默认将MonoBehaviour序列化为引擎要求的一部分(这会将内容保存到场景文件中) . 如果您希望在编辑器中显示“经典”类,只需这样说:

    [System.Serializable]
    public class NonMonoBehaviourClass{
       public int myVar = 10;
    }
    

    显然,您无法将其添加到游戏对象中,因此您需要在MonoBehaviour中使用:

    public class MyScript:MonoBehaviour{
         public NonMonoBehaviourClass obj = new NonMonoBehaviourClass();
    }
    

    这将在检查器中显示该对象,并允许修改 NonMonoBehaviourClass 实例中的 myVar 变量 . 同样,在序列化值并将其存储到场景后,将不会考虑脚本中对 myVar 的任何更改 .

    有关在检查器中显示内容的其他提示

    要完成,接口不会显示在检查器中,因为它们不包含任何变量 - 只是方法和属性 . 在调试模式下,属性不是默认显示 . 您可以使用检查器右上角有三行的按钮来更改此模式 . 前两个设置是Normal / Debug . 第一个是默认值,第二个也显示私有变量 . 这对于观察它们的值很有用,但不能从编辑器中更改 .

    因此,如果您需要显示一个接口,则必须考虑一个抽象类,因为它提供了类似的功能(多继承除外),但可以是MonoBehaviour .

    参考文献:

    http://docs.unity3d.com/ScriptReference/SerializeField.html

    http://docs.unity3d.com/Manual/script-Serialization.html

    https://www.youtube.com/watch?v=9gscwiS3xsU

    https://www.youtube.com/watch?v=MmUT0ljrHNc

相关问题