Press "Enter" to skip to content

Unity 3D Tutorial – Custom Transform Inspector

Hi everyone!
One of the awesome things about Unity is you can customize the editor to add all sorts of cool functionality. This article will show you how to override Unity’s transform inspector with a more useful customized version. The Custom Transform Component includes:

  • The ability to view and edit the quaternion rotation (nice for experimenting and learning how quaternions work).
  • The ability to show the local axes in the Scene view even when the object is not selected.
  • Some special operations for aligning to other objects, random rotation, random scale, and random position.

Here’s what the final product will look like:

custom-transform-inspector

Part I: Extending the Default Transform Inspector


Overriding the Defaults

First, thing’s first. We’ll need to recreate Unity’s transform inspector (not completely necessary). To make any custom editor scripts you’ll first need a folded called “Editor” in your Assets folder. This is a special folder that Unity looks in to for scripts that can only run in the Editor i.e. any script using UnityEditor;. You’re not limited to one Editor folder per project so you can organize your editor scripts right alongside your MonoBehaviour scripts if you like. More info on on Unity’s special folders can be found here. The UnityEditor namespace This gives us access to a whole bunch of awesome tools for writing editor windows, inspectors, importing assets, and pretty much anything that you need in the Editor. 
 
The first part of our script is a bit boring and doesn’t really add any functionality to the Transform component. Some other notes before proceeding:
  • Custom inspector scripts need to inherit from Unity’s Editor class.
  • To indicate what component (in our case the Transform component) the custom editor is for we will use the CustomEditor attribute.
  • Very commonly, you’ll want to be able to select multiple items simultaneously and edit them. For this you’ll need to add the CanEditMultpleObjects attribute. There are some caveats to this and you may need to write your own methods to apply changes to all selected objects as in the example below.

The first part of our class will basically recreate Unity’s transform component. Unity draws the inspector in the method OnInspectorGUI() we will override this to make our custom inspector. Now, you don’t need to recreate the default inspector (the part that shows position, rotation, and scale). I’m doing it here to give you insight on how custom editors work,  If you just want to use Unity’s default then you can call base.OnInspectorGUI() from your override of OnInspetorGUI(). As you may see from the extents of the code below, that may be your preferred option.


 

Some extra explanation beyond the comments as to what’s going on here. First, we set up a class level Transform (aptly named _transform) to hold the transform of the target (the selected object) so that we can refer to it throughout the class. We override OnInspectorGUI() and immediately cast target (a property of the Editor class) to a Transform type and set _transform.. Then we call our new method, StandardTransformInspector()

In the StandardTransformInspector() method we have a few things going on. First, we set up some bools to flag when a value is changed in the inspector and we store the initial position, rotation, and scale of our transform so that we can make a comparison later. Then you’ll see three spots where we check for changes with EditorGUI.BeginChangeCheck() and flag our bools if a change has occurred by checking EditorGUI.EndChangeCheck(). Once that has finished and when know what if something has changed we now use Undo.RecordObject() to record the state of the object before any change has been made (this allows use to Undo the operation). Then we simply apply only the changes that have occurred.

Next, we have some adjustments to make to get our inspector to work like Unity’s default inspector. Since we’re using BeginChangeCheck() and EndChangeCheck() our changes will only be applied to the last selected object. So this basically breaks multi-selection editing which is what we’re fixing next.To help us out I created another method, ApplyChangesOnly(), which will only apply a change to the x, y, or z value of the vector if it was changed in the selected transform. So if you change the x position it only changes the x position of all selected objects. Similarly I set up an undo state if anything will be changed, then iterate through all of the selected transforms (retrieved with Selection.transforms) and apply changes when it is appropriate.

That’ll cover all the changes we need for recreating the default inspector for transforms. The only actual change I made over the default is I named the “Rotation” fields to “Euler Rotation”. This is just so that we can see how to do this, and we’ll be showing the Quaternion rotation in the inspector next.

Showing the Quaternion Rotation

