Helper scripts, runtime extensions and Editor tooling for Unity.
Install via Package Manager → Add package from git URL…
https://github.com/NMeJa/NMJToolBox.git
- Unity: 2022.3 LTS or newer (uses C# 9 syntax: target-typed
new(), range operators, switch expressions). The manifest said 2021.2 until v4.0.0, which was never accurate — the Scene view overlays useListView.selectionChanged(2022.x; it replacedonSelectionChange) andFindAnyObjectByType(2021.3.18+). - Dependencies: none.
JsonExtensions.ParseJson<T>needscom.unity.nuget.newtonsoft-json, but it lives in an opt-in assembly — see Optional package integrations - License: MIT
The PlayerPrefs Manager that used to live in this package now ships separately — see Related packages.
| Area | Assembly | Path |
|---|---|---|
| Runtime | NMJ.NMJTools.Runtime |
NMJToolBox/NMJ/Runtime/ |
| Editor | NMJ.NMJTools.Editor |
NMJToolBox/NMJ/Editor/ |
| Scene view overlays | NMJ.NMJTools.SceneTools.Editor |
NMJToolBox/NMJ/Editor.SceneTools/ |
| Inspector attributes (opt-in) | NMJ.NMJTools.Attributes (+ .Editor) |
NMJToolBox/NMJ/Runtime.Attributes/, Editor.Attributes/ |
| UniTask integration (opt-in) | NMJ.NMJTools.UniTask |
NMJToolBox/NMJ/Runtime.Integrations.UniTask/ |
| LitMotion integration (opt-in) | NMJ.NMJTools.LitMotion |
NMJToolBox/NMJ/Runtime.Integrations.LitMotion/ |
| Newtonsoft integration (opt-in) | NMJ.NMJTools.Newtonsoft |
NMJToolBox/NMJ/Runtime.Integrations.Newtonsoft/ |
The opt-in assemblies are gated by defineConstraints and compile only when the matching package
is present — see Optional package integrations.
Namespaces mirror folders under each assembly root, per the usual C# convention:
| Folder | Namespace | Folder | Namespace | |
|---|---|---|---|---|
Runtime/Extensions |
NMJTools.Extensions |
Editor/BatchRename |
NMJTools.BatchRename |
|
Runtime/Pooling |
NMJTools.Pooling |
Editor/BuildTools |
NMJTools.BuildTools |
|
Runtime/Patterns |
NMJTools.Patterns |
Editor/Drawers |
NMJTools.Drawers |
|
Runtime/Attributes |
NMJTools.Attributes |
Editor/Hierarchy |
NMJTools.Hierarchy |
|
Runtime/Debugging |
NMJTools.Debugging |
Editor/Prefabs |
NMJTools.Prefabs |
|
Runtime/Scenes |
NMJTools.Scenes |
Editor/Setup |
NMJTools.Setup |
|
Runtime/Tools |
NMJTools.Tools |
Editor/Scaffolding |
NMJTools.Scaffolding |
|
Editor/MaterialCreation |
NMJTools.MaterialCreation |
|||
Editor/Tools |
NMJTools.Tools |
Folder names are constrained by that convention. A namespace segment under
NMJToolsshadows any Unity namespace or class of the same name, for every namespace nested beneath it — C# resolves enclosing namespaces beforeusingdirectives.Runtime/Gizmos/madeUnityEngine.Gizmosunreachable package-wide, forcingUnityEngine.Gizmos.DrawLine(...)even in files that had nothing to do with it. It is nowRuntime/Debugging/.Segments to avoid for the same reason: Gizmos · Editor · Serialization · Build · Rendering · Events · Audio · Video · UI · Animations · Playables · Profiling · Assertions · Scripting · SceneManagement · Search · Experimental · Compilation · Tilemaps · Pool.
Editoris the worst of them — it would break every: Editorand[CustomEditor]class in the package.The same applies to your own types: a folder must not be named after a class inside it.
Runtime/Patterns/StateMachine/containingclass StateMachinemade the class unreachable by its bare name from anywhere else.
| Menu path | Shortcut | Script |
|---|---|---|
Tools ▸ NMJ ▸ Reset Transform |
(unbound) | GameplayTools |
Tools ▸ NMJ ▸ Setup ▸ Create Folder Structure |
FolderStructureWindow |
|
Tools ▸ NMJ ▸ State Machine Generator |
StateMachineGenerator |
|
Tools ▸ NMJ ▸ Batch Rename |
Ctrl+Shift+R |
BatchRenameWindow |
Tools ▸ NMJ ▸ Shortcuts ▸ Batch Rename — Take Over F2 |
BatchRenameShortcut |
|
Assets ▸ NMJ ▸ Batch Rename |
BatchRenameWindow |
|
GameObject ▸ NMJ ▸ Create Collection |
Ctrl+G |
CreateCollection |
GameObject ▸ NMJ ▸ Dissolve Collection |
Ctrl+Shift+G |
CreateCollection |
GameObject ▸ NMJ ▸ Pool Manager |
PoolManagerEditor |
|
GameObject ▸ NMJ ▸ Replace With Prefab… |
PrefabTools |
|
Assets ▸ NMJ ▸ Select Instances In Open Scenes |
PrefabTools |
|
Assets ▸ NMJ ▸ Find Usages |
AssetUsageBridge |
|
Tools ▸ NMJ ▸ Find Missing Scripts In Open Scenes |
PrefabTools |
|
Tools ▸ NMJ ▸ Setup ▸ Project Setup |
ProjectSetupWindow |
|
Tools ▸ NMJ ▸ Setup ▸ Re-scan Packages |
PackageDetector |
|
Tools ▸ NMJ ▸ Setup ▸ Open manifest.json |
PackageManifest |
|
Assets ▸ NMJ ▸ Create Material From Textures |
MaterialFromTextures |
|
Assets ▸ NMJ ▸ Open in External Editor |
OpenInExternalEditor |
|
Assets ▸ NMJ ▸ Reveal in Explorer |
OpenInExternalEditor |
|
Tools ▸ NMJ ▸ Build ▸ Build Timestamped |
Ctrl+Alt+B |
BuildTools |
Tools ▸ NMJ ▸ Build ▸ Increment … Version |
BuildTools |
|
Tools ▸ NMJ ▸ Open ▸ … |
BuildTools |
|
Component ⋮ ▸ Rename GameObject To This Component |
GameObjectName |
|
Scene view ⋮ ▸ Mini Hierarchy / Inspector / Selection History / Isolate |
` |
Scene view overlays |
Extension methods for IEnumerable<T>, IList<T>, T[], List<T>, Queue<T> and IDictionary<,>.
| Method | Description |
|---|---|
IsEmpty<T>(this IEnumerable<T>) |
true when the source is null or has no elements. |
DistinctBy<T,TKey>(this IEnumerable<T>, Func<T,TKey>) |
Distinct by a projected key, keeping the first of each group. |
Shuffle<T>(this IEnumerable<T>, bool canBeNull = false) |
Returns a shuffled copy (uses System.Random). Throws on an empty source unless canBeNull is set. |
Shuffle<T>(this IEnumerable<T>, int seed, bool canBeNull = false) |
Same, deterministic for a given seed. |
Shuffle<T>(this IList<T>) |
Fisher–Yates in place, using UnityEngine.Random. |
GetRandomElement<T>(this IList<T>) / (this T[]) |
Random element. |
RandomElement<T>(this IEnumerable<T>) |
Random element; throws on an empty source (Editor only). |
GetRandomValue<TKey,TVal>(this IDictionary<TKey,TVal>) |
Random value. |
GetRandomKey<TKey,TVal>(this IDictionary<TKey,TVal>) |
Random key. |
GetRandomKeyValuePair<TKey,TVal>(this IDictionary<TKey,TVal>) |
Random pair (key and value are drawn from the same index). |
Pop<T>(this List<T>) |
Removes and returns the last element. |
PopAt<T>(this List<T>, int index) |
Removes and returns the element at index. |
Dequeue<T>(this List<T>) |
Removes and returns the first element. |
Loop<T>(this T[], int index) |
Returns element[index], wrapping to 0 when index >= Length. |
DequeueChunk<T>(this Queue<T>, int chunkSize) |
Lazily dequeues up to chunkSize items. |
using NMJTools.Extensions;
var deck = new List<Card>(allCards);
deck.Shuffle(); // in place
var one = deck.GetRandomElement();
var top = deck.Pop();
if (deck.IsEmpty()) Reshuffle();| Method | Description |
|---|---|
TrimStart(this string, string wordToRemove) |
Removes wordToRemove from the start, if present. |
TrimEnd(this string, string wordToRemove) |
Removes wordToRemove from the end, if present. |
ReplaceUnderscoresWithSpaces() |
"player_healthBar" → "player healthBar" |
SplitCamelCase() |
"player_healthBar" → "player_health Bar" |
CapitalizeFirstLetter() |
Title-cases every word (invariant culture) |
RemoveExcessWhitespace() |
Collapses runs of whitespace to one space |
Beautify() |
All four of the above, in order → "Player Health Bar" |
All methods return string.Empty for null/empty input.
TrimStart/TrimEndare string overloads of BCL names that normally takechar[]. The compiler picks the extension only because no instance overload accepts astring.
| Method | Description |
|---|---|
ParseJson<T>(this string json) |
JsonConvert.DeserializeObject<T>, rethrowing with the offending JSON in the message. |
Lives in its own defineConstraints-gated assembly (NMJ.NMJTools.Newtonsoft, define
NMJ_NEWTONSOFT) — the same pattern as the UniTask and LitMotion integrations.
That is what lets NMJ ToolBox declare no package dependencies at all. Without
com.unity.nuget.newtonsoft-json installed the define is never set, the assembly is skipped, and
ParseJson<T> is simply absent; nothing else in the package notices. Install it from
Tools ▸ NMJ ▸ Setup ▸ Project Setup to switch it on.
| Method | Description |
|---|---|
DisplayVisibility(this IStyle, DisplayStyle) |
Sets style.display. |
ChangeBackgroundColor(this IStyle, Color) |
Sets style.backgroundColor. |
[DefaultExecutionOrder(-10)] abstract MonoBehaviour singleton base.
public class AudioManager : SingletonBase<AudioManager>
{
protected override void PersonalAwake() { /* your Awake code */ }
}
AudioManager.Singleton.Play(clip);Singleton— static accessor. If no instance exists it runsFindObjectOfType<T>(), and failing that creates a newGameObjectnamed after the type and adds the component.dontDestroy— serialized bool; when set, callsDontDestroyOnLoadinAwake.PersonalAwake()— override this instead ofAwake(the baseAwakeis private).
A minimal, allocation-free state machine.
[Serializable]
public class IdleState : State
{
public override void Enter() { }
public override void Exit() { }
// Update and FixedUpdate are virtual — override only the one you need.
public override void Update() { }
}
var fsm = new StateMachine(new IdleState()); // calls Enter() immediately
fsm.Change(new RunState()); // Exit() old → Enter() new
private void Update() => fsm.Tick();
private void FixedUpdate() => fsm.FixedTick();State exposes a MonoBehaviour User property (and a protected user field) for the owning
behaviour, plus a constructor taking one.
StateMachine also has Current, IsIn<T>(), Restart() and a StateChanged event.
Change ignores a transition to the state already running — an accidental self-transition would
otherwise fire Exit then Enter for a state that never changed, resetting whatever they set up.
Use Restart() when a genuine re-entry is what you want.
Changed in v4.0.0.
UpdateandFixedUpdatewere abstract onStatebut nothing ever called them, so every state was obliged to implement two methods that could not run. They are now virtual, andTick/FixedTickdrive them.
TransformExtensions · Children(), Descendants(), DestroyChildren() (edit-mode safe),
ResetLocal(), SetX/Y/Z, Translate, DirectionTo, SqrDistanceTo, GetPath(),
GetComponentInAncestors<T>().
GameObjectExtensions · GetOrAddComponent<T>(), HasComponent<T>(), SetLayerRecursively
(by index or name), IsInLayerMask, SafeDestroy(), WithActive().
VectorExtensions · With(x:, y:, z:) and Add(...) for partial copies, Flatten(),
FlatDirection(), ToXZ(), Multiply/Divide, Rotate(degrees), IsCloserThan (squared, no
square root), Abs(), SnapToGrid(), Bounds.RandomPointInside().
MathExtensions · Remap / RemapClamped / Normalize, Approximately(other, tolerance),
Wrap (int and float, negatives handled — unlike %), IsBetween, PercentBetween, SnapTo,
ToBoolean(useAbsolute, useInverse, threshold).
transform.position = transform.position.With(y: 0f);
var t = health.Remap(0f, maxHealth, 0f, 1f);
var rb = gameObject.GetOrAddComponent<Rigidbody>();Unity 6.6 serializes Dictionary<,> natively, so the wrapper and its drawer are gone. Migration is
dropping the type name:
[SerializeField] private Dictionary<string, int> scores = new();Three differences worth knowing:
- It is opt-in.
[SerializeField]is required on every dictionary field — a public dictionary is not serialized automatically, unlike other public fields. [SerializeReference]is not supported on dictionary fields. If you relied on polymorphic values,com.mackysoft.serializereference-extensionscovers that case and is listed in the Optional category of Project setup.- No multi-object editing, and a dictionary cannot nest directly inside another collection —
List<Dictionary<string, int>>is invalid. Duplicate and null keys behave as the old drawer did: flagged in the inspector, first occurrence wins at runtime.
On Unity 2022.3–6.5 there is no built-in equivalent; stay on v3.1.0 or vendor the old file.
Turns any string field into a scene picker.
[SerializeField, SceneReference] private string mainMenu;
...
SceneManager.LoadScene(mainMenu);Stores the scene's asset path — which is what SceneManager wants — while editing it as a
SceneAsset. The drawer rewrites the path when the asset moves, warns when the scene is missing
from Build Settings, and offers a one-click Add to Build.
This replaced
SceneChangerEditor, which hand-rolled the same logic for one component.SceneChangeris now a plain field with no custom inspector, and the stored value is unchanged.
An inspector-driven object pool. It does not reimplement pooling — UnityEngine.Pool.ObjectPool<T>
has shipped with Unity since 2021.1 and does the hard part. What this adds is everything that pool
leaves to you: instantiation, activation ordering, parenting, lifecycle callbacks, and a setup that
a designer can drive without opening a script.
GameObject ▸ NMJ ▸ Pool Manager- Add entries to its Pools list — a prefab, a prewarm count, a max size.
- Add a Pool Spawner to whatever should spawn, point it at the pool id.
- Add a Return To Pool to the prefab so instances come back on their own.
Nothing else is required. In Play mode the Pool Manager inspector shows live active/pooled counts per pool with a usage bar, and warns when a pool has grown past its max size — so prewarm and max values can be set from evidence rather than guesswork.
| Member | Description |
|---|---|
Pools |
The configured PrefabPool list. |
GetPool(string id) / GetPool(GameObject prefab) |
Look up a pool. The prefab overload creates one on demand when Auto Create Missing Pools is on. |
PoolManager.Spawn(id | prefab, position, rotation, parent) |
Static. Drop-in replacement for Instantiate. |
PoolManager.Despawn(GameObject) |
Static. Drop-in replacement for Destroy — falls back to Destroy for objects that were never pooled, so it is safe to use everywhere. |
Don't Destroy On Load |
Keeps the manager and its pools across scene loads. |
using NMJTools.Pooling;
var bullet = PoolManager.Spawn("Bullet", muzzle.position, muzzle.rotation);
// ... later
PoolManager.Despawn(bullet);Serializable, so it is configured entirely in the inspector: Id (defaults to the prefab name),
Prefab, Prewarm, Max Size, Collection Check. Exposes CountActive, CountInactive
and CountAll.
Instances are positioned and parented before being activated, so OnEnable, particle systems and
physics all see the correct transform on their first frame — which is not what you get if you
activate inside ObjectPool<T>'s own get callback.
Spawning with no code. Every entry point is UnityEvent-compatible.
| Field | Description |
|---|---|
| Pool Id / Prefab | What to spawn. Prefab is used when the id is blank. |
| Spawn At | ThisTransform, SpawnPoint or WorldOrigin. |
| Offset / Random Spread | Local-space offset, plus optional per-axis randomisation. |
| Parent To Spawn Point | Off for projectiles and impact effects, on for attached objects. |
| Spawn On Enable / Repeat Interval | Fire-and-forget emitters. |
On Spawned |
UnityEvent receiving the new instance. |
Spawn() and SpawnMany(int) are public — wire them to a Button, an Animation Event or a
Timeline signal. The spawn point and spread box draw as gizmos when selected.
The despawn half. Put it on the prefab: return after a Delay, optionally on Unscaled Time,
and/or When Particles Finished for one-shot effects of varying length. The timer restarts on
every spawn, which is why Destroy(go, t) in Start stops working the moment an object is pooled.
Optional. Implement OnSpawned() / OnDespawned() on any component of the prefab (children
included) when OnEnable is the wrong moment because you need to run after the pool has
positioned and parented the instance. Resolved once at creation and cached.
The shapes Gizmos leaves out. Call from OnDrawGizmos; they respect Gizmos.color and
Gizmos.matrix like the built-ins.
DrawArrow · DrawCircle · DrawWireSphere · DrawWireCapsule (matches a CapsuleCollider,
which Gizmos cannot draw) · DrawArc · DrawViewCone (FOV / detection ranges) · DrawCross ·
DrawBounds · DrawPath · DrawLabel (editor-only, compiles away in a build).
Some classes gain extra behaviour when a package you already use is in the project. Detection and the matching define symbols are automatic — see Project setup.
| Package | Define | What lights up |
|---|---|---|
| UniTask | NMJ_UNITASK |
UniTaskReturnToPool, GameObject.DespawnAfterAsync(), SpawnForAsync(), awaitable SceneChanger.LoadAsync() / LoadAdditiveAsync() / UnloadAsync() |
| LitMotion | NMJ_LITMOTION |
MotionReturnToPool (tweened spawn/despawn for pooled objects), PoolMotionExtensions.SpawnWithPop() |
| Odin Inspector | ODIN_INSPECTOR |
Switches NMJ's own [ReadOnly] / [Required] / [Button] off, so the two cannot collide |
| Newtonsoft Json | NMJ_NEWTONSOFT |
JsonExtensions.ParseJson<T>() |
| Asset Usage Detector | NMJ_ASSETUSAGEDETECTOR |
Assets ▸ NMJ ▸ Find Usages |
Each integration lives in its own assembly gated by defineConstraints. The core assemblies never
reference these packages, so NMJ ToolBox has no hard dependency on any of them — an uninstalled
package means the integration assembly is skipped, not that the build breaks.
UniTaskReturnToPool is worth calling out: the built-in ReturnToPool polls a timer in Update,
which is fine for a handful of objects and wasteful for hundreds. The UniTask version runs off one
shared PlayerLoop hook, so the per-instance cost is zero.
MonoBehaviour that stores a scene by path and exposes UnityEvent-friendly methods.
Pair it with the custom inspector (below) to pick the scene as an asset.
| Member | Description |
|---|---|
string ScenePath { get; set; } |
The serialized scene path (Assets/…/Foo.unity). |
bool IsActiveScene |
true when the active scene's path matches. |
_LoadScene() |
SceneManager.LoadScene(scenePath) |
_LoadScene(string scene) |
Load an arbitrary scene by name or path. |
_LoadParallelScene() |
Async additive load. |
_LoadParallelScene(LoadSceneMode mode) |
Async load with an explicit mode. |
_UnLoadParallelScene() |
Async unload (UnloadAllEmbeddedSceneObjects). |
The serialized sceneName field was renamed to scenePath and carries a [FormerlySerializedAs],
so existing prefabs/scenes keep their value.
Custom inspector for SceneChanger:
- Drag a
SceneAssetin — the read-onlyscenePathfield is filled fromAssetDatabase.GetAssetPath. - Add Scene To BuildSettings — appends the scene to
EditorBuildSettings.scenesif not already there.
Adds Rename GameObject To This Component to every component's context menu (the ⋮ / hamburger
button in its Inspector header). It renames the host GameObject to the beautified type name —
a PlayerHealthBar component renames its object to "Player Health Bar".
- Registers an Undo step and flags the object dirty.
- Hidden on
Transform/RectTransform, and on components whose name already matches. GameObjectName.Rename(Component)is public if you want to call it from your own tooling.
Unity exposes no public API for adding a button to an arbitrary component's header row; the context menu is the supported equivalent. (An earlier version of this script used Ultimate Editor Enhancer for a real header button — that dependency has been removed.)
Renames many objects at once, in both the Hierarchy and the Project window.
Press F2 with more than one object selected to open it. With a single object selected, F2 still
starts Unity's own inline rename — the batch window only appears when batching is what you actually
asked for. Ctrl+Shift+R, Tools ▸ NMJ ▸ Batch Rename and the GameObject/Assets context menus
open the same window.
Enabling F2 is one click. Unity already binds F2 to
Main Menu/Edit/Rename, and its shortcut system disables both entries when two shortcuts claim one key — a conflict an attribute cannot resolve. So the binding ships unassigned; runTools ▸ NMJ ▸ Shortcuts ▸ Batch Rename — Take Over F2once. The takeover is lossless: for a single selection the handler simply invokesEdit/Rename, so ordinary F2 behaves exactly as before.Batch Rename — Restore Unity F2reverses it.
Anything in {} is a token; everything else is literal text. An unknown token is echoed back into
the preview verbatim rather than vanishing, so typos are visible instead of silent.
| Token | Arguments | Result |
|---|---|---|
{N} |
— | Original name |
{N:i} {N:i,c} |
index, count | Substring. Negative i counts from the end |
{S:i,c} |
index, count | Same as {N:i,c} |
{C} |
start, step, pad | Counter, starts at 1 |
{I} |
start, step, pad | Index, starts at 0 |
{A} {AU} |
start | Letter counter — a, b, … z, aa; AU uppercases |
{T} |
— | Total items in the batch |
{P} |
— | Parent GameObject / containing folder |
{TY} |
— | Type name — asset type, or first non-Transform component |
{E} |
— | File extension (assets only) |
{U} {L} {TC} |
— | Upper / lower / Title Case |
{B} |
— | Beautified — my_healthBar → My Health Bar (uses StringExtensions.Beautify) |
{D} |
format | Date, default yyyy-MM-dd |
{{ }} |
— | A literal { or } |
{N}_{C:1,1,2} Crate → Crate_01, Crate_02, Crate_03
Enemy_{TY}_{I} (mixed) → Enemy_Rigidbody_0, Enemy_Collider_1
{P}_{A} Level/Cube → Level_a, Level_b
{N:0,4}_{C:10,10} Barrel → Barr_10, Barr_20, Barr_30
Find & Replace with optional regex and case sensitivity · Prefix / Suffix · Trim start / Trim end by character count · Order (Natural, Alphabetical, Alphabetical descending, Selection order) which decides how counters are assigned.
Operations compose in a fixed order — trim → find/replace → pattern → prefix/suffix — and {N}
sees the result of the steps before it, so a pattern can build on a replace rather than fight it.
Natural order reproduces exactly the top-to-bottom order the Hierarchy draws, by comparing the chain of sibling indices from root to leaf.
The live preview marks changed names green and colliding names orange. Renaming is blocked outright only for empty names or characters a filename cannot contain.
Scene-object renames go through
Undoas a single collapsed step. Asset renames do not —AssetDatabase.RenameAssetis outside Unity's undo system entirely.
The grouping command Unity has never shipped.
| Command | Shortcut | Behaviour |
|---|---|---|
GameObject ▸ NMJ ▸ Create Collection |
Ctrl+G |
Wraps the selection in a new empty parent |
GameObject ▸ NMJ ▸ Dissolve Collection |
Ctrl+Shift+G |
The inverse — re-parents children out, deletes the empty |
It works at any depth, which is the point: the new parent is created as a sibling of the selection, not at the scene root, so grouping three objects six levels deep leaves them exactly where they were.
- Inserted at the sibling index of the topmost selected object, so the hierarchy does not reshuffle.
- Children keep their relative order.
- Pivot is placed at the centre of the selection's renderer bounds — which is what makes the collection usable as a rotate/scale handle afterwards — falling back to the average pivot when nothing selected has a renderer.
- Selecting a parent and its child groups only the parent; descendants of a selected object are filtered out, so nothing gets re-parented twice.
- A selection of
RectTransforms produces aRectTransformgroup stretched to its parent, so UI layouts survive grouping. - Drops straight into inline rename, so naming the collection is part of the same gesture.
- Fully undoable.
Tools ▸ NMJ ▸ Setup ▸ Project Setup — brings a fresh project up to a standard package set, and
keeps the integration define symbols in sync.
One button, no prompts. Everything selected is applied as a single Client.AddAndRemove
transaction — one resolve, one domain reload, and a dependency graph solved once with the whole set
visible to it, rather than one Client.Add and one reload per package. Cached Asset Store packages
are then imported back to back with interactive: false, chained on the import callbacks, so the
"Import Unity Package" dialog never appears. The queue lives in SessionState and survives the
domain reload the transaction causes.
Per-item toggles, saved in the project. The selection is a SetupProfile asset
(Assets ▸ Create ▸ NMJ ▸ Setup Profile) with presets — Standard, Mobile, Prototype, Everything,
Empty. It is a project asset rather than an editor preference for the same reason FolderTemplate
is: the set of packages a project depends on is a property of the project, not of the machine
building it. Committed, a clone reproduces the environment in one click. The window works with no
asset at all, against an in-memory profile, until you press Save Profile….
Removals are first-class. Unity's default template ships the Visual Studio integration, the VS Code integration and Visual Scripting; each costs compile and domain-reload time and none is wanted in a Rider project. They are listed as toggles alongside the installs and go into the same transaction.
OpenUPM registry. OpenUPM packages need a scoped registry in Packages/manifest.json before
Client can resolve the name, and Unity exposes no API for it — Client has Add, Remove and
Resolve, and nothing that touches scopedRegistries. The window parses the manifest properly and
merges: it unions scopes into an existing OpenUPM entry, leaves other registries alone, and writes
through a temp file so a half-written manifest can never land. (Until v4.0.0 it spliced a JSON
string in and gave up whenever any scopedRegistries block already existed — the common case.)
What it cannot do — and why: paid Asset Store packages cannot be installed by script. Unity
checks entitlement server-side against the signed-in account, so no API can make a package you don't
own appear. What is possible: if you already downloaded one on this machine, Unity keeps the
.unitypackage in a shared per-user cache, and the window imports it straight from there —
silently, as part of the same run. Only a package never downloaded on this machine needs a trip to
the store page. This is the honest ceiling on "one-button install".
PackageCatalog is a plain data table — adding a package is one entry, not a code change. Entries
are grouped so a preset can switch a whole category:
| Category | Contents |
|---|---|
| Core | Newtonsoft Json, UniTask, PlayerPrefs Manager, Rider Editor, Test Framework, Editor Coroutines, Burst, Collections, Input System |
| Debugging | In-Game Debug Console, Runtime Inspector, Asset Usage Detector, Graphy, Profile Analyzer, Memory Profiler, Compilation Visualizer |
| Authoring | Cinemachine, Timeline, Recorder, ProBuilder, Addressables, LitMotion, BroAudio |
| Optional | R3, ZString, SerializeReference Extensions, NuGetForUnity, Code Coverage, Localization |
| Mobile | Mobile Notifications, Adaptive Performance |
| Paid | Odin Inspector, Init(args), vHierarchy/vInspector/vFolders/vTabs/vRuler, Editor Console Pro, Hot Reload |
Two notes on what is not there. ProGrids was dropped: it never left preview and was discontinued — ProBuilder 5 has its own grid and snapping. Input System and Addressables are off by default; the first forces an editor restart when the input backend switches, and the second changes how the whole project loads content. Both are decisions, not defaults.
PackageDetector scans loaded assemblies after every recompile and adds or removes the NMJ_*
defines to match. Assembly scanning is used rather than asmdef versionDefines because it covers
both cases uniformly: UPM packages, and Asset Store content dropped into Assets/, which is not a
package at all and has no version to bind to.
Defines are only written when the set actually changes — writing them triggers a recompile, which re-enters the detector, so that check is what stops it looping.
NMJ's [ReadOnly], [Required] and [Button] live behind NMJ_ATTRIBUTES, which the detector sets
only while Odin is absent. Odin declares all three names too, so with both imported every use
becomes ambiguous — and both claim the default MonoBehaviour inspector. The window offers a
Force NMJ attributes alongside Odin override if you intend to fully qualify them.
| Attribute | Behaviour |
|---|---|
[ReadOnly] |
Shown but not editable |
[Required] |
Red error box under the field while a reference is unassigned or a string is blank |
[Button("Label", ButtonMode.PlayModeOnly)] |
Draws a button that invokes the method; works on ScriptableObjects and multi-selections |
| Command | Description |
|---|---|
GameObject ▸ NMJ ▸ Replace With Prefab… |
Swaps the selection for prefab instances. Position, rotation, parent, sibling index and active state are always kept; name, scale and layer/tag are optional. Undoable as one step. |
Assets ▸ NMJ ▸ Select Instances In Open Scenes |
Selects every instance of the selected prefab asset. |
Assets ▸ NMJ ▸ Find Usages |
Opens Asset Usage Detector when installed. |
Tools ▸ NMJ ▸ Find Missing Scripts In Open Scenes |
Lists and selects objects with missing scripts, then offers to strip the empty components. |
| Command | Description |
|---|---|
Tools ▸ NMJ ▸ Build ▸ Increment Patch/Minor/Major Version |
Bumps bundleVersion and zeroes the components after it. Also bumps Android.bundleVersionCode and the iOS build number, which stores require and a display-version bump alone does not cover. |
Tools ▸ NMJ ▸ Build ▸ Auto-Increment Patch On Build |
Toggle; runs as a pre-build step so the baked version is the incremented one. |
Tools ▸ NMJ ▸ Build ▸ Build Timestamped (Ctrl+Shift+B) |
Builds into Builds/<Target>/<Product>_<version>_<timestamp>/ and reveals the folder. |
Tools ▸ NMJ ▸ Open ▸ … |
Persistent data path, project folder, editor log folder. |
| Method | Description |
|---|---|
CreateAsset(this ScriptableObject, string folderPath, string objectName) |
Creates the folder chain if needed, then writes the asset at a unique path. folderPath must start with Assets/; objectName must not include .asset. |
var config = ScriptableObject.CreateInstance<GameConfig>();
config.CreateAsset($"{AssetDatabaseUtilities.AssetPath}__Game/Runtime/ScriptableObjects", "GameConfig");| Member | Description |
|---|---|
const string AssetPath |
"Assets/" |
GetAllInstances<T>() |
Every ScriptableObject of type T in the project (via FindAssets("t:T")). |
LoadUss(string path) |
Loads a StyleSheet. Assets/ prefix and .uss suffix are optional. |
LoadUssWithName(string name) |
Finds a StyleSheet by file name anywhere in the project. Throws if the name is ambiguous. |
LoadUxml(string path) |
Same as LoadUss, for VisualTreeAsset. |
LoadUxmlWithName(string name) |
Same as LoadUssWithName, for VisualTreeAsset. |
GetAllScenes(SerializedProperty) |
Resolves an array property of scene paths into SceneAssets. |
GetScene(SerializedProperty) |
Resolves a single scene-path property into a SceneAsset. |
Tools ▸ NMJ ▸ Setup ▸ Create Folder Structure — pick a layout, edit it, see exactly what will be
created, then run it.
Five ship with the package, as code rather than assets so a fresh install has something to offer immediately:
| Template | For |
|---|---|
| Standard | Full 3D project — Runtime/Editor split with art, audio, data and UI broken out |
| Minimal | Jams and prototypes: Scripts, Scenes, Prefabs, Art, Audio |
| Feature-based | Grouped by feature rather than asset type; scales better on large projects |
| 2D Game | Sprites, atlases, tilemaps, 2D animation |
| Empty | Root only — a starting point for your own |
Any of them can be edited in the window and saved with Save as Template…, producing a
FolderTemplate asset that appears in the dropdown under Project/. You can also create one
directly from Assets ▸ Create ▸ NMJ ▸ Folder Template.
A template asset is deliberately the project-level setting rather than an editor preference. It lives in the repository, so a team shares one structure and changes to it show up in review; editor settings are per-user and per-machine, which is the wrong scope for a shared convention.
| Option | Description |
|---|---|
| Root folder | Created under the target path; everything nests inside it. Defaults to __Game. |
| Create in | Target parent. Defaults to the selected Project-window folder. |
| Write .gitkeep files | On by default — see below. |
| Create assembly definitions | Generates <Root>.Runtime and <Root>.Editor asmdefs, the editor one referencing the runtime one by GUID (read back after import, so it cannot be mistyped). |
| Write README.md | Documents the layout in the root folder, so the convention travels with the project. |
The preview lists every folder, ancestors included, marking new ones green and existing ones grey.
Creation is batched inside StartAssetEditing/StopAssetEditing, reports one summary line instead
of one log per folder, and checks AssetDatabase.CreateFolder's return value so a failure is not
silent. Because folder creation is not undoable, it asks for confirmation first.
Git cannot track an empty directory. Unity writes a folder's .meta file beside the folder,
so a commit carries Art/Textures.meta but not Art/Textures/. On clone, Unity finds an orphaned
.meta with no folder, warns, and deletes it — and the whole scaffold quietly evaporates for
everyone except the person who generated it.
A .gitkeep fixes it: git tracks the file so the directory survives, and Unity ignores dot-files
entirely, so it never appears in the Project window and never gets a .meta of its own.
Tools ▸ NMJ ▸ State Machine Generator — a code generator window that writes
<chosen folder>/<Prefix>StateMachine.cs.
This is deliberately still the enum-and-switch shape rather than the class-per-state
StateMachine: they solve different problems, and a switch is the better
fit when the states are a fixed, small, closed set.
Naming
- Prefix — reduced to a valid PascalCase type-name fragment.
- Namespace — optional; empty generates into the global namespace. Remembered between runs.
- Folder — browse, or take the current Project-window selection. Remembered between runs.
- States —
+/-buttons. Each name is upper-cased with non-alphanumerics turned into_. One state is flagged Default with a radio, and duplicates are reported before you can generate.
Code styling parameters
| Option | Effect |
|---|---|
| Generate UnityEvents | Emits public UnityEvent on<State>Enter/Exit and invokes them. |
| Use regions / Space regions | Wraps sections in #region, optionally with blank lines between them. |
| Group by state / Group by phase | Methods ordered per state (OnEnterIdle, OnUpdateIdle, OnExitIdle, …) or per phase (all OnEnter*, then all OnUpdate*, then all OnExit*). |
Generated shape
public enum FooState { IDLE, RUN }
public class FooStateMachine : MonoBehaviour
{
public FooState CurrentState { get; private set; }
private void Start() => OnStateEnter(CurrentState);
private void Update() => OnStateUpdate(CurrentState);
private void OnStateEnter(FooState state) { /* switch → OnEnterIdle() … */ }
private void OnStateUpdate(FooState state) { /* switch → OnUpdateIdle() … */ }
private void OnStateExit(FooState state) { /* switch → OnExitIdle() … */ }
public void TransitionToState(FooState toState); // ignores a self-transition
private void OnEnterIdle() { }
private void OnUpdateIdle() { }
private void OnExitIdle() { }
}Generate is disabled when the target file already exists — it never overwrites.
Fixed in v4.0.0. With Group by phase on, the generator opened three
#regiondirectives and closed none, so the generated file did not compile. Use Foldout on Events emitted[Foldout(...)]from NaughtyAttributes, which this package stopped bundling — that option is gone. Output also always landed in the root ofAssets/with no namespace.
Tools ▸ NMJ ▸ Reset Transform resets every selected transform: localPosition = zero,
localRotation = identity, localScale = one. For a RectTransform it also sets
pivot/anchorMin/anchorMax to (0.5, 0.5) and sizeDelta to (100, 100). The whole selection is one
Undo step.
Changed in v4.0.0. This used to be bound to
Ctrl+R, which is Unity's own Assets ▸ Refresh. Unity resolves a shortcut collision by silently disabling both entries, so the binding cost the refresh shortcut and did not work itself. It is now registered through the shortcut system with no default binding — assign one inEdit ▸ Shortcuts(search "NMJ"), as with the batch-rename and selection-history commands.
A MonoBehaviour that lays copies of a prefab out on a grid, built from a Build Grid button in
its inspector.
| Field | Description |
|---|---|
objectToInstantiate |
Prefab or scene object to clone. |
parent |
Parent for the copies. Falls back to this transform. |
columns / rows |
Grid dimensions (X and Z). |
spacing |
Spacing on each axis, as a Vector2. |
origin |
Local-space offset of the first cell — a full Vector3, so Y works. |
centred |
Centre the grid on the origin instead of growing towards +X/+Z. |
Copies are named <prefab> [column,row]. Clear removes only children carrying that suffix, so
anything you parented there by hand is left alone. Cell positions are drawn as gizmos when selected.
Fixed in v4.0.0. This lived in
Editor/Tools/, inside an assembly whose only platform is Editor. AMonoBehaviourin an editor-only assembly cannot exist in a build — Unity strips the assembly, and every scene or prefab referencing it loads with a missing script. It moved to the runtime assembly, keeping its.metaso existing scene references survive. The old[ContextMenu]also used plainInstantiate, producing prefab-disconnected clones that could not be undone; edit-mode building now goes throughPrefabUtility.InstantiatePrefaband registers a single Undo step for the whole grid.
Assets ▸ NMJ ▸ Create Material From Textures — right-click one or more texture folders. Creates
(or updates) one material per folder, wiring up the textures found there by filename suffix and
correcting each one's import settings.
Everything it needs to know comes from a MaterialSetupProfile asset
(Assets ▸ Create ▸ NMJ ▸ Material Setup Profile), pre-filled with the URP Lit conventions: the
shader name, the material prefix, the albedo filename pattern, and a table of map slots. Each slot
carries its filename suffixes (current convention first, so a folder holding two generators' output
resolves predictably), the shader property it feeds, and how it must be imported.
Two details that matter more than they look:
- The albedo is the map with no suffix, so any suffix the table does not know falls through and can be mistaken for it. That is why roughness, cavity, ORM and friends are listed with an empty shader property — recognised, import flags corrected, but assigned nowhere.
- Import settings are silent when wrong. A normal map not flagged as one lights incorrectly, and any mask left on sRGB has a gamma curve applied to what is data, not colour.
Re-running updates textures but never the tuning values — the defaults list is written only when
a material is first created, so nothing stamps over what you tuned in the inspector.
New in v4.0.0. This replaces
StandardMaterialCreator, which was not a reusable tool: it was one project's code copied into the package, with a hardcodedStockAndSorcerynamespace, anElinis/TCP2shader, aRamp_WarmCosyasset and anAssets ▸ Elinismenu item. Installed anywhere else it added a menu entry that could only ever fail with "shader not found".
Assets ▸ NMJ ▸ Open in External Editor opens the selection in whatever editor the project is
configured to use, via CodeEditor.CurrentEditor — so Rider opens the solution it already has
loaded rather than a detached file. Anything no script editor will take (a .png, an .fbx) falls
back to the OS default application. Assets ▸ NMJ ▸ Reveal in Explorer selects the file itself
rather than its folder.
New in v4.0.0. Replaces
OpenWithVSCode, which hardcodedcodeas the executable, ignored the editor set inPreferences ▸ External Tools, sat in the global namespace inside a package, and silently did nothing where VS Code was not onPATH.
For working with the Scene view maximised. Both are standard Unity Overlays — enable them from
the Scene view's ⋮ menu, or press ` to open the overlay picker. They can be docked to any
edge, floated, or collapsed to icons like any built-in overlay.
They live in their own assembly (NMJ.NMJTools.SceneTools.Editor, namespace NMJTools.SceneTools)
with no references to the rest of the package — see Splitting them out.
A compact scene tree inside the Scene view, so the full Hierarchy window can stay closed.
- Search box; while filtering, results are flat, and the walk descends through collapsed branches so a deep match is still found.
- Expand/collapse arrows, with clicks on the arrow not stealing selection.
- Inactive objects are dimmed; icons match the Hierarchy's.
- Click selects, double-click frames the object in the view.
- Follows the current prefab stage — in prefab isolation mode it shows the prefab contents rather than the scene roots.
- Tracks
hierarchyChangedand scrolls to keep the current selection visible. - Caps at 5000 rows and says so, rather than stalling on a huge scene.
The selected object's inspector, docked in the Scene view.
Built on InspectorElement, so every custom editor, property drawer and attribute in the project
renders exactly as it does in the real Inspector — this is a second view onto the same machinery,
not a reimplementation. A GameObject shows one foldout per component (Transform open by default);
assets show a single inspector. With several objects selected it shows the active one and says how
many others there are.
Back/forward through everything you have selected — the browser history Unity has never had. Buttons plus the full list; clicking an entry jumps straight to it.
The recorder is static and [InitializeOnLoad], not owned by the overlay, so history survives the
overlay being closed, the Scene view re-docked, and a domain reload. Selecting something new after
stepping back discards the forward branch, exactly like a browser. Destroyed objects are pruned.
Two shortcuts are registered without default bindings (NMJ/Selection History/Back and
/Forward) — a shortcut that collides with an existing one leaves both disabled, and there is no
key here that is free on every platform. Bind them yourself in Edit ▸ Shortcuts; Alt+Left /
Alt+Right match the browser convention.
Solo and hide controls: Isolate Selection, Exit Isolation, Hide, Show All.
Drives Unity's own SceneVisibilityManager rather than toggling SetActive. That distinction
matters — scene visibility is a pure editor concern that never touches the serialized scene, so
nothing here can dirty a scene, break a prefab override, or leak into a build. Hiding things by
deactivating them does all three.
These two are the strongest candidate in this package to become their own package, and the
assembly boundary is already drawn so it is a single git mv away:
- They change the Scene view's UX persistently, which is a different kind of opt-in than a library
of helper methods. Someone who wants
GetOrAddComponenthas not asked for their viewport rearranged. - They are the piece most likely to overlap with an editor-enhancer package already in a project.
- They have the most room to grow — pinning, filters, favourites, multi-object inspectors — and that growth does not belong in a general toolbox.
Everything else here (pooling, batch rename, collections) is general-purpose and stays.
| Assembly | Platforms | References |
|---|---|---|
NMJ.NMJTools.Runtime |
All | — |
NMJ.NMJTools.Editor |
Editor | NMJ.NMJTools.Runtime |
NMJ.NMJTools.SceneTools.Editor |
Editor | — |
NMJ.NMJTools.Attributes |
All | — · gated by NMJ_ATTRIBUTES |
NMJ.NMJTools.Attributes.Editor |
Editor | NMJ.NMJTools.Attributes · gated by NMJ_ATTRIBUTES |
NMJ.NMJTools.UniTask |
All | NMJ.NMJTools.Runtime, UniTask · gated by NMJ_UNITASK |
NMJ.NMJTools.LitMotion |
All | NMJ.NMJTools.Runtime, LitMotion, LitMotion.Extensions · gated by NMJ_LITMOTION |
NMJ.NMJTools.Newtonsoft |
All | NMJ.NMJTools.Runtime, Newtonsoft.Json · gated by NMJ_NEWTONSOFT |
No core assembly has a third-party reference, so the package compiles in a completely clean
project and package.json declares no dependencies. Every optional integration lives in its own
assembly behind defineConstraints, so an uninstalled package means that assembly is skipped rather
than the build breaking.
Third-party assemblies are referenced by GUID, not by name, wherever the GUID is known. A by-name reference that does not resolve takes the whole assembly down with the "references non-existent assemblies" error — the failure that used to affect the entire editor assembly via Ultimate Editor Enhancer.
PlayerPrefs Manager — an extended PlayerPrefs API (bool, Color, Vector2/3/4,
Quaternion, Transform) plus a cross-platform Editor window for browsing, editing, filtering,
categorising and live-monitoring every pref in a project. Formerly bundled here; now its own
package.
MIT — see LICENSE.md.
Third-party components are listed in Third Party Notices.md.