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:

However, those restrictions apply only to public classes derived from MonoBehaviour
(or maybe from UnityEngine.Object
?). You can have as many other private MonoBehaviour
s 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 MonoBehaviour
s... 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!