Setting Up Scripts in a Mod¶
The mechanics of getting a C# file from "it exists in my project" to "it runs inside the game". If you have not read the Scripting overview yet, start there — it explains what a mod script is and what you are and are not allowed to write.
Before you start¶
| Requirement | How to check |
|---|---|
| You have a mod folder, created via Mod Tools → Create → New Mod | It exists somewhere under Assets/, e.g. Assets/Mods/YourNick_ModName/ |
| The mod folder is inside the Unity project | uMod refuses otherwise: "Failed to create mod at path '…' because it is not inside the 'Assets' folder". Pointing it at Assets itself is refused too: "The specified mod folder path points to the 'Assets' folder which is not allowed. Please select a subfolder of 'Assets' as the mod folder" |
| Your C# project files have been generated | Run Assets → Open C# Project once. If it does nothing, install the Visual Studio or Rider editor package from the Package Manager first |
| The project compiles cleanly | No red errors in the Console. A project that does not compile in the Editor cannot be built into a mod |
If you have not built a mod at all yet, do Getting Started first and confirm you can produce a working .mod without any code. Adding scripting to a build that already works is far easier to debug than doing both at once.
Step 1 — Add a Scripts folder¶
Keep code beside the rest of your mod's content, inside the mod folder:
Assets/
└── Mods/
└── YourNick_ModName/
├── Definitions/
├── Icons/
├── Models/
├── Prefabs/
└── Scripts/ ← your .cs files go here
The folder name does not matter — Scripts is just a convention. What matters is that the file sits somewhere inside the mod folder. uMod is explicit about this: "Only scripts inside the mod folder will be compiled".
Step 2 — Create a .cs file¶
Right-click inside your Scripts folder and use Unity's Create menu to add a C# script. The exact label of that entry has moved around between Unity versions, so pick whichever one creates a plain C# / MonoBehaviour script.
The file name must match the class name
This is a Unity rule, not a Mod Tools one: a MonoBehaviour in HelloMod.cs must be called HelloMod. If they differ, Unity cannot attach the component to a prefab, and the Inspector shows an empty script slot.
Step 3 — Namespace it with your nickname¶
Put every class you write inside a namespace that starts with your modder nickname:
Two concrete reasons, not just tidiness:
- A single Unity project can hold several mods, and in the Editor all of their scripts compile into the same
Assembly-CSharp. If two mods both define aFireworkclass with no namespace, that is a hard compile error that stops you building either of them. - The Messenger bus keys events by the message type's full name, namespace included. Unique namespaces are what stop your custom message types colliding with someone else's.
Class names tend to end up baked into saved blueprints
If you later implement ISaveableComponent, the key written into players' blueprint files is whatever your SaveableComponentTypeId property returns. Every implementation shipped with the game returns this.GetType().Name — the short class name, without the namespace — and if you follow that convention, renaming the class breaks every blueprint that already contains your item. Choose the name once and keep it forever. See Saving & Loading (Blueprints).
Step 4 — Write a MonoBehaviour¶
A complete, minimal first script. It does nothing useful, which is the point — build this first and confirm it reaches the game before writing anything real.
using UnityEngine;
namespace YourNick.YourMod
{
public class HelloMod : MonoBehaviour
{
[SerializeField]
private string _message = "Hello from YourMod!";
private void Start()
{
Debug.Log($"[YourMod] {_message}");
}
private void OnDestroy()
{
Debug.Log("[YourMod] Cleaning up.");
}
}
}
Notes on the shape of it:
[SerializeField] privaterather thanpublic— the field shows in the Inspector without becoming part of your public API.Start()andOnDestroy()are the two lifecycle hooks you can rely on everywhere, including on a prefab spawned by a StartupPrefabDefinition.- Prefixing logs with your mod name makes them findable in a player's log file. You are sharing the Console with the game and every other mod.
Step 5 — Put it on something the game will load¶
A script only runs if something in your mod puts it into the scene. The routes:
| Route | Use it for |
|---|---|
A component on a prefab referenced by an EntityDefinition |
Behaviour that belongs to a spawnable item — a firework, a prop |
A component on a prefab referenced by a StartupPrefabDefinition |
Map-wide logic that should run once after the map and all mods have loaded |
| A component on a GameObject in a scene shipped as a custom map | Logic that only makes sense on that one map |
Drag HelloMod onto the prefab's root GameObject like any other component. Full detail on the definition-driven routes, and the order things happen in, is on Entry Points & Lifecycle; the map route is on Custom Maps.
A script nobody references never runs
There is no auto-discovery, no plugin registry and no [RuntimeInitializeOnLoadMethod] entry point in Fireworks Mania. If your code is not on a prefab that an asset in your mod points at, or on an object in a map scene you ship, it is dead weight in the assembly.
Step 6 — Build and check the log¶
Run Mod Tools → Build Mod (Ctrl+Shift+B) and watch the Console. When your mod contains script content you should see a line beginning:
That line is emitted once per compiled mod assembly, so seeing it is a good signal your scripts were picked up and compiled. If your mod has no NetworkBehaviour in it, the next thing you see is normal and harmless:
Netcode diagnostics don't go to the Unity Console
The code generator's own diagnostics are written with Console.WriteLine, which means they land in Editor.log, not the Unity Console window. If a networked build fails in a way the Console cannot explain, open Editor.log.
Do not add an Assembly Definition¶
An .asmdef in your mod folder will silently ship a mod with no code
This is the opposite of normal Unity advice, and it is the single most common way to end up with a build that "succeeds" but contains none of your components.
Why¶
The mod build pipeline does not consume Unity's compiled assemblies. It locates the project's script .csproj — the one Unity generates for Assembly-CSharp — takes the compiler settings and references from it, and compiles the subset of .cs files that live inside your mod folder into a fresh assembly.1
Unity generates one .csproj per Assembly Definition. The moment you add an .asmdef to your mod folder, your scripts move out of the Assembly-CSharp project and into YourAsmdef.csproj — a file the mod build pipeline is not looking at.
What the failure looks like¶
It is quiet. The build reports success and produces a .mod file — it just does not contain your code, so your components are missing when the game loads the mod.
There is no dedicated warning for it. What you have instead is the compile log: uMod prints one line per file it is actually going to compile,
so if your .cs files are not in that list, they are not in the mod. When nothing at all matches, it says so:
Stale project files produce the same symptom, so if you see it and you have no .asmdef, run Assets → Open C# Project to regenerate and build again.
You already have every reference an asmdef would give you¶
See What you get for free — FireworksMania.Core, Unity.Netcode, Unity.Collections, UniTask, DOTween, TextMeshPro and Newtonsoft.Json are all auto-referenced into Assembly-CSharp. There is no reference an asmdef would add that you do not already have.
Build-time script diagnostics¶
Messages you may see during Mod Tools → Build Mod, quoted as they appear so you can search for them.
| Message | What it means | Fix |
|---|---|---|
Failed to locate script project file. Scripts cannot be compiled for the mod export. Make sure the .csproj file exists |
No Assembly-CSharp.csproj in the project root |
Assets → Open C# Project, or install the Visual Studio / Rider editor package and regenerate |
Script compilation will be skipped. No source files were specified! |
uMod found the project file but none of your mod folder's .cs files in it — stale project files, or the scripts sit in their own .asmdef |
Regenerate project files; remove the .asmdef |
Only scripts inside the mod folder will be compiled |
Informational. Code outside the mod folder is not exported | Move the code inside the mod folder |
Scripts will not be compiled because the API compatibility level is not supported by the target game. Please switch to a lower API level |
Player Settings API level is too high | Set Player → Other Settings → Api Compatibility Level back to .NET Standard, which is what the Mod Tools project ships with |
One or more C# scripts compiled into '…' failed security verification |
You used something on the deny list | See the next section |
More build errors — asset validation, scene checks — are on Troubleshooting & Build Errors.
Security restrictions¶
Every mod build runs a code security check over your compiled assembly before packing it. The check exists so a player can install a mod from the internet without auditing it first: a mod cannot read their files, cannot load code at runtime, cannot call into native libraries, and cannot shut their game down.
The deny list¶
| Category | Denied entries |
|---|---|
| Assembly references | UnityEditor, Mono.Cecil |
| Namespace references | System.IO.*, System.Reflection.*, System.Runtime.InteropServices |
| Type references | System.AppDomain, System.Threading.Process |
| Member references | UnityEngine.Application.Quit |
| P/Invoke | Disallowed entirely — no [DllImport] |
Treat the whole area as off-limits
The entries above are reproduced exactly as they are stored in the Mod Tools' security settings — some carry a .* suffix and some do not. Rather than reasoning about which sub-namespace might slip through, just stay out of file I/O, reflection and interop altogether. There is no supported way to get at them.
The failure message¶
When the check trips, the build stops during its Running Code Validation / Code Security Checks steps and you get:
One or more C# scripts compiled into '<YourAssembly>' failed security verification
Code security validation failed
The useful detail is in the per-item lines logged alongside it, one per offending reference:
Illegal reference to disallowed namespace: <namespace>
Indirect illegal reference via namespace exclusion to disallowed type: <type>
Indirect illegal reference via type exclusion to disallowed member: <member>
Read those to find out which category you tripped, then find the offending using in your mod folder.
What to do instead¶
| You cannot | Do this instead |
|---|---|
Read or write files (System.IO) — including your own config file |
Put tunable values in [SerializeField] fields on your prefab, and persist per-instance state through the blueprint system: Saving & Loading (Blueprints) |
Use reflection (System.Reflection) to poke at game internals |
Reference the public FireworksMania.Core API directly. If something you need is not public, it is not part of the mod surface |
Call native code (System.Runtime.InteropServices, [DllImport]) |
Nothing equivalent exists. Solve it in managed C# or not at all |
Quit the game (UnityEngine.Application.Quit) |
Nothing. A mod is not allowed to close the player's game |
Reference UnityEditor from a runtime script |
Keep editor tooling outside your mod folder entirely — it still works for your own workflow, and it never ends up in the mod. Do not rely on #if UNITY_EDITOR to hide it |
Ship a precompiled .dll |
Vendor the third-party library as .cs source inside your mod folder. It has to pass the same security check, which rules out most general-purpose libraries |
Sharing code between two of your own mods¶
A shared "common" folder does not work. Only scripts inside a given mod folder are compiled into that mod, so helper code parked elsewhere in your project builds fine in the Editor and is then missing at runtime.
If two of your mods genuinely need the same helper, copy the source into both mod folders and give each copy its own namespace — remember that in the Editor they all share one Assembly-CSharp, so two identically-named classes in the same namespace will not compile.
Before you build, check¶
- Every script is inside the mod folder
- There is no
.asmdefanywhere in the mod folder - Every class is in a namespace starting with your nickname
- The project compiles in the Editor with no Console errors
- Your component is attached to a prefab that an
EntityDefinitionorStartupPrefabDefinitionin your mod points at, or to an object in a map scene you ship - The build log contains a
Netcode for Gameobject CodeGen patching assembly:line - No
System.IO,System.Reflection,System.Runtime.InteropServices,AppDomainorApplication.Quitanywhere in your mod's code
Where to next¶
| I want to… | Page |
|---|---|
| Know exactly when my code runs, and in what order | Entry Points & Lifecycle |
| Subclass the firework base class | Writing a Custom Firework |
| Make it work in multiplayer | Multiplayer & Netcode |
| React to game events | The Messenger Event Bus |
| Look up a component's fields | Script Reference |
-
The exact mechanism here is inferred from the diagnostic strings inside uMod's build engine assembly (
Failed to locate script project file. Scripts cannot be compiled for the mod export. Make sure the .csproj file exists,Failed to locate script firstpass project file…,found {0} potential matches when locating project file, selecting project {1}, plus MSBuild vocabulary such asPropertyGroup/ItemGroup/Reference/DefineConstants), not from source we can read. The practical rule — keep your scripts inAssembly-CSharp, i.e. no.asmdef— is what all of the evidence points at, and it costs you nothing to follow. ↩