Quick answer: Wrap editor-only paths with Engine.IsEditorHint(). Defer static initialization to _Ready. Rebuild the C# project after changes and reopen the scene.
A tool script that generates UI elements at edit time works once. Reload the project; the editor crashes on scene load. The stack trace points to a null reference inside your tool class, before _Ready ever fires.
Tool Scripts Run in the Editor
The [Tool] attribute makes a class active during editor scene operations — opening, saving, reloading. The class is instantiated; its constructor runs; _Ready may fire if the script is in an open scene; _Process runs every editor frame.
None of this happens in pure runtime classes. Tool scripts have to handle “Am I in the editor?” and “Is the engine fully ready?”
Fix 1: Guard with IsEditorHint
[Tool]
public partial class ProcGenLevel : Node
{
public override void _Ready()
{
if (Engine.IsEditorHint())
{
// Editor-only setup
GenerateLevelLayout();
}
else
{
// Runtime initialization
SpawnEnemies();
}
}
}
IsEditorHint returns true in the editor, false at runtime. Branch based on which environment you’re in.
Fix 2: Defer Static Initialization
// Wrong: runs as soon as the type loads, possibly before engine ready
private static readonly Texture2D _icon = ResourceLoader.Load<Texture2D>("res://icon.png");
// Right: lazy property
private static Texture2D _icon;
private static Texture2D Icon => _icon ??= ResourceLoader.Load<Texture2D>("res://icon.png");
Lazy initialization happens at first access, not at type load. By then, the engine is ready.
Fix 3: Rebuild After Code Changes
Godot caches compiled C# assemblies. Editing a tool script and reopening the scene without rebuilding can leave stale references that crash. Always:
- Save the C# file.
- Project → Tools → C# → Build (or use the build button in the toolbar).
- Wait for build to complete.
- Then reload/reopen the affected scene.
Fix 4: Avoid Heavy _Process in Tool
Don’t do continuous expensive work in editor _Process — it ticks every editor frame, even when you’re not focused on the relevant scene. Gate with IsEditorHint and use Engine.GetProcessFrames() % N == 0 to throttle.
Verifying
Reload the project. Open the scene with the tool script. No crash. Edit the script; rebuild; reload — still no crash. The editor behaves normally with the tool functionality active.
“Tool scripts run twice: editor and runtime. Branch on IsEditorHint and the editor stays stable.”
When in doubt, comment out the [Tool] attribute to confirm the crash is tool-related — isolates the diagnosis quickly.