Quaternions are what Unity (and many other 3D applications) use to store rotational information. They’re a bit complex and not as intuitive as euler angles (degrees for x, y, and z). However, they are a necessity in 3D applications because euler angles don’t fully describe rotation and the suffer from Gimbal lock. Check out the links to learn more on these. A discussion on Quaternions go well beyond the aim of this post and, honestly, I’d probably do a bad job explaining them. A quaternions are often a mystery to many and I decided to expose them in the inspector so that we can view and optionally edit them to get a better understanding of their behavior.
This section of the inspector will use a foldout (the little arrow head that shows more info when clicked). To maintain the state of the foldout we use a bool (is it open or closed?) and we’ll use a new method, QuaterionInspector(), to display the quaternion’s values.We’ll also need to add a call to the method in OnInspetorGUI() as can be seen below:
The new QuaternionInspector() method uses familiar components as the StandardTransformInspector() method, but also uses EditorGUILayout.Foldout() to tell us whether the foldout should be open or not. A special note here: I used a static bool to store the state of the foldout. This means that when you open the foldout for any game object they will all be open since they’re using a global static value. This decision was made because I didn’t like opening it on one object, then making a multiselection and having to open it again. There’s also a couple utility methods for converting the quaternion to and from a Vector4 since Unity doesn’t offer a GUI item for quaternions and quaternions at their base are Vector4s, Those utility methods are pretty basic so hopefully you’ll understand without extra explanation. Here’s the new chunk of code we add to get it all working:

 

Now we have a custom inspector for transform components that shows us (and allows us to edit) the quaternion rotation. Pretty cool, but not overly useful. Next up we’re going to add some utilities to our inspector to get the most out of it.

Showing the Local Axis

Oftentimes I’ve found that I’ll really need to see the direction an object is pointing when I don’t have it selected. It can be a pain to try to align rotations when you can’t see the rotation of the object you’re trying to align to! So I created a simple MonoBehaviour class that does just that. Now, you don’t need the custom transform component for this, but the goal here is to show you how to integrate another component with your custom inspector. We could control all of the values of the ShowLocalAxis component via our custom transform component, but for such a simple component it is pretty pointless. Besides Unity already exposes the only variable we have in the component: handleLength. The goal here is to show you how to properly add and remove a component from another component via the inspector.
Here’s the ShowLocalAxis class. It’s pretty short and makes use of OnDrawgizmos() to show some lines that represent the axes of the object it is attached to. It allows for variable length of the lines (handleLength) which have been limited using the Range attribute. The Range attribute sets up a nice slider in the inspector for us. We are also using the HideInInspector attribute to prevent Unity from showing a public bool, destroyWhenSafe, whose purpose I will explain in a bit. Otherwise, the class should be pretty straight forward. It is just drawing a line on the local x, y, and z axes. To set the Gizmo’s space to the object’s local space we set Gizmos.matrix to transform.localToWorldMatrix.

To accomplish safe destruction of component via another component we’ll need a custom inspector that waits for the proper time to destroy the component. Otherwise, we’ll get some nasty errors in Unity. The below script does just that. We don’t care about any special display, but since we used HideInInspector and Range in ShowLocalAxis, we’ll use base.DrawDefaultInspector() to show the default view instead of base.OnInspectorGUI() that I mentioned before. So what happens is our custom transform component will set the destroyWhenSafe bool to true, then when the editor’s state is Repaint we can safely destroy the ShowLocalAxis component. 


 

Now we have our component all set up, let’s integrate it with our custom transform component. For this we’ll make a new method called ShowLocalAxisComponentToggle() and change our CustomTranformComponent‘s OnInspectorGUI() to so it calls the method like this:

In Next up is the ShowLocalAxisComponentToggle() method, For this we’ll need a private bool, showLocalAxisToggle, that will be used to toggle on/off the ShowLocalAxis component. And we’ll need a First, we start off by adding some padding above the rest of the GUI elements with two calls to Unity’s EditorGUILayout.Space() method. Next we attempt to see if there is a ShowLocalAxis component attached to the game object. If there is then we set our showLocalAxisToggle to true and display it in the inspector. This time we’re going to use a horizontal layout (which starts a horizontal section where the GUI elements will be placed) by calling EditorGUILayout.BeginHorizontal(). Then we use a EditorGUILayout.LabelField(), start to monitor changes, and display a EditorGUILayout.ToggleLeft() with a corresponding label of “on” or “off” based on the existence of the ShowLocalAxis component. If the toggle changes value then we add the component and move it up to the second position so that it is right under the transform component. If we’re toggling the ShowLocalAxis to off then we flag it with destroyWhenSafe and allow the ShowLocalAxis component to destroy itself when it is safe to do so. The full method looks like this:


 

That takes care of showing the local axis. Now we’ve learned how to create custom inspectors, do some layout, show axis line gizmos, and safely add/remove a component from another component. In the next section I will show you how to add even more functionality to the transform component. We’ll set up methods for aligning selected game objects to each other, randomly rotating them, randomly scaling them, and randomly positioning them. Each of which can be very helpful in speeding up level design.

Part II: Special Operations


