2

I am using Unity 5.4.2f2 Personal, and I have written a C# script similar to the following:

using UnityEngine;
using System;

public class Outer : MonoBehaviour {

    // Some public outer fields

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

}

I would like to be able to attach both Outer and Inner to GameObjects in my scene. Unfortunately, Inner does not show up as an available script in the Unity Inspector, even if I add [Serializable] to it. I know a lot of people will probably say there's something wrong with my setup if I'm trying to make a nested class visible to the outside world, but for the sake of this question, I just want to know if this can be done. I.e., is there any way to make nested classes usable as Unity components?

Rabadash8820
  • 2,328
  • 3
  • 27
  • 49

5 Answers5

5

This answer is wrong!

At least it is only semi-true:

Okey, yes, it is true if your goal is to use and add this component via the Inspector's Add Component button, then NO it needs to sit in an individual script file with matching class name!

However, if you only add that component on runtime via script, then YES you actually can do that!

Unity itself does it e.g. in the Dropdown component (excerpt from the source code decompiled with Rider)

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

(Selectable inherits from UIBehaviour which inherits from MonoBehaviour)

Which on runtime later looks like this in the Inspector due to this nesting

enter image description here

but is definitely possible!

To prove this you can easily do the same thing e.g.

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

so as you can see even on edit time outside of playmode I can simply add this "hidden" component via the context menu

enter image description here

and it is still serialized and also still there after closing and reloading the scene:

enter image description here

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Excellent answer; I appreciate all the source code and GIFs. To be fair, @luizcarlosfx 's answer is still correct in the original spirit of my question: one cannot just declare a nested component and add it thru the Inspector. Adding that `ContextMenu` helper method and `SerializeField` on every field is quite a pain. But, you've acknowledged the "semi-truth" of both answers, and I think you've offered a really useful workaround here, so I'll mark yours as correct. Please edit out the line about the accepted answer being wrong. Thanks again for the input! – Rabadash8820 Aug 11 '21 at 18:14
  • Also worth noting that this was the same solution proposed by @TEXTUS GAMES answer, but you've definitely provided more detail. – Rabadash8820 Aug 11 '21 at 18:15
  • 1
    The `SerializeField` is only then required if the field is not `public` ;) – derHugo Aug 12 '21 at 07:07
4

No, there's no way to use a nested class as a unity component. To use your MonoBehaviour class as a unity component the class name must be equal to the file name of your script, so use nested class would't be possible.

If you rename your outer class to whatever other name you want without renaming the script file you will see that it will not be possible to use the new outer class too.

[EDIT]

I found the documentation. And as I said: "The class name and file name must be the same to enable the script component to be attached to a GameObject."

luizcarlosfx
  • 399
  • 1
  • 4
  • 11
  • Thanks for the answer! I found a question on Unity Answers that appears to agree with you (link below), but I couldn't find any official mention of this rule in the documentation. Do you have a link to it by chance? http://answers.unity3d.com/questions/497914/does-every-c-class-need-to-have-its-own-script-wit.html – Rabadash8820 Nov 09 '16 at 20:20
  • When I saw your question I searched on unity documentation, but I couldn't find that info too. I only knew it because of previous experience. Try renaming your outer class and draging your script to some game object, you'll see an error message – luizcarlosfx Nov 09 '16 at 20:25
  • Awesome, thanks for the link. Your help is much appreciated! – Rabadash8820 Nov 09 '16 at 20:36
1

You can not add it via unity standard menues. But you can add it through script. AddComponent<Outer.Inner>();

1

I've learned a lot since I asked this question so I figure I'll add some more info. @luizcarlosfx's answer and @derHugo's answer are still the most correct answers.

How Unity compiles scripts

Unity compiles all scripts in the project using a built-in version of the Mono compiler (a cross-platform C# compiler). It assigns a unique GUID to every script file (in the associated .meta file) just as it does for every other asset. This comes with some nice usability improvements: classes can be renamed without breaking existing script references because the GUID does not change, and scripts can be grouped together in folders with other related assets (e.g., all "soldier" scripts/sounds/textures can be grouped together).

Unity's script naming restrictions

The downside of that usability is that Unity enforces some naming restrictions:

  • Only one class per file, with the same name as the file, because otherwise Unity wouldn't know which GUID to use in Component script references
  • No nested classes
  • No classes named TheBehaviour in a file named MyBehaviour.cs
  • Probably no partial classes As stated in @derHugo's comment, this IS possible. The partial declarations can be in the same or different files, even with different names, as long as one of the file names match the name of the class.

If you select the script asset in the Project Window and see the following message, then you know that you're trying to name things in a way that Unity doesn't support:

Unity info message: No MonoBehaviour scripts in the file, or their names do not match the file name.

However, those restrictions apply only to public classes derived from MonoBehaviour (or maybe from UnityEngine.Object?). You can have as many other private MonoBehaviours or POCOs as you want in a file, because Unity will never serialize them, so they don't need their own GUID. Public POCOs marked with [Serializable] may have the same naming restrictions as public MonoBehaviours... I haven't tested this so I'm not sure. Why haven't I tested this? Well...

Avoiding these restrictions with managed plugins

If you're like and me, and come from a pure .NET background, then any restrictions on which classes can be defined in which files at all is probably maddening to you. In that case, you can store your scripts outside of the Unity project (I use another folder within the same repo as the Unity project), and build them manually with an IDE like Visual Studio or JetBrains Rider. The generated assemblies (.dll and .pdb files) can then be copied into your Unity project (ideally through some automated post-build event) as managed plugins. You can then:

  • Define as many classes per file as you want
  • Use whatever naming conventions you want
  • Use nested classes and partial classes, whatever you want, same as pure .NET
  • Write unit tests for your POCOs that don't rely on Unity or the Unity Test Framework (which has the awesome side benefit of letting you run unit tests in parallel; Unity can only run one test at a time)
  • You can even use whatever C# version you want (though some language features require runtime features that aren't present in Unity's version of Mono). C# version restrictions back in Unity 5.x are actually what led me to pursue this option in the first place!

This is definitely a more low-level approach to scripting though, that requires more knowledge of C#, the CLR, MSBuild, and the like. However, if you're not afraid to get your hands dirty, then Ashley Davis has an 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 to make cross-platform and future-proof references to Unity assemblies in your source code projects.

Hope that clears things up, and thanks again for the previous answers/comments!

Rabadash8820
  • 2,328
  • 3
  • 27
  • 49
  • `Probably no partial classes`, nope that's not true, `partial` is totally fine! Indeed you can even have the one part in a different folder with the same matching class name ... then it makes no difference which of both scripts you drag onto a GameObject, it simply adds that component ^^ – derHugo Aug 10 '21 at 13:17
  • @derHugo Fascinating! I'd never tried that. I'm curious how Unity maintains the references to both file GUIDs (assuming that's what it does...) Like what happens in that case if you move or rename one of the `partial` files? – Rabadash8820 Aug 11 '21 at 17:55
  • @derHugo nvm, I was able to test it myself. :) I've edited my answer to provide this additional info and link to your answer. A like would be much appreciated ;) – Rabadash8820 Aug 11 '21 at 18:38
-1

I don't believe that previous answer is correct.

You should be able to add a component that is nested by creating an editor script with a static method using the [MenuItem] attr to create and bind your MonoBehaviour.

This has worked in Unity 5 and up.

Lee Wood
  • 55
  • 7
  • saying something "should" work is purely speculative and isn't a constructive answer without proper example or just more effort put into instruction. – FanManPro Feb 16 '21 at 15:38