As of today, I have 3 sales of CYOI since releasing v1.0! That’s awesome! I’m pleased that people are interested, for sure. Hopefully, they all find it useful.
Still only the one sale of JumpTo to date. I should probably look into some kind of marketing. Not sure where one would even market a Unity editor tool other than Unity-centric spaces, such as the forums. But, I think that using the forums to advertise is in poor taste. Maybe it’s just me…
Awesome! Choose Your Own Inspector is now on the Asset Store. Link
A few days ago, I finally got an email reply from someone with the Unity Asset Store. They said that the content from this website was not visible to them, and that my submission was declined because of it. I immediately checked the site, and it seemed fine to me. Even from my phone over the cell network, it was fine. But, I checked it the next day from another location, and it wasn’t loading correctly. It turned out to be a bad .htaccess file.
I mean…couldn’t their initial email have told me exactly what was going on so that I could immediately resolve the issue? No, instead they sent a canned email which told me nothing. Three full days passed before the next email came, and I could actually take some kind of action! Far too long! And, yes, I did have to resubmit CYOI, essentially starting the process over, meaning probably two more weeks before I hear back again. That makes for a horrendous turnaround time! This whole process could use some streamlining.
Bad processes frustrate me to no end, which is why I started this little thing. There is simply no reason to keep a bad process when you could just as easily have a good one. For more interesting reading, see Wikipedia’s entry for Adhocracy. Maybe I’ll write a blog entry containing my thoughts on the matter sometime.
Maybe you have an idea for a cool unique tool, and you want to put it on the Asset Store. But, what if you only have the free, or personal, edition of Unity, like I do. That means that you only have the light skin, right? Not…exactly. It would not make sense for Unity to write their UI code twice, once for light and again for dark, so they actually include both within every Editor installation.
Unity uses GUISkins for “Immediate Mode” (IMGUI) controls. This is also true for the Unity Editor itself. They even provided a way for us to access the built-in skins that they use.
public static GUISkin EditorGUIUtility.GetBuiltinSkin(EditorSkin skin);
This method takes an enum value and returns an instance of a GUISkin. The enum values are Game, Inspector, and Scene. These names are a bit misleading. They actually have no connection with the EditorWindows of the same names. At least, none that I can find. Pretty much everything that the Unity devs use to draw every part of every built-in EditorWindow can be found within these three GUISkins.
Okay, so we can get a reference to the built-in Unity GUISkins. So what? What’s that got to do with the light and dark skins? Well it turns out that the light skin corresponds to EditorSkin.Inspector, and the dark skin is EditorSkin.Scene. The skin for Game doesn’t actually have much in it of note.
So, we can get to the built-in skins that Unity uses for the Editor. Now we need to figure out how to use them. A GUISkin is simply a collection of named GUIStyles, along with a few settings that are used by some GUI controls. Some styles are accessible through properties, such as box, label, button, etc. There is also an array called customStyles. This can hold as many GUIStyles as you need. If you have a GUISkin asset (one can be created using the Create menu), you can add styles to the array and fill them out using the Inspector. To access any style within a skin, use the following method.
public GUIStyle GUISkin.GetStyle(string styleName);
This method takes the name of a style in the skin and returns the associated style. But, what are the style names that Unity is using? Is there a list somewhere? Nope. Besides, what we really need is some way to browse through all of the styles in the Inspector. There are a couple of ways to do this.
The first is very simple. Make the skin the selected object.
public static class InspectEditorSkins { [MenuItem("Tools/Inspect Dark Skin")] public static void MenuItem_InspectDarkSkin() { Selection.activeObject = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene); } [MenuItem("Tools/Inspect Light Skin")] public static void MenuItem_InspectLightSkin() { Selection.activeObject = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector); } [MenuItem("Tools/Inspect Game Skin")] public static void MenuItem_InspectGameSkin() { Selection.activeObject = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Game); } }
Now, selecting one of the menu items will let us inspect each skin. Notice, though, that they are all showing as ‘disabled’ in the Inspector. This is because these assets are, in a way, locked. Read-only, you might say. It makes sense. You wouldn’t want just anyone to screw around with these if you were Unity.
What I have found easier to work with long term is to make copies of the skins and save them as assets. Here is the code I have been using.
public static class EditorSkinsExtractor { [MenuItem("Assets/Extract Editor Skins")] public static void MenuItem_ExtractEditorSkins() { string unityVersion = Application.unityVersion; string skinsFolder = "EditorSkins_" + unityVersion; string fullPath = Application.dataPath + "/" + skinsFolder; if (!System.IO.Directory.Exists(fullPath)) { AssetDatabase.CreateFolder("Assets", skinsFolder); } if (System.IO.Directory.Exists(fullPath)) { string relativePath = "Assets/" + skinsFolder + "/EditorSkin-"; ExtractSkin(relativePath, EditorSkin.Game); ExtractSkin(relativePath, EditorSkin.Inspector); ExtractSkin(relativePath, EditorSkin.Scene); } else { Debug.LogError("Failed to create folder"); } } private static void ExtractSkin(string relativePath, EditorSkin skinType) { GUISkin skin = ScriptableObject.Instantiate(EditorGUIUtility.GetBuiltinSkin(skinType)) as GUISkin; AssetDatabase.CreateAsset(skin, relativePath + skinType + ".guiskin"); } }
This will create a folder for the Unity version currently running, and then create copies of the skins and save them as assets within the folder. Now you can peruse at will!
Now remember, the Scene skin is the dark skin and the Inspector skin is the light one. Select either and expand Custom Styles. Let’s find something familiar. As you scroll through the list, you’ll notice that the names are in alphabetical order. This is nice for when you’re looking for something specific. Scroll all the way down to “IN LockButton”. Can you guess what that might be? It’s the lock button from the IN-spector. Now take a look at “PR Insertion”. That is the blue line that shows up when you are moving items in the PR-oject view! Fun! Of course, that blue line is also used in other places, like the Hierarchy window. I’m also using it directly in JumpTo, so that my window feels native.
An excerpt straight from JumpTo…
//create some public properties to hold your styles public GUIStyle LinkLabelStyle { get; private set; } public GUIStyle DragDropInsertionStyle { get; private set; } //NOTE: you must work with GUIStyles within OnGUI() GUISkin editorSkin = null; if (EditorGUIUtility.isProSkin) editorSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene); else editorSkin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector); //here we are creating copies of the built-in style, and then making // whatever changes may be necessary for our use-case LinkLabelStyle = new GUIStyle(editorSkin.GetStyle("PR Label")); LinkLabelStyle.name = "Link Label Style"; LinkLabelStyle.padding.left = 8; DragDropInsertionStyle = new GUIStyle(editorSkin.GetStyle("PR Insertion")); DragDropInsertionStyle.imagePosition = ImagePosition.ImageOnly; DragDropInsertionStyle.contentOffset = new Vector2(0.0f, -16.0f);
The if statement will guarantee that we are pulling the correct style for the end-user’s version of Unity. Now your IMGUI code can be completely ignorant of the skin being used. In theory, anyway…
These styles can be passed as arguments to GUILayout methods, or used directly using the GUIStyle.Draw() method.
I always try to give credit where it is due. The inspiration came from this thread in the Unity forum, in a post from user dodo. I’d had the same question as the OP, Tonzie, and this was what was needed.
Unity declined my first submission of CYOI! Here’s the email:
Hi Imp Rock,
Thanks very much for your submission to the Asset Store.
‘Choose Your Own Inspector’ has been declined.
We are concerned about the level of your professional appearance online. We ask that you provide a link to a professional portfolio site to show your experience on your publisher page. This is beneficial to establish yourself as a trustworthy seller to your user base.
If you’re a student or a start up publisher and don’t have a ready website yet, please provide a Linkedin profile, or Github account.
Kind regards,
The Asset Store Team
I had to look this up because it isn’t clear what is being asked for by the reviewer. Well, it turns out that this is a canned email response. Others have posted this exact text in various forums, including Unity’s own. Basically, this is the email that Unity sends when the asset author doesn’t provide a link to some “home” website. However, I did provide a link to a website. This website! It was in the asset description I submitted for CYOI. On top of that, the Asset Store Publisher account info requires a URL to your “publisher page.” It shows up on an asset’s store page as a link that says “Visit Publisher’s Website.” Well, that URL was already provided when I submitted JumpTo.
So, with the exact same data, JumpTo was already approved. Twice, now, in fact. And, of course, JumpTo has the same information provided as CYOI’s submission. If it worked for JumpTo, why would it not be acceptable for CYOI? I have no idea what the reviewer was thinking, but, as far as I can tell, there isn’t a problem. It seems obvious that what is described in that email isn’t relevant at all. If the reviewers are looking for a specific thing, then that specific thing needs to be specified. It just would have been nice to get some personalized feedback instead of the irrelevant canned email.
Other asset store publisher discussions said that uploading a new draft of the asset was in order, which puts the asset back into the approval queue. I’ve done that just now. Does this mean I have to wait another two weeks to hear back from Unity? It’s infuriating! I guess it’s a good thing that I don’t rely on asset store sales as a source of income.
Submitted on Oct. 5, actually. Still pending review.
On another note: I got an email today about 24 failed login attempts from somewhere else. This is new. Is it a brute force attack on improck.com? Why? No one even comes here but me! And spammers, I guess, but I’ve disabled comments.
It was accepted into the Asset Store sometime yesterday. It was nice to work on it again. Despite its simple UI, it’s fairly complex underneath. Lots of parts in there. And lots of manual labor! JumpTo doesn’t use GUILayout, and I had to make so much of it from scratch. This has been a great learning experience, though. I’d like to log some more of the wisdom I’ve gained on the journey to v2.0.
I’ve got a couple of more tools I’d like to add to the Asset Store. These are things that I may have prototyped already, or they may only live in my development notes. I’m already working on one that was already pretty far along. Not up to a v1.0 gold release yet, but I’d say that it’s in a beta state.
Unity 5 broke a major feature of JumpTo, the ability to save links to prefab instances in the hierarchy. I submitted a bug to Unity over a year ago (see 674553 in Issue Tracker) from Unity 5.0.0f2. Finally, they got around to fixing it in 5.4.0b15! That’s a long time to wait, and the entire time, JumpTo has not been full featured.
But, at least they fixed it, I guess! Anyway, I’m now going to fix JumpTo and add support for multi-scene editing. Especially since…I made a sale! That’s right! Someone actually bought a copy of JumpTo! I ended up with a whopping $7, CASH MONEY! I may even pull 7 dollar bills out, spread them on the bed, and roll around in the pile. Or, better yet, $7 in quarters! Might make it feel more substantial, if not cold.
I’m kidding, of course. Not looking to get rich off of a Unity Editor tool on the Asset Store. Still, I was very surprised. I mean, it’s been published for well over a year now. Either way, to my one and only customer: my thanks!
I’ve been wanting to make a public example of this ever since I found out how to do it. Here goes!
When you double-click a GameObject with a mesh, the Scene View will focus on that object and zoom to the mesh’s extents in the scene. That’s great for objects with a mesh, but how would you do it for other kinds of objects?
It’s actually pretty simple. As we know, for any MonoBehaviour based class, you can define a custom editor. Say you have a script that defines a set of points in local space (meaning Vector3 points that are relative to the object’s transform.position). When you double-click this object under normal circumstances, the Scene View will focus on it as expected, but you may not see all of the points you’ve defined. This is because the default inspector doesn’t know to take these points into account when trying to figure out how big your object is in world space.
So, you need to make a custom editor for your class. In that editor you will need to create two methods that the Scene View will look for when the object is double-clicked:
private bool HasFrameBounds()
private Bounds OnGetFrameBounds()
Returning true from HasFrameBounds tells the Scene View that there is an OnGetFrameBounds method. Return a Bounds object from OnGetFrameBounds to define the size of your object. In the case of the example, you would want the bounds to encapsulate all of the points as well as the object’s transform.position value.
Let’s look at an example class.
using UnityEngine; using System.Collections; public class FrameBoundsObject : MonoBehaviour { [Range(0.01f, 2.0f)] [SerializeField] private float m_ExtentsMultiplier = 1.0f; [SerializeField] private bool m_HasFrameBounds = true; [SerializeField] private Vector3[] m_Points; public Vector3[] Points { get { return m_Points; } } public float ExtentsMultiplier { get { return m_ExtentsMultiplier; } } public bool HasFrameBounds { get { return m_HasFrameBounds; } } private void OnDrawGizmos() { Vector3 position = transform.position; Gizmos.color = Color.blue; Gizmos.DrawSphere(position, 1.0f); if (m_Points == null) return; Gizmos.color = Color.green; for (int i = 0; i < m_Points.Length; i++) { Gizmos.DrawSphere(position + m_Points[i], 1.0f); } } }
It’s just a data class, so there’s no logic here. Using Gizmos, the object’s position is drawn as a blue sphere, and each point in the array of points is drawn as a green sphere. Let’s drop this into a scene and add a few points to it.
So, now we have six points all at 100m from the center where the object’s transform sits. At this point, we haven’t made a custom editor for the class so double-clicking the object will exhibit the default behavior. Now, double-click the object in the Hierarchy. The Scene View zooms in so that the blue sphere is in the center. But, all of the green spheres representing each point aren’t in view!
Okay. Let’s add the custom editor so that the object bounds can be defined.
using UnityEditor; using UnityEngine; using System.Collections; [CustomEditor(typeof(FrameBoundsObject))] public class FrameBoundsEditor : Editor { private FrameBoundsObject m_Target; private Transform m_Transform; private void OnEnable() { m_Target = target as FrameBoundsObject; m_Transform = m_Target.transform; } private bool HasFrameBounds() { return m_Target.HasFrameBounds; } private Bounds OnGetFrameBounds() { Bounds frameBounds = new Bounds(); frameBounds.Encapsulate(m_Transform.position); if (m_Target.Points != null) { for (int i = 0; i < m_Target.Points.Length; i++) { frameBounds.Encapsulate(m_Target.Points[i]); } } frameBounds.extents *= m_Target.ExtentsMultiplier; return frameBounds; } }
Take a look at the OnGetFrameBounds method body. Notice that it is creating a Bounds object and “bumping out” the extents using Encapsulate. Also, notice that the target’s transform.position is being encapsulated. This is because I wanted it to work this way for the example. If your object won’t need to take the transform into account, then don’t. The bounds can be whatever you need!
Now double-click the object. All of the green spheres should now be in the Scene View. Looks great, right? Well, not really.
It’s too far away! What’s up? It turns out that Unity, behind the scenes is…uhm…fixing the bounds that you return from OnGetFrameBounds. There are good reasons for doing this. The Editor is trying to make sure that your whole object is visible from every angle. But, the adjustments can yield unexpected results. Crack open ILSpy and look at the SceneView.FrameSelected method. Toward the bottom you’ll see that the bounds’ extents are being scaled by 1.5f and then again by 2.2f. Because of this, you may want to adjust the extents value to counteract the scaling a little.
Adjust the object’s Extents Multiplier value to 0.45 and double-click again. Everything should be much closer now.
Feel free to play with that value while changing the angle of the Scene View camera to see the effect of adjusting the extents by a static value. Also, toggle the Has Frame Bounds and double-click to see that when the HasFrameBounds method returns false, the frame bounds aren’t calculated when framing the object.
There’s an undocumented method you can implement that catches modifier key state changes called ModifierKeysChanged(). Any time the shift, ctrl, alt, win, or command keys go up or down, this method will be called.
using UnityEditor; using UnityEngine; public class ModifierKeysChangedEditorWindow : EditorWindow { private void ModifierKeysChanged() { //NOTE: Event.current is null at this point, so // it can't be used to check the state of // the modifier keys Debug.Log("Modifier keys changed"); } [MenuItem("Tools/Modifier Keys Window")] public static void Init_ToolsMenu() { EditorWindow window = EditorWindow.GetWindow<ModifierKeysChangedEditorWindow>(); window.title = "Modifier Keys"; window.Show(); } }
Underneath, the method is simply being added to EditorApplication.modifierKeysChanged automatically. This callback doesn’t pass any parameters, and Event.current is null at the time it is invoked. At this time, I can’t find any way to test which modifier key state actually changed.
A possible use-case is when the window should change somehow when shift is pressed/released, you could call Repaint() here. There are, however, other ways to handle modifier keys in an EditorWindow. You should keep in mind that this is undocumented, so it may cease to work with any Unity update in the future. As of 4.5.3p2, it works.