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

45

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

当声明secondsBetweenBombDrops时没有使用static修饰符时,Unity会忽略我在代码中指定的secondsBetweenBombDrops值,并使用一些默认值(即1):

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

然而,一旦我给secondsBetweenBombDrops添加了static修饰符,代码就按预期运行,并使用正确的值10:

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

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

在 Unity 的检视器中,脚本组件显示 secondsBetweenBombDrops 是 1。无论我是否在游戏启动时实例化预制件或在游戏运行时创建预制件实例,这个默认值 1 都存在。

1个回答

53

序列化的双刃剑

Unity希望让每个人,包括那些编码知识有限(初学者、设计师)的人,都能轻松操作。为此,Unity在检视面板中显示数据。这使得程序员可以编写代码,设计师们可以通过调整数值进行设计,而无需打开MonoDevelop或IDE。

有两种方式可以在检视面板中显示数值:

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

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

在编辑器中显示变量时,在拖动脚本时使用脚本中给定的值。Unity然后序列化这些值,并不再关心任何脚本修改。如果在脚本内部将myVar设置为20,则可能会导致混淆,因为该值将不再被使用。序列化写入场景文件中。

示例中的两行代码完全相同。

可能的解决方案

可以通过在脚本组件的设置菜单上按重置来使Unity考虑脚本中的新值。这也会重置组件的所有其他变量,所以只有在打算这样做时才这样做。

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

向Unity添加组件时,会创建该组件类型的新对象。显示的值是从该对象的序列化值中获取的。因此,只能显示成员值,而静态变量则不能,因为它们不可序列化。 (这是.NET规范,不仅适用于Unity。)因为Unity不序列化静态字段,这就是为什么添加static修饰符似乎解决了问题的原因。

解释OP

在OP案例中,根据评论,您的公共字段在编辑器中显示值为1。您认为这个值是默认值,但实际上它很可能是您最初声明该字段时给出的值。将脚本添加为组件后,您使该值为10,并认为它有问题,因为它仍在使用值1。现在您应该明白它按设计正常工作了。

Unity序列化了什么?

默认情况下,Unity会序列化并显示值类型(int、float、enum等)以及字符串、数组、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


感谢您为这个问题添加了详细的答案!希望您不介意我加上一条注释,解释为什么MsYvette添加了“static”似乎修复了这个问题。 - Serlite
你能提供一些Unity文档的链接吗,这些信息是从哪里得到的? - zypro
哪一部分?因为大部分你可以通过运行引擎自己看到。对于序列化部分,我在末尾添加了一些链接。其中一个视频涉及可编写对象,但在开头就触及了这个主题。 - Everts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接