为什么 Unity 会忽略非静态 public 字段的初始化值?

Why is Unity ignoring the initialized value of a non-static public field?

我正在使用 InvokeRepeating() 来调用游戏中的方法。我在 GameObject 类 之一的 Start() 方法中调用 InvokeRepeating()。要为 InvokeRepeating() 设置 repeatRate 参数,我向其传递一个名为 secondsBetweenBombDrops.

的 public 字段

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。无论我是在游戏开始时实例化预制件还是在游戏 运行.

连载双刃剑

Unity 希望让每个人的工作变得更轻松,包括编码知识有限的人(初学者、设计师)。

为了帮助他们,Unity 在检查器中显示数据。这允许编码人员在不打开 MonoDevelop/an IDE.

的情况下通过调整值来进行编码和设计。

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

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

第二种更好,因为它符合封装原则(变量是private/protected,通过方法或属性修改)。

当您在编辑器中显示变量时,脚本中给出的值仅在拖动脚本时使用。然后 Unity 序列化这些值并且不再关心任何脚本修改。这可能会导致混淆,例如,如果 myVar 在脚本中设置为 20,那么它不会被使用。连载写在场景文件里

示例中的两行完全相同。

可能的解决方案

可以通过在脚本组件的设置轮上按重置来让 Unity 考虑脚本中的新值。这也将重置组件的所有其他变量,所以只有在需要时才这样做。

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

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

解释 OP

在 OP 案例中,根据评论,您的 public 字段在编辑器中显示的值为 1。您认为此值是默认值,但实际上它是您最初声明该字段时最有可能赋予该字段的值。将脚本添加为组件后,您将值设置为 10,并认为它有问题,因为它仍在使用值 1。您现在应该明白它工作得很好,正如设计的那样。

Unity 序列化了什么?

默认情况下,Unity 会序列化和显示值类型(int、float、enum 等)以及字符串、数组、List 和 MonoBehaviour。 (可以使用编辑器脚本修改它们的外观,但这是题外话。)

以下:

public class NonMonoBehaviourClass{
   public int myVar;
}

默认不序列化。同样,这是 .NET 规范。 Unity 默认将 MonoBehaviour 序列化为引擎要求的一部分(这会将内容保存到场景文件中)。如果你想在编辑器中显示一个"classic" class,就这么说:

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

显然,您不能将它添加到游戏对象中,因此您需要在 MonoBehaviour 中使用:

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

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

关于在检查器中显示内容的额外提示

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

因此,如果您需要显示一个界面,则必须考虑抽象 class,因为它提供类似的功能(多继承除外),但可以是 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