Quick and Dirty Tooling
Published 9 months ago
How to speed up your workflow when you would prefer it to work than be pretty.
The Unity editor might feel like it's independent of your game, but from a scripting point of view this is not really true. Or perhaps more accurately, the Unity editor is part of your product until you build it.
There are various utilities available to you in the UnityEditor package, which may at first seem completely overwhelming. How can you even decide where to start, the editor has so many different pieces? Well the truth is, you can customize the editor functionality pretty heavily, but those are just trappings to make the designer's job easier. Most of the time you need to do one of two things:
  1. Provide editing assistance in the scene with interactive tools.
  2. Manipulate assets for custom import/export.
Custom import and export is pretty use case specific, but in the end you just have use standard file I/O and maybe UnityEditor.EditorApplication to present save and load dialogs. That's work the programer already knows how to do, so instead we'll look at how programmers can he designers position objects in the scene by providing quick and dirty scene manipulators. Some placement involve repetitious calculations, today we'll look at spawning objects in a line.
Thanks to the fact that your game and the Unity editor are running in lockstep, a simple scene manipulation script works just like a runtime behaviour. But that also means that you have to be careful not to accidentally procedurally edit the scene during play mode. First things first, we'll tell your script it's allowed to run when not in play mode. To do this use the [ExecuteAlways] attribute at your class declaration. This will call all the standard event functions to be called in the editor, and allow object positions to be adjusted programatically while in edit mode. Now let's build a simple behaviour
[ExecuteInEditMode] class SpawnCircle() { #if UNITY_EDITOR public Gameobject prefab; public int qty=5; public Transform start; public Transform end; [HideInInspector] public Transform[] spawned; [HideInInspector] public bool spawned=false; [ContextMenu("Spawn")] void Spawn() { if(spawned){Debug.LogError("Already Spawned"); return; Undo.RegisterFullObjectHierarchyUndo(this.gameobject,"Spawn objects"); float step = 1f/qty; spawned = new Transform[qty]; for(int i=0;i<qty;i++) { Gameobject go = UnityEditor.PrefabUtil.InstantiatePrefab(prefab); go.transform.position=Vector3.Lerp(start.position,end.position,step*i); spawned[i]=go.transform; go.transform.parent=this.transform; } } void FixedUpdate() { if(UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) return; for(int i=0;i<qty;i++) { spawned[i].position= Vector3.Lerp(start.position,end.position,step*i); } } #endif }
You might recognize a some of those things and other might be new to you so let us take a look at what happened. First there is the conditional compilation directive #if UNITY_EDITOR , anything found from this until #endif will not be included in a player build. You see also that we are exiting in edit mode so FixedUpdate() gets called even when the game is not running. Then since we actually don't want any behaviour when the game is running or about to run we stop any updates.
This script spawns a series of uniformly spaced objects in a line, and then allows you to move them dynamically, you'll note that everything looks to be the same as you would do it at runtime, except that the PrefabUtil is being used to spawn the objects. When using the Gameobject.Instantiate() method normally, this would break the prefab connection for the cloned objects, so we use the editor variant instead.
Finally you might be wondering about [ContextMenu("Spawn")]. Remember we want quick and dirty tools. This one liner adds a menu item to the gear dropdown of a MonoBehaviour editor that normally contains the "Reset" command. Here a menu item called "Spawn" will call our function Spawn() .
One curious thing remains... what's the deal with [HideInInspector], if we don't want the values visible, then why are they public? This has to do with serialization, suppose we closed and then re-opened the scene. If these fields were not serialized, our editor script would forget which objects it had spawned, and happily spawn a new set, in addition to forgetting to update our existing objects. Because we never do this at runtime, it feels off, but is nonetheless quite sensible working with the editor.
A closing note, you might be wondering what to do when you want a hybrid of editor and runtime behaviours. This is pretty easy to achieve, simply use a bunch of #if directives to cut behaviour not needed or impossible at runtime, and make sure to test UnityEditor.EditorApplication if you need to. Note that the UnityEditor package cannot be used at runtime, so you'll get an error if you try to get editor information from a build.
Abram Wiebe
Freelance Software Developer - Programmer
Akhil Gupta
8 months ago
Game Developer
Akhil GuptaGreat article.
I have a stupid doubt. The prefabs instantiated here will get included in the build right? It's just that particular portion of the script which will not be included, if I understand correctly.
Akhil Gupta
8 months ago
Game Developer
Great article.