This section is split into subsections for each of the Special Operations. First, I’m going to show the full code for handling an animated foldout for showing/hiding the special operations. First, we need to have a class-level AnimBool object to maintain the state of the foldout, and a static bool to set the foldout states on all custom transform components globally. We set assign the AnimBool in OnEnable() which fires when a game object is selected in the editor. The states will then be properly set and we’ll need to add a call to the SpecialOperations() method in our OnInspectorGUI() method like we’ve done before. For now, you won’t have the AlignmentInspector(), RandomRotationInspector(), or RandomPositionInspector() methods, so you can just create stubs for them now if you like. But we’ll cover them in the following sections.

 

Custom Button

Before we dig in, each of these inspectors will have buttons associated with them. Unity’s default for an inspector button is to stretch its width to fit the inspector window. I don’t really like that so I went ahead and created my own button. First I define a GUILayoutOption to set the  width of all the buttons to 200 (I found this number after I made all my buttons and could see their width). Then in the Button() method I wrap everything in a horizontal layout, add flexible spaces to the left and right of the button (this centers it), I then get the bool value from Unity’s GUILayout.Button() so I can return it from the new method.


 

Alignment Inspector

The Alignment special operation will allow us to select multiple objects and line them up on their X, Y, or Z axis (or any combination of). This can be quite handy when doing level design. Definitely much better than having to figure out your snap interval or manually inputting position values. First thing we’re going to need is a couple of enums. AlignToType is just either the last selected GameObject or the first selected GameObject. AxisFlag is a bit flag enum that allows us to make any combination of X, Y, and/or Z (the values you see are bits – i.e. 2 to the power of 0, 1, and 2 respectively). Then we set some default values up for these in alignTo and alignmentAxis. Like so:

 

Next up is our method to actually perform the alignment operation, this is called AlignTo(). First it gets an array of all of the selected transformas then it determines the index in that array (targetIndex) of the transform (or GameObject) we’ll be aligning to. This is index 0 for the first selected and the last index of the array to align to the last selected transform. Then we iterate through all of the selected transforms and set their positions that should be aligned to the position of the selected transform. While doing this for loop, we ensure we skip over the selected transfor, because it makes no sense to move it to itself. Finally, we use Undo.RecordObject() so that we can reverse these operations.Some of you may be familiar with bitwise operations and may see the & in the conditions. Others of you are probably wonder what statements like

actually mean. The single & is a binary AND operator. What this does is it copies the bit to the results IF the bit exists in both operands (axis and AxisFlag.X). So if axis contains the bit for AxisFlax.X the result will be AxisFlag.X. This is why we’re using powers of 2 for our AxisFlag enum’s values. Basics on bitwise operators in C# can be found here at tutorialspoint.com. The full AlignTo() method is as follows:


 

Finally, we get to our AlignmentInspector() method which will display this special operation in our inspector. We’re now making use of some enums, so there’s two new methods for that – EditorGUILayout.EnumPopup() and EditorGUILayout.EnumMaskField(), The EnumPopup will allow us to select from a single enum value (our align to first/last selected) while the EnumMaskField will allow us to pick from any combination of enum elements (as long as they’re powers of 2). It’s also important to note here that these two methods return a generic enum so we must cast them back to our enum type to ensure they’re correct. The we set up a string to hold our button label if only one game object is selected. We check to see if multiple game objects are selected. If they are we label the button more appropriately and enable the button. Finally we check to see when the button is pressed and if it is we execute AlignTo(). The following code illustrates this in its entirety:

 

Random Rotation Inspector

The Random Rotation inspector doesn’t really cover anything new. In brief we’re doing many things similar to the Alignment inspector. We set the axis flag(s) for what axis to rotate on, change the button label to indicate if 1 or more transforms are selected, then apply a random rotation (in euler angles) to the appropriate axis. See, things are getting easier!

 

Random Scale Inspector and Random Position Inspector

Last, but not least, are the Random Scale and the Random Position inspectors. These are also pretty easy now that we’ve gotten through all the drudgery. No new concepts appear here other than using a min and max value for the random value. The reason for this is that we need some bounds for our random value. With rotation, that’s simple (0-360), but with position and scale… it could be anything to +/- infinity. So we ask the user to set those values in the inspector (of face the consequences!). The code below completes our custom inspector:

 

Final Thoughts (and some homework)

Ha! Didn’t think you were going to be given homework, did ya?
All of this was done to provide you with a practical example of customizing Unity’s inspectors. You can apply these to your own classes, or override inspectors that you don’t like (or think need more functionality). This is one of the many features that makes Unity powerful, it’s extendible. Not only can you make these custom inspectors to help you with tasks, but you can also make custom context menus, custom menus, and editor windows. My rule is: if I’m going to be doing something a lot, then why not have a script do it for me. One example is: I ran into a project with about 60 scenes (yeah…) each scene had its own camera, like usual. Well, my client wanted to change settings on those cameras. I don’t want to think how long it would have taken me to go through all of those scenes. And what happens when the client wants to make another adjustment to all the cameras (they did a few times)? Or if I miss something because I was trying to do it fast. Unity editor scripting to the rescue! I spent maybe an hour writing a custom script that would go through every scene and change the main camera to the settings I wanted. Changes now take about 1 minute instead of a couple hours. I sure was glad they were using Unity.
On to the homework!
The Custom Transform Inspector isn’t quite complete. It needs at least one more thing: an even distribution component. Wouldn’t it be amazing to select a few objects, align them on an axis and then make them nicely distributed? Yes, it would! Your task is to add on to the custom transform inspector and make this happen! The first person to do this will get a free copy of my Mobile Screenshot Helper asset from the Unity Asset Store. Just make the code neat and clean and keep to the same naming conventions Im using in the script.
Here are the full scripts for you to play with:
As always, thanks for reading!
.

Loading

12 Comments

  1. Jane Doe
    Jane Doe 2018/02/03

    Thank you for this, i’ve been trying to make my own custom transform which snaps to the pixel grid but EditorGUI.BeginChangeCheck() only seems to work when changing the value directly in the inspector and not when using the transform tool, also when rounding the transform animations no longer seem to work, do you know why?

    Thanks

    • Sean Mann
      Sean Mann 2018/02/03

      BeginChangeCheck only works on fields that are contained within the Begin/End change check and only those in the inspector (it’s actually checking for user-editing of the fields). If you want to detect Scene view changes then you’ll have to continually compare a previous value with the new value. I’m not 100% sure that will work. This may also work for you: https://docs.unity3d.com/ScriptReference/Transform-hasChanged.html

      As for the transform animations no longer working, I’ll need some more details on what you’re talking about to help with that.

      • Jane Doe
        Jane Doe 2018/02/03

        I appreciate the quick reply

        Basically what i’m trying to do is round all transforms so that my sprites move pixel-by-pixel, the floating point precision is a problem for my project.

        I tried to round the transform and even tho it looked a bit jittery when moved it sorta snapped in place, but animated transforms no longer move.

        • Jane Doe
          Jane Doe 2018/02/03

          It’s as if animations translate objects by adding to their transform, in my case that added value is constantly being snapped back to the rounded position, so it never moves.

          By the way if “(_transform.hasChanged)” works perfectly

      • Jane Doe
        Jane Doe 2018/02/03

        Solved it by using OnSceneGUI() to do the snap, i’m new to Custom Editor stuff so still not sure of what i’m doing.

  2. Sean Mann
    Sean Mann 2018/02/03

    I’ve never had to use OnSceneGUI() and I’m surprised that a custom editor was affecting animations. Custom Inspector code should only be running on a selected game object. Did you know that Unity has snapping? Might not help if you have a lot of different snap sizes, but thought you should know.
    https://unity3d.com/learn/tutorials/topics/tips/snapping

    • Jane Doe
      Jane Doe 2018/02/03

      Yeah i know Unity has snapping but i’m getting tired of holding the CTRL key all the time and making sure that no sprites have accidentally moved without the snap, so i wanted to make my own custom transform component that is limited to the pixel grid (rounded to two decimals) , i had other issues so i just gave up the idea, it’s nice to know what can be done tho.

  3. anmol
    anmol 2021/02/18

    Thanks Sharing Good Article

  4. Steven P
    Steven P 2021/03/20

    Just a fair warning, there is a bug in the ApplyChangesOnly() function where no matter what value you are changing (position, rotation, or scale) It checks if it should be changed using the changed.x/y/z but then always uses _transform.localScale.x/y/z regardless of what is being changed.

    I believe it should be using change.x/y/z

    With this bug, if you set the _rotation_ value to 90 but the y _position_ is 4000, it will try to set the rotation of all selected objects to 4000. This only appears to cause problems when setting values on multiple objects at once. If you set a single object it doesn’t use that function so you don’t see it happen.

    • Sean Mann
      Sean Mann 2021/03/20

      Thanks for letting us know!

  5. Mckinney Via
    Mckinney Via 2021/11/18

    Very much appreciated. Thank you for this excellent article. Keep posting!

Leave a Reply

Your email address will not be published. Required fields are marked *