Read this when you write C# for a Unity 6 VR or XR teleoperation app (for example, on Meta Quest) or for a plain .NET tool, and want Rider, Visual Studio, VS Code and CI to apply the same rules.
We follow Microsoft's C# coding conventions and pick, from the options Unity's C# style guide offers, the naming that matches Microsoft's. One .editorconfig carries the formatting rules, the naming rules and the analyzer severities, including the Unity-specific analyzers. The one thing to remember: your editor enforces these rules while you type; Unity's own compiler does not run the .NET code-style rules, so CI checks them with dotnet format.
- The style we follow
- Naming and file layout
- What the config does
- How Unity 6 uses analyzers and .editorconfig
- Install and editor setup
- Adopt the config in a repo
- Check in CI
- Common mistakes
- Related
- Microsoft's common C# code conventions and identifier naming rules: Allman braces (each brace on its own line), four-space indentation, PascalCase for types and members.
- Unity's C# style guide (Unity 6 edition) for Unity-specific advice: serialized fields, MonoBehaviour messages, organizing scripts. Its naming tips say Unity's own guides prefix private fields with
m_, constants withk_and statics withs_, and that others use an underscore instead. We use the underscore, because it is what Microsoft's conventions and the .NET libraries use, and the Inspector hides it (_maxSpeedshows as "Max Speed").
Unity 6 compiles C# 9.0, and its default API compatibility level is .NET Standard 2.1 (C# compiler, .NET profile support). So no file-scoped namespaces, record types in serialized data or init setters in Unity code.
| Thing | Style | Example |
|---|---|---|
| Namespaces | PascalCase, by feature |
Atr.Teleoperation |
| Classes, structs, enums, delegates | PascalCase |
HandTargetFollower, TeleopCommand |
| Interfaces | I + PascalCase |
IRobotLink |
| Methods, properties, events | PascalCase |
Engage(), LastCommand |
| Public fields | PascalCase (prefer a property or a [SerializeField] private field) |
MaxSpeed |
Private and internal fields, including [SerializeField] |
_camelCase |
_maxSpeed |
| Private static fields | s_camelCase |
s_instanceCount |
| Constants | PascalCase |
DefaultSpeed |
| Parameters and locals | camelCase |
angleStep |
| Type parameters | T + PascalCase |
TMessage |
| Files | One class per file, named after it (Unity requires this for MonoBehaviours) | HandTargetFollower.cs |
Keep your scripts under Assets/Scripts/<Feature>/ with an assembly definition (.asmdef) per feature, so a change recompiles less and analyzers can be scoped to your code.
configs/csharp-unity/.editorconfig has four parts:
- Formatting (
IDE0055): Allman braces, four spaces,using Systemfirst. Unity's serialized files (.unity,.prefab,.asset,.metaand others) are left exactly as Unity writes them. - Code style: braces always (
IDE0011), explicit access modifiers (IDE0040), nothis.,usingoutside the namespace, block-scoped namespaces (C# 9). - Naming (
IDE1006): the table above, asdotnet_naming_ruleentries (naming rules reference). - Analyzer severities: the rules above as warnings, plus these Microsoft.Unity.Analyzers rules raised from their default "info" level, which is easy to miss:
| Rule | Catches |
|---|---|
UNT0001 |
Empty Unity message, such as an empty Update() that still costs a call every frame |
UNT0002 |
Tag comparison with ==; use CompareTag |
UNT0006 |
A Unity message with the wrong signature, which Unity will never call |
UNT0007, UNT0008, UNT0029 |
??, ?. and is null on Unity objects, which skip Unity's destroyed-object check |
UNT0026 |
GetComponent that allocates; use TryGetComponent |
This part confuses most people, so here is what the Unity 6 manual documents:
- Your editor is where you see the rules. Visual Studio, Rider and VS Code (with the Unity extension) read
.editorconfigfor formatting, naming and severities. Microsoft.Unity.Analyzers ships with Visual Studio's Unity workload and the VS Code Unity extension. - Analyzers inside the Unity Editor are DLLs that you add to the project. Put the DLL under
Assets/, turn off every platform in its import settings and give it the asset labelRoslynAnalyzer(case-sensitive). It applies to the assembly definition it sits in and to assemblies that reference it (install an analyzer). Diagnostics then show in the Unity Console. - Severities for those analyzers can come from
Assets/Default.rulesetor, as the manual says, fromdotnet_diagnostic.<ID>.severitylines in a root.editorconfig(analyzer scope and rule sets). A Unity issue reported on 2022.3 says the Editor ignored.editorconfigthere, so check the Console after you change a severity; if nothing changes, use the rule set file. - The .NET code-style rules (IDE0055, IDE1006 and friends) come with the .NET SDK and your editor, not with Unity. That is why CI runs
dotnet format.
If you want Microsoft.Unity.Analyzers in the Unity Console too, its maintainers describe the rule-set route in issue 242; their stated target is Visual Studio, so treat the Console as a bonus.
Install Unity 6 through Unity Hub, and the .NET 10 SDK (the current long-term support release, supported until Nov. 14, 2028, per the .NET support policy) for dotnet format:
# macOS
brew install --cask dotnet-sdk
# Ubuntu 24.04 (in the built-in Ubuntu feed)
sudo apt-get update && sudo apt-get install -y dotnet-sdk-10.0Editors (pick one, and set it under Unity's Preferences > External Tools):
- Rider reads
.editorconfigfor naming styles and inspection severities (Rider docs). - Visual Studio with the "Game development with Unity" workload: reads
.editorconfigand includes Microsoft.Unity.Analyzers. - VS Code with the Unity extension (
visualstudiotoolsforunity.vstuc), which installs C# Dev Kit and needs the Visual Studio Editor package 2.0.20 or later in Unity (VS Code Unity guide).
For a Meta Quest project, start with Meta's Unity setup guide.
Run from the repo root (the folder that holds Assets/):
curl -fsSLO https://raw.githubusercontent.com/ATR-Lab/dev-guidelines/master/configs/csharp-unity/.editorconfig
# Fix whitespace in your scripts once, in its own commit (works without project files)
dotnet format whitespace Assets --folderThen open a script in your editor and fix the naming warnings it shows, one feature at a time. For a plain .NET project, add these to the .csproj so the build enforces the style too:
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>EnforceCodeStyleInBuild runs the IDE rules during dotnet build, which is off by default (MSBuild reference); GenerateDocumentationFile lets IDE0005 (unused usings) run on build.
Unity projects: CI cannot build without a Unity license, but it can check formatting from the source files alone.
name: csharp
on: [push, pull_request]
jobs:
format:
runs-on: ubuntu-latest
container: mcr.microsoft.com/dotnet/sdk:10.0
steps:
- uses: actions/checkout@v7
- run: dotnet format whitespace Assets --folder --verify-no-changesPlain .NET projects: check everything, including naming and analyzers.
- run: dotnet format --verify-no-changes --severity warn
- run: dotnet build -warnaserror?.,??oris nullon aGameObjector component. Unity objects override==to report destroyed objects as null; the C# operators skip that check. Useif (_hand == null).- Public fields just to see them in the Inspector. Use
[SerializeField] privateso other scripts cannot change them. GetComponentinUpdate(). Cache the reference inAwake(), or useTryGetComponent.- Unity's
.metafiles missing from Git. Commit every.metafile; Unity uses them to keep references. Use the Unity template from github/gitignore soLibrary/,Temp/andLogs/stay out. - Coordinate frames. Unity is left-handed with Y up; ROS 2 is right-handed with Z up (REP 103). Convert in one place (the bridge) and say so in a comment.
- Large assets in plain Git. Track textures, models and audio with Git LFS (see Git and GitHub).
- Config file:
configs/csharp-unity/.editorconfig; example scripts and the check harness inexamples/csharp-unity/ - TeleBot-4R-XR: the lab's public XR interface for TeleBot-4R
- ROS 2 packages: the robot side of a Unity teleoperation setup
dotnet formatreference, analyzer configuration options, Microsoft.Unity.Analyzers rule list
Last reviewed: Sept. 30, 2026 · Sources checked on that date · Suggest a change