使用嵌套 Class 作为 Unity3D 组​​件

Using Nested Class as Unity3D Component

我使用的是Unity 5.4.2f2 Personal,我写了一个类似下面的C#脚本:

using UnityEngine;
using System;

public class Outer : MonoBehaviour {

    // Some public outer fields

    public class Inner : MonoBehaviour {
        // Some public inner fields
    }

}

我希望能够将 OuterInner 附加到场景中的游戏对象。不幸的是,即使我向它添加 [Serializable]Inner 也不会在 Unity Inspector 中显示为可用脚本。我知道很多人可能会说如果我试图让嵌套的 class 对外界可见,我的设置有问题,但为了这个问题,我只想知道这是否可以完成。即,是否有任何方法可以使嵌套的 classes 可用作 Unity 组件?

不,无法将嵌套的 class 用作统一组件。要将 MonoBehaviour class 用作统一组件,class 名称 必须 等于脚本的文件名,因此使用嵌套 class 会有可能。

如果您在不重命名脚本文件的情况下将外部 class 重命名为您想要的任何其他名称,您将发现也无法使用新的外部 class。

[编辑]

我找到了 documentation。正如我所说: "The class name and file name must be the same to enable the script component to be attached to a GameObject."

我认为之前的回答不正确。

您应该能够通过使用 [MenuItem] 属性创建和绑定 MonoBehaviour 的静态方法创建编辑器脚本来添加嵌套的组件。

这在 Unity 5 及更高版本中有效。

您不能通过统一标准菜单添加它。 但是你可以通过脚本添加它。 添加组件();

自问这个问题以来,我学到了很多东西,所以我想我会添加更多信息。 and 仍然是最正确的答案。

Unity如何编译脚本

Unity 使用内置版本的 Mono 编译器(跨平台 C# 编译器)编译项目中的所有脚本。它为每个脚本文件(在关联的 .meta 文件中)分配一个唯一的 GUID,就像它为每个其他资产所做的那样。这带来了一些不错的可用性改进:classes 可以在不破坏现有脚本引用的情况下重命名,因为 GUID 不会更改,并且脚本可以与其他相关资产(例如,所有“士兵”scripts/sounds/textures可以组合在一起)。

Unity的脚本命名限制

这种可用性的缺点是 Unity 实施了一些命名限制:

  • 每个文件只有一个 class,与文件同名,否则 Unity 将不知道在组件脚本引用中使用哪个 GUID
  • 没有嵌套 classes
  • 在名为 MyBehaviour.cs
  • 的文件中没有 class 名为 TheBehaviour 的文件
  • 可能 没有 partial classes 如@derHugo 的评论所述,这是可能的。部分声明可以在相同或不同的文件中,即使名称不同,只要其中一个文件名与 class.
  • 的名称匹配即可

如果您 select 项目 Window 中的脚本资产并看到以下消息,那么您就知道您正在尝试以 Unity 不支持的方式命名事物:

但是,这些限制仅适用于 public class 派生自 MonoBehaviour(或者可能来自 UnityEngine.Object? ).您可以在文件中包含任意数量的其他私有 MonoBehaviourPOCOs,因为 Unity 永远不会序列化它们,因此它们不需要自己的 GUID。 Public 标记为 [Serializable] 的 POCOs 可能 具有与 public MonoBehaviours 相同的命名限制......我没有测试过这个所以我不确定。为什么我没有测试过这个?嗯...

使用托管插件避免这些限制

如果您和我一样,并且来自纯 .NET 背景,那么对可以在哪些文件中定义 classes 的任何限制可能会让您抓狂。在这种情况下,您可以将脚本存储在 Unity 项目之外(我使用与 Unity 项目相同的存储库中的另一个文件夹),并使用 IDE 像 Visual Studio 或 JetBrains Rider 手动构建它们。然后可以将生成的程序集(.dll 和 .pdb 文件)复制到您的 Unity 项目中(最好通过一些自动化的 post-build 事件)作为 managed plugins。然后您可以:

  • 为每个文件定义任意数量的 classes
  • 使用您想要的任何命名约定
  • 使用嵌套的 classes 和 partial classes,随心所欲,与纯 .NET
  • 相同
  • 为您的 POCO 编写不依赖于 Unity 或 Unity 测试框架的单元测试(它具有让您 运行 并行单元测试的令人敬畏的附带好处;Unity 只能 运行一次一个测试)
  • 您甚至可以使用您想要的任何 C# 版本(尽管某些语言功能需要 运行时间功能,而这些功能在 Unity 的 Mono 版本中不存在)。 Unity 5.x 中的 C# 版本限制实际上是促使我首先选择此选项的原因!

不过,这绝对是一种更底层的脚本编写方法,需要更多的 C#、CLR、MSBuild 等知识。但是,如果您不怕弄脏自己的双手,那么 Ashley Davis 有一个 excellent article about setting up Visual Studio to generated managed plugins, and the pros/cons of that approach. You can also use the excellent Unity3D NuGet package 可以在您的源代码项目中对 Unity 程序集进行跨平台和面向未来的引用。

希望事情已经解决了,再次感谢之前的 answers/comments!

错了!

至少它只是半真:

好的,是的,如果您的目标是通过 Inspector 的 Add Component 按钮使用和添加此组件,那么 NO 它需要位于具有匹配 class 名称的单个脚本文件中!

但是,如果你只在运行时通过脚本添加那个组件,那么YES你实际上可以做到!

Unity 本身就是这样做的,例如在 Dropdown 组件中(摘自使用 Rider 反编译的源代码)

public class Dropdown : Selectable, IPointerClickHandler, ISubmitHandler, ICancelHandler
{
    protected internal class DropdownItem : MonoBehaviour, IPointerEnterHandler, ICancelHandler
    {
        ...
    }
    
    ...
}

(Selectable 继承自 UIBehaviour 继承自 MonoBehaviour)

由于这种嵌套,在运行时稍后在检查器中看起来像这样

但绝对有可能!

要证明这一点,您可以轻松地做同样的事情,例如

public class TEST : MonoBehaviour
{
    private class NESTED : MonoBehaviour
    {
        [SerializeField] private int someInt;
        [SerializeField] private Transform someReference;
    }

    [ContextMenu("Add NESTED component")]
    private void AddNested()
    {
        gameObject.AddComponent<NESTED>();
    }
}

所以你可以看到,即使在播放模式之外的编辑时间,我也可以通过上下文菜单简单地添加这个“隐藏”组件

它仍然是序列化的,并且在关闭并重新加载场景后仍然存在: