Project setup

Which scripts Udonite compiles, how assembly definitions fit in, and how to keep code out of Udon.

Updated 2026-09-05

Which scripts become programs

A class becomes an Udon program when all of this is true:

  • it derives from MonoBehaviour, whether directly or through UdoniteBehaviour
  • it is not abstract, not generic, and not nested inside another type
  • its script lives somewhere under Assets/

Every class that qualifies is a root, and each root gets its own Udon program attached beside it on the GameObject. Nothing has to be registered, and no folder is special: a script in Assets/Scripts/Doors/Door.cs and one in Assets/Door.cs are found the same way.

A class that does not qualify is not an error. Abstract base classes, generic helpers, plain data classes and static utilities are ordinary C# that your behaviours use; they simply are not programs themselves.

Assembly definitions

Udonite works the same whether or not you use them.

With no assembly definition anywhere, your scripts land in Unity's default Assembly-CSharp and Udonite compiles them. Add an .asmdef and your scripts land in that assembly instead, and Udonite compiles those. Neither is the supported path with the other as a workaround; the rule is only that the assembly has source under Assets/.

This matters more than it sounds, because the assembly is what decides two things your code can see:

References. A behaviour can use any type its own assembly references. If you split a world into World.Runtime and World.Interactables and the second references the first, a behaviour in World.Interactables can use types from World.Runtime normally.

Define symbols. Your own scripting define symbols are the ones Udonite compiles with, so #if MY_FEATURE behaves here exactly as it does in the rest of the project.

Four of Unity's own are deliberately not passed through: UNITY_EDITOR, its platform variants, DEBUG and TRACE. A world is a release player build, and the Editor defines all four while a release build defines none of them. So #if UNITY_EDITOR code stays out of your Udon program exactly as it stays out of a Unity build, and a #if DEBUG block does too — without that, debug-only code would compile into an uploaded world. If you want a switch you control, declare your own define rather than leaning on the Editor's.

Editor-only assemblies are skipped. An .asmdef with its platform list set to Editor holds tools, not world logic, and nothing in it is compiled to Udon.

More than one file

A behaviour is not limited to what one file contains. Helper classes, enums, interfaces and base classes in other files of the same assembly are available to it, so you can lay a world out across as many files as it deserves.

The boundary is the assembly, not the file. If a helper lives in a different assembly, the behaviour's assembly has to reference it, the same as anywhere else in Unity.

Scripts in packages

Scripts under Packages/ are not compiled to Udon. This is deliberate rather than a gap. An imported package can contain hundreds of MonoBehaviour classes written with no idea Udon exists, and compiling them would bury the console in refusals about code you did not write and cannot change.

The one exception is the components Udonite itself ships, such as NetworkTransform. Those are built to run in a world, so they are compiled even though they live in a package.

If you want a package's behaviour in your world, the reliable route is to write your own behaviour under Assets/ and drive the package's component from it.

Alongside UdonSharp

A class deriving from UdonSharpBehaviour is left alone for UdonSharp to compile. Udonite claims MonoBehaviour classes and nothing else, so both compilers can run in one project without arguing over the same script.

Keeping code out of Udon

Some code under Assets/ should never become a program: an editor tool, a vendored component that only runs on desktop, a script you are part way through porting. Exclude it and Udonite stops compiling it.

The fastest way is a checkbox: open Udonite → Open Window, and in the Overview tab's tree, uncheck the script or folder. Unchecking a folder excludes everything beneath it; checking an excluded one back on removes the exclusion rather than leaving a disabled line behind. An excluded script or folder stays visible in the tree, greyed out, rather than disappearing — with a warning if it still has a live instance in the open scene, since that instance is about to get no Udon program. The same window's Diagnostics and Settings tabs are covered in Getting started.

Every checkbox reads and writes the same file underneath, so hand-editing it works exactly as well as the checkbox does. Exclusions live in ProjectSettings/UdoniteExclusions.txt, one per line, and order does not matter. A line is either an assembly name or an asset GUID:

# Udonite exclusions. One per line; order is not significant.
asm:SomeVendor.Runtime                   # a package that will never run in a world
guid:3c6e5249679282e459858775b10f38d0    # Assets/Editor/LevelBuilder.cs

asm: takes an assembly name, which is usually what you want for a whole third-party package, since Unity compiles one of those into one assembly.

guid: takes Unity's 32 hexadecimal digits, from the .meta file beside the asset. It can name a single script or a folder: point it at a folder's GUID and everything beneath that folder is excluded, scripts added later included, with no separate entry needed per file.

The GUID is deliberate. A path stops matching the moment somebody moves the file, and it stops matching silently, which is the worst way for a rule like this to fail. A GUID survives the move, so the trailing # note is there for a human reading the file, not to survive one: if the asset it names is ever deleted, the line is removed automatically the next time anything reads the file — opening the window, or the next compile — rather than being left behind naming nothing.

A folder's GUID is awkward to find by hand — Unity does not show one anywhere in its own UI. Excluding a folder from Overview never needs it typed in at all; that is the reliable way to get a folder's line into the file.

There is no re-include. A folder exclusion cannot be narrowed by then including one file back out of it: exclude that one file's own GUID from outside the folder instead, or move it out of the excluded folder.

An entry can be switched off without deleting it: a leading !, as in !asm:Cinemachine, keeps the line (and its note) in the file but excludes nothing while it reads that way — Overview shows that script as included, the same as if the line were not there. Writing a ! line is a hand edit; nothing in Overview does it for you. But excluding that same script from Overview afterward re-enables the line instead of writing a duplicate, so a hand-added ! needs no re-typed GUID to undo — including it again after that removes the line outright rather than disabling it a second time.

Two things go wrong here often enough to have their own codes. A line Udonite cannot read is reported as UDN0007, with the form it expected. A behaviour that still calls into excluded code is reported as UDN0008, because the call would have nothing to run in the world.

What Udonite tells you while you work

Two things report before a compile, and both are meant to be read where you are already looking.

Squiggles in the editor. Udonite ships a Roslyn analyzer, so a handful of refusals appear as you type rather than after a compile: a mutable static field, a catch clause, and a Unity event Udon never dispatches. They carry the same UDN codes the compiler uses, so a search finds one answer rather than two.

They are warnings, and they do not catch everything. A clean editor does not mean a program compiles; only compiling it tells you that.

An empty behaviour reference. When a public field that points at another behaviour has nothing wired to it, the console says so, naming the behaviour and the field:

Udonite: Switch on 'Door Controls' has an empty behaviour reference: 'target'.
Calling through one halts the behaviour for the rest of the session with nothing
logged, so wire it in the inspector, or make the field private if the script
assigns it itself.

Only references to other behaviours are reported. An empty GameObject or AudioSource field is ordinary and often deliberate; a reference to a behaviour exists to be called, and calling a null one is the failure that leaves no trace.

Where the programs go

Each program is attached next to its behaviour on the same GameObject, and it is what ships when you upload. Your C# stays in the project for the next edit. Nothing needs to be committed beyond the usual: the programs are rebuilt whenever Unity recompiles and whenever a scene is saved.

Something went wrong Reload